Completed
Push — master ( 701aa5...50a1f3 )
by Klaus
01:33
created

Experiment.__init__()   B

Complexity

Conditions 1

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
dl 0
loc 30
rs 8.8571
1
#!/usr/bin/env python
2
# coding=utf-8
3
"""This module defines the Experiment class, which is central to sacred."""
4
from __future__ import division, print_function, unicode_literals
5
6
import inspect
7
import sys
8
from collections import OrderedDict
9
10
from sacred.arg_parser import get_config_updates, parse_args
11
from sacred.commandline_options import gather_command_line_options, ForceOption
12
from sacred.commands import print_config, print_dependencies
13
from sacred.ingredient import Ingredient
14
from sacred.utils import print_filtered_stacktrace
15
16
__sacred__ = True  # marks files that should be filtered from stack traces
17
18
__all__ = ('Experiment',)
19
20
21
class Experiment(Ingredient):
22
    """
23
    The central class for each experiment in Sacred.
24
25
    It manages the configuration, the main function, captured methods,
26
    observers, commands, and further ingredients.
27
28
    An Experiment instance should be created as one of the first
29
    things in any experiment-file.
30
    """
31
32
    def __init__(self, name, ingredients=(), interactive=False):
33
        """
34
        Create a new experiment with the given name and optional ingredients.
35
36
        Parameters
37
        ----------
38
        name : str
39
            The name of this experiment.
40
41
        ingredients : list[sacred.Ingredient]
42
            A list of ingredients to be used with this experiment.
43
44
        interactive : bool
45
            If set to True will allow the experiment to be run in interactive
46
            mode (e.g. IPython or Jupyter notebooks).
47
            However, this mode is discouraged since it won't allow storing the
48
            source-code or reliable reproduction of the runs.
49
        """
50
        caller_globals = inspect.stack()[1][0].f_globals
51
        super(Experiment, self).__init__(path=name,
52
                                         ingredients=ingredients,
53
                                         interactive=interactive,
54
                                         _caller_globals=caller_globals)
55
        self.default_command = ""
56
        self.command(print_config, unobserved=True)
57
        self.command(print_dependencies, unobserved=True)
58
        self.observers = []
59
        self.current_run = None
60
        self.captured_out_filter = None
61
        """Filter function to be applied to captured output of a run"""
62
63
    # =========================== Decorators ==================================
64
65
    def main(self, function):
66
        """
67
        Decorator to define the main function of the experiment.
68
69
        The main function of an experiment is the default command that is being
70
        run when no command is specified, or when calling the run() method.
71
72
        Usually it is more convenient to use ``automain`` instead.
73
        """
74
        captured = self.command(function)
75
        self.default_command = captured.__name__
76
        return captured
77
78
    def automain(self, function):
79
        """
80
        Decorator that defines *and runs* the main function of the experiment.
81
82
        The decorated function is marked as the default command for this
83
        experiment, and the command-line interface is automatically run when
84
        the file is executed.
85
86
        The method decorated by this should be last in the file because is
87
        equivalent to:
88
89
        .. code-block:: python
90
91
            @ex.main
92
            def my_main():
93
                pass
94
95
            if __name__ == '__main__':
96
                ex.run_commandline()
97
        """
98
        captured = self.main(function)
99
        if function.__module__ == '__main__':
100
            # Ensure that automain is not used in interactive mode.
101
            import inspect
102
            main_filename = inspect.getfile(function)
103
            if (main_filename == '<stdin>' or
104
                    (main_filename.startswith('<ipython-input-') and
105
                     main_filename.endswith('>'))):
106
                raise RuntimeError('Cannot use @ex.automain decorator in '
107
                                   'interactive mode. Use @ex.main instead.')
108
109
            self.run_commandline()
110
        return captured
111
112
    # =========================== Public Interface ============================
113
114
    def run(self, config_updates=None, named_configs=()):
115
        """
116
        Run the main function of the experiment.
117
118
        Parameters
119
        ----------
120
        config_updates : dict
121
            Changes to the configuration as a nested dictionary
122
123
        named_configs : list[str]
124
            list of names of named_configs to use
125
126
        Returns
127
        -------
128
        sacred.run.Run
129
            the Run object corresponding to the finished run
130
        """
131
        assert self.default_command, "No main function found"
132
        return self.run_command(self.default_command,
133
                                config_updates=config_updates,
134
                                named_configs=named_configs)
