configaroo 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {configaroo-0.2.2/src/configaroo.egg-info → configaroo-0.2.4}/PKG-INFO +11 -4
  2. configaroo-0.2.4/README.md +18 -0
  3. configaroo-0.2.4/pyproject.toml +93 -0
  4. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo/__init__.py +6 -5
  5. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo/configuration.py +80 -35
  6. configaroo-0.2.4/src/configaroo/exceptions.py +23 -0
  7. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo/loaders/__init__.py +18 -6
  8. configaroo-0.2.4/src/configaroo/loaders/json.py +16 -0
  9. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo/loaders/toml.py +3 -3
  10. {configaroo-0.2.2 → configaroo-0.2.4/src/configaroo.egg-info}/PKG-INFO +11 -4
  11. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo.egg-info/SOURCES.txt +1 -0
  12. configaroo-0.2.4/src/configaroo.egg-info/requires.txt +2 -0
  13. {configaroo-0.2.2 → configaroo-0.2.4}/tests/test_configuration.py +40 -38
  14. {configaroo-0.2.2 → configaroo-0.2.4}/tests/test_dynamic.py +17 -15
  15. {configaroo-0.2.2 → configaroo-0.2.4}/tests/test_environment.py +16 -14
  16. {configaroo-0.2.2 → configaroo-0.2.4}/tests/test_json.py +14 -13
  17. configaroo-0.2.4/tests/test_loaders.py +45 -0
  18. configaroo-0.2.4/tests/test_print.py +56 -0
  19. {configaroo-0.2.2 → configaroo-0.2.4}/tests/test_toml.py +14 -13
  20. configaroo-0.2.4/tests/test_validation.py +70 -0
  21. configaroo-0.2.2/README.md +0 -10
  22. configaroo-0.2.2/pyproject.toml +0 -64
  23. configaroo-0.2.2/src/configaroo/exceptions.py +0 -13
  24. configaroo-0.2.2/src/configaroo/loaders/json.py +0 -13
  25. configaroo-0.2.2/src/configaroo.egg-info/requires.txt +0 -2
  26. configaroo-0.2.2/tests/test_loaders.py +0 -38
  27. configaroo-0.2.2/tests/test_validation.py +0 -61
  28. {configaroo-0.2.2 → configaroo-0.2.4}/LICENSE +0 -0
  29. {configaroo-0.2.2 → configaroo-0.2.4}/setup.cfg +0 -0
  30. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo/py.typed +0 -0
  31. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo.egg-info/dependency_links.txt +0 -0
  32. {configaroo-0.2.2 → configaroo-0.2.4}/src/configaroo.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: configaroo
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Bouncy handling of configuration files
5
5
  Author-email: Geir Arne Hjelle <geirarne@gmail.com>
6
6
  Maintainer-email: Geir Arne Hjelle <geirarne@gmail.com>
@@ -8,7 +8,7 @@ License-Expression: MIT
8
8
  Project-URL: homepage, https://github.com/gahjelle/configaroo
9
9
  Project-URL: github, https://github.com/gahjelle/configaroo
10
10
  Project-URL: issues, https://github.com/gahjelle/configaroo/issues
11
- Project-URL: changelog, https://github.com/gahjelle/configaroo/blob/main/CHANGELOG.md
11
+ Project-URL: changelog, https://github.com/gahjelle/configaroo/releases
12
12
  Keywords: configuration,configuration-management,toml,json
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
@@ -17,7 +17,6 @@ Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Programming Language :: Python :: 3.14
21
20
  Classifier: Programming Language :: Python :: 3
22
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
22
  Classifier: Typing :: Typed
@@ -25,11 +24,19 @@ Requires-Python: >=3.11
25
24
  Description-Content-Type: text/markdown
26
25
  License-File: LICENSE
27
26
  Requires-Dist: pydantic>=2.0
28
- Requires-Dist: pyplugs>=0.4.0
27
+ Requires-Dist: pyplugs>=0.5.4
29
28
  Dynamic: license-file
30
29
 
31
30
  # Configaroo - Bouncy Configuration Handling
32
31
 
