Source code for datalad_next.patches.configuration

"""Enable ``configuration()`` to query ``global`` scope without a dataset

"""

__docformat__ = 'restructuredtext'


import logging

from datalad import cfg as dlcfg
from datalad.distribution.dataset import require_dataset
from datalad_next.commands import (
    build_doc,
    datasetmethod,
    eval_results,
    get_status_dict,
)
from datalad.interface.common_cfg import definitions as cfg_defs
from datalad.local import configuration as conf_mod
from datalad.local.configuration import (
    config_actions,
    _dump,
    _get,
    _set,
    _unset,
)
from datalad_next.exceptions import NoDatasetFound
from datalad_next.utils import ensure_list
from datalad_next.datasets import (
    Dataset,
)

lgr = logging.getLogger('datalad.local.configuration')


[docs] @build_doc class Configuration(conf_mod.Configuration): """"""
[docs] @staticmethod @datasetmethod(name='configuration') @eval_results def __call__( action='dump', spec=None, *, scope=None, dataset=None, recursive=False, recursion_limit=None): # check conditions # - global and recursion makes no sense if action == 'dump': if scope: raise ValueError( 'Scope selection is not supported for dumping') # normalize variable specifications specs = [] for s in ensure_list(spec): if isinstance(s, tuple): specs.append((str(s[0]), str(s[1]))) elif '=' not in s: specs.append((str(s),)) else: specs.append(tuple(s.split('=', 1))) if action == 'set': missing_values = [s[0] for s in specs if len(s) < 2] if missing_values: raise ValueError( 'Values must be provided for all configuration ' 'settings. Missing: {}'.format(missing_values)) invalid_names = [s[0] for s in specs if '.' not in s[0]] if invalid_names: raise ValueError( 'Name must contain a section (i.e. "section.name"). ' 'Invalid: {}'.format(invalid_names)) ds = None if scope != 'global' or recursive: try: ds = require_dataset( dataset, check_installed=True, purpose='configure') except NoDatasetFound: if action not in ('dump', 'get') or dataset: raise res_kwargs = dict( action='configuration', logger=lgr, ) if ds: res_kwargs['refds'] = ds.path yield from configuration(action, scope, specs, res_kwargs, ds) if not recursive: return for subds in ds.subdatasets( state='present', recursive=True, recursion_limit=recursion_limit, on_failure='ignore', return_type='generator', result_renderer='disabled'): yield from configuration( action, scope, specs, res_kwargs, Dataset(subds['path']))
[docs] def configuration(action, scope, specs, res_kwargs, ds=None): # go with the more specific dataset configmanager, if we are # operating on a dataset cfg = dlcfg if ds is None else ds.config if action not in config_actions: raise ValueError("Unsupported action '{}'".format(action)) if action == 'dump': if not specs: # dumping is querying for all known keys specs = [ (n,) for n in sorted( set(cfg_defs.keys()).union(cfg.keys())) ] scope = None for spec in specs: if '.' not in spec[0]: yield get_status_dict( ds=ds, status='error', message=( "Configuration key without a section: '%s'", spec[0], ), **res_kwargs) continue # TODO without get-all there is little sense in having add #if action == 'add': # res = _add(cfg, scope, spec) if action == 'get': res = _get(cfg, scope, spec[0]) # `None` is a value that cannot be set in the config. # if it is returned, it indicates that no value was set # we need to communicate that back, because a None value # cannot be reported as such by the CLI # (only via, e.g. JSON, encoding). # It makes sense to communicate that getting this specific # configuration item is "impossible" (because it is not set). # if a caller wants to tollerate this scenario, they can # set on_failure='ignore' if res.get('value') is None: res['status'] = 'impossible' res['message'] = ( 'key %r not set in configuration%s', res['name'], f" scope '{scope}'" if scope else '', ) elif action == 'dump': res = _dump(cfg, spec[0]) # TODO this should be there, if we want to be comprehensive # however, we turned this off by default in the config manager # because we hardly use it, and the handling in ConfigManager # is not really well done. #elif action == 'get-all': # res = _get_all(cfg, scope, spec) elif action == 'set': res = _set(cfg, scope, *spec) elif action == 'unset': res = _unset(cfg, scope, spec[0]) if ds: res['path'] = ds.path if 'status' not in res: res['status'] = 'ok' yield dict(res_kwargs, **res) if action in ('add', 'set', 'unset'): # we perform a single reload, rather than one for each modification # TODO: can we detect a call from cmdline? We could skip the reload. cfg.reload(force=True)
conf_mod.Configuration.__call__ = Configuration.__call__ conf_mod.Configuration._params_['scope']._doc = """\ scope for getting or setting configuration. If no scope is declared for a query, all configuration sources (including overrides via environment variables) are considered according to the normal rules of precedence. A 'get' action can be constrained to scope 'branch', otherwise 'global' is used when not operating on a dataset, or 'local' (including 'global', when operating on a dataset. For action 'dump', a scope selection is ignored and all available scopes are considered.""" conf_mod.Configuration.__call__.__doc__ = None conf_mod.Configuration = build_doc(conf_mod.Configuration)