env-proxy 1.2.0__tar.gz → 1.3.0__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.
- {env_proxy-1.2.0 → env_proxy-1.3.0}/PKG-INFO +40 -1
- {env_proxy-1.2.0 → env_proxy-1.3.0}/README.md +38 -0
- {env_proxy-1.2.0 → env_proxy-1.3.0}/env_proxy/env_config.py +149 -92
- {env_proxy-1.2.0 → env_proxy-1.3.0}/pyproject.toml +8 -1
- {env_proxy-1.2.0 → env_proxy-1.3.0}/LICENSE.md +0 -0
- {env_proxy-1.2.0 → env_proxy-1.3.0}/env_proxy/__init__.py +0 -0
- {env_proxy-1.2.0 → env_proxy-1.3.0}/env_proxy/_sentinel.py +0 -0
- {env_proxy-1.2.0 → env_proxy-1.3.0}/env_proxy/env_proxy.py +0 -0
- {env_proxy-1.2.0 → env_proxy-1.3.0}/env_proxy/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: env-proxy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Creates a class used to query environmental variables with typehinting a conversion to basic Python types.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: typing-extensions (>=4.6.0) ; python_version < "3.11"
|
|
17
18
|
Project-URL: Homepage, https://github.com/tomasvotava/env-proxy
|
|
18
19
|
Project-URL: Repository, https://github.com/tomasvotava/env-proxy
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
@@ -174,6 +175,44 @@ debug = config.debug # Looks for MYAPP_DEBUG in the environment
|
|
|
174
175
|
database_url = config.database_url # Raises ValueError if not found
|
|
175
176
|
```
|
|
176
177
|
|
|
178
|
+
#### Overriding Values per Instance
|
|
179
|
+
|
|
180
|
+
`EnvConfig` accepts keyword arguments to override individual fields on a per-instance basis.
|
|
181
|
+
Overrides take precedence over the environment, letting you layer the env-derived config with
|
|
182
|
+
values from any other source — a config file, CLI arguments, programmatic wiring, fixtures —
|
|
183
|
+
without touching `os.environ`.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
class AppConfig(EnvConfig):
|
|
187
|
+
env_proxy = EnvProxy(prefix="APP")
|
|
188
|
+
timeout: int = Field(default=30)
|
|
189
|
+
services: list[str] = Field(default=[])
|
|
190
|
+
|
|
191
|
+
# Layer env with values loaded from a config file:
|
|
192
|
+
file_config = load_yaml("app.yaml") # {"timeout": 5, "services": ["redis", "rabbitmq"]}
|
|
193
|
+
cfg = AppConfig(**file_config)
|
|
194
|
+
|
|
195
|
+
assert cfg.timeout == 5
|
|
196
|
+
assert cfg.services == ["redis", "rabbitmq"]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Semantics:
|
|
200
|
+
|
|
201
|
+
- Keys are **Python field names** (not env-var keys), regardless of any `alias` or `env_prefix`.
|
|
202
|
+
- Values are **used as-is** — no string parsing or type conversion. Pass real `int`, `list`, `dict`, etc.
|
|
203
|
+
- Overrides **shadow the environment** for reads on that instance only; other instances and direct
|
|
204
|
+
`os.environ` access are unaffected.
|
|
205
|
+
- Unknown override keys raise `ValueError`, listing the valid field names — typo-proof.
|
|
206
|
+
- Fields with `allow_set=False` can be initialized via override but cannot be reassigned afterwards;
|
|
207
|
+
the `allow_set` contract is unchanged.
|
|
208
|
+
- For fields with `allow_set=True`, assignment after construction updates both the override entry
|
|
209
|
+
*and* `os.environ` (preserving the existing side-effect contract).
|
|
210
|
+
|
|
211
|
+
Overrides are statically type-checked. `EnvConfig` is decorated with PEP 681's `dataclass_transform`,
|
|
212
|
+
so mypy and Pyright/Pylance synthesize a typed `__init__` from each subclass's annotated fields:
|
|
213
|
+
typos (`AppConfig(timout=5)`) and wrong value types (`AppConfig(timeout="bad")`) are flagged at
|
|
214
|
+
type-check time, and IDEs autocomplete field names with their declared types.
|
|
215
|
+
|
|
177
216
|
#### Generating a Sample `.env` File
|
|
178
217
|
|
|
179
218
|
You can export a sample `.env` file from your `EnvConfig` class, which documents all fields with their
|
|
@@ -154,6 +154,44 @@ debug = config.debug # Looks for MYAPP_DEBUG in the environment
|
|
|
154
154
|
database_url = config.database_url # Raises ValueError if not found
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
#### Overriding Values per Instance
|
|
158
|
+
|
|
159
|
+
`EnvConfig` accepts keyword arguments to override individual fields on a per-instance basis.
|
|
160
|
+
Overrides take precedence over the environment, letting you layer the env-derived config with
|
|
161
|
+
values from any other source — a config file, CLI arguments, programmatic wiring, fixtures —
|
|
162
|
+
without touching `os.environ`.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
class AppConfig(EnvConfig):
|
|
166
|
+
env_proxy = EnvProxy(prefix="APP")
|
|
167
|
+
timeout: int = Field(default=30)
|
|
168
|
+
services: list[str] = Field(default=[])
|
|
169
|
+
|
|
170
|
+
# Layer env with values loaded from a config file:
|
|
171
|
+
file_config = load_yaml("app.yaml") # {"timeout": 5, "services": ["redis", "rabbitmq"]}
|
|
172
|
+
cfg = AppConfig(**file_config)
|
|
173
|
+
|
|
174
|
+
assert cfg.timeout == 5
|
|
175
|
+
assert cfg.services == ["redis", "rabbitmq"]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Semantics:
|
|
179
|
+
|
|
180
|
+
- Keys are **Python field names** (not env-var keys), regardless of any `alias` or `env_prefix`.
|
|
181
|
+
- Values are **used as-is** — no string parsing or type conversion. Pass real `int`, `list`, `dict`, etc.
|
|
182
|
+
- Overrides **shadow the environment** for reads on that instance only; other instances and direct
|
|
183
|
+
`os.environ` access are unaffected.
|
|
184
|
+
- Unknown override keys raise `ValueError`, listing the valid field names — typo-proof.
|
|
185
|
+
- Fields with `allow_set=False` can be initialized via override but cannot be reassigned afterwards;
|
|
186
|
+
the `allow_set` contract is unchanged.
|
|
187
|
+
- For fields with `allow_set=True`, assignment after construction updates both the override entry
|
|
188
|
+
*and* `os.environ` (preserving the existing side-effect contract).
|
|
189
|
+
|
|
190
|
+
Overrides are statically type-checked. `EnvConfig` is decorated with PEP 681's `dataclass_transform`,
|
|
191
|
+
so mypy and Pyright/Pylance synthesize a typed `__init__` from each subclass's annotated fields:
|
|
192
|
+
typos (`AppConfig(timout=5)`) and wrong value types (`AppConfig(timeout="bad")`) are flagged at
|
|
193
|
+
type-check time, and IDEs autocomplete field names with their declared types.
|
|
194
|
+
|
|
157
195
|
#### Generating a Sample `.env` File
|
|
158
196
|
|
|
159
197
|
You can export a sample `.env` file from your `EnvConfig` class, which documents all fields with their
|
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
auto-documenting approach.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import json
|
|
6
8
|
import logging
|
|
7
9
|
import os
|
|
10
|
+
import sys
|
|
8
11
|
from collections.abc import Callable
|
|
9
12
|
from functools import cached_property, partial
|
|
10
13
|
from inspect import get_annotations
|
|
11
14
|
from pathlib import Path
|
|
12
15
|
from types import NoneType, UnionType
|
|
13
|
-
from typing import Any, Literal, TextIO, TypeVar, get_args, get_origin
|
|
16
|
+
from typing import Any, ClassVar, Literal, TextIO, TypeVar, get_args, get_origin
|
|
14
17
|
|
|
15
18
|
from env_proxy.env_proxy import EnvProxy
|
|
16
19
|
|
|
17
20
|
from ._sentinel import UNSET
|
|
18
21
|
|
|
22
|
+
if sys.version_info >= (3, 11):
|
|
23
|
+
from typing import dataclass_transform
|
|
24
|
+
else: # pragma: no cover
|
|
25
|
+
from typing_extensions import dataclass_transform
|
|
26
|
+
|
|
19
27
|
logger = logging.getLogger(__name__)
|
|
20
28
|
|
|
21
29
|
T = TypeVar("T")
|
|
@@ -70,96 +78,6 @@ def _get_simplified_annotation(annotation: Any) -> Any:
|
|
|
70
78
|
return None
|
|
71
79
|
|
|
72
80
|
|
|
73
|
-
class FieldDocsBuilder:
|
|
74
|
-
__env_field_doc_template = "# {key_name} ({field_type}) [{required}]\n{description}{env_key}={default}\n"
|
|
75
|
-
|
|
76
|
-
def __init__(self, fields: list["EnvField"]) -> None:
|
|
77
|
-
self.fields = list(fields)
|
|
78
|
-
|
|
79
|
-
@staticmethod
|
|
80
|
-
def _get_field_type(field: "EnvField") -> str:
|
|
81
|
-
if field.type_hint is not None:
|
|
82
|
-
return field.type_hint
|
|
83
|
-
if field.simplified_annotation is not None:
|
|
84
|
-
if isinstance(field.simplified_annotation, type):
|
|
85
|
-
return field.simplified_annotation.__name__
|
|
86
|
-
return str(field.simplified_annotation).lower() # pragma: no cover, unreachable
|
|
87
|
-
return "unknown type"
|
|
88
|
-
|
|
89
|
-
@staticmethod
|
|
90
|
-
def _get_field_default(field: "EnvField") -> str:
|
|
91
|
-
if field.default in (UNSET, None):
|
|
92
|
-
return ""
|
|
93
|
-
if not isinstance(field.default, str) and field.type_hint == "json":
|
|
94
|
-
try:
|
|
95
|
-
return json.dumps(field.default)
|
|
96
|
-
except (ValueError, TypeError) as error:
|
|
97
|
-
raise ValueError(
|
|
98
|
-
f"Failed to export default for field {field.field_name!r}. "
|
|
99
|
-
"Its default value cannot be encoded as a JSON."
|
|
100
|
-
) from error
|
|
101
|
-
if isinstance(field.default, list):
|
|
102
|
-
return ",".join(field.default)
|
|
103
|
-
return str(field.default)
|
|
104
|
-
|
|
105
|
-
def generate_env_file_content(self, include_defaults: bool = True, sort_by_name: bool = False) -> str:
|
|
106
|
-
lines: list[str] = []
|
|
107
|
-
if sort_by_name:
|
|
108
|
-
self.fields.sort(key=lambda field: field.key_name)
|
|
109
|
-
for field in self.fields:
|
|
110
|
-
required = "required" if field.default is UNSET else "optional"
|
|
111
|
-
default = self._get_field_default(field) if include_defaults else ""
|
|
112
|
-
field_type = self._get_field_type(field)
|
|
113
|
-
multiline_description = ""
|
|
114
|
-
if field.description:
|
|
115
|
-
multiline_description = "\n# ".join(field.description.splitlines())
|
|
116
|
-
multiline_description = "# " + multiline_description.rstrip("\n") + "\n"
|
|
117
|
-
lines.append(
|
|
118
|
-
self.__env_field_doc_template.format(
|
|
119
|
-
key_name=field.key_name,
|
|
120
|
-
field_type=field_type,
|
|
121
|
-
required=required,
|
|
122
|
-
description=multiline_description,
|
|
123
|
-
env_key=field.env_key,
|
|
124
|
-
default=default,
|
|
125
|
-
)
|
|
126
|
-
)
|
|
127
|
-
return "\n".join(lines)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
class EnvConfig:
|
|
131
|
-
"""A base class for your configurations based on environment variables.
|
|
132
|
-
|
|
133
|
-
Use fields along with Field factory to easily describe your configuration in an self-documenting way.
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
@classmethod
|
|
137
|
-
def __generate_env_file_content(
|
|
138
|
-
cls: "type[EnvConfig]", include_defaults: bool = True, sort_by_name: bool = False
|
|
139
|
-
) -> str:
|
|
140
|
-
fields: list[EnvField] = []
|
|
141
|
-
for field_name, field in vars(cls).items():
|
|
142
|
-
if not isinstance(field, EnvField):
|
|
143
|
-
logger.debug(f"Skipping class variable {field_name!r}, not a Field.")
|
|
144
|
-
continue
|
|
145
|
-
fields.append(field)
|
|
146
|
-
builder = FieldDocsBuilder(fields)
|
|
147
|
-
return builder.generate_env_file_content(include_defaults=include_defaults, sort_by_name=sort_by_name)
|
|
148
|
-
|
|
149
|
-
@classmethod
|
|
150
|
-
def export_env(
|
|
151
|
-
cls: "type[EnvConfig]",
|
|
152
|
-
file_or_path: Path | str | TextIO,
|
|
153
|
-
include_defaults: bool = True,
|
|
154
|
-
sort_by_name: bool = False,
|
|
155
|
-
) -> None:
|
|
156
|
-
content = cls.__generate_env_file_content(include_defaults=include_defaults, sort_by_name=sort_by_name)
|
|
157
|
-
if isinstance(file_or_path, str | Path):
|
|
158
|
-
Path(file_or_path).write_text(content, encoding="utf-8")
|
|
159
|
-
return
|
|
160
|
-
file_or_path.write(content)
|
|
161
|
-
|
|
162
|
-
|
|
163
81
|
class EnvField:
|
|
164
82
|
def __init__(
|
|
165
83
|
self,
|
|
@@ -319,6 +237,9 @@ class EnvField:
|
|
|
319
237
|
def __set__(self, instance: EnvConfig, value: Any) -> None:
|
|
320
238
|
if not self.allow_set:
|
|
321
239
|
raise TypeError(f"Field {self.field_name!r} of {instance.__class__.__name__!r} is read-only.")
|
|
240
|
+
overrides = instance.__dict__.get("_overrides")
|
|
241
|
+
if overrides is not None and self.field_name in overrides:
|
|
242
|
+
overrides[self.field_name] = value
|
|
322
243
|
key = self.env_proxy._get_key(self.key_name)
|
|
323
244
|
logger.debug(f"Setting {key!r} in os.environ.")
|
|
324
245
|
if value is None:
|
|
@@ -327,7 +248,12 @@ class EnvField:
|
|
|
327
248
|
else:
|
|
328
249
|
os.environ[key] = str(value)
|
|
329
250
|
|
|
330
|
-
def __get__(self, instance: EnvConfig, instance_type: type[EnvConfig]) -> Any:
|
|
251
|
+
def __get__(self, instance: EnvConfig | None, instance_type: type[EnvConfig]) -> Any:
|
|
252
|
+
if instance is None:
|
|
253
|
+
return self
|
|
254
|
+
overrides = instance.__dict__.get("_overrides")
|
|
255
|
+
if overrides is not None and self.field_name in overrides:
|
|
256
|
+
return overrides[self.field_name]
|
|
331
257
|
return self.value_getter(self.key_name, self.default)
|
|
332
258
|
|
|
333
259
|
|
|
@@ -343,3 +269,134 @@ def Field( # noqa: N802
|
|
|
343
269
|
) -> Any:
|
|
344
270
|
# A factory function that will help us deal with our descriptor's typehinting issues.
|
|
345
271
|
return EnvField(alias, description, default, env_proxy, env_prefix, strict, allow_set, type_hint)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class FieldDocsBuilder:
|
|
275
|
+
__env_field_doc_template = "# {key_name} ({field_type}) [{required}]\n{description}{env_key}={default}\n"
|
|
276
|
+
|
|
277
|
+
def __init__(self, fields: list[EnvField]) -> None:
|
|
278
|
+
self.fields = list(fields)
|
|
279
|
+
|
|
280
|
+
@staticmethod
|
|
281
|
+
def _get_field_type(field: EnvField) -> str:
|
|
282
|
+
if field.type_hint is not None:
|
|
283
|
+
return field.type_hint
|
|
284
|
+
if field.simplified_annotation is not None:
|
|
285
|
+
if isinstance(field.simplified_annotation, type):
|
|
286
|
+
return field.simplified_annotation.__name__
|
|
287
|
+
return str(field.simplified_annotation).lower() # pragma: no cover, unreachable
|
|
288
|
+
return "unknown type"
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def _get_field_default(field: EnvField) -> str:
|
|
292
|
+
if field.default in (UNSET, None):
|
|
293
|
+
return ""
|
|
294
|
+
if not isinstance(field.default, str) and field.type_hint == "json":
|
|
295
|
+
try:
|
|
296
|
+
return json.dumps(field.default)
|
|
297
|
+
except (ValueError, TypeError) as error:
|
|
298
|
+
raise ValueError(
|
|
299
|
+
f"Failed to export default for field {field.field_name!r}. "
|
|
300
|
+
"Its default value cannot be encoded as a JSON."
|
|
301
|
+
) from error
|
|
302
|
+
if isinstance(field.default, list):
|
|
303
|
+
return ",".join(field.default)
|
|
304
|
+
return str(field.default)
|
|
305
|
+
|
|
306
|
+
def generate_env_file_content(self, include_defaults: bool = True, sort_by_name: bool = False) -> str:
|
|
307
|
+
lines: list[str] = []
|
|
308
|
+
if sort_by_name:
|
|
309
|
+
self.fields.sort(key=lambda field: field.key_name)
|
|
310
|
+
for field in self.fields:
|
|
311
|
+
required = "required" if field.default is UNSET else "optional"
|
|
312
|
+
default = self._get_field_default(field) if include_defaults else ""
|
|
313
|
+
field_type = self._get_field_type(field)
|
|
314
|
+
multiline_description = ""
|
|
315
|
+
if field.description:
|
|
316
|
+
multiline_description = "\n# ".join(field.description.splitlines())
|
|
317
|
+
multiline_description = "# " + multiline_description.rstrip("\n") + "\n"
|
|
318
|
+
lines.append(
|
|
319
|
+
self.__env_field_doc_template.format(
|
|
320
|
+
key_name=field.key_name,
|
|
321
|
+
field_type=field_type,
|
|
322
|
+
required=required,
|
|
323
|
+
description=multiline_description,
|
|
324
|
+
env_key=field.env_key,
|
|
325
|
+
default=default,
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
return "\n".join(lines)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass_transform(kw_only_default=True)
|
|
332
|
+
class EnvConfig:
|
|
333
|
+
"""A base class for your configurations based on environment variables.
|
|
334
|
+
|
|
335
|
+
Use fields along with Field factory to easily describe your configuration in an self-documenting way.
|
|
336
|
+
|
|
337
|
+
The constructor accepts keyword arguments to override individual fields on a
|
|
338
|
+
per-instance basis. Overrides take precedence over the environment, allowing
|
|
339
|
+
callers to layer env-derived config with values from any other source — a
|
|
340
|
+
config file, CLI arguments, programmatic wiring, fixtures — without mutating
|
|
341
|
+
``os.environ``. Override values are keyed by Python field name (not env-var
|
|
342
|
+
key), are used as-is (no type conversion), and shadow the environment for
|
|
343
|
+
reads on this instance only::
|
|
344
|
+
|
|
345
|
+
cfg = MyConfig(timeout=5, services=["a", "b"])
|
|
346
|
+
|
|
347
|
+
Unknown override keys raise :class:`ValueError`. Fields with ``allow_set=False``
|
|
348
|
+
can still be initialized via override but cannot be reassigned afterwards.
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
_valid_fields: ClassVar[frozenset[str]] = frozenset()
|
|
352
|
+
|
|
353
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
354
|
+
super().__init_subclass__(**kwargs)
|
|
355
|
+
seen: set[str] = set()
|
|
356
|
+
valid: set[str] = set()
|
|
357
|
+
# Leaf-to-root: the first occurrence of each name wins, so a subclass
|
|
358
|
+
# that shadows an inherited EnvField with a non-EnvField correctly
|
|
359
|
+
# excludes that name from the valid override set.
|
|
360
|
+
for klass in cls.__mro__:
|
|
361
|
+
for name, attr in vars(klass).items():
|
|
362
|
+
if name in seen:
|
|
363
|
+
continue
|
|
364
|
+
seen.add(name)
|
|
365
|
+
if isinstance(attr, EnvField):
|
|
366
|
+
valid.add(name)
|
|
367
|
+
cls._valid_fields = frozenset(valid)
|
|
368
|
+
|
|
369
|
+
def __init__(self, **overrides: Any) -> None:
|
|
370
|
+
unknown = overrides.keys() - self._valid_fields
|
|
371
|
+
if unknown:
|
|
372
|
+
raise ValueError(
|
|
373
|
+
f"Unknown override key(s) for {type(self).__name__}: {sorted(unknown)}. "
|
|
374
|
+
f"Valid field names: {sorted(self._valid_fields)}"
|
|
375
|
+
)
|
|
376
|
+
self._overrides: dict[str, Any] = dict(overrides)
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def __generate_env_file_content(
|
|
380
|
+
cls: type[EnvConfig], include_defaults: bool = True, sort_by_name: bool = False
|
|
381
|
+
) -> str:
|
|
382
|
+
fields: list[EnvField] = []
|
|
383
|
+
for field_name, field in vars(cls).items():
|
|
384
|
+
if not isinstance(field, EnvField):
|
|
385
|
+
logger.debug(f"Skipping class variable {field_name!r}, not a Field.")
|
|
386
|
+
continue
|
|
387
|
+
fields.append(field)
|
|
388
|
+
builder = FieldDocsBuilder(fields)
|
|
389
|
+
return builder.generate_env_file_content(include_defaults=include_defaults, sort_by_name=sort_by_name)
|
|
390
|
+
|
|
391
|
+
@classmethod
|
|
392
|
+
def export_env(
|
|
393
|
+
cls: type[EnvConfig],
|
|
394
|
+
file_or_path: Path | str | TextIO,
|
|
395
|
+
include_defaults: bool = True,
|
|
396
|
+
sort_by_name: bool = False,
|
|
397
|
+
) -> None:
|
|
398
|
+
content = cls.__generate_env_file_content(include_defaults=include_defaults, sort_by_name=sort_by_name)
|
|
399
|
+
if isinstance(file_or_path, str | Path):
|
|
400
|
+
Path(file_or_path).write_text(content, encoding="utf-8")
|
|
401
|
+
return
|
|
402
|
+
file_or_path.write(content)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "env-proxy"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "Creates a class used to query environmental variables with typehinting a conversion to basic Python types."
|
|
5
5
|
authors = ["Tomas Votava <info@tomasvotava.eu>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -13,9 +13,11 @@ pytest = "^9"
|
|
|
13
13
|
mypy = "<2"
|
|
14
14
|
ruff = "<1"
|
|
15
15
|
pytest-cov = "^6.0.0"
|
|
16
|
+
pytest-benchmark = "^5.2.3"
|
|
16
17
|
|
|
17
18
|
[tool.poetry.dependencies]
|
|
18
19
|
python = ">=3.10,<3.15"
|
|
20
|
+
typing-extensions = { version = ">=4.6.0", python = "<3.11" }
|
|
19
21
|
|
|
20
22
|
[build-system]
|
|
21
23
|
requires = ["poetry-core>=1.0.0"]
|
|
@@ -60,6 +62,11 @@ select = [
|
|
|
60
62
|
"RUF018", # allow asserts with walrus in tests
|
|
61
63
|
"D", # ignore missing documentation in tests
|
|
62
64
|
]
|
|
65
|
+
"benchmarks/**" = [
|
|
66
|
+
"S101", # allow asserts in benchmarks
|
|
67
|
+
"RUF018", # allow asserts with walrus in benchmarks
|
|
68
|
+
"D", # ignore missing documentation in benchmarks
|
|
69
|
+
]
|
|
63
70
|
|
|
64
71
|
[tool.mypy]
|
|
65
72
|
strict = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|