Skip to content

Commit

Permalink
WIP: first working version of config_hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
Qwlouse committed May 6, 2015
1 parent 612ec04 commit f46d04d
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 76 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Contents
ingredients
examples
apidoc
internals


Index
Expand Down
29 changes: 29 additions & 0 deletions docs/internals.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Internals of Sacred
*******************
Here I'll describe some internals of Sacred.


Configuration Process
=====================
The configuration process is run when an experiment is started:

#. Determine the order for running the ingredients

- topological
- in the order they where added

#. For each ingredient do:

- gather all config updates that apply (needs ``config_updates``)
- gather all named configs to use (needs ``named_configs``)
- gather all fallbacks that apply from subrunners (needs ``subrunners.config``)
- make the fallbacks read-only
- run all named configs and use the results as additional config updates,
but with lower priority than the global ones. (needs ``named_configs``, ``config_updates``)
- run all normal configs
- update the global ``config``
- run the config hook
- update the global ``config_updates``



27 changes: 0 additions & 27 deletions examples/pre_run.py

This file was deleted.

11 changes: 5 additions & 6 deletions sacred/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Ingredient(object):
def __init__(self, path, ingredients=(), _generate_seed=False,
_caller_globals=None):
self.path = path
self._pre_run = None
self.config_hooks = []
self.cfgs = []
self.named_configs = dict()
self.ingredients = list(ingredients)
Expand All @@ -50,11 +50,10 @@ def __init__(self, path, ingredients=(), _generate_seed=False,
self.current_run = None

# =========================== Decorators ==================================
def pre_run(self, func):
if self._pre_run is None:
self._pre_run = func
else:
raise RuntimeError('Can only have one pre_run!')
def config_hook(self, func):
"""Decorator for the new config hook mechanism"""
self.config_hooks.append(ConfigScope(func))
return self.config_hooks[-1]

def command(self, function=None, prefix=None):
"""
Expand Down
104 changes: 61 additions & 43 deletions sacred/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@
from sacred.run import Run
from sacred.utils import (convert_to_nested_dict, create_basic_stream_logger,
get_by_dotted_path, is_prefix, iter_path_splits,
iterate_flattened, set_by_dotted_path)
iterate_flattened, set_by_dotted_path,
recursive_update)

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


class Scaffold(object):
def __init__(self, config_scopes, subrunners, path, captured_functions,
commands, named_configs, generate_seed):
commands, named_configs, config_hooks, generate_seed):
self.config_scopes = config_scopes
self.named_configs = named_configs
self.subrunners = subrunners
self.path = path
self.generate_seed = generate_seed
self.config_hooks = config_hooks
self.config_updates = {}
self.named_configs_to_use = []
self.config = None
self.fallback = None
self.fixture = None # TODO: rename
self.logger = None
self.seed = None
Expand Down Expand Up @@ -59,11 +62,20 @@ def set_up_seed(self, rnd=None):
if is_prefix(self.path, subrunner_path):
subrunner.set_up_seed(self.rnd)

def set_up_config(self):
if self.config is not None:
return self.config
def pick_relevant_config_updates(self, config_updates, past_paths):
if config_updates is None:
return

# gather presets
for path, value in iterate_flattened(config_updates):
for prefix, suffix in reversed(list(iter_path_splits(path))):
if prefix in past_paths:
# don't use config_updates for prior ingredients
break
elif prefix == self.path:
set_by_dotted_path(self.config_updates, suffix, value)
break

def gather_fallbacks(self):
fallback = {}
for sr_path, subrunner in self.subrunners.items():
if self.path and is_prefix(self.path, sr_path):
Expand All @@ -73,34 +85,41 @@ def set_up_config(self):
set_by_dotted_path(fallback, sr_path, subrunner.config)

# dogmatize to make the subrunner configurations read-only
const_fallback = dogmatize(fallback)
const_fallback.revelation()

self.config = {}
self.fallback = dogmatize(fallback)
self.fallback.revelation()

# named configs first
cfg_list = []
for ncfg in self.named_configs_to_use:
if os.path.exists(ncfg):
cfg_list.append(ConfigDict(load_config_file(ncfg)))
else:
cfg_list.append(self.named_configs[ncfg])
def use_named_config(self, config_name):
if os.path.exists(config_name):
self.named_configs_to_use.append(ConfigDict(load_config_file(config_name)))
else:
self.named_configs_to_use.append(self.named_configs[config_name])

def set_up_config(self):
# named configs go first
self.config_updates, _ = chain_evaluate_config_scopes(
cfg_list,
self.named_configs_to_use,
fixed=self.config_updates,
preset=self.config,
fallback=const_fallback)
preset={},
fallback=self.fallback)

# unnamed (default) configs second
self.config, self.summaries = chain_evaluate_config_scopes(
self.config_scopes,
fixed=self.config_updates,
preset=self.config,
fallback=const_fallback)
fallback=self.fallback)

self.get_config_modifications()

def run_config_hooks(self, config, config_updates):
cfg_upup, _ = chain_evaluate_config_scopes(
self.config_hooks,
fixed=config_updates,
preset=config,
fallback={})

return cfg_upup

def get_config_modifications(self):
self.config_mods = ConfigSummary()
for cfg_summary in self.summaries:
Expand Down Expand Up @@ -159,37 +178,25 @@ def _warn_about_suspicious_changes(self):
def get_configuration(scaffolding):
config = {}
for sc_path, scaffold in reversed(list(scaffolding.items())):
if not scaffold.config:
continue
if sc_path:
set_by_dotted_path(config, sc_path, scaffold.config)
else:
config.update(scaffold.config)
return config


def distribute_config_updates(scaffolding, config_updates):
if config_updates is None:
return
nested_config_updates = convert_to_nested_dict(config_updates)
for path, value in iterate_flattened(nested_config_updates):
for prefix, suffix in reversed(list(iter_path_splits(path))):
if prefix in scaffolding:
set_by_dotted_path(scaffolding[prefix].config_updates, suffix,
value)
break
# this is guaranteed to occur for one of the modrunners,
# because the exrunner has path ''


def distribute_named_configs(scaffolding, named_configs):
for ncfg in named_configs:
if os.path.exists(ncfg):
scaffolding[''].named_configs_to_use.append(ncfg)
scaffolding[''].use_named_config(ncfg)
else:
path, _, cfg_name = ncfg.rpartition('.')
if path not in scaffolding:
raise KeyError('Ingredient for named config "{}" not found'
.format(ncfg))
scaffolding[path].named_configs_to_use.append(cfg_name)
scaffolding[path].use_named_config(cfg_name)


def initialize_logging(experiment, scaffolding, loglevel=None):
Expand Down Expand Up @@ -218,13 +225,14 @@ def create_scaffolding(experiment, sorted_ingredients):
scaffolding = OrderedDict()
for ingredient in sorted_ingredients:
scaffolding[ingredient] = Scaffold(
ingredient.cfgs,
config_scopes=ingredient.cfgs,
subrunners=OrderedDict([(scaffolding[m].path, scaffolding[m])
for m in ingredient.ingredients]),
path=ingredient.path if ingredient != experiment else '',
captured_functions=ingredient.captured_functions,
commands=ingredient.commands,
named_configs=ingredient.named_configs,
config_hooks=ingredient.config_hooks,
generate_seed=ingredient.gen_seed)
return OrderedDict([(sc.path, sc) for sc in scaffolding.values()])

Expand Down Expand Up @@ -272,22 +280,32 @@ def create_run(experiment, command_name, config_updates=None, log_level=None,
sorted_ingredients = gather_ingredients_topological(experiment)
scaffolding = create_scaffolding(experiment, sorted_ingredients)

command_name, config_updates, named_configs = \
execute_pre_runs(sorted_ingredients, command_name, config_updates,
named_configs)

distribute_config_updates(scaffolding, config_updates)
# --------- configuration process -------------------
distribute_named_configs(scaffolding, named_configs)
config_updates = config_updates or {}
config_updates = convert_to_nested_dict(config_updates)

past_paths = set()
for scaffold in scaffolding.values():
scaffold.pick_relevant_config_updates(config_updates, past_paths)
past_paths.add(scaffold.path)
scaffold.gather_fallbacks()
scaffold.set_up_config()

# update global config
config = get_configuration(scaffolding)
# run config hooks
config_updates_update = scaffold.run_config_hooks(config, config_updates)
recursive_update(config_updates, config_updates_update)

for scaffold in reversed(list(scaffolding.values())):
scaffold.set_up_seed() # partially recursive

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

# ----------------------------------------------------

experiment_info = experiment.get_experiment_info()
host_info = get_host_info()
main_function = get_command(scaffolding, command_name)
Expand Down

0 comments on commit f46d04d

Please sign in to comment.