Example #1
0
    def test_all_enclosing_scopes(self):
        """`all_enclosing_scopes` should repeatedly apply `enclosing_scope` to any
    valid single- or multiple- component scope. `all_enclosing_scopes` should
    not yield the global scope if `allow_global=False`."""
        global_closure = list(
            all_enclosing_scopes(GLOBAL_SCOPE, allow_global=True))
        self.assertEqual(global_closure, [GLOBAL_SCOPE])

        global_closure_excluded = list(
            all_enclosing_scopes(GLOBAL_SCOPE, allow_global=False))
        self.assertEqual(global_closure_excluded, [])

        base_scope = 'scope'
        base_scope_closure = list(all_enclosing_scopes(base_scope))
        self.assertEqual(base_scope_closure, [base_scope, GLOBAL_SCOPE])

        subscope = 'subscope'
        compound_scope = '{}.{}'.format(base_scope, subscope)
        compound_scope_closure = list(all_enclosing_scopes(compound_scope))
        self.assertEqual(compound_scope_closure,
                         [compound_scope, base_scope, GLOBAL_SCOPE])

        compound_scope_closure_no_global = list(
            all_enclosing_scopes(compound_scope, allow_global=False))
        self.assertEqual(compound_scope_closure_no_global,
                         [compound_scope, base_scope])
    def test_valid_invalid_scope(self) -> None:
        """Scopes with dashes or underscores are treated as a single component, and scopes with
        empty components raise an InvalidScopeError."""
        base_dashed_scope = "base-scope"
        self.assertEqual(enclosing_scope(base_dashed_scope), GLOBAL_SCOPE)

        subscope_underscore = "sub_scope"
        self.assertEqual(enclosing_scope(subscope_underscore), GLOBAL_SCOPE)

        compound_scope = f"{base_dashed_scope}.{subscope_underscore}"
        self.assertEqual(enclosing_scope(compound_scope), base_dashed_scope)
        self.assertEqual(
            list(all_enclosing_scopes(compound_scope)),
            [
                compound_scope,
                base_dashed_scope,
                GLOBAL_SCOPE,
            ],
        )

        invalid_scope = "a.b..c.d"
        with self.assertRaises(InvalidScopeError):
            enclosing_scope(invalid_scope)
        with self.assertRaises(InvalidScopeError):
            # need to bounce to list to get it to error since this is a generator
            list(all_enclosing_scopes(invalid_scope))
Example #3
0
  def complete_scopes(cls, scope_infos):
    """Expand a set of scopes to include all enclosing scopes.

    E.g., if the set contains `foo.bar.baz`, ensure that it also contains `foo.bar` and `foo`.

    Also adds any deprecated scopes.
    """
    ret = {GlobalOptionsRegistrar.get_scope_info()}
    original_scopes = dict()
    for si in scope_infos:
      ret.add(si)
      if si.scope in original_scopes:
        raise cls.DuplicateScopeError('Scope `{}` claimed by {}, was also claimed by {}.'.format(
            si.scope, si, original_scopes[si.scope]
          ))
      original_scopes[si.scope] = si
      if si.deprecated_scope:
        ret.add(ScopeInfo(si.deprecated_scope, si.category, si.optionable_cls))
        original_scopes[si.deprecated_scope] = si

    # TODO: Once scope name validation is enforced (so there can be no dots in scope name
    # components) we can replace this line with `for si in scope_infos:`, because it will
    # not be possible for a deprecated_scope to introduce any new intermediate scopes.
    for si in copy.copy(ret):
      for scope in all_enclosing_scopes(si.scope, allow_global=False):
        if scope not in original_scopes:
          ret.add(ScopeInfo(scope, ScopeInfo.INTERMEDIATE))
    return ret
Example #4
0
    def complete_scopes(
            cls,
            scope_infos: Iterable[ScopeInfo]) -> FrozenOrderedSet[ScopeInfo]:
        """Expand a set of scopes to include all enclosing scopes.

        E.g., if the set contains `foo.bar.baz`, ensure that it also contains `foo.bar` and `foo`.

        Also adds any deprecated scopes.
        """
        ret: OrderedSet[ScopeInfo] = OrderedSet()
        original_scopes: Dict[str, ScopeInfo] = {}
        for si in sorted(scope_infos, key=lambda si: si.scope):
            ret.add(si)
            if si.scope in original_scopes:
                raise cls.DuplicateScopeError(
                    "Scope `{}` claimed by {}, was also claimed by {}.".format(
                        si.scope, si, original_scopes[si.scope]))
            original_scopes[si.scope] = si
            if si.deprecated_scope:
                ret.add(
                    ScopeInfo(si.deprecated_scope, si.category,
                              si.optionable_cls))
                original_scopes[si.deprecated_scope] = si

        # TODO: Once scope name validation is enforced (so there can be no dots in scope name
        # components) we can replace this line with `for si in scope_infos:`, because it will
        # not be possible for a deprecated_scope to introduce any new intermediate scopes.
        for si in copy.copy(ret):
            for scope in all_enclosing_scopes(si.scope, allow_global=False):
                if scope not in original_scopes:
                    ret.add(ScopeInfo(scope, ScopeInfo.INTERMEDIATE))
        return FrozenOrderedSet(ret)
