Exemplo n.º 1
0
 def get_config_modifications(self):
     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)
Exemplo n.º 2
0
 def __call__(self, fixed=None, preset=None, fallback=None):
     result = dogmatize(fixed or {})
     recursive_fill_in(result, self._conf)
     recursive_fill_in(result, preset or {})
     added = result.revelation()
     config_summary = ConfigSummary(added, result.modified, result.typechanges)
     config_summary.update(undogmatize(result))
     return config_summary
Exemplo n.º 3
0
 def __call__(self, fixed=None, preset=None, fallback=None):
     result = dogmatize(fixed or {})
     result.update(preset)
     result.update(self._conf)
     added = result.revelation()
     config_summary = ConfigSummary(added, result.modified, result.typechanges)
     config_summary.update(undogmatize(result))
     return config_summary
Exemplo n.º 4
0
def run():
    config = {'a': 17, 'foo': {'bar': True, 'baz': False}, 'seed': 1234}
    config_mod = ConfigSummary()
    main_func = mock.Mock(return_value=123)
    logger = mock.Mock()
    observer = [mock.Mock()]
    return Run(config, config_mod, main_func, observer, logger, logger, {}, {},
               [], [])
Exemplo n.º 5
0
    def __call__(self, fixed=None, preset=None, fallback=None):
        """
        Evaluate this ConfigScope.

        This will evaluate the function body and fill the relevant local
        variables into entries into keys in this dictionary.

        :param fixed: Dictionary of entries that should stay fixed during the
                      evaluation. All of them will be part of the final config.
        :type fixed: dict
        :param preset: Dictionary of preset values that will be available
                       during the evaluation (if they are declared in the
                       function argument list). All of them will be part of the
                       final config.
        :type preset: dict
        :param fallback: Dictionary of fallback values that will be available
                         during the evaluation (if they are declared in the
                         function argument list). They will NOT be part of the
                         final config.
        :type fallback: dict
        :return: self
        :rtype: ConfigScope
        """
        cfg_locals = dogmatize(fixed or {})
        fallback = fallback or {}
        preset = preset or {}
        fallback_view = {}

        available_entries = set(preset.keys()) | set(fallback.keys())

        for arg in self.arg_spec.args:
            if arg not in available_entries:
                raise KeyError("'{}' not in preset for ConfigScope. "
                               "Available options are: {}".format(
                                   arg, available_entries))
            if arg in preset:
                cfg_locals[arg] = preset[arg]
            else:  # arg in fallback
                fallback_view[arg] = fallback[arg]

        cfg_locals.fallback = fallback_view
        eval(self._body_code, copy(self._func.__globals__), cfg_locals)

        added = cfg_locals.revelation()
        config_summary = ConfigSummary(added,
                                       cfg_locals.modified,
                                       cfg_locals.typechanges,
                                       cfg_locals.fallback_writes,
                                       docs=self._var_docs)
        # fill in the unused presets
        recursive_fill_in(cfg_locals, preset)

        for key, value in cfg_locals.items():
            try:
                config_summary[key] = normalize_or_die(value)
            except ValueError:
                pass
        return config_summary
Exemplo n.º 6
0
def run():
    config = {'a': 17, 'foo': {'bar': True, 'baz': False}, 'seed': 1234}
    config_mod = ConfigSummary()
    signature = mock.Mock()
    signature.name = 'main_func'
    main_func = mock.Mock(return_value=123, prefix='', signature=signature)
    logger = mock.Mock()
    observer = [mock.Mock(priority=10)]
    return Run(config, config_mod, main_func, observer, logger, logger, {},
               {}, [], [])
Exemplo n.º 7
0
def run():
    config = {"a": 17, "foo": {"bar": True, "baz": False}, "seed": 1234}
    config_mod = ConfigSummary()
    signature = mock.Mock()
    signature.name = "main_func"
    main_func = mock.Mock(return_value=123, prefix="", signature=signature)
    logger = mock.Mock()
    observer = [mock.Mock(priority=10)]
    return Run(config, config_mod, main_func, observer, logger, logger, {}, {},
               [], [])