135
136
    def run_command(self, command_name, config_updates=None,
137
                    named_configs=(), args=()):
138
        """Run the command with the given name.
139
140
        Parameters
141
        ----------
142
        command_name : str
143
            Name of the command to be run.
144
145
        config_updates : dict
146
            A dictionary of parameter values that should be updates. (optional)
147
148
        named_configs : list[str]
149
            List of names of named configurations to use. (optional)
150
151
        args : dict
152
            Dictionary of command-line options.
153
154
        Returns
155
        -------
156
        sacred.run.Run
157
            The Run object corresponding to the finished run.
158
        """
159
        force_flag = '--' + ForceOption.get_flag()[1]
160
        force = args[force_flag] if force_flag in args else False
161
162
        run = self._create_run_for_command(command_name, config_updates,
163
                                           named_configs, force=force)
164
        self.current_run = run
165
166
        for option in gather_command_line_options():
167
            op_name = '--' + option.get_flag()[1]
168
            if op_name in args and args[op_name]:
169
                option.apply(args[op_name], run)
170
        self.current_run.run_logger.info("Running command '%s'", command_name)
171
        run()
172
        self.current_run = None
173
        return run
174
175
    def run_commandline(self, argv=None):
176
        """
177
        Run the command-line interface of this experiment.
178
179
        If ``argv`` is omitted it defaults to ``sys.argv``.
180
181
        Parameters
182
        ----------
183
        argv : list[str]
184
            Split command-line like ``sys.argv``.
185
186
        Returns
187
        -------
188
        sacred.run.Run
189
            The Run object corresponding to the finished run.
190
        """
191
        if argv is None:
192
            argv = sys.argv
193
        all_commands = self.gather_commands()
194
195
        args = parse_args(argv,
196
                          description=self.doc,
197
                          commands=OrderedDict(all_commands))
198
        config_updates, named_configs = get_config_updates(args['UPDATE'])
199
        cmd_name = args.get('COMMAND') or self.default_command
200
201
        try:
202
            return self.run_command(cmd_name, config_updates, named_configs,
203
                                    args)
204
        except Exception:
205
            if not self.current_run or self.current_run.debug:
206
                raise
207
            elif self.current_run.pdb:
208
                import traceback
209
                import pdb
210
                traceback.print_exception(*sys.exc_info())
211
                pdb.post_mortem()
212
            else:
213
                print_filtered_stacktrace()
214
215
    def open_resource(self, filename):
216
        """Open a file and also save it as a resource.
217
218
        Opens a file, reports it to the observers as a resource, and returns
219
        the opened file.
220
221
        In Sacred terminology a resource is a file that the experiment needed
222
        to access during a run. In case of a MongoObserver that means making
223
        sure the file is stored in the database (but avoiding duplicates) along
224
        its path and md5 sum.
225
226
        This function can only be called during a run, and just calls the
227
        :py:meth:`sacred.run.Run.open_resource` method.
228
229
        Parameters
230
        ----------
231
        filename: str
232
            name of the file that should be opened
233
234
        Returns
235
        -------
236
        file
237
            the opened file-object
238
        """
239
        assert self.current_run is not None, "Can only be called during a run."
240
        return self.current_run.open_resource(filename)
241
242
    def add_artifact(self, filename):
243
        """Add a file as an artifact.
244
245
        In Sacred terminology an artifact is a file produced by the experiment
246
        run. In case of a MongoObserver that means storing the file in the
247
        database.
248
249
        This function can only be called during a run, and just calls the
250
        :py:meth:`sacred.run.Run.add_artifact` method.
251
252
        Parameters
253
        ----------
254
        filename : str
255
            name of the file to be stored as artifact
256
        """
257
        assert self.current_run is not None, "Can only be called during a run."
258
        self.current_run.add_artifact(filename)
259
260
    @property
261
    def info(self):
262
        """Access the info-dict for storing custom information.
263
264
        Only works during a run and is essentially a shortcut to:
265
266
        .. code-block:: python
267
268
            @ex.capture
269
            def my_captured_function(_run):
270
                # [...]
271
                _run.info   # == ex.info
272
        """
273
        return self.current_run.info
274
275
    def gather_commands(self):
276
        for cmd_name, cmd in self.commands.items():
277
            yield cmd_name, cmd
278
279
        for ingred in self.ingredients:
280
            for cmd_name, cmd in ingred.gather_commands():
281
                yield cmd_name, cmd
282