Example #5
0
    def get_fingerprintable_for_scope(
        self,
        bottom_scope: str,
        include_passthru: Optional[bool] = None,
        fingerprint_key: str = "fingerprint",
        invert: bool = False,
    ):
        """Returns a list of fingerprintable (option type, option value) pairs for the given scope.

        Fingerprintable options are options registered via a "fingerprint=True" kwarg. This flag
        can be parameterized with `fingerprint_key` for special cases.

        This method also searches enclosing options scopes of `bottom_scope` to determine the set of
        fingerprintable pairs.

        :param bottom_scope: The scope to gather fingerprintable options for.
        :param include_passthru: Whether to include passthru args captured by `bottom_scope` in the
                                 fingerprintable options.
        :param fingerprint_key: The option kwarg to match against (defaults to 'fingerprint').
        :param invert: Whether or not to invert the boolean check for the fingerprint_key value.

        :API: public
        """

        deprecated_conditional(
            predicate=lambda: include_passthru is not None,
            removal_version="1.31.0.dev0",
            entity_description="get_fingerprintable_for_scope `include_passthru` arg",
            hint_message=(
                "passthru arguments are fingerprinted if their associated option value is."
            ),
        )

        fingerprint_default = bool(invert)
        pairs = []

        # Note that we iterate over options registered at `bottom_scope` and at all
        # enclosing scopes, since option-using code can read those values indirectly
        # via its own OptionValueContainer, so they can affect that code's output.
        for registration_scope in all_enclosing_scopes(bottom_scope):
            parser = self._parser_hierarchy.get_parser_by_scope(registration_scope)
            # Sort the arguments, so that the fingerprint is consistent.
            for (_, kwargs) in sorted(parser.option_registrations_iter()):
                if kwargs.get("recursive", False) and not kwargs.get("recursive_root", False):
                    continue  # We only need to fprint recursive options once.
                if not kwargs.get(fingerprint_key, fingerprint_default):
                    continue
                # Note that we read the value from scope, even if the registration was on an enclosing
                # scope, to get the right value for recursive options (and because this mirrors what
                # option-using code does).
                val = self.for_scope(bottom_scope)[kwargs["dest"]]
                # If we have a list then we delegate to the fingerprinting implementation of the members.
                if is_list_option(kwargs):
                    val_type = kwargs.get("member_type", str)
                else:
                    val_type = kwargs.get("type", str)
                pairs.append((val_type, val))
        return pairs
Example #6
0
    def get_fingerprintable_for_scope(self,
                                      bottom_scope,
                                      include_passthru=False,
                                      fingerprint_key=None,
                                      invert=False):
        """Returns a list of fingerprintable (option type, option value) pairs for the given scope.

    Fingerprintable options are options registered via a "fingerprint=True" kwarg. This flag
    can be parameterized with `fingerprint_key` for special cases.

    This method also searches enclosing options scopes of `bottom_scope` to determine the set of
    fingerprintable pairs.

    :param str bottom_scope: The scope to gather fingerprintable options for.
    :param bool include_passthru: Whether to include passthru args captured by `bottom_scope` in the
                                  fingerprintable options.
    :param string fingerprint_key: The option kwarg to match against (defaults to 'fingerprint').
    :param bool invert: Whether or not to invert the boolean check for the fingerprint_key value.

    :API: public
    """
        fingerprint_key = fingerprint_key or 'fingerprint'
        fingerprint_default = bool(invert)
        pairs = []

        if include_passthru:
            # Passthru args can only be sent to outermost scopes so we gather them once here up-front.
            passthru_args = self.passthru_args_for_scope(bottom_scope)
            # NB: We can't sort passthru args, the underlying consumer may be order-sensitive.
            pairs.extend((str, pass_arg) for pass_arg in passthru_args)

        # Note that we iterate over options registered at `bottom_scope` and at all
        # enclosing scopes, since option-using code can read those values indirectly
        # via its own OptionValueContainer, so they can affect that code's output.
        for registration_scope in all_enclosing_scopes(bottom_scope):
            parser = self._parser_hierarchy.get_parser_by_scope(
                registration_scope)
            # Sort the arguments, so that the fingerprint is consistent.
            for (_, kwargs) in sorted(parser.option_registrations_iter()):
                if kwargs.get('recursive', False) and not kwargs.get(
                        'recursive_root', False):
                    continue  # We only need to fprint recursive options once.
                if kwargs.get(fingerprint_key,
                              fingerprint_default) is not True:
                    continue
                # Note that we read the value from scope, even if the registration was on an enclosing
                # scope, to get the right value for recursive options (and because this mirrors what
                # option-using code does).
                val = self.for_scope(bottom_scope)[kwargs['dest']]
                # If we have a list then we delegate to the fingerprinting implementation of the members.
                if is_list_option(kwargs):
                    val_type = kwargs.get('member_type', str)
                else:
                    val_type = kwargs.get('type', str)
                pairs.append((val_type, val))
        return pairs
  def test_all_enclosing_scopes(self):
    """`all_enclosing_scopes` should repeatedly apply `enclosing_scope` to any
    valid single- or multiple- component scope. `all_enclosing_scopes` should
    not yield the global scope if `allow_global=False`."""
    global_closure = list(all_enclosing_scopes(GLOBAL_SCOPE, allow_global=True))
    self.assertEqual(global_closure, [GLOBAL_SCOPE])

    global_closure_excluded = list(all_enclosing_scopes(GLOBAL_SCOPE, allow_global=False))
    self.assertEqual(global_closure_excluded, [])

    base_scope = 'scope'
    base_scope_closure = list(all_enclosing_scopes(base_scope))
    self.assertEqual(base_scope_closure, [base_scope, GLOBAL_SCOPE])

    subscope = 'subscope'
    compound_scope = '{}.{}'.format(base_scope, subscope)
    compound_scope_closure = list(all_enclosing_scopes(compound_scope))
    self.assertEqual(compound_scope_closure, [compound_scope, base_scope, GLOBAL_SCOPE])

    compound_scope_closure_no_global = list(all_enclosing_scopes(compound_scope, allow_global=False))
    self.assertEqual(compound_scope_closure_no_global, [compound_scope, base_scope])
