Skip to content

Commit

Permalink
Allow nested keys when doing replace (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
gahjelle authored Jun 8, 2021
1 parent 3240eea commit 86c3e0b
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 7 deletions.
29 changes: 22 additions & 7 deletions pyconfs/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,20 +194,26 @@ def update_from_str(self, string: str, format: str, *, source=None) -> None:
entries = readers.from_str(format, string=string)
self.update_from_dict(entries, source=source)

def get(self, key, default=None):
def get(self, key, default=None, strict=False):
"""Get a value from the configuration, allow nested keys"""
if isinstance(key, list):
first_key, *tail_keys = key
value = super().get(first_key, default)
value = self.get(first_key, default, strict=strict)
if tail_keys:
try:
return value.get(list(tail_keys), default)
return value.get(list(tail_keys), default, strict=strict)
except AttributeError:
return default
if strict:
raise KeyError(tail_keys)
else:
return default
else:
return value
else:
return super().get(key, default)
if strict:
return self.data[key]
else:
return super().get(key, default)

@property
def name_stem(self) -> str:
Expand Down Expand Up @@ -342,10 +348,19 @@ def replace(
) -> Any:
"""Replace values in an entry based on {} format strings"""
all_vars = Variables(**self.vars, **replace_vars, default=default)
value = textwrap.dedent(self.data[key]) if dedent else self.data[key]
value = (
textwrap.dedent(self.get(key, strict=True))
if dedent
else self.get(key, strict=True)
)

# Replace variables
replaced = value.format_map(all_vars)
try:
replaced = value.format_map(all_vars)
except AttributeError:
raise _exceptions.ConversionError(
f"Only strings can be replaced, not {type(value).__name__!r} "
) from None

# Optionally convert value to a different data type
if converter is None:
Expand Down
87 changes: 87 additions & 0 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Test Configuration object"""

# Standard library imports
import pathlib

# Third party imports
import pytest

# PyConfs imports
import pyconfs
from pyconfs import _exceptions


@pytest.fixture
def cfg():
"""A basic configuration for testing"""
return pyconfs.Configuration.from_dict(
{
"name": "pyconfs",
"dependencies": [
{
"name": "python",
"url": "https://www.python.org/",
"versions": [3.6, 3.7, 3.8, 3.9],
},
{"name": "pyplugs", "url": "https://pyplugs.readthedocs.io"},
],
"author": {"firstname": "Geir Arne", "lastname": "Hjelle"},
"files": {
"configuration": "{basepath}/pyconfs/configuration.py",
"toml-reader": "{basepath}/pyconfs/readers/toml.py",
"test": "{basepath}/tests/test_configuration.py",
},
"number": 7,
},
name="test_config",
)


def test_replace(cfg):
"""Test that a variable can be replaced"""
expected = "/home/tests/test_configuration.py"
assert cfg.files.replace("test", basepath="/home") == expected


def test_replace_empty(cfg):
"""Test that a variable can be replaced by an empty string"""
expected = "/tests/test_configuration.py"
assert cfg.files.replace("test", basepath="") == expected


def test_replace_passthrough(cfg):
"""Test that a variable is passed through if it's not specified"""
expected = "{basepath}/tests/test_configuration.py"
assert cfg.files.replace("test") == expected


def test_replace_converter(cfg):
"""Test that a converter can be applied when doing replace"""
assert isinstance(
cfg.files.replace("test", basepath="", converter="path"), pathlib.Path
)


def test_replace_callable_converter(cfg):
"""Test that a callable can be used as a converter when doing replace"""
assert isinstance(
cfg.files.replace("test", basepath="", converter=pathlib.Path), pathlib.Path
)


def test_replace_nonexisting(cfg):
"""Test that replace gives a proper error message when a key is missing"""
with pytest.raises(KeyError):
cfg.files.replace("nonexisting")


def test_replace_list_key(cfg):
"""Test that a list can be used to specify nested keys in replace"""
expected = "{basepath}/tests/test_configuration.py"
assert cfg.replace(["files", "test"]) == expected


def test_replace_notstring(cfg):
"""Test that replacing a non-string raises a proper error"""
with pytest.raises(_exceptions.ConversionError):
cfg.replace("number")

0 comments on commit 86c3e0b

Please sign in to comment.