Skip to content

Commit

Permalink
feat: added JSON and YAML file loading.
Browse files Browse the repository at this point in the history
  • Loading branch information
robinvandernoord committed Jun 14, 2023
1 parent c11a338 commit e4f920f
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 16 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class Config:


if __name__ == '__main__':
my_config = load_into(Config, "example_from_docs.toml")
my_config = load_into(Config, "example_from_docs.toml") # or .json, .yaml

print(my_config.name)
# Hello World!
Expand All @@ -100,7 +100,7 @@ class OtherConfig(TypedConfig):


if __name__ == '__main__':
my_config = OtherConfig.load("example_from_docs.toml")
my_config = OtherConfig.load("example_from_docs.toml") # or .json, .yaml

print(my_config.name)
# Hello World!
Expand Down
13 changes: 13 additions & 0 deletions examples/example_from_docs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"config": {
"name": "Hello World!",
"reference": {
"number": 42,
"numbers": [
41,
43
],
"string": "42"
}
}
}
4 changes: 2 additions & 2 deletions examples/example_from_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Config:


if __name__ == '__main__':
my_config = load_into(Config, "example_from_docs.toml")
my_config = load_into(Config, "example_from_docs.json")

print(my_config.name)
# Hello World!
Expand All @@ -40,7 +40,7 @@ class OtherConfig(TypedConfig):


if __name__ == '__main__':
my_config = OtherConfig.load("example_from_docs.toml")
my_config = OtherConfig.load("example_from_docs.json")

print(my_config.name)
# Hello World!
Expand Down
8 changes: 8 additions & 0 deletions examples/example_from_docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
config:
name: "Hello World!"
reference:
number: 42
numbers:
- 41
- 43
string: "42"
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ classifiers = [
dependencies = [
"typeguard",
"tomlkit; python_version < '3.11'",
"PyYAML",
]

[template.plugins.default]
Expand All @@ -34,6 +35,7 @@ src-layout = true
dev = [
"su6[all]",
"hatch",
"types-PyYAML",
]

[project.urls]
Expand Down
13 changes: 13 additions & 0 deletions pytest_examples/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"config": {
"name": "Hello World!",
"reference": {
"number": 42,
"numbers": [
41,
43
],
"string": "42"
}
}
}
7 changes: 7 additions & 0 deletions pytest_examples/example.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[config]
name = "Hello World!"

[config.reference]
number = 42
numbers = [41, 43]
string = "42"
8 changes: 8 additions & 0 deletions pytest_examples/example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
config:
name: "Hello World!"
reference:
number: 42
numbers:
- 41
- 43
string: "42"
4 changes: 2 additions & 2 deletions src/configuraptor/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ def _load_data(data: T_data, key: str = None, classname: str = None) -> dict[str
if isinstance(data, str):
data = Path(data)
if isinstance(data, Path):
# todo: more than toml
with data.open("rb") as f:
data = loaders.toml(f)
loader = loaders.get(data.suffix)
data = loader(f)

if not data:
return {}
Expand Down
26 changes: 25 additions & 1 deletion src/configuraptor/loaders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,34 @@
"""

import sys
import typing

from ._types import T_config
from .loaders_shared import json, yaml

if sys.version_info > (3, 11):
from .loaders_311 import toml
else: # pragma: no cover
from .loaders_310 import toml

__all__ = ["toml"]
__all__ = ["get", "toml", "json", "yaml"]

T_loader = typing.Callable[[typing.BinaryIO], T_config]

LOADERS: dict[str, T_loader] = {
"toml": toml,
"json": json,
"yml": yaml,
"yaml": yaml,
}


def get(extension: str) -> T_loader:
"""
Get the right loader for a specific extension.
"""
extension = extension.removeprefix(".")
if loader := LOADERS.get(extension):
return loader
else:
raise ValueError(f"Invalid extension {extension}")
11 changes: 11 additions & 0 deletions src/configuraptor/loaders/_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import typing

T_config = dict[str, typing.Any]


def as_tconfig(data: typing.Any) -> T_config:
"""
Does not actually do anything, but tells mypy the 'data' of type Any (json, pyyaml, tomlkit) \
is actually a dict of string keys and Any values.
"""
return typing.cast(T_config, data)
10 changes: 5 additions & 5 deletions src/configuraptor/loaders/loaders_310.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"""

import sys
import typing
from typing import BinaryIO

from ._types import T_config, as_tconfig

if sys.version_info > (3, 11):
raise EnvironmentError("Wrong Python version!")
else: # pragma: no cover
import tomlkit

T_toml = dict[str, typing.Any]

def toml(f: BinaryIO) -> T_toml:
def toml(f: BinaryIO) -> T_config:
"""
Load a toml file.
"""
return typing.cast(T_toml, tomlkit.load(f))
data = tomlkit.load(f)
return as_tconfig(data)
5 changes: 3 additions & 2 deletions src/configuraptor/loaders/loaders_311.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
Loaders for Python 3.11+.
"""
import sys
import typing
from typing import BinaryIO

from ._types import T_config

if sys.version_info < (3, 11): # pragma: no cover
raise EnvironmentError("Wrong Python version!")
else:
import tomllib

def toml(f: BinaryIO) -> dict[str, typing.Any]:
def toml(f: BinaryIO) -> T_config:
"""
Load a toml file.
"""
Expand Down
26 changes: 26 additions & 0 deletions src/configuraptor/loaders/loaders_shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
File loaders that work regardless of Python version.
"""

import json as json_lib
from typing import BinaryIO

import yaml as yaml_lib

from ._types import T_config, as_tconfig


def json(f: BinaryIO) -> T_config:
"""
Load a JSON file.
"""
data = json_lib.load(f)
return as_tconfig(data)


def yaml(f: BinaryIO) -> T_config:
"""
Load a YAML file.
"""
data = yaml_lib.load(f, yaml_lib.SafeLoader)
return as_tconfig(data)
9 changes: 7 additions & 2 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
import pytest

if sys.version_info > (3, 11):

def test_loader_310_fails():
with pytest.raises(EnvironmentError):
from src.configuraptor.loaders.loaders_310 import toml

toml()

else:

def test_loader_311_fails():
with pytest.raises(EnvironmentError):
from src.configuraptor.loaders.loaders_311 import toml

toml()


def test_invalid_extension():
from src.configuraptor.loaders import get

with pytest.raises(ValueError):
get(".doesntexist")
21 changes: 21 additions & 0 deletions tests/test_json_yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from src.configuraptor import load_into
from tests.constants import PYTEST_EXAMPLES


class SomeRegularClass:
number: int
numbers: list[int]
string: str


class Config:
name: str
reference: SomeRegularClass


def test_basic_json_and_yaml():
toml = load_into(Config, PYTEST_EXAMPLES / "example.toml")
json = load_into(Config, PYTEST_EXAMPLES / "example.json")
yaml = load_into(Config, PYTEST_EXAMPLES / "example.yaml")

assert toml.reference.numbers and toml.reference.numbers == json.reference.numbers == yaml.reference.numbers

0 comments on commit e4f920f

Please sign in to comment.