Skip to content

Commit

Permalink
Bugfix release 0.6.4
Browse files Browse the repository at this point in the history
Merge branch 'release/0.6.4'
  • Loading branch information
Qwlouse committed Jun 12, 2015
2 parents aa270ee + 329c5dc commit 1b3cb03
Show file tree
Hide file tree
Showing 18 changed files with 490 additions and 118 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ script:
branches:
only:
- master
- develop
- /^release.*$/
19 changes: 9 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ Sacred
| *If an experiment is wasted*
| *God gets quite irate*
.. image:: https://zenodo.org/badge/doi/10.5281/zenodo.16386.svg
:target: http://dx.doi.org/10.5281/zenodo.16386
:alt: DOI for this release

.. image:: http://img.shields.io/pypi/v/sacred.png
.. image:: https://img.shields.io/pypi/v/sacred.svg
:target: https://pypi.python.org/pypi/sacred
:alt: Current PyPi Version

.. image:: http://img.shields.io/badge/license-MIT-blue.png
.. image:: https://img.shields.io/pypi/pyversions/sacred.svg
:target: https://pypi.python.org/pypi/sacred
:alt: Supported Python Versions

.. image:: https://img.shields.io/badge/license-MIT-blue.png
:target: http://choosealicense.com/licenses/mit/
:alt: MIT licensed

Expand All @@ -34,10 +34,9 @@ Sacred
:target: https://scrutinizer-ci.com/g/IDSIA/sacred/
:alt: Code Scrutinizer Quality

.. image:: https://pypip.in/wheel/sacred/badge.svg?style=flat
:target: https://pypi.python.org/pypi/sacred/
:alt: Wheel Status

.. image:: https://zenodo.org/badge/doi/10.5281/zenodo.16386.svg
:target: http://dx.doi.org/10.5281/zenodo.16386
:alt: DOI for this release

Sacred is a tool to help you configure, organize, log and reproduce experiments.
It is designed to do all the tedious overhead work that you need to do around
Expand Down
5 changes: 3 additions & 2 deletions docs/apidoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ The Run Object
.. autoclass:: sacred.run.Run
:members:
:undoc-members:
:special-members: __call__

ConfigScope
===========
.. autoclass:: sacred.config_scope.ConfigScope
.. autoclass:: sacred.config.config_scope.ConfigScope
:members:
:undoc-members:

ConfigDict
==========
.. autoclass:: sacred.config_scope.ConfigDict
.. autoclass:: sacred.config.config_dict.ConfigDict
:members:
:undoc-members:

Expand Down
2 changes: 1 addition & 1 deletion sacred/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

__all__ = ("__version__", "__author__", "__author_email__", "__url__")

__version__ = "0.6.3"
__version__ = "0.6.4"

