Coverage for sacred/sacred/commandline_options.py: 59%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Provides the basis for all command-line options (flags) in sacred.
4It defines the base class CommandLineOption and the standard supported flags.
5Some further options that add observers to the run are defined alongside those.
6"""
8from typing import Callable
9import inspect
10import re
12from sacred.run import Run
13from sacred.commands import print_config
14from sacred.utils import convert_camel_case_to_snake_case
17CLIFunction = Callable[[str, Run], None]
20class CLIOption:
21 def __init__(
22 self,
23 apply_function: CLIFunction,
24 short_flag: str,
25 long_flag: str,
26 is_flag: bool,
27 ):
29 if not re.match(r"-\w$", short_flag):
30 raise ValueError(
31 "Short flag malformed. " "One correct short flag would be: `-j`."
32 )
33 if not re.match(r"--[\w-]+\w$", long_flag):
34 raise ValueError(
35 "Long flag malformed. One correct long flag "
36 "would be: `--my-pretty-flag`"
37 )
38 self.apply_function = apply_function
39 self.short_flag = short_flag
40 self.long_flag = long_flag
41 self.is_flag = is_flag
43 # trick for backward compatibility:
44 self.arg = None if is_flag else "VALUE"
45 self.arg_description = None if is_flag else ""
47 def __call__(self, *args, **kwargs):
48 return self.apply_function(*args, **kwargs)
50 def get_flag(self):
51 """Legacy function. Should be removed at some point."""
52 return self.long_flag
54 def get_short_flag(self):
55 """Legacy function. Should be removed at some point."""
56 return self.short_flag
58 def get_flags(self):
59 """Legacy function. Should be removed at some point."""
60 return self.short_flag, self.long_flag
62 def apply(self, args, run):
63 """Legacy function. Should be removed at some point."""
64 return self.apply_function(args, run)
66 def get_name(self):
67 return self.apply_function.__name__
69 def get_description(self):
70 return inspect.getdoc(self.apply_function) or ""
73def cli_option(short_flag: str, long_flag: str, is_flag=False):
74 def wrapper(f: CLIFunction):
75 return CLIOption(f, short_flag, long_flag, is_flag)
77 return wrapper
80class CommandLineOption:
81 """
82 Base class for all command-line options.
84 To implement a new command-line option just inherit from this class.
85 Then add the `flag` class-attribute to specify the name and a class
86 docstring with the description.
87 If your command-line option should take an argument you must also provide
88 its name via the `arg` class attribute and its description as
89 `arg_description`.
90 Finally you need to implement the `execute` classmethod. It receives the
91 value of the argument (if applicable) and the current run. You can modify
92 the run object in any way.
94 If the command line option depends on one or more installed packages, those
95 should be imported in the `apply` method to get a proper ImportError
96 if the packages are not available.
97 """
99 _enabled = True
101 short_flag = None
102 """ The (one-letter) short form (defaults to first letter of flag) """
104 arg = None
105 """ Name of the argument (optional) """
107 arg_description = None
108 """ Description of the argument (optional) """
110 @classmethod
111 def get_flag(cls):
112 # Get the flag name from the class name
113 flag = cls.__name__
114 if flag.endswith("Option"):
115 flag = flag[:-6]
116 return "--" + convert_camel_case_to_snake_case(flag)
118 @classmethod
119 def get_short_flag(cls):
120 if cls.short_flag is None:
121 return "-" + cls.get_flag()[2]
122 else:
123 return "-" + cls.short_flag
125 @classmethod
126 def get_flags(cls):
127 """
128 Return the short and the long version of this option.
130 The long flag (e.g. '--foo_bar'; used on the command-line like this:
131 --foo_bar[=ARGS]) is derived from the class-name by stripping away any
132 -Option suffix and converting the rest to snake_case.
134 The short flag (e.g. '-f'; used on the command-line like this:
135 -f [ARGS]) the short_flag class-member if that is set, or the first
136 letter of the long flag otherwise.
138 Returns
139 -------
140 (str, str)
141 tuple of short-flag, and long-flag
143 """
144 return cls.get_short_flag(), cls.get_flag()
146 @classmethod
147 def apply(cls, args, run):
148 """
149 Modify the current Run base on this command-line option.
151 This function is executed after constructing the Run object, but
152 before actually starting it.
154 Parameters
155 ----------
156 args : bool | str
157 If this command-line option accepts an argument this will be value
158 of that argument if set or None.
159 Otherwise it is either True or False.
160 run : sacred.run.Run
161 The current run to be modified
163 """
164 pass
167def get_name(option):
168 if isinstance(option, CLIOption):
169 return option.get_name()
170 else:
171 return option.__name__
174@cli_option("-h", "--help", is_flag=True)
175def help_option(args, run):
176 """Print this help message and exit."""
177 pass
180@cli_option("-d", "--debug", is_flag=True)
181def debug_option(args, run):
182 """
183 Set this run to debug mode.
185 Suppress warnings about missing observers and don't filter the stacktrace.
186 Also enables usage with ipython `--pdb`.
187 """
188 run.debug = True
191@cli_option("-D", "--pdb", is_flag=True)
192def pdb_option(args, run):
193 """Automatically enter post-mortem debugging with pdb on failure."""
194 run.pdb = True
197@cli_option("-l", "--loglevel")
198def loglevel_option(args, run):
199 """
200 Set the LogLevel.
202 Loglevel either as 0 - 50 or as string: DEBUG(10),
203 INFO(20), WARNING(30), ERROR(40), CRITICAL(50)
204 """
205 # TODO: sacred.initialize.create_run already takes care of this
207 try:
208 lvl = int(args)
209 except ValueError:
210 lvl = args
211 run.root_logger.setLevel(lvl)
214@cli_option("-c", "--comment")
215def comment_option(args, run):
216 """Add a comment to this run."""
217 run.meta_info["comment"] = args
220@cli_option("-b", "--beat-interval")
221def beat_interval_option(args, run):
222 """
223 Set the heart-beat interval for this run.
225 Time between two heartbeat events is measured in seconds.
226 """
227 run.beat_interval = float(args)
230@cli_option("-u", "--unobserved", is_flag=True)
231def unobserved_option(args, run):
232 """Ignore all observers for this run."""
233 run.unobserved = True
236@cli_option("-q", "--queue", is_flag=True)
237def queue_option(args, run):
238 """Only queue this run, do not start it."""
239 run.queue_only = True
242@cli_option("-f", "--force", is_flag=True)
243def force_option(args, run):
244 """Disable warnings about suspicious changes for this run."""
245 run.force = True
248@cli_option("-P", "--priority")
249def priority_option(args, run):
250 """Sets the priority for a queued up experiment.
252 `--priority=NUMBER`
253 The number represent the priority for this run.
254 """
255 try:
256 priority = float(args)
257 except ValueError:
258 raise ValueError(
259 "The PRIORITY argument must be a number! (but was '{}')".format(args)
260 )
261 run.meta_info["priority"] = priority
264@cli_option("-e", "--enforce_clean", is_flag=True)
265def enforce_clean_option(args, run):
266 """Fail if any version control repository is dirty."""
267 repos = run.experiment_info["repositories"]
268 if not repos:
269 raise RuntimeError(
270 "No version control detected. "
271 "Cannot enforce clean repository.\n"
272 "Make sure that your sources under VCS and the "
273 "corresponding python package is installed."
274 )
275 else:
276 for repo in repos:
277 if repo["dirty"]:
278 raise RuntimeError(
279 "EnforceClean: Uncommited changes in "
280 'the "{}" repository.'.format(repo)
281 )
284@cli_option("-p", "--print-config", is_flag=True)
285def print_config_option(args, run):
286 """Always print the configuration first."""
287 print_config(run)
288 print("-" * 79)
291@cli_option("-i", "--id")
292def id_option(args, run):
293 """Set the id for this run."""
294 run._id = args
297@cli_option("-n", "--name")
298def name_option(args, run):
299 """Set the name for this run."""
300 run.experiment_info["name"] = args
301 run.run_logger = run.root_logger.getChild(args)
304@cli_option("-C", "--capture")
305def capture_option(args, run):
306 """
307 Control the way stdout and stderr are captured.
309 The argument value must be one of [no, sys, fd]
310 """
311 run.capture_mode = args