gin/config.py
def _make_configurable(fn_or_cls,
                       name=None,
                       module=None,
                       whitelist=None,
                       blacklist=None,
                       subclass=False):
  """Wraps `fn_or_cls` to make it configurable.

  Infers the configurable name from `fn_or_cls.__name__` if necessary, and
  updates global state to keep track of configurable name <-> function
  mappings, as well as whitelisted and blacklisted parameters.

  Args:
    fn_or_cls: The function or class to decorate.
    name: A name for the configurable. If `None`, the name will be inferred from
      from `fn_or_cls`. The `name` may also include module components to be used
      for disambiguation (these will be appended to any components explicitly
      specified by `module`).
    module: The module to associate with the configurable, to help handle naming
      collisions. If `None`, `fn_or_cls.__module__` will be used (if no module
      is specified as part of `name`).
    whitelist: A whitelisted set of parameter names to supply values for.
    blacklist: A blacklisted set of parameter names not to supply values for.
    subclass: If `fn_or_cls` is a class and `subclass` is `True`, decorate by
      subclassing `fn_or_cls` and overriding its `__init__` method. If `False`,
      replace the existing `__init__` with a decorated version.

  Returns:
    A wrapped version of `fn_or_cls` that will take parameter values from the
    global configuration.

  Raises:
    RuntimeError: If the config is locked.
    ValueError: If a configurable with `name` (or the name of `fn_or_cls`)
      already exists, or if both a whitelist and blacklist are specified.
  """
  if config_is_locked():
    err_str = 'Attempted to add a new configurable after the config was locked.'
    raise RuntimeError(err_str)

  name = fn_or_cls.__name__ if name is None else name
  if config_parser.IDENTIFIER_RE.match(name):
    default_module = getattr(fn_or_cls, '__module__', None)
    module = default_module if module is None else module
  elif not config_parser.MODULE_RE.match(name):
    raise ValueError("Configurable name '{}' is invalid.".format(name))

  if module is not None and not config_parser.MODULE_RE.match(module):
    raise ValueError("Module '{}' is invalid.".format(module))

  selector = module + '.' + name if module else name
  if not _INTERACTIVE_MODE and selector in _REGISTRY:
    err_str = "A configurable matching '{}' already exists."
    raise ValueError(err_str.format(selector))

  if whitelist and blacklist:
    err_str = 'A whitelist or a blacklist can be specified, but not both.'
    raise ValueError(err_str)

  if whitelist and not isinstance(whitelist, (list, tuple)):
    raise TypeError('Whitelist should be a list or tuple.')

  if blacklist and not isinstance(blacklist, (list, tuple)):
    raise TypeError('Blacklist should be a list or tuple.')

  _validate_parameters(fn_or_cls, whitelist, 'whitelist')
  _validate_parameters(fn_or_cls, blacklist, 'blacklist')

  def apply_config(fn):
    """Wraps `fn` so that it obtains parameters from the configuration."""

    @six.wraps(fn)
    def wrapper(*args, **kwargs):
      """Supplies fn with parameter values from the configuration."""
      scope_components = current_scope()
      new_kwargs = {}
      for i in range(len(scope_components) + 1):
        partial_scope_str = '/'.join(scope_components[:i])
        new_kwargs.update(_CONFIG.get((partial_scope_str, selector), {}))
      gin_bound_args = list(new_kwargs.keys())
      scope_str = partial_scope_str

      arg_names = _get_supplied_positional_parameter_names(fn, args)

      for arg in args[len(arg_names):]:
        if arg is REQUIRED:
          raise ValueError(
              'gin.REQUIRED is not allowed for unnamed (vararg) parameters. If '
              'the function being called is wrapped by a non-Gin decorator, '
              'try explicitly providing argument names for positional '
              'parameters.')

      required_arg_names = []
      required_arg_indexes = []
      for i, arg in enumerate(args[:len(arg_names)]):
        if arg is REQUIRED:
          required_arg_names.append(arg_names[i])
          required_arg_indexes.append(i)

      required_kwargs = []
      for kwarg, value in six.iteritems(kwargs):
        if value is REQUIRED:
          required_kwargs.append(kwarg)

      # If the caller passed arguments as positional arguments that correspond
      # to a keyword arg in new_kwargs, remove the keyword argument from
      # new_kwargs to let the caller win and avoid throwing an error. Unless it
      # is an arg marked as REQUIRED.
      for arg_name in arg_names:
        if arg_name not in required_arg_names:
          new_kwargs.pop(arg_name, None)

      # Get default values for configurable parameters.
      operative_parameter_values = _get_default_configurable_parameter_values(
          fn, whitelist, blacklist)
      # Update with the values supplied via configuration.
      operative_parameter_values.update(new_kwargs)

      # Remove any values from the operative config that are overridden by the
      # caller. These can't be configured, so they won't be logged. We skip
      # values that are marked as REQUIRED.
      for k in arg_names:
        if k not in required_arg_names:
          operative_parameter_values.pop(k, None)
      for k in kwargs:
        if k not in required_kwargs:
          operative_parameter_values.pop(k, None)

      # An update is performed in case another caller of this same configurable
      # object has supplied a different set of arguments. By doing an update, a
      # Gin-supplied or default value will be present if it was used (not
      # overridden by the caller) at least once.
      _OPERATIVE_CONFIG.setdefault((scope_str, selector), {}).update(
          operative_parameter_values)

      # We call deepcopy for two reasons: First, to prevent the called function
      # from modifying any of the values in `_CONFIG` through references passed
      # in via `new_kwargs`; Second, to facilitate evaluation of any
      # `ConfigurableReference` instances buried somewhere inside
      # `new_kwargs`. See the docstring on `ConfigurableReference.__deepcopy__`
      # above for more details on the dark magic happening here.
      new_kwargs = copy.deepcopy(new_kwargs)

      # Validate args marked as REQUIRED have been bound in the Gin config.
      missing_required_params = []
      new_args = list(args)
      for i, arg_name in zip(required_arg_indexes, required_arg_names):
        if arg_name not in new_kwargs:
          missing_required_params.append(arg_name)
        else:
          new_args[i] = new_kwargs.pop(arg_name)

      # Validate kwargs marked as REQUIRED have been bound in the Gin config.
      for kwarg in required_kwargs:
        if kwarg not in new_kwargs:
          missing_required_params.append(kwarg)
        else:
          # Remove from kwargs and let the new_kwargs value be used.
          kwargs.pop(kwarg)

      if missing_required_params:
        err_str = 'Required bindings for `{}` not provided in config: {}'
        minimal_selector = _REGISTRY.minimal_selector(selector)
        err_str = err_str.format(minimal_selector, missing_required_params)
        raise RuntimeError(err_str)

      # Now, update with the caller-supplied `kwargs`, allowing the caller to
      # have the final say on keyword argument values.
      new_kwargs.update(kwargs)

      try:
        return fn(*new_args, **new_kwargs)
      except Exception as e:  # pylint: disable=broad-except
        err_str = ''
        if isinstance(e, TypeError):
          all_arg_names = _get_all_positional_parameter_names(fn)
          if len(new_args) < len(all_arg_names):
            unbound_positional_args = list(
                set(all_arg_names[len(new_args):]) - set(new_kwargs))
            if unbound_positional_args:
              caller_supplied_args = list(
                  set(arg_names + list(kwargs)) -
                  set(required_arg_names + list(required_kwargs)))
              fmt = ('\n  No values supplied by Gin or caller for arguments: {}'
                     '\n  Gin had values bound for: {gin_bound_args}'
                     '\n  Caller supplied values for: {caller_supplied_args}')
              canonicalize = lambda x: list(map(str, sorted(x)))
              err_str += fmt.format(
                  canonicalize(unbound_positional_args),
                  gin_bound_args=canonicalize(gin_bound_args),
                  caller_supplied_args=canonicalize(caller_supplied_args))
        err_str += "\n  In call to configurable '{}' ({}){}"
        scope_info = " in scope '{}'".format(scope_str) if scope_str else ''
        err_str = err_str.format(name, fn, scope_info)
        utils.augment_exception_message_and_reraise(e, err_str)

    return wrapper

  decorated_fn_or_cls = _decorate_fn_or_cls(
      apply_config, fn_or_cls, subclass=subclass)

  _REGISTRY[selector] = Configurable(
      decorated_fn_or_cls,
      name=name,
      module=module,
      whitelist=whitelist,
      blacklist=blacklist,
      selector=selector)
  return decorated_fn_or_cls
