def assert_option_resolved( *, old_configured: bool = False, new_configured: bool = False, expected: str, ) -> None: old_container, new_container = OptionValueContainer(), OptionValueContainer() old_container.my_opt = old_configured_rv if old_configured else old_default_rv new_container.my_opt = new_configured_rv if new_configured else new_default_rv assert ( resolve_options(old_container=old_container, new_container=new_container) == expected )
def test_iterator(self): o = OptionValueContainer() o.a = RankedValue(RankedValue.FLAG, 3) o.b = RankedValue(RankedValue.FLAG, 2) o.c = RankedValue(RankedValue.FLAG, 1) names = list(iter(o)) self.assertListEqual(['a', 'b', 'c'], names)
def for_scope(self, scope, inherit_from_enclosing_scope=True): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. :API: public """ # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE or not inherit_from_enclosing_scope: values = OptionValueContainer() else: values = copy.copy(self.for_scope(enclosing_scope(scope))) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) parse_args_request = self._make_parse_args_request(flags_in_scope, values) self._parser_hierarchy.get_parser_by_scope(scope).parse_args(parse_args_request) # Check for any deprecation conditions, which are evaluated using `self._flag_matchers`. if inherit_from_enclosing_scope: self._check_and_apply_deprecations(scope, values) return values
def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() if self._legacy_values: values.update(vars( self._legacy_values)) # Proxy any legacy option values. else: values = copy.copy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. try: flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args( flags_in_scope, values) self._values_by_scope[scope] = values return values except ParseError as e: self.print_help(str(e)) sys.exit(1)
def test_iterator(self) -> None: o = OptionValueContainer() o.a = RankedValue(RankedValue.FLAG, 3) o.b = RankedValue(RankedValue.FLAG, 2) o.c = RankedValue(RankedValue.FLAG, 1) names = list(iter(o)) self.assertListEqual(["a", "b", "c"], names)
def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. :API: public """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() else: values = copy.copy(self.for_scope(enclosing_scope(scope))) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args( flags_in_scope, values) self._values_by_scope[scope] = values for option in values: self._option_tracker.record_option(scope=scope, option=option, value=values[option], rank=values.get_rank(option)) return values
def for_scope(self, scope, inherit_from_enclosing_scope=True): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. :API: public """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE or not inherit_from_enclosing_scope: values = OptionValueContainer() else: values = copy.copy(self.for_scope(enclosing_scope(scope))) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args( flags_in_scope, values) # Check for any deprecation conditions, which are evaluated using `self._flag_matchers`. self._check_deprecations(scope, flags_in_scope, values) # Cache the values. self._values_by_scope[scope] = values return values
def test_iterator(self): o = OptionValueContainer() o.a = 3 o.b = 2 o.c = 1 names = list(iter(o)) self.assertListEqual(['a', 'b', 'c'], names)
def test_unknown_values(self) -> None: o = OptionValueContainer() o.foo = RankedValue(RankedValue.HARDCODED, 1) self.assertEqual(1, o.foo) with self.assertRaises(AttributeError): o.bar
def test_standard_values(self): o = OptionValueContainer() o.foo = 1 self.assertEqual(1, o.foo) with self.assertRaises(AttributeError): o.bar
def test_iterator(self): o = OptionValueContainer() o.add_forwardings({'a': '_a'}) o.add_forwardings({'b': '_b'}) o.add_forwardings({'b_prime': '_b'}) # Should be elided in favor of b. o.add_forwardings({'c': '_c'}) names = list(iter(o)) self.assertListEqual(['a', 'b', 'c'], names)
def for_scope(self, scope, inherit_from_enclosing_scope=True): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. :API: public """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE or not inherit_from_enclosing_scope: values = OptionValueContainer() else: values = copy.copy(self.for_scope(enclosing_scope(scope))) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args( flags_in_scope, values) # If we're the new name of a deprecated scope, also get values from that scope. deprecated_scope = self.known_scope_to_info[scope].deprecated_scope # Note that deprecated_scope and scope share the same Optionable class, so deprecated_scope's # Optionable has a deprecated_options_scope equal to deprecated_scope. Therefore we must # check that scope != deprecated_scope to prevent infinite recursion. if deprecated_scope is not None and scope != deprecated_scope: # Do the deprecation check only on keys that were explicitly set on the deprecated scope # (and not on its enclosing scopes). explicit_keys = self.for_scope( deprecated_scope, inherit_from_enclosing_scope=False).get_explicit_keys() if explicit_keys: warn_or_error( self.known_scope_to_info[scope]. deprecated_scope_removal_version, 'scope {}'.format(deprecated_scope), 'Use scope {} instead (options: {})'.format( scope, ', '.join(explicit_keys))) # Update our values with those of the deprecated scope (now including values inherited # from its enclosing scope). # Note that a deprecated val will take precedence over a val of equal rank. # This makes the code a bit neater. values.update(self.for_scope(deprecated_scope)) # Record the value derivation. for option in values: self._option_tracker.record_option(scope=scope, option=option, value=values[option], rank=values.get_rank(option)) # Cache the values. self._values_by_scope[scope] = values return values
def _create_scoped_options( default_rank: Rank, **options: Union[RankedValue, Value]) -> OptionValueContainer: scoped_options = OptionValueContainer() for key, value in options.items(): if not isinstance(value, RankedValue): value = RankedValue(default_rank, value) setattr(scoped_options, key, value) return scoped_options
def test_indexing(self): o = OptionValueContainer() o.add_forwardings({'foo': 'bar'}) o.bar = 1 self.assertEqual(1, o['foo']) self.assertEqual(1, o['bar']) with self.assertRaises(AttributeError): o['baz']
def test_resolve_conflicting_options(self) -> None: resolve_options = partial( resolve_conflicting_options, old_option="my_opt", new_option="my_opt", old_scope="old-scope", new_scope="new-scope", ) old_val = "ancient" new_val = "modern" old_default_rv = RankedValue(RankedValue.HARDCODED, old_val) new_default_rv = RankedValue(RankedValue.HARDCODED, new_val) old_configured_rv = RankedValue(RankedValue.FLAG, old_val) new_configured_rv = RankedValue(RankedValue.FLAG, new_val) def assert_option_resolved( *, old_configured: bool = False, new_configured: bool = False, expected: str, ) -> None: old_container, new_container = OptionValueContainer( ), OptionValueContainer() old_container.my_opt = old_configured_rv if old_configured else old_default_rv new_container.my_opt = new_configured_rv if new_configured else new_default_rv assert resolve_options(old_container=old_container, new_container=new_container) == expected assert_option_resolved(expected=new_val) assert_option_resolved(old_configured=True, expected=old_val) assert_option_resolved(new_configured=True, expected=new_val) # both configured -> raise an error old_container, new_container = OptionValueContainer( ), OptionValueContainer() old_container.my_opt = old_configured_rv new_container.my_opt = new_configured_rv with pytest.raises(ValueError) as e: resolve_options(old_container=old_container, new_container=new_container) assert "--old-scope-my-opt" in str(e.value) assert "--new-scope-my-opt" in str(e.value)
def test_value_ranking(self): o = OptionValueContainer() o.add_forwardings({'foo': 'bar'}) o.bar = RankedValue(RankedValue.CONFIG, 11) self.assertEqual(11, o.foo) o.bar = RankedValue(RankedValue.HARDCODED, 22) self.assertEqual(11, o.foo) o.bar = RankedValue(RankedValue.ENVIRONMENT, 33) self.assertEqual(33, o.foo) o.bar = 44 # No explicit rank is assumed to be a FLAG. self.assertEqual(44, o.foo)
def test_deepcopy(self): # deepcopy semantics can get hairy when overriding __setattr__/__getattr__, so we test them. o = OptionValueContainer() o.add_forwardings({'foo': 'bar'}) o.add_forwardings({'baz': 'qux'}) o.bar = 1 o.qux = {'a': 111} p = copy.deepcopy(o) o.baz['b'] = 222 # Add to original dict. self.assertEqual(1, p.foo) self.assertEqual({'a': 111}, p.baz) # Ensure dict was copied.
def test_indexing(self) -> None: o = OptionValueContainer() o.foo = RankedValue(RankedValue.CONFIG, 1) self.assertEqual(1, o["foo"]) self.assertEqual(1, o.get("foo")) self.assertEqual(1, o.get("foo", 2)) self.assertIsNone(o.get("unknown")) self.assertEqual(2, o.get("unknown", 2)) with self.assertRaises(AttributeError): o["bar"]
def test_indexing(self): o = OptionValueContainer() o.foo = 1 self.assertEqual(1, o['foo']) self.assertEqual(1, o.get('foo')) self.assertEqual(1, o.get('foo', 2)) self.assertIsNone(o.get('unknown')) self.assertEqual(2, o.get('unknown', 2)) with self.assertRaises(AttributeError): o['bar']
def test_is_flagged(self) -> None: o = OptionValueContainer() o.foo = RankedValue(RankedValue.NONE, 11) self.assertFalse(o.is_flagged("foo")) o.foo = RankedValue(RankedValue.CONFIG, 11) self.assertFalse(o.is_flagged("foo")) o.foo = RankedValue(RankedValue.ENVIRONMENT, 11) self.assertFalse(o.is_flagged("foo")) o.foo = RankedValue(RankedValue.FLAG, 11) self.assertTrue(o.is_flagged("foo"))
def test_value_ranking(self) -> None: o = OptionValueContainer() o.foo = RankedValue(RankedValue.CONFIG, 11) self.assertEqual(11, o.foo) self.assertEqual(RankedValue.CONFIG, o.get_rank("foo")) o.foo = RankedValue(RankedValue.HARDCODED, 22) self.assertEqual(11, o.foo) self.assertEqual(RankedValue.CONFIG, o.get_rank("foo")) o.foo = RankedValue(RankedValue.ENVIRONMENT, 33) self.assertEqual(33, o.foo) self.assertEqual(RankedValue.ENVIRONMENT, o.get_rank("foo")) o.foo = RankedValue(RankedValue.FLAG, 44) self.assertEqual(44, o.foo) self.assertEqual(RankedValue.FLAG, o.get_rank("foo"))
def test_value_ranking(self): o = OptionValueContainer() o.foo = RankedValue(RankedValue.CONFIG, 11) self.assertEqual(11, o.foo) self.assertEqual(RankedValue.CONFIG, o.get_rank('foo')) o.foo = RankedValue(RankedValue.HARDCODED, 22) self.assertEqual(11, o.foo) self.assertEqual(RankedValue.CONFIG, o.get_rank('foo')) o.foo = RankedValue(RankedValue.ENVIRONMENT, 33) self.assertEqual(33, o.foo) self.assertEqual(RankedValue.ENVIRONMENT, o.get_rank('foo')) o.foo = 44 # No explicit rank is assumed to be a FLAG. self.assertEqual(44, o.foo) self.assertEqual(RankedValue.FLAG, o.get_rank('foo'))
def test_is_flagged(self): o = OptionValueContainer() o.add_forwardings({'foo': 'bar'}) o.bar = RankedValue(RankedValue.NONE, 11) self.assertFalse(o.is_flagged('foo')) o.bar = RankedValue(RankedValue.CONFIG, 11) self.assertFalse(o.is_flagged('foo')) o.bar = RankedValue(RankedValue.ENVIRONMENT, 11) self.assertFalse(o.is_flagged('foo')) o.bar = RankedValue(RankedValue.FLAG, 11) self.assertTrue(o.is_flagged('foo'))
def test_forwarding(self): o = OptionValueContainer() o.add_forwardings({'foo': 'bar'}) o.bar = 1 self.assertEqual(1, o.foo) o.bar = 2 self.assertEqual(2, o.foo) o.add_forwardings({'baz': 'qux'}) o.qux = 3 self.assertEqual(2, o.foo) self.assertEqual(3, o.baz) # Direct setting overrides forwarding. o.foo = 4 self.assertEqual(4, o.foo)
def _format_for_global_scope(show_advanced, show_deprecated, args, kwargs): parser = Parser( env={}, config=Config.load([]), scope_info=GlobalOptions.get_scope_info(), parent_parser=None, ) parser.register(*args, **kwargs) # Force a parse to generate the derivation history. parser.parse_args( Parser.ParseArgsRequest((), OptionValueContainer(), lambda: [], 0, [])) oshi = HelpInfoExtracter("").get_option_scope_help_info( "", parser, False) return HelpFormatter(show_advanced=show_advanced, show_deprecated=show_deprecated, color=False).format_options(oshi)
def test_deepcopy(self): # copy semantics can get hairy when overriding __setattr__/__getattr__, so we test them. o = OptionValueContainer() o.foo = 1 o.bar = {'a': 111} p = copy.deepcopy(o) # Verify that the result is in fact a copy. self.assertEqual(1, p.foo) # Has original attribute. o.baz = 42 self.assertFalse(hasattr( p, 'baz')) # Does not have attribute added after the copy. # Verify that it's a deep copy by modifying a referent in o and reading it in p. o.bar['b'] = 222 self.assertEqual({'a': 111}, p.bar)
def test_deepcopy(self) -> None: # copy semantics can get hairy when overriding __setattr__/__getattr__, so we test them. o = OptionValueContainer() o.foo = RankedValue(RankedValue.FLAG, 1) o.bar = RankedValue(RankedValue.FLAG, {"a": 111}) p = copy.deepcopy(o) # Verify that the result is in fact a copy. self.assertEqual(1, p.foo) # Has original attribute. o.baz = RankedValue(RankedValue.FLAG, 42) self.assertFalse(hasattr(p, "baz")) # Does not have attribute added after the copy. # Verify that it's a deep copy by modifying a referent in o and reading it in p. # TODO: add type hints to ranked_value.py and option_value_container.py so that this works. o.bar["b"] = 222 # type: ignore[index] self.assertEqual({"a": 111}, p.bar)
def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() else: values = copy.deepcopy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args(flags_in_scope, values) self._values_by_scope[scope] = values return values
def test_scope_existence() -> None: class NoScope(Subsystem): pass with pytest.raises(OptionsError) as excinfo: NoScope.get_scope_info() assert "NoScope must set options_scope" in str(excinfo.value) with pytest.raises(OptionsError) as excinfo: NoScope(OptionValueContainer({})) assert "NoScope must set options_scope" in str(excinfo.value) class StringScope(Subsystem): options_scope = "good" assert "good" == StringScope.options_scope class Intermediate(Subsystem): pass class Indirect(Intermediate): options_scope = "good" assert "good" == Indirect.options_scope