configaroo 0.2.1__tar.gz → 0.2.3__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 (31) hide show
  1. {configaroo-0.2.1 → configaroo-0.2.3}/LICENSE +1 -1
  2. {configaroo-0.2.1/src/configaroo.egg-info → configaroo-0.2.3}/PKG-INFO +10 -3
  3. configaroo-0.2.3/README.md +18 -0
  4. {configaroo-0.2.1 → configaroo-0.2.3}/pyproject.toml +43 -13
  5. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo/__init__.py +6 -5
  6. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo/configuration.py +95 -37
  7. configaroo-0.2.3/src/configaroo/exceptions.py +23 -0
  8. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo/loaders/__init__.py +18 -6
  9. configaroo-0.2.3/src/configaroo/loaders/json.py +16 -0
  10. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo/loaders/toml.py +3 -3
  11. {configaroo-0.2.1 → configaroo-0.2.3/src/configaroo.egg-info}/PKG-INFO +10 -3
  12. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo.egg-info/SOURCES.txt +1 -0
  13. configaroo-0.2.3/src/configaroo.egg-info/requires.txt +2 -0
  14. {configaroo-0.2.1 → configaroo-0.2.3}/tests/test_configuration.py +54 -34
  15. {configaroo-0.2.1 → configaroo-0.2.3}/tests/test_dynamic.py +17 -15
  16. {configaroo-0.2.1 → configaroo-0.2.3}/tests/test_environment.py +16 -14
  17. {configaroo-0.2.1 → configaroo-0.2.3}/tests/test_json.py +14 -13
  18. configaroo-0.2.3/tests/test_loaders.py +45 -0
  19. configaroo-0.2.3/tests/test_print.py +56 -0
  20. {configaroo-0.2.1 → configaroo-0.2.3}/tests/test_toml.py +14 -13
  21. configaroo-0.2.3/tests/test_validation.py +70 -0
  22. configaroo-0.2.1/README.md +0 -10
  23. configaroo-0.2.1/src/configaroo/exceptions.py +0 -13
  24. configaroo-0.2.1/src/configaroo/loaders/json.py +0 -13
  25. configaroo-0.2.1/src/configaroo.egg-info/requires.txt +0 -2
  26. configaroo-0.2.1/tests/test_loaders.py +0 -38
  27. configaroo-0.2.1/tests/test_validation.py +0 -61
  28. {configaroo-0.2.1 → configaroo-0.2.3}/setup.cfg +0 -0
  29. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo/py.typed +0 -0
  30. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo.egg-info/dependency_links.txt +0 -0
  31. {configaroo-0.2.1 → configaroo-0.2.3}/src/configaroo.egg-info/top_level.txt +0 -0
@@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
17
  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
18
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
19
  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: configaroo
3
- Version: 0.2.1
3
+ Version: 0.2.3
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>
@@ -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
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61"]
2
+ requires = ["setuptools>=61"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
 
@@ -21,18 +21,17 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
- "Programming Language :: Python :: 3.14",
25
24
  "Programming Language :: Python :: 3",
26
25
  "Topic :: Software Development :: Libraries :: Python Modules",
27
26
  "Typing :: Typed",
28
27
  ]
29
- dependencies = ["pydantic>=2.0", "pyplugs>=0.4.0"]
28
+ dependencies = ["pydantic>=2.0", "pyplugs>=0.5.4"]
30
29
  dynamic = ["version"]
31
30
 
32
31
  [project.urls]
33
- homepage = "https://github.com/gahjelle/configaroo"
34
- github = "https://github.com/gahjelle/configaroo"
35
- issues = "https://github.com/gahjelle/configaroo/issues"
32
+ homepage = "https://github.com/gahjelle/configaroo"
33
+ github = "https://github.com/gahjelle/configaroo"
34
+ issues = "https://github.com/gahjelle/configaroo/issues"
36
35
  changelog = "https://github.com/gahjelle/configaroo/blob/main/CHANGELOG.md"
37
36
 
38
37
  [dependency-groups]