def _make_configurable(fn_or_cls,
                       name=None,
                       module=None,
                       whitelist=None,
                       blacklist=None,
                       subclass=False):
  """Wraps `fn_or_cls` to make it configurable.

  Infers the configurable name from `fn_or_cls.__name__` if necessary, and
  updates global state to keep track of configurable name <-> function
  mappings, as well as whitelisted and blacklisted parameters.

  Args:
    fn_or_cls: The function or class to decorate.
    name: A name for the configurable. If `None`, the name will be inferred from
      from `fn_or_cls`. The `name` may also include module components to be used
      for disambiguation (these will be appended to any components explicitly
      specified by `module`).
    module: The module to associate with the configurable, to help handle naming
      collisions. If `None`, `fn_or_cls.__module__` will be used (if no module
      is specified as part of `name`).
    whitelist: A whitelisted set of parameter names to supply values for.
    blacklist: A blacklisted set of parameter names not to supply values for.
    subclass: If `fn_or_cls` is a class and `subclass` is `True`, decorate by
      subclassing `fn_or_cls` and overriding its `__init__` method. If `False`,
      replace the existing `__init__` with a decorated version.

  Returns:
    A wrapped version of `fn_or_cls` that will take parameter values from the
    global configuration.

  Raises:
    RuntimeError: If the config is locked.
    ValueError: If a configurable with `name` (or the name of `fn_or_cls`)
      already exists, or if both a whitelist and blacklist are specified.
  """
  if config_is_locked():
    err_str = 'Attempted to add a new configurable after the config was locked.'
    raise RuntimeError(err_str)

  name = fn_or_cls.__name__ if name is None else name
  if config_parser.IDENTIFIER_RE.match(name):
    default_module = getattr(fn_or_cls, '__module__', None)
    module = default_module if module is None else module
  elif not config_parser.MODULE_RE.match(name):
    raise ValueError("Configurable name '{}' is invalid.".format(name))

  if module is not None and not config_parser.MODULE_RE.match(module):
    raise ValueError("Module '{}' is invalid.".format(module))

  selector = module + '.' + name if module else name
  if not _INTERACTIVE_MODE and selector in _REGISTRY:
    err_str = "A configurable matching '{}' already exists."
    raise ValueError(err_str.format(selector))

  if whitelist and blacklist:
    err_str = 'A whitelist or a blacklist can be specified, but not both.'
    raise ValueError(err_str)

  if whitelist and not isinstance(whitelist, (list, tuple)):
    raise TypeError('Whitelist should be a list or tuple.')

  if blacklist and not isinstance(blacklist, (list, tuple)):
    raise TypeError('Blacklist should be a list or tuple.')

  _validate_parameters(fn_or_cls, whitelist, 'whitelist')
  _validate_parameters(fn_or_cls, blacklist, 'blacklist')

  def apply_config(fn):
    """Wraps `fn` so that it obtains parameters from the configuration."""

    @six.wraps(fn)
    def wrapper(*args, **kwargs):
      """Supplies fn with parameter values from the configuration."""
      scope_components = current_scope()
      new_kwargs = {}
      for i in range(len(scope_components) + 1):
        partial_scope_str = '/'.join(scope_components[:i])
        new_kwargs.update(_CONFIG.get((partial_scope_str, selector), {}))
      gin_bound_args = list(new_kwargs.keys())
      scope_str = partial_scope_str

      arg_names = _get_supplied_positional_parameter_names(fn, args)

      for arg in args[len(arg_names):]:
        if arg is REQUIRED:
          raise ValueError(
              'gin.REQUIRED is not allowed for unnamed (vararg) parameters. If '
              'the function being called is wrapped by a non-Gin decorator, '
              'try explicitly providing argument names for positional '
              'parameters.')

      required_arg_names = []
      required_arg_indexes = []
      for i, arg in enumerate(args[:len(arg_names)]):
        if arg is REQUIRED:
          required_arg_names.append(arg_names[i])
          required_arg_indexes.append(i)

      required_kwargs = []
      for kwarg, value in six.iteritems(kwargs):
        if value is REQUIRED:
          required_kwargs.append(kwarg)

      # If the caller passed arguments as positional arguments that correspond
      # to a keyword arg in new_kwargs, remove the keyword argument from
      # new_kwargs to let the caller win and avoid throwing an error. Unless it
      # is an arg marked as REQUIRED.
      for arg_name in arg_names:
        if arg_name not in required_arg_names:
          new_kwargs.pop(arg_name, None)

      # Get default values for configurable parameters.
      operative_parameter_values = _get_default_configurable_parameter_values(
          fn, whitelist, blacklist)
      # Update with the values supplied via configuration.
      operative_parameter_values.update(new_kwargs)

      # Remove any values from the operative config that are overridden by the
      # caller. These can't be configured, so they won't be logged. We skip
      # values that are marked as REQUIRED.
      for k in arg_names:
        if k not in required_arg_names:
          operative_parameter_values.pop(k, None)
      for k in kwargs:
        if k not in required_kwargs:
          operative_parameter_values.pop(k, None)

      # An update is performed in case another caller of this same configurable
      # object has supplied a different set of arguments. By doing an update, a
      # Gin-supplied or default value will be present if it was used (not
      # overridden by the caller) at least once.
      _OPERATIVE_CONFIG.setdefault((scope_str, selector), {}).update(
          operative_parameter_values)

      # We call deepcopy for two reasons: First, to prevent the called function
      # from modifying any of the values in `_CONFIG` through references passed
      # in via `new_kwargs`; Second, to facilitate evaluation of any
      # `ConfigurableReference` instances buried somewhere inside
      # `new_kwargs`. See the docstring on `ConfigurableReference.__deepcopy__`
      # above for more details on the dark magic happening here.
      new_kwargs = copy.deepcopy(new_kwargs)

      # Validate args marked as REQUIRED have been bound in the Gin config.
      missing_required_params = []
      new_args = list(args)
      for i, arg_name in zip(required_arg_indexes, required_arg_names):
        if arg_name not in new_kwargs:
          missing_required_params.append(arg_name)
        else:
          new_args[i] = new_kwargs.pop(arg_name)

      # Validate kwargs marked as REQUIRED have been bound in the Gin config.
      for kwarg in required_kwargs:
        if kwarg not in new_kwargs:
          missing_required_params.append(kwarg)
        else:
          # Remove from kwargs and let the new_kwargs value be used.
          kwargs.pop(kwarg)

      if missing_required_params:
        err_str = 'Required bindings for `{}` not provided in config: {}'
        minimal_selector = _REGISTRY.minimal_selector(selector)
        err_str = err_str.format(minimal_selector, missing_required_params)
        raise RuntimeError(err_str)

      # Now, update with the caller-supplied `kwargs`, allowing the caller to
      # have the final say on keyword argument values.
      new_kwargs.update(kwargs)

      try:
        return fn(*new_args, **new_kwargs)
      except Exception as e:  # pylint: disable=broad-except
        err_str = ''
        if isinstance(e, TypeError):
          all_arg_names = _get_all_positional_parameter_names(fn)
          if len(new_args) < len(all_arg_names):
            unbound_positional_args = list(
                set(all_arg_names[len(new_args):]) - set(new_kwargs))
            if unbound_positional_args:
              caller_supplied_args = list(
                  set(arg_names + list(kwargs)) -
                  set(required_arg_names + list(required_kwargs)))
              fmt = ('\n  No values supplied by Gin or caller for arguments: {}'
                     '\n  Gin had values bound for: {gin_bound_args}'
                     '\n  Caller supplied values for: {caller_supplied_args}')
              canonicalize = lambda x: list(map(str, sorted(x)))
              err_str += fmt.format(
                  canonicalize(unbound_positional_args),
                  gin_bound_args=canonicalize(gin_bound_args),
                  caller_supplied_args=canonicalize(caller_supplied_args))
        err_str += "\n  In call to configurable '{}' ({}){}"
        scope_info = " in scope '{}'".format(scope_str) if scope_str else ''
        err_str = err_str.format(name, fn, scope_info)
        utils.augment_exception_message_and_reraise(e, err_str)

    return wrapper

  decorated_fn_or_cls = _decorate_fn_or_cls(
      apply_config, fn_or_cls, subclass=subclass)

  _REGISTRY[selector] = Configurable(
      decorated_fn_or_cls,
      name=name,
      module=module,
      whitelist=whitelist,
      blacklist=blacklist,
      selector=selector)
  return decorated_fn_or_cls
