Skip to content

Commit

Permalink
feat: a list of data can now also be loaded
Browse files Browse the repository at this point in the history
  • Loading branch information
robinvandernoord committed Jul 25, 2023
1 parent c23bc1f commit e962cf8
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 2 deletions.
38 changes: 38 additions & 0 deletions examples/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,44 @@ if __name__ == '__main__':

```

## Multiple data sources

Sometimes, you need to combine different sources of configuration.
This can be done by providing a list of filenames as the first argument (`data`) of `configuraptor.load_into`.
A dictionary of data can also be used to extend the config files. The files are loaded in order and overwrite any keys
that were already defined in previous files, so be careful with in which order you load them.

```toml
# config.toml
[my_config]
public_key = "some key"
private_key = "<overwrite me>"
```

```env
# secrets.env
PRIVATE_KEY="some private key"
```

```python
from configuraptor import load_into


class MyConfig:
public_key: str
private_key: str
extra: int


data = load_into(MyConfig, ["config.toml", "secrets.env", {"extra": 3}],
# lower_keys=True, # <- automatically set to True when loading a list.
# other settings such as `convert_types` and `key` are still available.
)

data.private_key == "some private key" # because secrets.env was after config.toml in the list, it has overwritten the private_key setting.
data.public_key == "some key" # because secrets.env did not have a public_key setting, the one from config.toml is used.
```

## Inheriting from TypedConfig

In addition to the `MyClass.load` shortcut, inheriting from TypedConfig also gives you the ability to `.update` your
Expand Down
3 changes: 3 additions & 0 deletions pytest_examples/my_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[my_config.custom]
public_key = "this is public"
private_key = "<overwite me>"
1 change: 1 addition & 0 deletions pytest_examples/my_secrets.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PRIVATE_KEY="THIS IS PRIVATE"
14 changes: 13 additions & 1 deletion src/configuraptor/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
# t_typelike is anything that can be type hinted
T_typelike: typing.TypeAlias = type | types.UnionType # | typing.Union
# t_data is anything that can be fed to _load_data
T_data = str | Path | dict[str, typing.Any] | None
T_data_typse = str | Path | dict[str, typing.Any] | None
T_data = T_data_typse | list[T_data_typse]

# c = a config class instance, can be any (user-defined) class
C = typing.TypeVar("C")
# type c is a config class
Expand Down Expand Up @@ -72,6 +74,16 @@ def __load_data(
E.g. class Tool will be mapped to key tool.
It also deals with nested keys (tool.extra -> {"tool": {"extra": ...}}
"""
if isinstance(data, list):
if not data:
raise ValueError("Empty list passed!")

final_data: dict[str, typing.Any] = {}
for source in data:
final_data |= _load_data(source, key=key, classname=classname, lower_keys=True)

return final_data

if isinstance(data, str):
data = Path(data)
if isinstance(data, Path):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_no_data():
with pytest.raises(ValueError):
configuraptor.core._load_data(42, key=None)
with pytest.raises(ValueError):
configuraptor.core._load_data(["joe"], key=None)
configuraptor.core._load_data([], key=None)

# but other than that, it should be fine:
configuraptor.core._load_data({}, key="")
Expand Down
24 changes: 24 additions & 0 deletions tests/test_toml_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ def test_empty():
configuraptor.load_into(First, EMPTY_FILE, key="tool.first")


class MyConfig:
public_key: str
private_key: str
extra: int


def test_multiple_files():
public_file = str(PYTEST_EXAMPLES / "my_config.toml")
private_file = str(PYTEST_EXAMPLES / "my_secrets.env")

config = configuraptor.load_into(MyConfig,
[public_file, # toml
private_file, # .env
{"extra": 3} # raw dict
],
key="my_config.custom" # should work even if only relevant for toml file
# lower keys is automatically set to True
)

assert config.public_key == "this is public"
assert config.private_key == "THIS IS PRIVATE" != "<overwite me>"


def test_basic_classes():
data = _load_toml()

Expand Down Expand Up @@ -192,6 +215,7 @@ def test_dict_of_custom():

try:
import contextlib

chdir = contextlib.chdir
except AttributeError:
from contextlib_chdir import chdir
Expand Down

0 comments on commit e962cf8

Please sign in to comment.