envproof 0.1.0__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.
- envguard/__init__.py +4 -0
- envguard/core.py +84 -0
- envproof-0.1.0.dist-info/METADATA +116 -0
- envproof-0.1.0.dist-info/RECORD +5 -0
- envproof-0.1.0.dist-info/WHEEL +4 -0
envguard/__init__.py
ADDED
envguard/core.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, get_type_hints
|
|
3
|
+
|
|
4
|
+
_MISSING = object()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnvGuardError(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EnvGuard:
|
|
12
|
+
"""
|
|
13
|
+
Subclass this and declare your env vars as type-annotated class attributes.
|
|
14
|
+
|
|
15
|
+
Required var: DATABASE_URL: str
|
|
16
|
+
Optional var: PORT: int = 8080
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
hints = get_type_hints(self.__class__)
|
|
21
|
+
missing = []
|
|
22
|
+
invalid = []
|
|
23
|
+
|
|
24
|
+
for name, type_ in hints.items():
|
|
25
|
+
if name.startswith("_"):
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
raw = os.environ.get(name)
|
|
29
|
+
has_default = name in self.__class__.__dict__
|
|
30
|
+
default = self.__class__.__dict__.get(name, _MISSING)
|
|
31
|
+
|
|
32
|
+
if raw is None:
|
|
33
|
+
if not has_default:
|
|
34
|
+
missing.append((name, type_))
|
|
35
|
+
else:
|
|
36
|
+
setattr(self, name, default)
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
setattr(self, name, _coerce(raw, type_))
|
|
41
|
+
except (ValueError, TypeError):
|
|
42
|
+
invalid.append((name, type_.__name__, raw))
|
|
43
|
+
|
|
44
|
+
errors = []
|
|
45
|
+
if missing:
|
|
46
|
+
errors.append("Missing required environment variables:")
|
|
47
|
+
for name, type_ in missing:
|
|
48
|
+
errors.append(f" - {name} ({type_.__name__}): not set")
|
|
49
|
+
if invalid:
|
|
50
|
+
if errors:
|
|
51
|
+
errors.append("")
|
|
52
|
+
errors.append("Invalid environment variable values:")
|
|
53
|
+
for name, type_name, raw in invalid:
|
|
54
|
+
errors.append(f" - {name}: expected {type_name}, got '{raw}'")
|
|
55
|
+
|
|
56
|
+
if errors:
|
|
57
|
+
raise EnvGuardError("\n" + "\n".join(errors))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _coerce(value: str, type_: type) -> Any:
|
|
61
|
+
if type_ is bool:
|
|
62
|
+
if value.lower() in ("true", "1", "yes", "on"):
|
|
63
|
+
return True
|
|
64
|
+
if value.lower() in ("false", "0", "no", "off"):
|
|
65
|
+
return False
|
|
66
|
+
raise ValueError(f"Cannot convert to bool: {value!r}")
|
|
67
|
+
if type_ is list:
|
|
68
|
+
return [v.strip() for v in value.split(",") if v.strip()]
|
|
69
|
+
return type_(value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def guard(**schema) -> Any:
|
|
73
|
+
"""
|
|
74
|
+
One-liner usage without subclassing:
|
|
75
|
+
|
|
76
|
+
env = guard(DATABASE_URL=str, PORT=int, DEBUG=bool)
|
|
77
|
+
print(env.DATABASE_URL)
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
class _Env(EnvGuard):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
_Env.__annotations__ = schema
|
|
84
|
+
return _Env()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envproof
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Validate environment variables at startup with typed, clear error messages. Zero dependencies.
|
|
5
|
+
Project-URL: Homepage, https://github.com/CoderSufiyan/envguard
|
|
6
|
+
Project-URL: Repository, https://github.com/CoderSufiyan/envguard
|
|
7
|
+
Project-URL: Issues, https://github.com/CoderSufiyan/envguard/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: config,dotenv,env,environment,settings,validation
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# envguard
|
|
24
|
+
|
|
25
|
+
Validate environment variables at startup with typed, clear error messages. Zero dependencies.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from envguard import EnvGuard
|
|
29
|
+
|
|
30
|
+
class Env(EnvGuard):
|
|
31
|
+
DATABASE_URL: str
|
|
32
|
+
PORT: int = 8080
|
|
33
|
+
DEBUG: bool = False
|
|
34
|
+
ALLOWED_HOSTS: list = []
|
|
35
|
+
|
|
36
|
+
env = Env()
|
|
37
|
+
print(env.PORT) # 8080 (int, not string)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If `DATABASE_URL` is missing, you get this instead of a cryptic `KeyError` somewhere deep in your app:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
EnvGuardError:
|
|
44
|
+
Missing required environment variables:
|
|
45
|
+
- DATABASE_URL (str): not set
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install envguard
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Subclass style (recommended)
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from envguard import EnvGuard
|
|
60
|
+
|
|
61
|
+
class Env(EnvGuard):
|
|
62
|
+
# Required — raises if not set
|
|
63
|
+
DATABASE_URL: str
|
|
64
|
+
API_KEY: str
|
|
65
|
+
|
|
66
|
+
# Optional — uses default if not set
|
|
67
|
+
PORT: int = 8080
|
|
68
|
+
DEBUG: bool = False
|
|
69
|
+
LOG_LEVEL: str = "INFO"
|
|
70
|
+
ALLOWED_HOSTS: list = []
|
|
71
|
+
|
|
72
|
+
env = Env()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### One-liner style
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from envguard import guard
|
|
79
|
+
|
|
80
|
+
env = guard(DATABASE_URL=str, PORT=int, DEBUG=bool)
|
|
81
|
+
print(env.DATABASE_URL)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Supported types
|
|
85
|
+
|
|
86
|
+
| Type | Example env value | Python value |
|
|
87
|
+
|---------|---------------------------|--------------------------|
|
|
88
|
+
| `str` | `"hello"` | `"hello"` |
|
|
89
|
+
| `int` | `"8080"` | `8080` |
|
|
90
|
+
| `float` | `"3.14"` | `3.14` |
|
|
91
|
+
| `bool` | `"true"`, `"1"`, `"yes"` | `True` |
|
|
92
|
+
| `bool` | `"false"`, `"0"`, `"no"` | `False` |
|
|
93
|
+
| `list` | `"a,b,c"` | `["a", "b", "c"]` |
|
|
94
|
+
|
|
95
|
+
## Error messages
|
|
96
|
+
|
|
97
|
+
All errors are collected and reported together — you won't fix one missing var only to discover another:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
EnvGuardError:
|
|
101
|
+
Missing required environment variables:
|
|
102
|
+
- DATABASE_URL (str): not set
|
|
103
|
+
- API_KEY (str): not set
|
|
104
|
+
|
|
105
|
+
Invalid environment variable values:
|
|
106
|
+
- PORT: expected int, got 'abc'
|
|
107
|
+
- DEBUG: expected bool, got 'maybe'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Why not pydantic-settings?
|
|
111
|
+
|
|
112
|
+
`pydantic-settings` is great but pulls in Pydantic as a dependency (~2MB). `envguard` is a single file with zero dependencies — useful when you want validation without adding weight to your project.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
envguard/__init__.py,sha256=Hd4HRBh0brb7sAxI6TrOZd9fxkvfrLhpArYgVIc97iE,121
|
|
2
|
+
envguard/core.py,sha256=C52SdIjUFC6PB4-_tVHAhezTMJw0Rb9TUu-Rvcnhll4,2328
|
|
3
|
+
envproof-0.1.0.dist-info/METADATA,sha256=dIZBj7jlLKtNDqUE4AUrkgtQIgmCSbpX8CHxbZ0eUsw,3232
|
|
4
|
+
envproof-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
envproof-0.1.0.dist-info/RECORD,,
|