def _make_configurable ( fn_or_cls , name = None , module = None , whitelist = None , blacklist = None , subclass = False ) : if config_is_locked ( ) : err_str = 'Attempted to add a new configurable after the config was locked.' raise RuntimeError ( err_str ) name = fn_or_cls . __name__ if name is None else name if config_parser . IDENTIFIER_RE . match ( name ) : default_module = getattr ( fn_or_cls , '__module__' , None ) module = default_module if module is None else module elif not config_parser . MODULE_RE . match ( name ) : raise ValueError ( "Configurable name '{}' is invalid." . format ( name ) ) if module is not None and not config_parser . MODULE_RE . match ( module ) : raise ValueError ( "Module '{}' is invalid." . format ( module ) ) selector = module + '.' + name if module else name if not _INTERACTIVE_MODE and selector in _REGISTRY : err_str = "A configurable matching '{}' already exists." raise ValueError ( err_str . format ( selector ) ) if whitelist and blacklist : err_str = 'A whitelist or a blacklist can be specified, but not both.' raise ValueError ( err_str ) if whitelist and not isinstance ( whitelist , ( list , tuple ) ) : raise TypeError ( 'Whitelist should be a list or tuple.' ) if blacklist and not isinstance ( blacklist , ( list , tuple ) ) : raise TypeError ( 'Blacklist should be a list or tuple.' ) _validate_parameters ( fn_or_cls , whitelist , 'whitelist' ) _validate_parameters ( fn_or_cls , blacklist , 'blacklist' ) def apply_config ( fn ) : """Wraps `fn` so that it obtains parameters from the configuration.""" @ six . wraps ( fn ) def wrapper ( * args , * * kwargs ) : """Supplies fn with parameter values from the configuration.""" scope_components = current_scope ( ) new_kwargs = { } for i in range ( len ( scope_components ) + 1 ) : partial_scope_str = '/' . join ( scope_components [ : i ] ) new_kwargs . update ( _CONFIG . get ( ( partial_scope_str , selector ) , { } ) ) gin_bound_args = list ( new_kwargs . keys ( ) ) scope_str = partial_scope_str arg_names = _get_supplied_positional_parameter_names ( fn , args ) for arg in args [ len ( arg_names ) : ] : if arg is REQUIRED : raise ValueError ( 'gin.REQUIRED is not allowed for unnamed (vararg) parameters. If ' 'the function being called is wrapped by a non-Gin decorator, ' 'try explicitly providing argument names for positional ' 'parameters.' ) required_arg_names = [ ] required_arg_indexes = [ ] for i , arg in enumerate ( args [ : len ( arg_names ) ] ) : if arg is REQUIRED : required_arg_names . append ( arg_names [ i ] ) required_arg_indexes . append ( i ) required_kwargs = [ ] for kwarg , value in six . iteritems ( kwargs ) : if value is REQUIRED : required_kwargs . append ( kwarg ) # If the caller passed arguments as positional arguments that correspond # to a keyword arg in new_kwargs, remove the keyword argument from # new_kwargs to let the caller win and avoid throwing an error. Unless it # is an arg marked as REQUIRED. for arg_name in arg_names : if arg_name not in required_arg_names : new_kwargs . pop ( arg_name , None ) # Get default values for configurable parameters. operative_parameter_values = _get_default_configurable_parameter_values ( fn , whitelist , blacklist ) # Update with the values supplied via configuration. operative_parameter_values . update ( new_kwargs ) # Remove any values from the operative config that are overridden by the # caller. These can't be configured, so they won't be logged. We skip # values that are marked as REQUIRED. for k in arg_names : if k not in required_arg_names : operative_parameter_values . pop ( k , None ) for k in kwargs : if k not in required_kwargs : operative_parameter_values . pop ( k , None ) # An update is performed in case another caller of this same configurable # object has supplied a different set of arguments. By doing an update, a # Gin-supplied or default value will be present if it was used (not # overridden by the caller) at least once. _OPERATIVE_CONFIG . setdefault ( ( scope_str , selector ) , { } ) . update ( operative_parameter_values ) # We call deepcopy for two reasons: First, to prevent the called function # from modifying any of the values in `_CONFIG` through references passed # in via `new_kwargs`; Second, to facilitate evaluation of any # `ConfigurableReference` instances buried somewhere inside # `new_kwargs`. See the docstring on `ConfigurableReference.__deepcopy__` # above for more details on the dark magic happening here. new_kwargs = copy . deepcopy ( new_kwargs ) # Validate args marked as REQUIRED have been bound in the Gin config. missing_required_params = [ ] new_args = list ( args ) for i , arg_name in zip ( required_arg_indexes , required_arg_names ) : if arg_name not in new_kwargs : missing_required_params . append ( arg_name ) else : new_args [ i ] = new_kwargs . pop ( arg_name ) # Validate kwargs marked as REQUIRED have been bound in the Gin config. for kwarg in required_kwargs : if kwarg not in new_kwargs : missing_required_params . append ( kwarg ) else : # Remove from kwargs and let the new_kwargs value be used. kwargs . pop ( kwarg ) if missing_required_params : err_str = 'Required bindings for `{}` not provided in config: {}' minimal_selector = _REGISTRY . minimal_selector ( selector ) err_str = err_str . format ( minimal_selector , missing_required_params ) raise RuntimeError ( err_str ) # Now, update with the caller-supplied `kwargs`, allowing the caller to # have the final say on keyword argument values. new_kwargs . update ( kwargs ) try : return fn ( * new_args , * * new_kwargs ) except Exception as e : # pylint: disable=broad-except err_str = '' if isinstance ( e , TypeError ) : all_arg_names = _get_all_positional_parameter_names ( fn ) if len ( new_args ) < len ( all_arg_names ) : unbound_positional_args = list ( set ( all_arg_names [ len ( new_args ) : ] ) - set ( new_kwargs ) ) if unbound_positional_args : caller_supplied_args = list ( set ( arg_names + list ( kwargs ) ) - set ( required_arg_names + list ( required_kwargs ) ) ) fmt = ( '\n  No values supplied by Gin or caller for arguments: {}' '\n  Gin had values bound for: {gin_bound_args}' '\n  Caller supplied values for: {caller_supplied_args}' ) canonicalize = lambda x : list ( map ( str , sorted ( x ) ) ) err_str += fmt . format ( canonicalize ( unbound_positional_args ) , gin_bound_args = canonicalize ( gin_bound_args ) , caller_supplied_args = canonicalize ( caller_supplied_args ) ) err_str += "\n  In call to configurable '{}' ({}){}" scope_info = " in scope '{}'" . format ( scope_str ) if scope_str else '' err_str = err_str . format ( name , fn , scope_info ) utils . augment_exception_message_and_reraise ( e , err_str ) return wrapper decorated_fn_or_cls = _decorate_fn_or_cls ( apply_config , fn_or_cls , subclass = subclass ) _REGISTRY [ selector ] = Configurable ( decorated_fn_or_cls , name = name , module = module , whitelist = whitelist , blacklist = blacklist , selector = selector ) return decorated_fn_or_cls
Wraps `fn_or_cls` to make it configurable.

  Infers the configurable name from `fn_or_cls.__name__` if necessary, and
  updates global state to keep track of configurable name <-> function
  mappings, as well as whitelisted and blacklisted parameters.

  Args:
    fn_or_cls: The function or class to decorate.
    name: A name for the configurable. If `None`, the name will be inferred from
      from `fn_or_cls`. The `name` may also include module components to be used
      for disambiguation (these will be appended to any components explicitly
      specified by `module`).
    module: The module to associate with the configurable, to help handle naming
      collisions. If `None`, `fn_or_cls.__module__` will be used (if no module
      is specified as part of `name`).
    whitelist: A whitelisted set of parameter names to supply values for.
    blacklist: A blacklisted set of parameter names not to supply values for.
    subclass: If `fn_or_cls` is a class and `subclass` is `True`, decorate by
      subclassing `fn_or_cls` and overriding its `__init__` method. If `False`,
      replace the existing `__init__` with a decorated version.

  Returns:
    A wrapped version of `fn_or_cls` that will take parameter values from the
    global configuration.

  Raises:
    RuntimeError: If the config is locked.
    ValueError: If a configurable with `name` (or the name of `fn_or_cls`)
      already exists, or if both a whitelist and blacklist are specified.
Wraps fn_or_cls to make it configurable .