Exemplo n.º 8
0
def test_iterate_marked(cfg):
    assert list(_iterate_marked(cfg, ConfigSummary())) == [
        ("a", ConfigEntry("a", 0, False, False, None, None)),
        ("b", ConfigEntry("b", {}, False, False, None, None)),
        ("c", PathEntry("c", False, False, None, None)),
        ("c.cA", ConfigEntry("cA", 3, False, False, None, None)),
        ("c.cB", ConfigEntry("cB", 4, False, False, None, None)),
        ("c.cC", PathEntry("cC", False, False, None, None)),
        ("c.cC.cC1", ConfigEntry("cC1", 6, False, False, None, None)),
        ("d", PathEntry("d", False, False, None, None)),
        ("d.dA", ConfigEntry("dA", 8, False, False, None, None)),
    ]
Exemplo n.º 9
0
def test_iterate_marked(cfg):
    assert list(_iterate_marked(cfg, ConfigSummary())) == \
        [('a', ConfigEntry('a', 0, False, False, None, None)),
         ('b', ConfigEntry('b', {}, False, False, None, None)),
         ('c', PathEntry('c', False, False, None, None)),
         ('c.cA', ConfigEntry('cA', 3, False, False, None, None)),
         ('c.cB', ConfigEntry('cB', 4, False, False, None, None)),
         ('c.cC', PathEntry('cC', False, False, None, None)),
         ('c.cC.cC1', ConfigEntry('cC1', 6, False, False, None, None)),
         ('d', PathEntry('d', False, False, None, None)),
         ('d.dA', ConfigEntry('dA', 8, False, False, None, None))
         ]
Exemplo n.º 10
0
def test_format_config(cfg):
    cfg_text = _format_config(cfg, ConfigSummary())
    lines = cfg_text.split('\n')
    assert lines[0].startswith('Configuration')
    assert lines[1].find(' a = 0') > -1
    assert lines[2].find(' b = {}') > -1
    assert lines[3].find(' c:') > -1
    assert lines[4].find(' cA = 3') > -1
    assert lines[5].find(' cB = 4') > -1
    assert lines[6].find(' cC:') > -1
    assert lines[7].find(' cC1 = 6') > -1
    assert lines[8].find(' d:') > -1
    assert lines[9].find(' dA = 8') > -1
Exemplo n.º 11
0
def test_iterate_marked_added(cfg):
    added = {'a', 'c.cB', 'c.cC.cC1'}
    assert list(iterate_marked(cfg, ConfigSummary(added=added))) == \
        [('a', ConfigEntry('a', 0, True, False, None)),
         ('b', ConfigEntry('b', {}, False, False, None)),
         ('c', PathEntry('c', False, True, None)),
         ('c.cA', ConfigEntry('cA', 3, False, False, None)),
         ('c.cB', ConfigEntry('cB', 4, True, False, None)),
         ('c.cC', PathEntry('cC', False, True, None)),
         ('c.cC.cC1', ConfigEntry('cC1', 6, True, False, None)),
         ('d', PathEntry('d', False, False, None)),
         ('d.dA', ConfigEntry('dA', 8, False, False, None))
         ]
Exemplo n.º 12
0
def test_iterate_marked_updated(cfg):
    modified = {"b", "c", "c.cC.cC1"}
    assert list(_iterate_marked(cfg, ConfigSummary(modified=modified))) == [
        ("a", ConfigEntry("a", 0, False, False, None, None)),
        ("b", ConfigEntry("b", {}, False, True, None, None)),
        ("c", PathEntry("c", False, True, None, None)),
        ("c.cA", ConfigEntry("cA", 3, False, False, None, None)),
        ("c.cB", ConfigEntry("cB", 4, False, False, None, None)),
        ("c.cC", PathEntry("cC", False, True, None, None)),
        ("c.cC.cC1", ConfigEntry("cC1", 6, False, True, None, None)),
        ("d", PathEntry("d", False, False, None, None)),
        ("d.dA", ConfigEntry("dA", 8, False, False, None, None)),
    ]
Exemplo n.º 13
0
def test_format_config(cfg):
    cfg_text = _format_config(cfg, ConfigSummary())
    lines = cfg_text.split("\n")
    assert lines[0].startswith("Configuration")
    assert " a = 0" in lines[1]
    assert " b = {}" in lines[2]
    assert " c:" in lines[3]
    assert " cA = 3" in lines[4]
    assert " cB = 4" in lines[5]
    assert " cC:" in lines[6]
    assert " cC1 = 6" in lines[7]
    assert " d:" in lines[8]
    assert " dA = 8" in lines[9]
