configaroo 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
configaroo/__init__.py CHANGED
@@ -6,3 +6,5 @@ from configaroo.exceptions import ( # noqa
6
6
  MissingEnvironmentVariableError,
7
7
  UnsupportedLoaderError,
8
8
  )
9
+
10
+ __version__ = "0.1.2"
@@ -1,11 +1,12 @@
1
1
  """A dict-like configuration with support for envvars, validation and type conversion"""
2
2
 
3
+ import inspect
3
4
  import os
5
+ import re
4
6
  from collections import UserDict
5
7
  from pathlib import Path
6
8
  from typing import Any, Self, Type
7
9
 
8
- import dotenv
9
10
  from pydantic import BaseModel
10
11
 
11
12
  from configaroo import loaders
@@ -27,7 +28,9 @@ class Configuration(UserDict):
27
28
  ) -> Self:
28
29
  """Read a Configuration from a file"""
29
30
  config_dict = loaders.from_file(file_path, loader=loader)
30
- return cls(**config_dict).initialize(envs=envs, model=model)
31
+ return cls(config_dict).initialize(
32
+ envs=envs, env_prefix=env_prefix, extra_dynamic=extra_dynamic, model=model
33
+ )
31
34
 
32
35
  def initialize(
33
36
  self,
@@ -51,7 +54,7 @@ class Configuration(UserDict):
51
54
  """Make sure nested sections have type Configuration"""
52
55
  value = self.data[key]
53
56
  if isinstance(value, dict | UserDict | Configuration):
54
- return Configuration(**value)
57
+ return Configuration(value)
55
58
  else:
56
59
  return value
57
60
 
@@ -76,39 +79,33 @@ class Configuration(UserDict):
76
79
 
77
80
  def get(self, key: str, default: Any = None) -> Any:
78
81
  """Allow dotted keys when using .get()"""
79
- if key not in self.data:
80
- prefix, _, rest = key.partition(".")
81
- try:
82
- return self[prefix].get(rest, default=default)
83
- except KeyError:
84
- return default
85
- else:
82
+ if key in self.data:
86
83
  return self[key]
87
84
 
85
+ prefix, _, rest = key.partition(".")
86
+ try:
87
+ return self[prefix].get(rest, default=default)
88
+ except KeyError:
89
+ return default
90
+
88
91
  def add(self, key: str, value: Any) -> Self:
89
92
  """Add a value, allow dotted keys"""
90
93
  prefix, _, rest = key.partition(".")
91
- if rest:
92
- cls = type(self)
93
- return self | {prefix: cls(**self.setdefault(prefix, {})).add(rest, value)}
94
- else:
94
+ if not rest:
95
95
  return self | {key: value}
96
+ cls = type(self)
97
+ return self | {prefix: cls(self.setdefault(prefix, {})).add(rest, value)}
96
98
 
97
- def add_envs(self, envs: dict[str, str], prefix: str = "", use_dotenv=True) -> Self:
99
+ def add_envs(self, envs: dict[str, str], prefix: str = "") -> Self:
98
100
  """Add environment variables to configuration"""
99
- if use_dotenv:
100
- dotenv.load_dotenv()
101
-
102
101
  for env, key in envs.items():
103
102
  env_key = f"{prefix}{env}"
104
- env_value = os.getenv(env_key)
105
- if env_value:
103
+ if env_value := os.getenv(env_key):
106
104
  self = self.add(key, env_value)
107
- else:
108
- if key not in self:
109
- raise MissingEnvironmentVariableError(
110
- f"required environment variable '{env_key}' not found"
111
- )
105
+ elif key not in self:
106
+ raise MissingEnvironmentVariableError(
107
+ f"required environment variable '{env_key}' not found"
108
+ )
112
109
  return self
113
110
 
114
111
  def parse_dynamic(self, extra: dict[str, Any] | None = None) -> Self:
@@ -116,15 +113,15 @@ class Configuration(UserDict):
116
113
  cls = type(self)
117
114
  variables = (
118
115
  self.to_flat_dict()
119
- | {"project_path": Path(__file__).parent.parent.parent}
116
+ | {"project_path": _find_pyproject_toml()}
120
117
  | ({} if extra is None else extra)
121
118
  )
122
119
  return cls(
123
- **{
120
+ {
124
121
  key: (
125
122
  value.parse_dynamic(extra=variables)
126
123
  if isinstance(value, Configuration)
127
- else value.format(**variables)
124
+ else _incomplete_format(value, variables)
128
125
  if isinstance(value, str)
129
126
  else value
130
127
  )
@@ -140,7 +137,7 @@ class Configuration(UserDict):
140
137
  def convert(self, model: Type[BaseModel]) -> Self:
141
138
  """Convert data types to match the given model"""
142
139
  cls = type(self)
143
- return cls(**model(**self.data).model_dump())
140
+ return cls(model(**self.data).model_dump())
144
141
 
145
142
  def to_dict(self) -> dict[str, Any]:
146
143
  """Dump the configuration into a Python dictionary"""
@@ -166,3 +163,53 @@ class Configuration(UserDict):
166
163
  self[nested_key].to_flat_dict(_prefix=f"{_prefix}{nested_key}.").items()
167
164
  )
168
165
  }
166
+
167
+
168
+ def _find_pyproject_toml(
169
+ path: Path | None = None, _file_name: str = "pyproject.toml"
170
+ ) -> Path:
171
+ """Find a directory that contains a pyproject.toml file.
172
+
173
+ This searches the given directory and all direct parents. If a
174
+ pyproject.toml file isn't found, then the root of the file system is
175
+ returned.
176
+ """
177
+ path = _get_foreign_path() if path is None else path
178
+ if (path / _file_name).exists() or path == path.parent:
179
+ return path.resolve()
180
+ else:
181
+ return _find_pyproject_toml(path.parent, _file_name=_file_name)
182
+
183
+
184
+ def _get_foreign_path() -> Path:
185
+ """Find the path to the library that called this package.
186
+
187
+ Search the call stack for the first source code file outside of configaroo.
188
+ """
189
+ self_prefix = Path(__file__).parent.parent
190
+ return next(
191
+ path
192
+ for frame in inspect.stack()
193
+ if not (path := Path(frame.filename)).is_relative_to(self_prefix)
194
+ )
195
+
196
+
197
+ def _incomplete_format(text: str, replacers: dict[str, Any]) -> str:
198
+ """Replace some, but not necessarily all format specifiers in a text string.
199
+
200
+ Regular .format() raises an error if not all {replace} parameters are
201
+ supplied. Here, we only replace the given replace arguments and leave the
202
+ rest untouched.
203
+ """
204
+ dot = "__DOT__" # Escape . in fields as they have special meaning in .format()
205
+ pattern = r"({{{word}(?:![ars])?(?:|:[^}}]*)}})" # Match {word} or {word:...}
206
+
207
+ for word, replacement in replacers.items():
208
+ for match in re.findall(pattern.format(word=word), text):
209
+ # Split expression to only replace . in the field name
210
+ field, colon, fmt = match.partition(":")
211
+ replacer = f"{field.replace('.', dot)}{colon}{fmt}".format(
212
+ **{word.replace(".", dot): replacement}
213
+ )
214
+ text = text.replace(match, replacer)
215
+ return text
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: configaroo
3
+ Version: 0.1.2
4
+ Summary: Bouncy handling of configuration files
5
+ Author-email: Geir Arne Hjelle <geirarne@gmail.com>
6
+ Maintainer-email: Geir Arne Hjelle <geirarne@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: homepage, https://github.com/gahjelle/configaroo
9
+ Project-URL: github, https://github.com/gahjelle/configaroo
10
+ Project-URL: issues, https://github.com/gahjelle/configaroo/issues
11
+ Project-URL: changelog, https://github.com/gahjelle/configaroo/blob/main/CHANGELOG.md
12
+ Keywords: configuration,configuration-management,toml,json
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: pydantic>=2.0
28
+ Requires-Dist: pyplugs>=0.4.0
29
+ Dynamic: license-file
30
+
31
+ # Configaroo - Bouncy Configuration Handling
32
+
33
+ Configaroo is a light configuration package for Python that offers the following features:
34
+
35
+ - Access configuration settings with dotted keys: `config.nested.key`
36
+ - Use different configuration file formats, including TOML and JSON
37
+ - Override key configuration settings with environment variables
38
+ - Validate a configuration based on a Pydantic model
39
+ - Convert the type of configuration values based on a Pydantic model
40
+ - Dynamically format certain configuration values
@@ -0,0 +1,12 @@
1
+ configaroo/__init__.py,sha256=Sze_TJz5IEKL__tZO6D6fT6LPlaxYiHJQb6ghJRMePg,255
2
+ configaroo/configuration.py,sha256=PJLWR2mA_bYM7iuRXqfBQ_rnZoughNCFA5kAEjjZz8A,7591
3
+ configaroo/exceptions.py,sha256=1h-6hV7VqqKaRFnu539pjsO0ESSNDibt2kUeF7jAM2M,374
4
+ configaroo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ configaroo/loaders/__init__.py,sha256=-6GAR4g7vYW7Zagq9Ev479kpkfY7kbvDl4JPXd4wQQ0,756
6
+ configaroo/loaders/json.py,sha256=6gs_ZegUEe70vb6uSJfKBos7UjfuJnyWgPE_kjDQISw,258
7
+ configaroo/loaders/toml.py,sha256=ZVbHutZ7V-Z_jf2aHjV18jwaqCXumlr06j48rojHcBM,264
8
+ configaroo-0.1.2.dist-info/licenses/LICENSE,sha256=2cbzsB4lU8qOO8fO1XVNJD7XPxzPhaWXm6U9DenSc0s,1089
9
+ configaroo-0.1.2.dist-info/METADATA,sha256=W0uyWmliw1ZgLfjEAVJI_Sp1SfCNQwFzLz7ph_pVSI8,1789
10
+ configaroo-0.1.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
11
+ configaroo-0.1.2.dist-info/top_level.txt,sha256=JVYICl1cWSjvSOZuZMYm976z9lnZaWtHVRSt373QCxg,11
12
+ configaroo-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2025 Geir Arne Hjelle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the “Software”), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
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.
@@ -1,21 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: configaroo
3
- Version: 0.1.0
4
- Summary: Bouncy handling of configuration files
5
- Author-email: Geir Arne Hjelle <geirarne@gmail.com>
6
- Requires-Python: >=3.11
7
- Description-Content-Type: text/markdown
8
- Requires-Dist: pydantic>=2.0
9
- Requires-Dist: pyplugs>=0.4.0
10
- Requires-Dist: python-dotenv>=1.1.0
11
-
12
- # Configaroo - Bouncy Configuration Handling
13
-
14
- Configaroo is a light configuration package for Python that offers the following features:
15
-
16
- - Access configuration settings with dotted keys: `config.nested.key`
17
- - Use different configuration file formats, including TOML and JSON
18
- - Override key configuration settings with environment variables
19
- - Validate a configuration based on a Pydantic model
20
- - Convert the type of configuration values based on a Pydantic model
21
- - Dynamically format certain configuration values
@@ -1,11 +0,0 @@
1
- configaroo/__init__.py,sha256=10BHeYLHiIz-oAGM1IdfaqBKyHbn2hiNnjS_zrFOtC8,232
2
- configaroo/configuration.py,sha256=V1Dx9LdkRlUSq46Djdq12sWb9JfNi_8N4VBnkXdB11s,5821
3
- configaroo/exceptions.py,sha256=1h-6hV7VqqKaRFnu539pjsO0ESSNDibt2kUeF7jAM2M,374
4
- configaroo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- configaroo/loaders/__init__.py,sha256=-6GAR4g7vYW7Zagq9Ev479kpkfY7kbvDl4JPXd4wQQ0,756
6
- configaroo/loaders/json.py,sha256=6gs_ZegUEe70vb6uSJfKBos7UjfuJnyWgPE_kjDQISw,258
7
- configaroo/loaders/toml.py,sha256=ZVbHutZ7V-Z_jf2aHjV18jwaqCXumlr06j48rojHcBM,264
8
- configaroo-0.1.0.dist-info/METADATA,sha256=Oy67b4ut26KSC43oh5g50fpjR_Mz5chJ0iqvo4nrWIo,827
9
- configaroo-0.1.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
10
- configaroo-0.1.0.dist-info/top_level.txt,sha256=JVYICl1cWSjvSOZuZMYm976z9lnZaWtHVRSt373QCxg,11
11
- configaroo-0.1.0.dist-info/RECORD,,