32
+ [![Latest version](https://img.shields.io/pypi/v/configaroo.svg)](https://pypi.org/project/configaroo/)
33
+ [![Python versions](https://img.shields.io/pypi/pyversions/configaroo.svg)](https://pypi.org/project/configaroo/)
34
+ [![License](https://img.shields.io/pypi/l/configaroo.svg)](https://github.com/gahjelle/configaroo/blob/main/LICENSE)
35
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
36
+ [![Linted](https://github.com/gahjelle/configaroo/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/gahjelle/configaroo/actions/workflows/lint.yml)
37
+ [![Tested with Pytest](https://github.com/gahjelle/configaroo/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/gahjelle/configaroo/actions/workflows/test.yml)
38
+ [![Type checked with mypy](https://img.shields.io/badge/type%20checked-mypy-green)](http://mypy-lang.org/)
39
+
33
40
  Configaroo is a light configuration package for Python that offers the following features:
34
41
 
35
42
  - Access configuration settings with dotted keys: `config.nested.key`
@@ -0,0 +1,18 @@
1
+ # Configaroo - Bouncy Configuration Handling
2
+
3
+ [![Latest version](https://img.shields.io/pypi/v/configaroo.svg)](https://pypi.org/project/configaroo/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/configaroo.svg)](https://pypi.org/project/configaroo/)
5
+ [![License](https://img.shields.io/pypi/l/configaroo.svg)](https://github.com/gahjelle/configaroo/blob/main/LICENSE)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
+ [![Linted](https://github.com/gahjelle/configaroo/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/gahjelle/configaroo/actions/workflows/lint.yml)
8
+ [![Tested with Pytest](https://github.com/gahjelle/configaroo/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/gahjelle/configaroo/actions/workflows/test.yml)
9
+ [![Type checked with mypy](https://img.shields.io/badge/type%20checked-mypy-green)](http://mypy-lang.org/)
10
+
11
+ Configaroo is a light configuration package for Python that offers the following features:
12
+
13
+ - Access configuration settings with dotted keys: `config.nested.key`
14
+ - Use different configuration file formats, including TOML and JSON
15
+ - Override key configuration settings with environment variables
16
+ - Validate a configuration based on a Pydantic model
17
+ - Convert the type of configuration values based on a Pydantic model
18
+ - Dynamically format certain configuration values
@@ -0,0 +1,93 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+
6
+ [project]
7
+ name = "configaroo"
8
+ description = "Bouncy handling of configuration files"
9
+ readme = "README.md"
10
+ authors = [{ name = "Geir Arne Hjelle", email = "geirarne@gmail.com" }]
11
+ maintainers = [{ name = "Geir Arne Hjelle", email = "geirarne@gmail.com" }]
12
+ requires-python = ">=3.11"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ keywords = ["configuration", "configuration-management", "toml", "json"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Developers",
19
+ "Natural Language :: English",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = ["pydantic>=2.0", "pyplugs>=0.5.4"]
29
+ dynamic = ["version"]
30
+
31
+ [project.urls]
32
+ homepage = "https://github.com/gahjelle/configaroo"
33
+ github = "https://github.com/gahjelle/configaroo"
34
+ issues = "https://github.com/gahjelle/configaroo/issues"
35
+ changelog = "https://github.com/gahjelle/configaroo/releases"
36
+
37
+ [dependency-groups]
38
+ build = ["build>=1.2.2.post1", "twine>=6.1.0"]
39
+ dev = [
40
+ "bumpver>=2024.1130",
41
+ "ipython>=8.36.0",
42
+ "mypy>=1.17.1",
43
+ "pre-commit>=4.2.0",
44
+ "pytest>=8.3.5",
45
+ "rich>=14.1.0",
46
+ "ruff>=0.11.11",
47
+ "tomli-w>=1.2.0",
48
+ ]
49
+
50
+
51
+ [tool.setuptools.dynamic]
52
+ version = { attr = "configaroo.__version__" }
53
+
54
+
55
+ [tool.ruff]
56
+ target-version = "py311"
57
+ exclude = [".git", "venv", "migrations", "node_modules"]
58
+
59
+ [tool.ruff.lint]
60
+ select = ["ALL"]
61
+ ignore = [
62
+ "COM812", # Trailing comma missing (handled by formatter)
63
+ "D203", # Force blank line before class docstring (incompatible with D211)
64
+ "D213", # Start docstring on second line (incompatible with D212)
65
+ ]
66
+
67
+ [tool.ruff.lint.per-file-ignores]
68
+ # D101: Missing docstring in public class (Basemodel classes may break this)
69
+ # PLR2004: Magic value used in comparison (OK for tests)
70
+ # S101: Use of `assert` (OK for tests)
71
+ # SLF001: Private member accessed (OK for tests)
72
+ # UP018: Replace with string/integer/... literal (OK for typed tests)
73
+ "examples/*/config.py" = ["D101"]
74
+ "tests/schema.py" = ["D101"]
75
+ "tests/test_*.py" = ["PLR2004", "S101", "SLF001", "UP018"]
76
+ "tests/*/test_*.py" = ["S101"]
77
+
78
+ [tool.mypy]
79
+ python_version = "3.11"
80
+ strict = true
81
+
82
+ [tool.bumpver]
83
+ current_version = "v0.2.4"
84
+ version_pattern = "vMAJOR.MINOR.PATCH"
85
+ commit_message = "bump version {old_version} -> {new_version}"
86
+ tag_message = "{new_version}"
87
+ commit = true
88
+ tag = true
89
+ push = true
90
+
91
+ [tool.bumpver.file_patterns]
92
+ "pyproject.toml" = ['current_version = "{version}"']
93
+ "src/configaroo/__init__.py" = ['__version__ = "MAJOR.MINOR.PATCH"']
@@ -1,17 +1,18 @@
1
- """Bouncy configuration handling"""
1
+ """Bouncy configuration handling."""
2
2
 
3
- from configaroo.configuration import Configuration
3
+ from configaroo.configuration import Configuration, print_configuration
4
4
  from configaroo.exceptions import (
5
- ConfigarooException,
5
+ ConfigarooError,
6
6
  MissingEnvironmentVariableError,
7
7
  UnsupportedLoaderError,
8
8
  )
9
9
 
10
10
  __all__ = [
11
+ "ConfigarooError",
11
12
  "Configuration",
12
- "ConfigarooException",
13
13
  "MissingEnvironmentVariableError",
14
14
  "UnsupportedLoaderError",
15
+ "print_configuration",
15
16
  ]
16
17
 
17
- __version__ = "0.2.2"
18
+ __version__ = "0.2.4"
@@ -1,11 +1,12 @@
1
- """A dict-like configuration with support for envvars, validation and type conversion"""
1
+ """A dict-like config with support for envvars, validation and type conversion."""
2
2
 
3
3
  import inspect
4
4
  import os
5
5
  import re
6
6
  from collections import UserDict
7
+ from collections.abc import Callable
7
8
  from pathlib import Path
8
- from typing import Any, Self, Type, TypeVar
9
+ from typing import Any, Self, TypeVar
9
10
 
10
11
  from pydantic import BaseModel
11
12
 
@@ -15,12 +16,12 @@ from configaroo.exceptions import MissingEnvironmentVariableError
15
16
  ModelT = TypeVar("ModelT", bound=BaseModel)
16
17
 
17
18
 
18
- class Configuration(UserDict):
19
- """A Configuration is a dict-like structure with some conveniences"""
19
+ class Configuration(UserDict[str, Any]):
20
+ """A Configuration is a dict-like structure with some conveniences."""
20
21
 
21
22
  @classmethod
22
23
  def from_dict(cls, data: dict[str, Any] | UserDict[str, Any] | Self) -> Self:
23
- """Construct a Configuration from a dictionary
24
+ """Construct a Configuration from a dictionary.
24
25
 
25
26
  The dictionary is referenced directly, a copy isn't made
26
27
  """
@@ -40,7 +41,7 @@ class Configuration(UserDict):
40
41
  env_prefix: str = "",
41
42
  extra_dynamic: dict[str, Any] | None = None,
42
43
  ) -> Self:
43
- """Read a Configuration from a file"""
44
+ """Read a Configuration from a file."""
44
45
  config_dict = loaders.from_file(file_path, loader=loader)
45
46
  return cls(config_dict).initialize(
46
47
  envs=envs, env_prefix=env_prefix, extra_dynamic=extra_dynamic
@@ -56,32 +57,34 @@ class Configuration(UserDict):
56
57
 
57
58
  The initialization adds environment variables and parses dynamic values.
58
59
  """
59
- self = self if envs is None else self.add_envs(envs, prefix=env_prefix)
60
+ self = self if envs is None else self.add_envs(envs, prefix=env_prefix) # noqa: PLW0642
60
61
  return self.parse_dynamic(extra_dynamic)
61
62
 
62
- def with_model(self, model: Type[ModelT]) -> ModelT:
63
+ def with_model(self, model: type[ModelT]) -> ModelT:
63
64
  """Apply a pydantic model to a configuration."""
64
65
  return self.validate_model(model).convert_model(model)
65
66
 
66
- def __getitem__(self, key: str) -> Any:
67
- """Make sure nested sections have type Configuration"""
67
+ def __getitem__(self, key: str) -> Any: # noqa: ANN401
68
+ """Make sure nested sections have type Configuration."""
68
69
  value = self.data[key]
69
70
  if isinstance(value, dict | UserDict | Configuration):
70
71
  return Configuration.from_dict(value)
71
- else:
72
- return value
73
72
 
74
- def __getattr__(self, key: str) -> Any:
75
- """Create attribute access for config keys for convenience"""
73
+ return value
74
+
75
+ def __getattr__(self, key: str) -> Any: # noqa: ANN401
76
+ """Create attribute access for config keys for convenience."""
76
77
  try:
77
78
  return self[key]
78
79
  except KeyError:
79
- raise AttributeError(
80
- f"'{type(self).__name__}' has no attribute or key '{key}'"
81
- )
80
+ message = f"'{type(self).__name__}' has no attribute or key '{key}'"
81
+ raise AttributeError(message) from None
82
82
 
83
83
  def __contains__(self, key: object) -> bool:
84
- """Add support for dotted keys"""
84
+ """Add support for dotted keys.
85
+
86
+ The type hint for key is object to match the UserDict class.
87
+ """
85
88
  if key in self.data:
86
89
  return True
87
90
  prefix, _, rest = str(key).partition(".")
@@ -90,8 +93,8 @@ class Configuration(UserDict):
90
93
  except KeyError:
91
94
  return False
92
95
 
93
- def get(self, key: str, default: Any = None) -> Any:
94
- """Allow dotted keys when using .get()"""
96
+ def get(self, key: str, default: Any = None) -> Any: # noqa: ANN401
97
+ """Allow dotted keys when using .get()."""
95
98
  if key in self.data:
96
99
  return self[key]
97
100
 
@@ -101,8 +104,8 @@ class Configuration(UserDict):
101
104
  except KeyError:
102
105
  return default
103
106
 
104
- def add(self, key: str, value: Any) -> Self:
105
- """Add a value, allow dotted keys"""
107
+ def add(self, key: str, value: Any) -> Self: # noqa: ANN401
108
+ """Add a value, allow dotted keys."""
106
109
  prefix, _, rest = key.partition(".")
107
110
  if not rest:
108
111
  return self | {key: value}
@@ -110,21 +113,19 @@ class Configuration(UserDict):
110
113
  return self | {prefix: cls(self.setdefault(prefix, {})).add(rest, value)}
111
114
 
112
115
  def add_envs(self, envs: dict[str, str], prefix: str = "") -> Self:
113
- """Add environment variables to configuration"""
116
+ """Add environment variables to configuration."""
114
117
  for env, key in envs.items():
115
118
  env_key = f"{prefix}{env}"
116
119
  if env_value := os.getenv(env_key):
117
- self = self.add(key, env_value)
120
+ self = self.add(key, env_value) # noqa: PLW0642
118
121
  elif key not in self:
119
- raise MissingEnvironmentVariableError(
120
- f"required environment variable '{env_key}' not found"
121
- )
122
+ raise MissingEnvironmentVariableError(env_key)
122
123
  return self
123
124
 
124
125
  def parse_dynamic(
125
- self, extra: dict[str, Any] | None = None, _include_self: bool = True
126
+ self, extra: dict[str, Any] | None = None, *, _include_self: bool = True
126
127
  ) -> Self:
127
- """Parse dynamic values of the form {section.key}"""
128
+ """Parse dynamic values of the form {section.key}."""
128
129
  cls = type(self)
129
130
  variables = (
130
131
  (self.to_flat_dict() if _include_self else {})
@@ -148,17 +149,17 @@ class Configuration(UserDict):
148
149
  # Continue parsing until no more replacements are made.
149
150
  return parsed.parse_dynamic(extra=extra, _include_self=_include_self)
150
151
 
151
- def validate_model(self, model: Type[BaseModel]) -> Self:
152
+ def validate_model(self, model: type[BaseModel]) -> Self:
152
153
  """Validate the configuration against the given model."""
153
154
  model.model_validate(self.data)
154
155
  return self
155
156
 
156
- def convert_model(self, model: Type[ModelT]) -> ModelT:
157
- """Convert data types to match the given model"""
157
+ def convert_model(self, model: type[ModelT]) -> ModelT:
158
+ """Convert data types to match the given model."""
158
159
  return model(**self.data)
159
160
 
160
161
  def to_dict(self) -> dict[str, Any]:
161
- """Dump the configuration into a Python dictionary"""
162
+ """Dump the configuration into a Python dictionary."""
162
163
  return {
163
164
  key: value.to_dict() if isinstance(value, Configuration) else value
164
165
  for key, value in self.items()
@@ -183,6 +184,50 @@ class Configuration(UserDict):
183
184
  }
184
185
 
185
186
 
187
+ def print_configuration(config: Configuration | BaseModel, indent: int = 4) -> None:
188
+ """Pretty print a configuration.
189
+
190
+ If rich is installed, then a rich console is used for the printing.
191
+ """
192
+ return _print_dict_as_tree(
193
+ config.model_dump() if isinstance(config, BaseModel) else config,
194
+ indent=indent,
195
+ _print=_get_rich_print(),
196
+ )
197
+
198
+
199
+ def _get_rich_print() -> Callable[[str], None]:
200
+ """Initialize a Rich console if Rich is installed, otherwise use built-in print."""
201
+ try:
202
+ from rich.console import Console # noqa: PLC0415
203
+
204
+ return Console().print
205
+ except ImportError:
206
+ import builtins # noqa: PLC0415
207
+
208
+ return builtins.print
209
+
210
+
211
+ def _print_dict_as_tree(
212
+ data: dict[str, Any] | UserDict[str, Any] | Configuration,
213
+ indent: int = 4,
214
+ current_indent: int = 0,
215
+ _print: Callable[[str], None] = print,
216
+ ) -> None:
217
+ """Print a nested dictionary as a tree."""
218
+ for key, value in data.items():
219
+ if isinstance(value, dict | UserDict | Configuration):
220
+ _print(" " * current_indent + f"- {key}")
221
+ _print_dict_as_tree(
222
+ value,
223
+ indent=indent,
224
+ current_indent=current_indent + indent,
225
+ _print=_print,
226
+ )
227
+ else:
228
+ _print(" " * current_indent + f"- {key}: {value!r}")
229
+
230
+
186
231
  def _find_pyproject_toml(
187
232
  path: Path | None = None, _file_name: str = "pyproject.toml"
188
233
  ) -> Path:
@@ -195,8 +240,8 @@ def _find_pyproject_toml(
195
240
  path = _get_foreign_path() if path is None else path
196
241
  if (path / _file_name).exists() or path == path.parent:
197
242
  return path.resolve()
198
- else:
199
- return _find_pyproject_toml(path.parent, _file_name=_file_name)
243
+
244
+ return _find_pyproject_toml(path.parent, _file_name=_file_name)
200
245
 
201
246
 
202
247
  def _get_foreign_path() -> Path:
@@ -0,0 +1,23 @@
1
+ """Configaroo specific exceptions."""
2
+
3
+
4
+ class ConfigarooError(Exception):
5
+ """Base exception for more specific Configaroo exceptions."""
6
+
7
+
8
+ class MissingEnvironmentVariableError(ConfigarooError, KeyError):
9
+ """A required environment variable is missing."""
10
+
11
+ def __init__(self, name: str) -> None:
12
+ """Set a consistent error message."""
13
+ super().__init__(f"required environment variable '{name}' not found")
14
+
15
+
16
+ class UnsupportedLoaderError(ConfigarooError, ValueError):
17
+ """An unsupported loader is called."""
18
+
19
+ def __init__(self, loader: str, available: list[str]) -> None:
20
+ """Set a consistent error message."""
21
+ super().__init__(
22
+ f"file type '{loader}' isn't supported. Use one of: {', '.join(available)}"
23
+ )
@@ -7,8 +7,23 @@ import pyplugs
7
7
 
8
8
  from configaroo.exceptions import UnsupportedLoaderError
9
9
 
10
- load = pyplugs.call_factory(__package__)
11
- loader_names = pyplugs.names_factory(__package__)
10
+ PACKAGE = str(__package__)
11
+
12
+
13
+ def load(loader: str, path: Path) -> dict[str, Any]:
14
+ """Load a file using the given loader."""
15
+ return pyplugs.call_typed(
16
+ PACKAGE,
17
+ plugin=loader,
18
+ func="load",
19
+ path=path,
20
+ _return_type=dict(), # noqa: C408
21
+ )
22
+
23
+
24
+ def loader_names() -> list[str]:
25
+ """List names of available loaders."""
26
+ return sorted(pyplugs.names(PACKAGE))
12
27
 
13
28
 
14
29
  def from_file(path: str | Path, loader: str | None = None) -> dict[str, Any]:
@@ -18,7 +33,4 @@ def from_file(path: str | Path, loader: str | None = None) -> dict[str, Any]:
18
33
  try:
19
34
  return load(loader, path=path)
20
35
  except pyplugs.UnknownPluginError:
21
- raise UnsupportedLoaderError(
22
- f"file type '{loader}' isn't supported. "
23
- f"Use one of: {', '.join(loader_names())}"
24
- ) from None
36
+ raise UnsupportedLoaderError(loader, loader_names()) from None
@@ -0,0 +1,16 @@
1
+ """Loader for JSON-files."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import pyplugs
8
+
9
+
10
+ @pyplugs.register
11
+ def load(path: Path) -> dict[str, Any]:
12
+ """Read a JSON-file.
13
+
14
+ Enforce that the JSON is an array/dict.
15
+ """
16
+ return dict(json.loads(path.read_text(encoding="utf-8")))
@@ -1,4 +1,4 @@
1
- """Loader for TOML-files"""
1
+ """Loader for TOML-files."""
2
2
 
3
3
  import tomllib
4
4
  from pathlib import Path
@@ -8,6 +8,6 @@ import pyplugs
8
8
 
9
9
 
10
10
  @pyplugs.register
11
- def load_toml_file(path: Path) -> dict[str, Any]:
12
- """Read a TOML-file"""
11
+ def load(path: Path) -> dict[str, Any]:
12
+ """Read a TOML-file."""
13
13
  return tomllib.loads(path.read_text(encoding="utf-8"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: configaroo
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Bouncy handling of configuration files
5
5
  Author-email: Geir Arne Hjelle <geirarne@gmail.com>
6
6
  Maintainer-email: Geir Arne Hjelle <geirarne@gmail.com>
@@ -8,7 +8,7 @@ License-Expression: MIT
8
8
  Project-URL: homepage, https://github.com/gahjelle/configaroo
9
9
  Project-URL: github, https://github.com/gahjelle/configaroo
10
10
  Project-URL: issues, https://github.com/gahjelle/configaroo/issues
11
- Project-URL: changelog, https://github.com/gahjelle/configaroo/blob/main/CHANGELOG.md
11
+ Project-URL: changelog, https://github.com/gahjelle/configaroo/releases
12
12
  Keywords: configuration,configuration-management,toml,json
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
@@ -17,7 +17,6 @@ Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Programming Language :: Python :: 3.14
21
20
  Classifier: Programming Language :: Python :: 3
22
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
22
  Classifier: Typing :: Typed
@@ -25,11 +24,19 @@ Requires-Python: >=3.11
25
24
  Description-Content-Type: text/markdown
26
25
  License-File: LICENSE
27
26
  Requires-Dist: pydantic>=2.0
28
- Requires-Dist: pyplugs>=0.4.0
27
+ Requires-Dist: pyplugs>=0.5.4
29
28
  Dynamic: license-file
30
29
 
31
30
  # Configaroo - Bouncy Configuration Handling
32
31
 
32
+ [![Latest version](https://img.shields.io/pypi/v/configaroo.svg)](https://pypi.org/project/configaroo/)
33
+ [![Python versions](https://img.shields.io/pypi/pyversions/configaroo.svg)](https://pypi.org/project/configaroo/)
34
+ [![License](https://img.shields.io/pypi/l/configaroo.svg)](https://github.com/gahjelle/configaroo/blob/main/LICENSE)
35
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
36
+ [![Linted](https://github.com/gahjelle/configaroo/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/gahjelle/configaroo/actions/workflows/lint.yml)
37
+ [![Tested with Pytest](https://github.com/gahjelle/configaroo/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/gahjelle/configaroo/actions/workflows/test.yml)
38
+ [![Type checked with mypy](https://img.shields.io/badge/type%20checked-mypy-green)](http://mypy-lang.org/)
39
+
33
40
  Configaroo is a light configuration package for Python that offers the following features:
34
41
 
35
42
  - Access configuration settings with dotted keys: `config.nested.key`
@@ -18,5 +18,6 @@ tests/test_dynamic.py
18
18
  tests/test_environment.py
19
19
  tests/test_json.py
20
20
  tests/test_loaders.py
21
+ tests/test_print.py
21
22
  tests/test_toml.py
22
23
  tests/test_validation.py
@@ -0,0 +1,2 @@
1
+ pydantic>=2.0
2
+ pyplugs>=0.5.4