def test_attribute_expand_env(self) -> None: """Test transparent environment variable expansion.""" os.environ['CMDKIT_TEST_A'] = 'foo-bar' ns = Namespace({'test_env': 'CMDKIT_TEST_A'}) assert ns.get('test') is None assert ns.get('test_env') == 'CMDKIT_TEST_A' assert ns.test == 'foo-bar'
def test_set(self) -> None: """Test Namespace[] setter.""" ns = Namespace() for key, value in zip(KEYS, VALUES): ns[key] = value assert list(ns.keys()) == list(DATA.keys()) assert list(ns.values()) == list(DATA.values())
def test_duplicates(self) -> None: """Configuration can find duplicate leaves in the trees.""" one = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}}) two = Namespace({'b': {'x': 4, 'z': 2}, 'c': {'j': True, 'k': 3.14}}) cfg = Configuration(one=one, two=two) assert cfg.duplicates() == {'x': {'one': [('a',), ('b',)], 'two': [('b',)]}, 'z': {'one': [('b',)], 'two': [('b',)]}}
def test_popitem(self) -> None: """Configuration cannot use inherited popitem method.""" one = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}}) two = Namespace({'b': {'x': 4, 'z': 2}, 'c': {'j': True, 'k': 3.14}}) cfg = Configuration(one=one, two=two) with pytest.raises(NotImplementedError): cfg.popitem()
def test_whereis(self) -> None: """Configuration can find paths to leaves in the tree.""" one = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}}) two = Namespace({'b': {'x': 4}, 'c': {'j': True, 'k': 3.14}}) cfg = Configuration(one=one, two=two) assert cfg.whereis('x') == {'one': [('a',), ('b',)], 'two': [('b',)]} assert cfg.whereis('x', 1) == {'one': [('a',)], 'two': []} assert cfg.whereis('x', lambda v: v % 3 == 0) == {'one': [('b',)], 'two': []}
def run(self) -> None: """Business logic for `refitt config get`.""" path = PATH[SITE].config for site in ('local', 'user', 'system'): if getattr(self, site) is True: path = PATH[site].config if not os.path.exists(path): raise RuntimeError(f'{path} does not exist') config = Namespace.from_local(path) if self.varpath == '.': self.print_result(config) return if '.' not in self.varpath: if self.varpath in config: self.print_result(config[self.varpath]) return else: raise RuntimeError(f'"{self.varpath}" not found in {path}') if self.varpath.startswith('.'): raise RuntimeError(f'section name cannot start with "."') section, *subsections, variable = self.varpath.split('.') if section not in config: raise RuntimeError(f'"{section}" is not a section') config_section = config[section] if subsections: subpath = f'{section}' try: for subsection in subsections: subpath += f'.{subsection}' if not isinstance(config_section[subsection], Mapping): raise RuntimeError( f'"{subpath}" not a section in {path}') else: config_section = config_section[subsection] except KeyError as error: raise RuntimeError( f'"{subpath}" not found in {path}') from error if self.expand: try: value = getattr(config_section, variable) except ValueError as error: raise RuntimeError(*error.args) from error if value is None: raise RuntimeError(f'"{variable}" not found in {path}') self.print_result(value) return if variable not in config_section: raise RuntimeError(f'"{self.varpath}" not found in {path}') self.print_result(config_section[variable])
def test_to_local(self) -> None: """Test Namespace.to_local dispatch method.""" # test round trip for ftype in FACTORIES: ns = Namespace(TEST_DICT) ns.to_local(f'{TMPDIR}/{ftype}.{ftype}') assert ns == Namespace.from_local(f'{TMPDIR}/{ftype}.{ftype}') # test not implemented with pytest.raises(NotImplementedError): ns = Namespace(TEST_DICT) ns.to_local(f'{TMPDIR}/config.special')
def test_init(self) -> None: """Test environment variable initialization along with Environ.reduce().""" # clean environment of any existing variables with the item prefix = 'CMDKIT' for var in dict(os.environ): if var.startswith(prefix): os.environ.pop(var) # populate environment with test variables for line in TEST_ENV.strip().split('\n'): field, value = line.strip().split('=') os.environ[field] = value # test base level Namespace|Environ equivalence assert Namespace.from_env(prefix=prefix) == Environ(prefix=prefix) assert Environ(prefix=prefix).expand() == Namespace(TEST_DICT)
def test_deep_inplace_assignment(self) -> None: """Test Namespace attribute access behavior.""" ns = Namespace({'a': {'x': 1, 'y': 2}}) assert isinstance(ns.a, Namespace) assert ns.a == {'x': 1, 'y': 2} assert ns.a.x == 1 and ns.a.y == 2 ns.a.x = 3 assert ns.a.x == 3
def test_update_complex(self) -> None: """Test Namespace.update() behavior.""" ns = Namespace({'a': {'x': 1, 'y': 2}}) ns.update({'b': {'z': 3}}) assert ns == {'a': {'x': 1, 'y': 2}, 'b': {'z': 3}} ns.update({'a': {'z': 3}}) assert ns == {'a': {'x': 1, 'y': 2, 'z': 3}, 'b': {'z': 3}} ns.update({'a': {'x': 5}}) assert ns == {'a': {'x': 5, 'y': 2, 'z': 3}, 'b': {'z': 3}}
def test_from_local(self) -> None: """Test Configuration.from_local factory method.""" # initial local files for label, data in CONFIG_SOURCES.items(): with open(f'{TMPDIR}/{label}.toml', mode='w') as output: output.write(data) # clean environment of any existing variables with the item prefix = 'CMDKIT' for var in dict(os.environ): if var.startswith(prefix): os.environ.pop(var) # populate environment with test variables for line in CONFIG_ENVIRON.strip().split('\n'): field, value = line.strip().split('=') os.environ[field] = value # build configuration default = Namespace.from_toml(StringIO(CONFIG_DEFAULT)) cfg = Configuration.from_local(default=default, env=True, prefix=prefix, system=f'{TMPDIR}/system.toml', user=f'{TMPDIR}/user.toml', local=f'{TMPDIR}/local.toml') # verify namespace isolation assert cfg.namespaces['default'] == Namespace.from_toml(StringIO(CONFIG_DEFAULT)) assert cfg.namespaces['system'] == Namespace.from_toml(StringIO(CONFIG_SYSTEM)) assert cfg.namespaces['user'] == Namespace.from_toml(StringIO(CONFIG_USER)) assert cfg.namespaces['local'] == Namespace.from_toml(StringIO(CONFIG_LOCAL)) assert cfg.namespaces['env'] == Environ(prefix).reduce() # verify parameter lineage assert cfg['a']['var0'] == 'default_var0' and cfg.which('a', 'var0') == 'default' assert cfg['a']['var1'] == 'system_var1' and cfg.which('a', 'var1') == 'system' assert cfg['a']['var2'] == 'user_var2' and cfg.which('a', 'var2') == 'user' assert cfg['b']['var3'] == 'local_var3' and cfg.which('b', 'var3') == 'local' assert cfg['c']['var4'] == 'env_var4' and cfg.which('c', 'var4') == 'env' assert cfg['c']['var5'] == 'env_var5' and cfg.which('c', 'var5') == 'env'
def test_update_simple(self) -> None: """Test Namespace.update() behavior""" ns = Namespace() for i, (key, value) in enumerate(DATA.items()): ns.update({key: value}) assert list(ns.keys()) == list(DATA.keys()) assert list(ns.values()) == list(DATA.values())
def test_setdefault(self) -> None: """Test Namespace.setdefault().""" ns = Namespace() for key, value in zip(KEYS, VALUES): ns.setdefault(key, value) assert list(ns.keys()) == list(DATA.keys()) assert list(ns.values()) == list(DATA.values())
def test_blending(self) -> None: """Test that configuration applied depth-first blending of namespaces.""" ns_a = Namespace({'a': {'x': 1, 'y': 2}}) ns_b = Namespace({'b': {'z': 3}}) # new section ns_c = Namespace({'a': {'z': 4}}) # new value in existing section ns_d = Namespace({'a': {'x': 5}}) # altered value in existing section # configuration blends nested namespaces cfg = Configuration(A=ns_a, B=ns_b, C=ns_c, D=ns_d) # confirm separate namespaces assert cfg.namespaces['A'] == ns_a assert cfg.namespaces['B'] == ns_b assert cfg.namespaces['C'] == ns_c assert cfg.namespaces['D'] == ns_d # confirm d << c << b << a look up assert cfg['a']['x'] == 5 assert cfg['a']['y'] == 2 assert cfg['a']['z'] == 4 assert cfg['b']['z'] == 3
def test_factories(self) -> None: """Test all implemented factory equivalents.""" # write all test data to local files for ftype, data in FACTORIES.items(): with open(f'{TMPDIR}/{ftype}.{ftype}', mode='w') as output: output.write(data) # test both file-like object and file path modes of construction assert (Namespace(TEST_DICT) == Namespace.from_toml(StringIO(TEST_TOML)) == Namespace.from_toml(f'{TMPDIR}/toml.toml') == Namespace.from_yaml(StringIO(TEST_YAML)) == Namespace.from_yaml(f'{TMPDIR}/yaml.yaml') == Namespace.from_json(StringIO(TEST_JSON)) == Namespace.from_json(f'{TMPDIR}/json.json'))
def test_flatten(self) -> None: """Test round-trip Environ('...').expand().flatten().""" # clean environment of any existing variables with the item prefix = 'CMDKIT' for var in dict(os.environ): if var.startswith(prefix): os.environ.pop(var) # populate environment with test variables for line in TEST_ENV_TYPES.strip().split('\n'): field, value = line.strip().split('=') os.environ[field] = value env = Namespace.from_env(prefix) assert env == env.expand().flatten(prefix=prefix)
def update_config(site: str, data: dict) -> None: """ Extend the current configuration and commit it to disk. Parameters: site (str): Either "local", "user", or "system" data (dict): Sectioned mappable to update configuration file. Example: >>> update_config('user', { ... 'database': { ... 'user': '******' ... } ... }) """ init_config(site) new_config = Namespace.from_local(CONF_PATH[site]) new_config.update(data) new_config.to_local(CONF_PATH[site])
def update_config(site: str, data: dict) -> None: """ Extend the current configuration and commit it to disk. Args: site (str): Either "local", "user", or "system" data (dict): Sectioned mappable to update configuration file. Example: >>> update_config('user', { ... 'database': { ... 'user': '******' ... } ... }) """ path = get_site(site).config new_config = Namespace.from_local(path, ignore_if_missing=True) new_config.update(data) new_config.to_local(path)
def test_live_update(self) -> None: """Test direct modification of configuration data.""" cfg = Configuration(a=Namespace(x=1)) assert repr(cfg) == 'Configuration(a=Namespace({\'x\': 1}))' assert dict(cfg) == {'x': 1} assert cfg.which('x') == 'a' cfg.x = 2 assert repr(cfg) == 'Configuration(a=Namespace({\'x\': 1}), _=Namespace({\'x\': 2}))' assert dict(cfg) == {'x': 2} assert cfg.which('x') == '_' cfg.update(y=2) assert dict(cfg) == {'x': 2, 'y': 2} assert repr(cfg) == 'Configuration(a=Namespace({\'x\': 1}), _=Namespace({\'x\': 2, \'y\': 2}))' assert cfg.which('y') == '_' cfg.update(y=3) assert dict(cfg) == {'x': 2, 'y': 3} assert repr(cfg) == 'Configuration(a=Namespace({\'x\': 1}), _=Namespace({\'x\': 2, \'y\': 3}))' assert cfg.which('y') == '_'
def test_init(self) -> None: """Test initialization.""" # test namespaces ns_a = Namespace(TEST_DICT) ns_b = Namespace.from_toml(StringIO(TEST_TOML)) ns_c = Namespace.from_yaml(StringIO(TEST_YAML)) ns_d = Namespace.from_json(StringIO(TEST_JSON)) # initialize construction results in member namespaces cfg = Configuration(A=ns_a, B=ns_b) assert dict(cfg) == cfg.namespaces['A'] == ns_a == cfg.namespaces['B'] == ns_b # extend the configuration cfg.extend(C=ns_c, D=ns_d) assert (cfg.namespaces['A'] == ns_a == cfg.namespaces['B'] == ns_b == cfg.namespaces['C'] == ns_c == cfg.namespaces['D'] == ns_d == dict(cfg)) # keys() and values() are available assert list(cfg.keys()) == list(ns_a.keys()) assert list(cfg.values()) == list(ns_a.values())
def test_attribute_expand_eval(self) -> None: """Test transparent shell expression expansion.""" ns = Namespace({'test_eval': 'echo foo-bar'}) assert ns.get('test') is None assert ns.get('test_eval') == 'echo foo-bar' assert ns.test == 'foo-bar'
from cmdkit.config import Namespace, Configuration, ConfigurationError # noqa: unused # internal libs from ..assets import load_asset # initialize module level logger log = logging.getLogger(__name__) # environment variables and configuration files are automatically # depth-first merged with defaults DEFAULT: Namespace = Namespace({ 'database': { 'backend': 'sqlite', 'database': ':memory:' }, 'logging': { 'level': 'warning', 'format': '%(asctime)s %(hostname)s %(levelname)-8s [%(name)s] %(msg)s', 'datefmt': '%Y-%m-%d %H:%M:%S' } }) CWD: str = os.getcwd() HOME: str = os.getenv('HOME') if os.name == 'nt': ROOT: bool = ctypes.windll.shell32.IsUserAnAdmin() == 1 SITE: str = 'system' if ROOT else 'user' ROOT_SITE: str = os.path.join(os.getenv('ProgramData'), 'StreamKit') USER_SITE: str = os.path.join(os.getenv('AppData'), 'StreamKit') else: ROOT: bool = os.getuid() == 0
def test_get(self) -> None: """Test Namespace[], Namespace.get().""" ns = Namespace(DATA) for key, value in zip(KEYS, VALUES): assert ns[key] == ns.get(key) == value assert ns.get(f'{key}_', False) is False
def test_iterate(self) -> None: """Test that Namespace can use [] syntax.""" ns = Namespace(DATA) for i, (key, value) in enumerate(ns.items()): assert key == KEYS[i] assert value == VALUES[i]
def test_from_local(self) -> None: """Test automatic file type deduction and allow for missing files.""" # clear existing files for ftype in FACTORIES: filepath = f'{TMPDIR}/{ftype}.{ftype}' if os.path.exists(filepath): os.remove(filepath) with pytest.raises(FileNotFoundError): Namespace.from_local(f'{TMPDIR}/toml.toml') with pytest.raises(FileNotFoundError): Namespace.from_local(f'{TMPDIR}/yaml.yaml') with pytest.raises(FileNotFoundError): Namespace.from_local(f'{TMPDIR}/json.json') assert (Namespace() == Namespace.from_local(f'{TMPDIR}/toml.toml', ignore_if_missing=True) == Namespace.from_local(f'{TMPDIR}/yaml.yaml', ignore_if_missing=True) == Namespace.from_local(f'{TMPDIR}/json.json', ignore_if_missing=True)) with pytest.raises(NotImplementedError): Namespace.from_local(f'{TMPDIR}/config.special') # write all test data to local files for ftype, data in FACTORIES.items(): with open(f'{TMPDIR}/{ftype}.{ftype}', mode='w') as output: output.write(data) assert (Namespace(TEST_DICT) == Namespace.from_local(f'{TMPDIR}/toml.toml') == Namespace.from_local(f'{TMPDIR}/tml.tml') == Namespace.from_local(f'{TMPDIR}/yaml.yaml') == Namespace.from_local(f'{TMPDIR}/yml.yml') == Namespace.from_local(f'{TMPDIR}/json.json'))
def test_attribute(self) -> None: """Test attribute access is the same as getitem.""" ns = Namespace({'a': 1, 'b': 'foo', 'c': {'x': 3.14}}) assert 1 == ns['a'] == ns.a assert 'foo' == ns['b'] == ns.b assert 3.14 == ns['c']['x'] == ns.c.x
def test_keys_and_values(self) -> None: """Ensure .keys() functions as expected.""" assert list(Namespace(DATA).keys()) == list(DATA.keys()) assert list(Namespace(DATA).values()) == list(DATA.values())
def test_whereis(self) -> None: """Namespace can find paths to leaves in the tree.""" ns = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}}) assert ns.whereis('x') == [('a',), ('b',)] assert ns.whereis('x', 1) == [('a',)] assert ns.whereis('x', lambda v: v % 3 == 0) == [('b',)]
def test_shared_group_option(capsys) -> None: app = SharedGroup.from_cmdline([CMD2, 'foo', '--debug']) assert isinstance(app, ApplicationGroup) assert app.command == CMD2 assert app.shared == Namespace({'opt': False}) assert app.cmdline == ['foo', '--debug']
def test_duplicates(self) -> None: """Namespace can find duplicate leaves in the tree.""" ns = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}}) assert ns.duplicates() == {'x': [('a',), ('b',)]}