@@ -40,7 +39,10 @@ build = ["build>=1.2.2.post1", "twine>=6.1.0"]
40
39
  dev = [
41
40
  "bumpver>=2024.1130",
42
41
  "ipython>=8.36.0",
42
+ "mypy>=1.17.1",
43
+ "pre-commit>=4.2.0",
43
44
  "pytest>=8.3.5",
45
+ "rich>=14.1.0",
44
46
  "ruff>=0.11.11",
45
47
  "tomli-w>=1.2.0",
46
48
  ]
@@ -49,15 +51,43 @@ dev = [
49
51
  [tool.setuptools.dynamic]
50
52
  version = { attr = "configaroo.__version__" }
51
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
+
52
82
  [tool.bumpver]
53
- current_version = "v0.2.1"
83
+ current_version = "v0.2.3"
54
84
  version_pattern = "vMAJOR.MINOR.PATCH"
55
- commit_message = "bump version {old_version} -> {new_version}"
56
- tag_message = "{new_version}"
57
- commit = true
58
- tag = true
59
- push = true
85
+ commit_message = "bump version {old_version} -> {new_version}"
86
+ tag_message = "{new_version}"
87
+ commit = true
88
+ tag = true
89
+ push = true
60
90
 
61
91
  [tool.bumpver.file_patterns]
62
- "pyproject.toml" = ['current_version = "{version}"']
92
+ "pyproject.toml" = ['current_version = "{version}"']
63
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.1"
18
+ __version__ = "0.2.3"
@@ -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,8 +16,21 @@ 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."""
21
+
22
+ @classmethod
23
+ def from_dict(cls, data: dict[str, Any] | UserDict[str, Any] | Self) -> Self:
24
+ """Construct a Configuration from a dictionary.
25
+
26
+ The dictionary is referenced directly, a copy isn't made
27
+ """
28
+ configuration = cls()
29
+ if isinstance(data, UserDict | Configuration):
30
+ configuration.data = data.data
31
+ else:
32
+ configuration.data = data
33
+ return configuration
20
34
 
21
35
  @classmethod
22
36
  def from_file(
@@ -27,7 +41,7 @@ class Configuration(UserDict):
27
41
  env_prefix: str = "",
28
42
  extra_dynamic: dict[str, Any] | None = None,
29
43
  ) -> Self:
30
- """Read a Configuration from a file"""
44
+ """Read a Configuration from a file."""
31
45
  config_dict = loaders.from_file(file_path, loader=loader)
32
46
  return cls(config_dict).initialize(
33
47
  envs=envs, env_prefix=env_prefix, extra_dynamic=extra_dynamic
@@ -43,42 +57,44 @@ class Configuration(UserDict):
43
57
 
44
58
  The initialization adds environment variables and parses dynamic values.
45
59
  """
46
- 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
47
61
  return self.parse_dynamic(extra_dynamic)
48
62
 
49
- def with_model(self, model: Type[ModelT]) -> ModelT:
63
+ def with_model(self, model: type[ModelT]) -> ModelT:
50
64
  """Apply a pydantic model to a configuration."""
51
65
  return self.validate_model(model).convert_model(model)
52
66
 
53
- def __getitem__(self, key: str) -> Any:
54
- """Make sure nested sections have type Configuration"""
67
+ def __getitem__(self, key: str) -> Any: # noqa: ANN401
68
+ """Make sure nested sections have type Configuration."""
55
69
  value = self.data[key]
56
70
  if isinstance(value, dict | UserDict | Configuration):
57
- return Configuration(value)
58
- else:
59
- return value
71
+ return Configuration.from_dict(value)
72
+
73
+ return value
60
74
 
61
- def __getattr__(self, key: str) -> Any:
62
- """Create attribute access for config keys for convenience"""
75
+ def __getattr__(self, key: str) -> Any: # noqa: ANN401
76
+ """Create attribute access for config keys for convenience."""
63
77
  try:
64
78
  return self[key]
65
79
  except KeyError:
66
- raise AttributeError(
67
- f"'{type(self).__name__}' has no attribute or key '{key}'"
68
- )
80
+ message = f"'{type(self).__name__}' has no attribute or key '{key}'"
81
+ raise AttributeError(message) from None
82
+
83
+ def __contains__(self, key: object) -> bool:
84
+ """Add support for dotted keys.
69
85
 
70
- def __contains__(self, key: str) -> bool:
71
- """Add support for dotted keys"""
86
+ The type hint for key is object to match the UserDict class.
87
+ """
72
88
  if key in self.data:
73
89
  return True
74
- prefix, _, rest = key.partition(".")
90
+ prefix, _, rest = str(key).partition(".")
75
91
  try:
76
92
  return rest in self[prefix]
77
93
  except KeyError:
78
94
  return False
79
95
 
80
- def get(self, key: str, default: Any = None) -> Any:
81
- """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()."""
82
98
  if key in self.data:
83
99
  return self[key]
84
100
 
@@ -88,8 +104,8 @@ class Configuration(UserDict):
88
104
  except KeyError:
89
105
  return default
90
106
 
91
- def add(self, key: str, value: Any) -> Self:
92
- """Add a value, allow dotted keys"""
107
+ def add(self, key: str, value: Any) -> Self: # noqa: ANN401
108
+ """Add a value, allow dotted keys."""
93
109
  prefix, _, rest = key.partition(".")
94
110
  if not rest:
95
111
  return self | {key: value}
@@ -97,21 +113,19 @@ class Configuration(UserDict):
97
113
  return self | {prefix: cls(self.setdefault(prefix, {})).add(rest, value)}
98
114
 
99
115
  def add_envs(self, envs: dict[str, str], prefix: str = "") -> Self:
100
- """Add environment variables to configuration"""
116
+ """Add environment variables to configuration."""
101
117
  for env, key in envs.items():
102
118
  env_key = f"{prefix}{env}"
103
119
  if env_value := os.getenv(env_key):
104
- self = self.add(key, env_value)
120
+ self = self.add(key, env_value) # noqa: PLW0642
105
121
  elif key not in self:
106
- raise MissingEnvironmentVariableError(
107
- f"required environment variable '{env_key}' not found"
108
- )
122
+ raise MissingEnvironmentVariableError(env_key)
109
123
  return self
110
124
 
111
125
  def parse_dynamic(
112
- self, extra: dict[str, Any] | None = None, _include_self: bool = True
126
+ self, extra: dict[str, Any] | None = None, *, _include_self: bool = True
113
127
  ) -> Self:
114
- """Parse dynamic values of the form {section.key}"""
128
+ """Parse dynamic values of the form {section.key}."""
115
129
  cls = type(self)
116
130
  variables = (
117
131
  (self.to_flat_dict() if _include_self else {})
@@ -135,17 +149,17 @@ class Configuration(UserDict):
135
149
  # Continue parsing until no more replacements are made.
136
150
  return parsed.parse_dynamic(extra=extra, _include_self=_include_self)
137
151
 
138
- def validate_model(self, model: Type[BaseModel]) -> Self:
152
+ def validate_model(self, model: type[BaseModel]) -> Self:
139
153
  """Validate the configuration against the given model."""
140
154
  model.model_validate(self.data)
141
155
  return self
142
156
 
143
- def convert_model(self, model: Type[ModelT]) -> ModelT:
144
- """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."""
145
159
  return model(**self.data)
146
160
 
147
161
  def to_dict(self) -> dict[str, Any]:
148
- """Dump the configuration into a Python dictionary"""
162
+ """Dump the configuration into a Python dictionary."""
149
163
  return {
150
164
  key: value.to_dict() if isinstance(value, Configuration) else value
151
165
  for key, value in self.items()
@@ -170,6 +184,50 @@ class Configuration(UserDict):
170
184
  }
171
185
 
172
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
+
173
231
  def _find_pyproject_toml(
174
232
  path: Path | None = None, _file_name: str = "pyproject.toml"
175
233
  ) -> Path:
@@ -182,8 +240,8 @@ def _find_pyproject_toml(
182
240
  path = _get_foreign_path() if path is None else path
183
241
  if (path / _file_name).exists() or path == path.parent:
184
242
  return path.resolve()
185
- else:
186
- return _find_pyproject_toml(path.parent, _file_name=_file_name)
243
+
244
+ return _find_pyproject_toml(path.parent, _file_name=_file_name)
187
245
 
188
246
 
189
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.1
3
+ Version: 0.2.3
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>
@@ -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