Exemplo n.º 14
0
def test_iterate_marked_updated(cfg):
    modified = {'b', 'c', 'c.cC.cC1'}
    assert list(_iterate_marked(cfg, ConfigSummary(modified=modified))) == \
        [('a', ConfigEntry('a', 0, False, False, None, None)),
         ('b', ConfigEntry('b', {}, False, True, None, None)),
         ('c', PathEntry('c', False, True, None, None)),
         ('c.cA', ConfigEntry('cA', 3, False, False, None, None)),
         ('c.cB', ConfigEntry('cB', 4, False, False, None, None)),
         ('c.cC', PathEntry('cC', False, True, None, None)),
         ('c.cC.cC1', ConfigEntry('cC1', 6, False, True, None, None)),
         ('d', PathEntry('d', False, False, None, None)),
         ('d.dA', ConfigEntry('dA', 8, False, False, None, None))
         ]
Exemplo n.º 15
0
def test_format_config(cfg):
    cfg_text = _format_config(cfg, ConfigSummary())
    lines = cfg_text.split('\n')
    assert lines[0].startswith('Configuration')
    assert ' a = 0' in lines[1]
    assert ' b = {}' in lines[2]
    assert ' c:' in lines[3]
    assert ' cA = 3' in lines[4]
    assert ' cB = 4' in lines[5]
    assert ' cC:' in lines[6]
    assert ' cC1 = 6' in lines[7]
    assert ' d:' in lines[8]
    assert ' dA = 8' in lines[9]
Exemplo n.º 16
0
def test_iterate_marked_typechanged(cfg):
    typechanged = {'a': (bool, int), 'd.dA': (float, int)}
    result = list(_iterate_marked(cfg, ConfigSummary(typechanged=typechanged)))
    assert result == \
        [('a', ConfigEntry('a', 0, False, False, (bool, int), None)),
         ('b', ConfigEntry('b', {}, False, False, None, None)),
         ('c', PathEntry('c', False, False, None, None)),
         ('c.cA', ConfigEntry('cA', 3, False, False, None, None)),
         ('c.cB', ConfigEntry('cB', 4, False, False, None, None)),
         ('c.cC', PathEntry('cC', False, False, None, None)),
         ('c.cC.cC1', ConfigEntry('cC1', 6, False, False, None, None)),
         ('d', PathEntry('d', False, True, None, None)),
         ('d.dA', ConfigEntry('dA', 8, False, False, (float, int), None))
         ]
Exemplo n.º 17
0
def test_iterate_marked_typechanged(cfg):
    typechanged = {"a": (bool, int), "d.dA": (float, int)}
    result = list(_iterate_marked(cfg, ConfigSummary(typechanged=typechanged)))
    assert result == [
        ("a", ConfigEntry("a", 0, False, False, (bool, int), None)),
        ("b", ConfigEntry("b", {}, False, False, None, None)),
        ("c", PathEntry("c", False, False, None, None)),
        ("c.cA", ConfigEntry("cA", 3, False, False, None, None)),
        ("c.cB", ConfigEntry("cB", 4, False, False, None, None)),
        ("c.cC", PathEntry("cC", False, False, None, None)),
        ("c.cC.cC1", ConfigEntry("cC1", 6, False, False, None, None)),
        ("d", PathEntry("d", False, True, None, None)),
        ("d.dA", ConfigEntry("dA", 8, False, False, (float, int), None)),
    ]
Exemplo n.º 18
0
def run():
    config = {"a": 17, "foo": {"bar": True, "baz": False}, "seed": 1234}
    config_mod = ConfigSummary()
    signature = mock.Mock()
    signature.name = "main_func"

    def side_effect(*args):
        # TODO : Type checking ? Does mock have a function for this ?
        for arg in args:
            arg = arg + 10

        return args

    main_func = mock.Mock(return_value=123,
                          prefix="",
                          signature=signature,
                          side_effect=side_effect)
    logger = mock.Mock()
    observer = [mock.Mock(priority=10)]
    return Run(config, config_mod, main_func, observer, logger, logger, {}, {},
               [], [])
Exemplo n.º 19
0
def get_config_modifications(scaffolding):
    config_modifications = ConfigSummary()
    for sc_path, scaffold in scaffolding.items():
        config_modifications.update_from(scaffold.config_mods, path=sc_path)
    return config_modifications