__author__ = 'Klaus Greff'
__author_email__ = 'qwlouse@gmail.com'
Expand Down
10 changes: 10 additions & 0 deletions sacred/config/config_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ def __init__(self, added=(), modified=(), typechanged=(),
self.ensure_coherence()

def update_from(self, config_mod, path=''):
added = config_mod.added
updated = config_mod.modified
typechanged = config_mod.typechanged
self.added &= {join_paths(path, a) for a in added}
self.modified |= {join_paths(path, u) for u in updated}
self.typechanged.update({join_paths(path, k): v
for k, v in typechanged.items()})
self.ensure_coherence()

def update_add(self, config_mod, path=''):
added = config_mod.added
updated = config_mod.modified
typechanged = config_mod.typechanged
Expand Down
12 changes: 6 additions & 6 deletions sacred/config/custom_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,10 @@ def __contains__(self, item):

def get(self, k, d=None):
if dict.__contains__(self, k):
return dict.__getitem__(self, k)()
return dict.__getitem__(self, k)
else:
return self.fallback.get(k, d)

def has_key(self, item):
return self.__contains__(item)

def items(self):
raise NotImplementedError()

Expand Down Expand Up @@ -229,6 +226,7 @@ def revelation(self):


SIMPLIFY_TYPE = {
type(None): type(None),
bool: bool,
float: float,
int: int,
Expand Down Expand Up @@ -265,5 +263,7 @@ def revelation(self):
SIMPLIFY_TYPE[np.bool_] = bool


def type_changed(old_type, new_type):
return SIMPLIFY_TYPE[type(old_type)] != SIMPLIFY_TYPE[type(new_type)]
def type_changed(old_value, new_value):
sot = SIMPLIFY_TYPE.get(type(old_value), type(old_value))
snt = SIMPLIFY_TYPE.get(type(new_value), type(new_value))
return sot != snt
79 changes: 39 additions & 40 deletions sacred/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ class Ingredient(object):
Ingredients can themselves use ingredients.
"""

def __init__(self, path, ingredients=(), _generate_seed=False,
_caller_globals=None):
def __init__(self, path, ingredients=(), _caller_globals=None):
self.path = path
self.cfgs = []
self.named_configs = dict()
self.ingredients = list(ingredients)
self.gen_seed = _generate_seed
self.logger = None
self.observers = []
self.captured_functions = []
self._is_traversing = False
self.commands = OrderedDict()
Expand Down Expand Up @@ -211,6 +211,33 @@ def run_command(self, command_name, config_updates=None,
self.current_run = None
return run

def get_experiment_info(self):
"""Get a dictionary with information about this experiment.
Contains:
* *name*: the name
* *sources*: a list of sources (filename, md5)
* *dependencies*: a list of package dependencies (name, version)
* *doc*: the docstring
:return: experiment information
:rtype: dict
"""
dependencies = set()
sources = set()
for ing, _ in self._traverse_ingredients():
dependencies |= ing.dependencies
sources |= ing.sources

for dep in dependencies:
dep.fill_missing_version()

return dict(
name=self.path,
sources=[s.to_tuple() for s in sorted(sources)],
dependencies=[d.to_tuple() for d in sorted(dependencies)],
doc=self.doc)

# ======================== Private Helpers ================================

def _traverse_ingredients(self):
Expand Down Expand Up @@ -264,12 +291,8 @@ def __init__(self, name, ingredients=()):
caller_globals = inspect.stack()[1][0].f_globals
super(Experiment, self).__init__(path=name,
ingredients=ingredients,
_generate_seed=True,
_caller_globals=caller_globals)
self.name = name
self.default_command = ""
self.logger = None
self.observers = []
self.command(print_config)
self.command(print_dependencies)

Expand Down Expand Up @@ -368,7 +391,7 @@ def run_commandline(self, argv=None):
config_updates=config_updates,
named_configs_to_use=named_configs,
log_level=loglevel)
except:
except Exception:
if args['--debug']:
import traceback
import pdb
Expand All @@ -388,7 +411,8 @@ def open_resource(self, filename):
sure the file is stored in the database (but avoiding duplicates) along
its path and md5 sum.
This function can only be called during a run.
This function can only be called during a run, and just calls the
:py:meth:`sacred.run.Run.open_resource` method.
:param filename: name of the file that should be opened
:type filename: str
Expand All @@ -405,7 +429,8 @@ def add_artifact(self, filename):
run. In case of a MongoObserver that means storing the file in the
database.
This function can only be called during a run.
This function can only be called during a run, and just calls the
:py:meth:`sacred.run.Run.add_artifact` method.
:param filename: name of the file to be stored as artifact
:type filename: str
Expand All @@ -419,41 +444,15 @@ def info(self):
Only works during a run and is essentially a shortcut to:
.. code-block:: python
@ex.capture
def my_captured_function(_run)
...
def my_captured_function(_run):
# [...]
_run.info # == ex.info
"""
return self.current_run.info

def get_experiment_info(self):
"""Get a dictionary with information about this experiment.
Contains:
* *name*: the name
* *sources*: a list of sources (filename, md5)
* *dependencies*: a list of package dependencies (name, version)
* *doc*: the docstring
:return: experiment information
:rtype: dict
"""
dependencies = set()
sources = set()
for ing, _ in self._traverse_ingredients():
dependencies |= ing.dependencies
sources |= ing.sources

for dep in dependencies:
dep.fill_missing_version()

return dict(
name=self.name,
sources=[s.to_tuple() for s in sorted(sources)],
dependencies=[d.to_tuple() for d in sorted(dependencies)],
doc=self.doc)

# =========================== Private Helpers =============================

def _gather_commands(self):
Expand Down
31 changes: 22 additions & 9 deletions sacred/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ def set_up_config(self):
self.get_config_modifications()

def get_config_modifications(self):
self.config_mods = ConfigSummary()
self.config_mods = ConfigSummary(
added={key
for key, value in iterate_flattened(self.config_updates)})
for cfg_summary in self.summaries:
self.config_mods.update_from(cfg_summary)

Expand Down Expand Up @@ -211,13 +213,13 @@ def initialize_logging(experiment, scaffolding, loglevel=None):
else:
scaffold.logger = root_logger

return root_logger.getChild(experiment.name)
return root_logger.getChild(experiment.path)


def create_scaffolding(experiment):
sorted_ingredients = gather_ingredients_topological(experiment)
scaffolding = OrderedDict()
for ingredient in sorted_ingredients:
for ingredient in sorted_ingredients[:-1]:
scaffolding[ingredient] = Scaffold(
ingredient.cfgs,
subrunners=OrderedDict([(scaffolding[m].path, scaffolding[m])
Expand All @@ -226,21 +228,31 @@ def create_scaffolding(experiment):
captured_functions=ingredient.captured_functions,
commands=ingredient.commands,
named_configs=ingredient.named_configs,
generate_seed=ingredient.gen_seed)
generate_seed=False)

scaffolding[experiment] = Scaffold(
experiment.cfgs,
subrunners=OrderedDict([(scaffolding[m].path, scaffolding[m])
for m in experiment.ingredients]),
path=experiment.path if experiment != experiment else '',
captured_functions=experiment.captured_functions,
commands=experiment.commands,
named_configs=experiment.named_configs,
generate_seed=True)
return OrderedDict([(sc.path, sc) for sc in scaffolding.values()])


def gather_ingredients_topological(ingredient):
sub_ingredients = defaultdict(int)
for ingredient, depth in ingredient._traverse_ingredients():
sub_ingredients[ingredient] = max(sub_ingredients[ingredient], depth)
for sub_ing, depth in ingredient._traverse_ingredients():
sub_ingredients[sub_ing] = max(sub_ingredients[sub_ing], depth)
return sorted(sub_ingredients, key=lambda x: -sub_ingredients[x])


def get_config_modifications(scaffolding):
def get_config_modifications(scaffolding, config_updates):
config_modifications = ConfigSummary()
for sc_path, scaffold in scaffolding.items():
config_modifications.update_from(scaffold.config_mods, path=sc_path)
config_modifications.update_add(scaffold.config_mods, path=sc_path)
return config_modifications


Expand Down Expand Up @@ -273,7 +285,8 @@ def create_run(experiment, command_name, config_updates=None, log_level=None,
scaffold.set_up_seed() # partially recursive

config = get_configuration(scaffolding)
config_modifications = get_config_modifications(scaffolding)
config_modifications = get_config_modifications(scaffolding,
config_updates)

experiment_info = experiment.get_experiment_info()
host_info = get_host_info()
Expand Down
4 changes: 2 additions & 2 deletions sacred/observers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
# coding=utf-8
from __future__ import division, print_function, unicode_literals

from sacred.observers.base import RunObserver, DebugObserver
from sacred.observers.base import RunObserver
import sacred.optional as opt

if opt.has_pymongo:
from sacred.observers.mongo import MongoObserver
else:
MongoObserver = opt.MissingDependencyMock('pymongo')

__all__ = ('RunObserver', 'DebugObserver', 'MongoObserver')
__all__ = ('RunObserver', 'MongoObserver')
25 changes: 1 addition & 24 deletions sacred/observers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

__sacred__ = True # marks files that should be filtered from stack traces

__all__ = ('RunObserver', 'DebugObserver')
__all__ = ('RunObserver',)


class RunObserver(object):
Expand All @@ -31,26 +31,3 @@ def resource_event(self, filename):

def artifact_event(self, filename):
pass


class DebugObserver(RunObserver):
def started_event(self, ex_info, host_info, start_time, config):
print('experiment_started_event')

def heartbeat_event(self, info, captured_out, beat_time):
print('experiment_info_updated')

def completed_event(self, stop_time, result):
print('experiment_completed_event')

def interrupted_event(self, interrupt_time):
print('experiment_interrupted_event')

def failed_event(self, fail_time, fail_trace):
print('experiment_failed_event')

def resource_event(self, filename):
print('resource_event: {}'.format(filename))

def artifact_event(self, filename):
print('artifact_event: {}'.format(filename))
5 changes: 2 additions & 3 deletions sacred/observers/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ def transform_outgoing(self, son, collection):
for (key, value) in son.items():
if isinstance(value, dict):
if "_type" in value and value["_type"] == "ndarray":
son[key] = pickle.loads(str(value["_value"]))
son[key] = pickle.loads(value["_value"])
else: # Again, make sure to recurse into sub-docs
son[key] = self.transform_outgoing(value,
collection)
son[key] = self.transform_outgoing(value, collection)
return son


Expand Down
Loading

0 comments on commit 1b3cb03

Please sign in to comment.