Example #8
0
  def get_fingerprintable_for_scope(self, bottom_scope, include_passthru=False,
                                    fingerprint_key=None, invert=False):
    """Returns a list of fingerprintable (option type, option value) pairs for the given scope.

    Fingerprintable options are options registered via a "fingerprint=True" kwarg. This flag
    can be parameterized with `fingerprint_key` for special cases.

    This method also searches enclosing options scopes of `bottom_scope` to determine the set of
    fingerprintable pairs.

    :param str bottom_scope: The scope to gather fingerprintable options for.
    :param bool include_passthru: Whether to include passthru args captured by `bottom_scope` in the
                                  fingerprintable options.
    :param string fingerprint_key: The option kwarg to match against (defaults to 'fingerprint').
    :param bool invert: Whether or not to invert the boolean check for the fingerprint_key value.

    :API: public
    """
    fingerprint_key = fingerprint_key or 'fingerprint'
    fingerprint_default = bool(invert)
    pairs = []

    if include_passthru:
      # Passthru args can only be sent to outermost scopes so we gather them once here up-front.
      passthru_args = self.passthru_args_for_scope(bottom_scope)
      # NB: We can't sort passthru args, the underlying consumer may be order-sensitive.
      pairs.extend((str, pass_arg) for pass_arg in passthru_args)

    # Note that we iterate over options registered at `bottom_scope` and at all
    # enclosing scopes, since option-using code can read those values indirectly
    # via its own OptionValueContainer, so they can affect that code's output.
    for registration_scope in all_enclosing_scopes(bottom_scope):
      parser = self._parser_hierarchy.get_parser_by_scope(registration_scope)
      # Sort the arguments, so that the fingerprint is consistent.
      for (_, kwargs) in sorted(parser.option_registrations_iter()):
        if kwargs.get('recursive', False) and not kwargs.get('recursive_root', False):
          continue  # We only need to fprint recursive options once.
        if kwargs.get(fingerprint_key, fingerprint_default) is not True:
          continue
        # Note that we read the value from scope, even if the registration was on an enclosing
        # scope, to get the right value for recursive options (and because this mirrors what
        # option-using code does).
        val = self.for_scope(bottom_scope)[kwargs['dest']]
        # If we have a list then we delegate to the fingerprinting implementation of the members.
        if is_list_option(kwargs):
          val_type = kwargs.get('member_type', str)
        else:
          val_type = kwargs.get('type', str)
        pairs.append((val_type, val))
    return pairs
  def test_valid_invalid_scope(self):
    """Scopes with dashes or underscores are treated as a single component, and
    scopes with empty components raise an InvalidScopeError."""
    base_dashed_scope = 'base-scope'
    self.assertEqual(enclosing_scope(base_dashed_scope), GLOBAL_SCOPE)

    subscope_underscore = 'sub_scope'
    self.assertEqual(enclosing_scope(subscope_underscore), GLOBAL_SCOPE)

    compound_scope = '{}.{}'.format(base_dashed_scope, subscope_underscore)
    self.assertEqual(enclosing_scope(compound_scope), base_dashed_scope)
    self.assertEqual(list(all_enclosing_scopes(compound_scope)), [
      compound_scope,
      base_dashed_scope,
      GLOBAL_SCOPE,
    ])

    invalid_scope = 'a.b..c.d'
    with self.assertRaises(InvalidScopeError):
      enclosing_scope(invalid_scope)
    with self.assertRaises(InvalidScopeError):
      # need to bounce to list to get it to error since this is a generator
      list(all_enclosing_scopes(invalid_scope))