Exemplo n.º 20
0
class Scaffold(object):
    def __init__(self, config_scopes, subrunners, path, captured_functions,
                 commands, named_configs, 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_updates = {}
        self.named_configs_to_use = []
        self.config = None
        self.fixture = None  # TODO: rename
        self.logger = None
        self.seed = None
        self.rnd = None
        self._captured_functions = captured_functions
        self.commands = commands
        self.config_mods = None
        self.summaries = []

    def set_up_seed(self, rnd=None):
        if self.seed is not None:
            return

        self.seed = self.config.get('seed') or get_seed(rnd)
        self.rnd = create_rnd(self.seed)

        if self.generate_seed:
            self.config['seed'] = self.seed

        if 'seed' in self.config and 'seed' in self.config_mods.added:
            self.config_mods.modified.add('seed')
            self.config_mods.added -= {'seed'}

        # Hierarchically set the seed of proper subrunners
        for subrunner_path, subrunner in reversed(list(
                self.subrunners.items())):
            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

        # gather presets
        fallback = {}
        for sr_path, subrunner in self.subrunners.items():
            if self.path and is_prefix(self.path, sr_path):
                path = sr_path[len(self.path):].strip('.')
                set_by_dotted_path(fallback, path, subrunner.config)
            else:
                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 = {}

        # 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])

        self.config_updates, _ = chain_evaluate_config_scopes(
            cfg_list,
            fixed=self.config_updates,
            preset=self.config,
            fallback=const_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)

        self.get_config_modifications()

    def get_config_modifications(self):
        self.config_mods = ConfigSummary()
        for cfg_summary in self.summaries:
            self.config_mods.update_from(cfg_summary)

    def get_fixture(self):
        if self.fixture is not None:
            return self.fixture

        self.fixture = copy(self.config)
        for sr_path, subrunner in self.subrunners.items():
            sub_fix = subrunner.get_fixture()
            sub_path = sr_path
            if is_prefix(self.path, sub_path):
                sub_path = sr_path[len(self.path):].strip('.')
            # Note: This might fail if we allow non-dict fixtures
            set_by_dotted_path(self.fixture, sub_path, sub_fix)
        return self.fixture

    def finalize_initialization(self, run):
        # look at seed again, because it might have changed during the
        # configuration process
        if 'seed' in self.config:
            self.seed = self.config['seed']
        self.rnd = create_rnd(self.seed)

        for cfunc in self._captured_functions:
            cfunc.logger = self.logger.getChild(cfunc.__name__)
            cfunc.config = get_by_dotted_path(self.get_fixture(), cfunc.prefix)
            seed = get_seed(self.rnd)
            cfunc.rnd = create_rnd(seed)
            cfunc.run = run

        self._warn_about_suspicious_changes()

    def _warn_about_suspicious_changes(self):
        for add in sorted(self.config_mods.added):
            self.logger.warning('Added new config entry: "%s"' % add)

        for key, (type_old, type_new) in self.config_mods.typechanged.items():
            if (isinstance(type_old, type(None)) or
                    (type_old in (int, float) and type_new in (int, float))):
                continue
            self.logger.warning(
                'Changed type of config entry "%s" from %s to %s' %
                (key, type_old.__name__, type_new.__name__))

        for cfg_summary in self.summaries:
            for key in cfg_summary.ignored_fallbacks:
                self.logger.warning(
                    'Ignored attempt to set value of "%s", because it is an '
                    'ingredient.' % key
                )
Exemplo n.º 21
0
 def get_config_modifications(self):
     self.config_mods = ConfigSummary()
     for cfg_summary in self.summaries:
         self.config_mods.update_from(cfg_summary)
Exemplo n.º 22
0
class Scaffold(object):
    def __init__(self, config_scopes, subrunners, path, captured_functions,
                 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 = {}
        self.fallback = None
        self.presets = {}
        self.fixture = None  # TODO: rename
        self.logger = None
        self.seed = None
        self.rnd = None
        self._captured_functions = captured_functions
        self.commands = commands
        self.config_mods = None
        self.summaries = []
        self.captured_args = {
            join_paths(cf.prefix, n)
            for cf in self._captured_functions for n in cf.signature.arguments
        }
        self.captured_args.add('__doc__')  # allow setting the config docstring

    def set_up_seed(self, rnd=None):
        if self.seed is not None:
            return

        self.seed = self.config.get('seed')
        if self.seed is None:
            self.seed = get_seed(rnd)

        self.rnd = create_rnd(self.seed)

        if self.generate_seed:
            self.config['seed'] = self.seed

        if 'seed' in self.config and 'seed' in self.config_mods.added:
            self.config_mods.modified.add('seed')
            self.config_mods.added -= {'seed'}

        # Hierarchically set the seed of proper subrunners
        for subrunner_path, subrunner in reversed(list(
                self.subrunners.items())):
            if is_prefix(self.path, subrunner_path):
                subrunner.set_up_seed(self.rnd)

    def gather_fallbacks(self):
        fallback = {'_log': self.logger}
        for sr_path, subrunner in self.subrunners.items():
            if self.path and is_prefix(self.path, sr_path):
                path = sr_path[len(self.path):].strip('.')
                set_by_dotted_path(fallback, path, subrunner.config)
            else:
                set_by_dotted_path(fallback, sr_path, subrunner.config)

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

    def run_named_config(self, config_name):
        if os.path.isfile(config_name):
            nc = ConfigDict(load_config_file(config_name))
        else:
            if config_name not in self.named_configs:
                raise NamedConfigNotFoundError(named_config=config_name,
                                               available_named_configs=tuple(
                                                   self.named_configs.keys()))
            nc = self.named_configs[config_name]

        cfg = nc(fixed=self.get_config_updates_recursive(),
                 preset=self.presets,
                 fallback=self.fallback)

        return undogmatize(cfg)

    def set_up_config(self):
        self.config, self.summaries = chain_evaluate_config_scopes(
            self.config_scopes,
            fixed=self.config_updates,
            preset=self.config,
            fallback=self.fallback)

        self.get_config_modifications()

    def run_config_hooks(self, config, command_name, logger):
        final_cfg_updates = {}
        for ch in self.config_hooks:
            cfg_upup = ch(deepcopy(config), command_name, logger)
            if cfg_upup:
                recursive_update(final_cfg_updates, cfg_upup)
        recursive_update(final_cfg_updates, self.config_updates)
        return final_cfg_updates

    def get_config_modifications(self):
        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)

    def get_config_updates_recursive(self):
        config_updates = self.config_updates.copy()
        for sr_path, subrunner in self.subrunners.items():
            if not is_prefix(self.path, sr_path):
                continue
            update = subrunner.get_config_updates_recursive()
            if update:
                config_updates[rel_path(self.path, sr_path)] = update
        return config_updates

    def get_fixture(self):
        if self.fixture is not None:
            return self.fixture

        def get_fixture_recursive(runner):
            for sr_path, subrunner in runner.subrunners.items():
                # I am not sure if it is necessary to trigger all
                subrunner.get_fixture()
                get_fixture_recursive(subrunner)
                sub_fix = copy(subrunner.config)
                sub_path = sr_path
                if is_prefix(self.path, sub_path):
                    sub_path = sr_path[len(self.path):].strip('.')
                # Note: This might fail if we allow non-dict fixtures
                set_by_dotted_path(self.fixture, sub_path, sub_fix)

        self.fixture = copy(self.config)
        get_fixture_recursive(self)

        return self.fixture

    def finalize_initialization(self, run):
        # look at seed again, because it might have changed during the
        # configuration process
        if 'seed' in self.config:
            self.seed = self.config['seed']
        self.rnd = create_rnd(self.seed)

        for cfunc in self._captured_functions:
            # Setup the captured function
            cfunc.logger = self.logger.getChild(cfunc.__name__)
            seed = get_seed(self.rnd)
            cfunc.rnd = create_rnd(seed)
            cfunc.run = run
            cfunc.config = get_by_dotted_path(self.get_fixture(),
                                              cfunc.prefix,
                                              default={})

            # Make configuration read only if enabled in settings
            if SETTINGS.CONFIG.READ_ONLY_CONFIG:
                cfunc.config = make_read_only(
                    cfunc.config,
                    error_message='The configuration is read-only in a '
                    'captured function!')

        if not run.force:
            self._warn_about_suspicious_changes()

    def _warn_about_suspicious_changes(self):
        for add in sorted(self.config_mods.added):
            if not set(iter_prefixes(add)).intersection(self.captured_args):
                if self.path:
                    add = join_paths(self.path, add)
                raise ConfigAddedError(add, config=self.config)
            else:
                self.logger.warning('Added new config entry: "%s"' % add)

        for key, (type_old, type_new) in self.config_mods.typechanged.items():
            if type_old in (int, float) and type_new in (int, float):
                continue
            self.logger.warning(
                'Changed type of config entry "%s" from %s to %s' %
                (key, type_old.__name__, type_new.__name__))

        for cfg_summary in self.summaries:
            for key in cfg_summary.ignored_fallbacks:
                self.logger.warning(
                    'Ignored attempt to set value of "%s", because it is an '
                    'ingredient.' % key)

    def __repr__(self):
        return "<Scaffold: '{}'>".format(self.path)
Exemplo n.º 23
0
class Scaffold(object):
    def __init__(self, config_scopes, subrunners, path, captured_functions,
                 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
        self.rnd = None
        self._captured_functions = captured_functions
        self.commands = commands
        self.config_mods = None
        self.summaries = []
        self.captured_args = {join_paths(cf.prefix, n)
                              for cf in self._captured_functions
                              for n in cf.signature.arguments}

    def set_up_seed(self, rnd=None):
        if self.seed is not None:
            return

        self.seed = self.config.get('seed') or get_seed(rnd)
        self.rnd = create_rnd(self.seed)

        if self.generate_seed:
            self.config['seed'] = self.seed

        if 'seed' in self.config and 'seed' in self.config_mods.added:
            self.config_mods.modified.add('seed')
            self.config_mods.added -= {'seed'}

        # Hierarchically set the seed of proper subrunners
        for subrunner_path, subrunner in reversed(list(
                self.subrunners.items())):
            if is_prefix(self.path, subrunner_path):
                subrunner.set_up_seed(self.rnd)

    def pick_relevant_config_updates(self, config_updates, past_paths):
        if config_updates is None:
            return

        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):
                path = sr_path[len(self.path):].strip('.')
                set_by_dotted_path(fallback, path, subrunner.config)
            else:
                set_by_dotted_path(fallback, sr_path, subrunner.config)

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

    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(
            self.named_configs_to_use,
            fixed=self.config_updates,
            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=self.fallback)

        self.get_config_modifications()

    def run_config_hooks(self, config, config_updates, command_name, logger):
        final_cfg_updates = {}
        for ch in self.config_hooks:
            cfg_upup = ch(deepcopy(config), command_name, logger)
            if cfg_upup:
                recursive_update(final_cfg_updates, cfg_upup)
        recursive_update(final_cfg_updates, config_updates)
        return final_cfg_updates

    def get_config_modifications(self):
        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)

    def get_fixture(self):
        if self.fixture is not None:
            return self.fixture

        self.fixture = copy(self.config)
        for sr_path, subrunner in self.subrunners.items():
            sub_fix = subrunner.get_fixture()
            sub_path = sr_path
            if is_prefix(self.path, sub_path):
                sub_path = sr_path[len(self.path):].strip('.')
            # Note: This might fail if we allow non-dict fixtures
            set_by_dotted_path(self.fixture, sub_path, sub_fix)
        return self.fixture

    def finalize_initialization(self, run):
        # look at seed again, because it might have changed during the
        # configuration process
        if 'seed' in self.config:
            self.seed = self.config['seed']
        self.rnd = create_rnd(self.seed)

        for cfunc in self._captured_functions:
            cfunc.logger = self.logger.getChild(cfunc.__name__)
            cfunc.config = get_by_dotted_path(self.get_fixture(), cfunc.prefix)
            seed = get_seed(self.rnd)
            cfunc.rnd = create_rnd(seed)
            cfunc.run = run

        if not run.force:
            self._warn_about_suspicious_changes()

    def _warn_about_suspicious_changes(self):
        for add in sorted(self.config_mods.added):
            if not set(iter_prefixes(add)).intersection(self.captured_args):
                raise KeyError('Added a new config entry "{}" that is not used'
                               ' anywhere'.format(add))
            else:
                self.logger.warning('Added new config entry: "%s"' % add)

        for key, (type_old, type_new) in self.config_mods.typechanged.items():
            if (isinstance(type_old, type(None)) or
                    (type_old in (int, float) and type_new in (int, float))):
                continue
            self.logger.warning(
                'Changed type of config entry "%s" from %s to %s' %
                (key, type_old.__name__, type_new.__name__))

        for cfg_summary in self.summaries:
            for key in cfg_summary.ignored_fallbacks:
                self.logger.warning(
                    'Ignored attempt to set value of "%s", because it is an '
                    'ingredient.' % key
                )
Exemplo n.º 24
0
 def get_config_modifications(self):
     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)
Exemplo n.º 25
0
class Scaffold(object):
    def __init__(self, config_scopes, subrunners, path, captured_functions,
                 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
        self.rnd = None
        self._captured_functions = captured_functions
        self.commands = commands
        self.config_mods = None
        self.summaries = []

    def set_up_seed(self, rnd=None):
        if self.seed is not None:
            return

        self.seed = self.config.get('seed') or get_seed(rnd)
        self.rnd = create_rnd(self.seed)

        if self.generate_seed:
            self.config['seed'] = self.seed

        if 'seed' in self.config and 'seed' in self.config_mods.added:
            self.config_mods.modified.add('seed')
            self.config_mods.added -= {'seed'}

        # Hierarchically set the seed of proper subrunners
        for subrunner_path, subrunner in reversed(list(
                self.subrunners.items())):
            if is_prefix(self.path, subrunner_path):
                subrunner.set_up_seed(self.rnd)

    def pick_relevant_config_updates(self, config_updates, past_paths):
        if config_updates is None:
            return

        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):
                path = sr_path[len(self.path):].strip('.')
                set_by_dotted_path(fallback, path, subrunner.config)
            else:
                set_by_dotted_path(fallback, sr_path, subrunner.config)

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

    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(
            self.named_configs_to_use,
            fixed=self.config_updates,
            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=self.fallback)

        self.get_config_modifications()

    def run_config_hooks(self, config, config_updates, command_name, logger):
        final_cfg_updates = {}
        for ch in self.config_hooks:
            cfg_upup = ch(deepcopy(config), command_name, logger)
            if cfg_upup:
                recursive_update(final_cfg_updates, cfg_upup)
        recursive_update(final_cfg_updates, config_updates)
        return final_cfg_updates

    def get_config_modifications(self):
        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)

    def get_fixture(self):
        if self.fixture is not None:
            return self.fixture

        self.fixture = copy(self.config)
        for sr_path, subrunner in self.subrunners.items():
            sub_fix = subrunner.get_fixture()
            sub_path = sr_path
            if is_prefix(self.path, sub_path):
                sub_path = sr_path[len(self.path):].strip('.')
            # Note: This might fail if we allow non-dict fixtures
            set_by_dotted_path(self.fixture, sub_path, sub_fix)
        return self.fixture

    def finalize_initialization(self, run):
        # look at seed again, because it might have changed during the
        # configuration process
        if 'seed' in self.config:
            self.seed = self.config['seed']
        self.rnd = create_rnd(self.seed)

        for cfunc in self._captured_functions:
            cfunc.logger = self.logger.getChild(cfunc.__name__)
            cfunc.config = get_by_dotted_path(self.get_fixture(), cfunc.prefix)
            seed = get_seed(self.rnd)
            cfunc.rnd = create_rnd(seed)
            cfunc.run = run

        self._warn_about_suspicious_changes()

    def _warn_about_suspicious_changes(self):
        for add in sorted(self.config_mods.added):
            self.logger.warning('Added new config entry: "%s"' % add)

        for key, (type_old, type_new) in self.config_mods.typechanged.items():
            if (isinstance(type_old, type(None)) or
                (type_old in (int, float) and type_new in (int, float))):
                continue
            self.logger.warning(
                'Changed type of config entry "%s" from %s to %s' %
                (key, type_old.__name__, type_new.__name__))

        for cfg_summary in self.summaries:
            for key in cfg_summary.ignored_fallbacks:
                self.logger.warning(
                    'Ignored attempt to set value of "%s", because it is an '
                    'ingredient.' % key)
Exemplo n.º 26
0
def get_config_modifications(scaffolding, config_updates):
    config_modifications = ConfigSummary()
    for sc_path, scaffold in scaffolding.items():
        config_modifications.update_add(scaffold.config_mods, path=sc_path)
    return config_modifications
Exemplo n.º 27
0
class Scaffold(object):
    def __init__(self, config_scopes, subrunners, path, captured_functions,
                 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 = {}
        self.fallback = None
        self.presets = {}
        self.fixture = None  # TODO: rename
        self.logger = None
        self.seed = None
        self.rnd = None
        self._captured_functions = captured_functions
        self.commands = commands
        self.config_mods = None
        self.summaries = []
        self.captured_args = {join_paths(cf.prefix, n)
                              for cf in self._captured_functions
                              for n in cf.signature.arguments}
        self.captured_args.add('__doc__')  # allow setting the config docstring

    def set_up_seed(self, rnd=None):
        if self.seed is not None:
            return

        self.seed = self.config.get('seed')
        if self.seed is None:
            self.seed = get_seed(rnd)

        self.rnd = create_rnd(self.seed)

        if self.generate_seed:
            self.config['seed'] = self.seed

        if 'seed' in self.config and 'seed' in self.config_mods.added:
            self.config_mods.modified.add('seed')
            self.config_mods.added -= {'seed'}

        # Hierarchically set the seed of proper subrunners
        for subrunner_path, subrunner in reversed(list(
                self.subrunners.items())):
            if is_prefix(self.path, subrunner_path):
                subrunner.set_up_seed(self.rnd)

    def gather_fallbacks(self):
        fallback = {'_log': self.logger}
        for sr_path, subrunner in self.subrunners.items():
            if self.path and is_prefix(self.path, sr_path):
                path = sr_path[len(self.path):].strip('.')
                set_by_dotted_path(fallback, path, subrunner.config)
            else:
                set_by_dotted_path(fallback, sr_path, subrunner.config)

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

    def run_named_config(self, config_name):
        if os.path.isfile(config_name):
            nc = ConfigDict(load_config_file(config_name))
        else:
            if config_name not in self.named_configs:
                raise NamedConfigNotFoundError(
                    named_config=config_name,
                    available_named_configs=tuple(self.named_configs.keys()))
            nc = self.named_configs[config_name]

        cfg = nc(fixed=self.get_config_updates_recursive(),
                 preset=self.presets,
                 fallback=self.fallback)

        return undogmatize(cfg)

    def set_up_config(self):
        self.config, self.summaries = chain_evaluate_config_scopes(
            self.config_scopes,
            fixed=self.config_updates,
            preset=self.config,
            fallback=self.fallback)

        self.get_config_modifications()

    def run_config_hooks(self, config, command_name, logger):
        final_cfg_updates = {}
        for ch in self.config_hooks:
            cfg_upup = ch(deepcopy(config), command_name, logger)
            if cfg_upup:
                recursive_update(final_cfg_updates, cfg_upup)
        recursive_update(final_cfg_updates, self.config_updates)
        return final_cfg_updates

    def get_config_modifications(self):
        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)

    def get_config_updates_recursive(self):
        config_updates = self.config_updates.copy()
        for sr_path, subrunner in self.subrunners.items():
            if not is_prefix(self.path, sr_path):
                continue
            update = subrunner.get_config_updates_recursive()
            if update:
                config_updates[rel_path(self.path, sr_path)] = update
        return config_updates

    def get_fixture(self):
        if self.fixture is not None:
            return self.fixture

        def get_fixture_recursive(runner):
            for sr_path, subrunner in runner.subrunners.items():
                # I am not sure if it is necessary to trigger all
                subrunner.get_fixture()
                get_fixture_recursive(subrunner)
                sub_fix = copy(subrunner.config)
                sub_path = sr_path
                if is_prefix(self.path, sub_path):
                    sub_path = sr_path[len(self.path):].strip('.')
                # Note: This might fail if we allow non-dict fixtures
                set_by_dotted_path(self.fixture, sub_path, sub_fix)

        self.fixture = copy(self.config)
        get_fixture_recursive(self)

        return self.fixture

    def finalize_initialization(self, run):
        # look at seed again, because it might have changed during the
        # configuration process
        if 'seed' in self.config:
            self.seed = self.config['seed']
        self.rnd = create_rnd(self.seed)

        for cfunc in self._captured_functions:
            cfunc.logger = self.logger.getChild(cfunc.__name__)
            cfunc.config = get_by_dotted_path(self.get_fixture(), cfunc.prefix,
                                              default={})
            seed = get_seed(self.rnd)
            cfunc.rnd = create_rnd(seed)
            cfunc.run = run

        if not run.force:
            self._warn_about_suspicious_changes()

    def _warn_about_suspicious_changes(self):
        for add in sorted(self.config_mods.added):
            if not set(iter_prefixes(add)).intersection(self.captured_args):
                if self.path:
                    add = join_paths(self.path, add)
                raise ConfigAddedError(add, config=self.config)
            else:
                self.logger.warning('Added new config entry: "%s"' % add)

        for key, (type_old, type_new) in self.config_mods.typechanged.items():
            if type_old in (int, float) and type_new in (int, float):
                continue
            self.logger.warning(
                'Changed type of config entry "%s" from %s to %s' %
                (key, type_old.__name__, type_new.__name__))

        for cfg_summary in self.summaries:
            for key in cfg_summary.ignored_fallbacks:
                self.logger.warning(
                    'Ignored attempt to set value of "%s", because it is an '
                    'ingredient.' % key
                )

    def __repr__(self):
        return "<Scaffold: '{}'>".format(self.path)