envproof 0.1.0__tar.gz → 0.2.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.
- envproof-0.2.0/CONTRIBUTING.md +48 -0
- envproof-0.2.0/LICENSE +21 -0
- envproof-0.2.0/PKG-INFO +195 -0
- envproof-0.2.0/README.md +172 -0
- envproof-0.2.0/envproof/__init__.py +4 -0
- {envproof-0.1.0/envguard → envproof-0.2.0/envproof}/core.py +4 -4
- {envproof-0.1.0 → envproof-0.2.0}/pyproject.toml +2 -2
- {envproof-0.1.0 → envproof-0.2.0}/tests/test_core.py +23 -23
- envproof-0.1.0/PKG-INFO +0 -116
- envproof-0.1.0/README.md +0 -94
- envproof-0.1.0/envguard/__init__.py +0 -4
- {envproof-0.1.0 → envproof-0.2.0}/.github/workflows/ci.yml +0 -0
- {envproof-0.1.0 → envproof-0.2.0}/.gitignore +0 -0
- {envproof-0.1.0 → envproof-0.2.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Contributing to envproof
|
|
2
|
+
|
|
3
|
+
Thanks for taking the time to contribute!
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/CoderSufiyan/envguard.git
|
|
9
|
+
cd envguard
|
|
10
|
+
python3 -m venv .venv
|
|
11
|
+
source .venv/bin/activate
|
|
12
|
+
pip install -e ".[dev]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Running tests
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pytest
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
All tests must pass before submitting a PR.
|
|
22
|
+
|
|
23
|
+
## How to contribute
|
|
24
|
+
|
|
25
|
+
- **Bug fix** — open an issue first describing the bug, then submit a PR
|
|
26
|
+
- **New feature** — open an issue first so we can discuss before you build it
|
|
27
|
+
- **Docs improvement** — go ahead and submit a PR directly
|
|
28
|
+
|
|
29
|
+
## What we welcome
|
|
30
|
+
|
|
31
|
+
- New supported types (e.g. `dict`, `Path`, `Enum`)
|
|
32
|
+
- Better error messages
|
|
33
|
+
- Framework integrations (FastAPI, Flask, Django startup hooks)
|
|
34
|
+
- Performance improvements
|
|
35
|
+
- More test coverage
|
|
36
|
+
|
|
37
|
+
## Pull request checklist
|
|
38
|
+
|
|
39
|
+
- [ ] Tests added or updated
|
|
40
|
+
- [ ] All 28+ tests pass (`pytest`)
|
|
41
|
+
- [ ] Code follows existing style (no external formatter required)
|
|
42
|
+
- [ ] PR description explains what and why
|
|
43
|
+
|
|
44
|
+
## Code style
|
|
45
|
+
|
|
46
|
+
- No external dependencies in `envguard/` — keep it zero-dep
|
|
47
|
+
- One function does one thing
|
|
48
|
+
- Clear variable names over comments
|
envproof-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sufiyan Khan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, 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,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
envproof-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envproof
|
|
3
|
+
Version: 0.2.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
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: config,dotenv,env,environment,settings,validation
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# envproof
|
|
25
|
+
|
|
26
|
+
> Validate environment variables at startup with typed, clear error messages. Zero dependencies. Production-ready.
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/envproof/)
|
|
29
|
+
[](https://pypi.org/project/envproof/)
|
|
30
|
+
[](https://github.com/CoderSufiyan/envguard/actions)
|
|
31
|
+
[](https://github.com/CoderSufiyan/envguard/blob/main/LICENSE)
|
|
32
|
+
[](https://github.com/CoderSufiyan/envguard)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Every backend app reads environment variables. The standard way fails silently or crashes with a cryptic error deep inside your app at runtime:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Crashes 10 minutes in, on an obscure code path
|
|
40
|
+
db = connect(os.environ["DATABASE_URL"]) # KeyError: DATABASE_URL
|
|
41
|
+
|
|
42
|
+
# PORT is a string — breaks math silently
|
|
43
|
+
port = os.environ["PORT"] # "8080", not 8080
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**envproof catches all of this at startup**, before your app serves a single request:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from envproof import EnvGuard
|
|
50
|
+
|
|
51
|
+
class Env(EnvGuard):
|
|
52
|
+
DATABASE_URL: str
|
|
53
|
+
PORT: int = 8080
|
|
54
|
+
DEBUG: bool = False
|
|
55
|
+
ALLOWED_HOSTS: list = []
|
|
56
|
+
|
|
57
|
+
env = Env()
|
|
58
|
+
print(env.PORT) # 8080 — already an int
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If anything is wrong, you get a clear error immediately:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
EnvProofError:
|
|
65
|
+
Missing required environment variables:
|
|
66
|
+
- DATABASE_URL (str): not set
|
|
67
|
+
|
|
68
|
+
Invalid environment variable values:
|
|
69
|
+
- PORT: expected int, got 'abc'
|
|
70
|
+
- DEBUG: expected bool, got 'maybe'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
All errors reported at once — not one at a time.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Install
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install envproof
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### Recommended: one `env.py` file in your project
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# env.py
|
|
91
|
+
from envproof import EnvGuard
|
|
92
|
+
|
|
93
|
+
class Env(EnvGuard):
|
|
94
|
+
# Required — raises EnvProofError at startup if not set
|
|
95
|
+
DATABASE_URL: str
|
|
96
|
+
SECRET_KEY: str
|
|
97
|
+
API_KEY: str
|
|
98
|
+
|
|
99
|
+
# Optional — uses default if not set
|
|
100
|
+
PORT: int = 8080
|
|
101
|
+
DEBUG: bool = False
|
|
102
|
+
LOG_LEVEL: str = "INFO"
|
|
103
|
+
ALLOWED_HOSTS: list = []
|
|
104
|
+
|
|
105
|
+
env = Env()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Then import `env` anywhere in your app:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# app.py
|
|
112
|
+
from env import env
|
|
113
|
+
|
|
114
|
+
app.run(port=env.PORT, debug=env.DEBUG)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# database.py
|
|
119
|
+
from env import env
|
|
120
|
+
|
|
121
|
+
db = connect(env.DATABASE_URL)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### One-liner style (no subclass needed)
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from envproof import guard
|
|
128
|
+
|
|
129
|
+
env = guard(DATABASE_URL=str, PORT=int, DEBUG=bool)
|
|
130
|
+
print(env.DATABASE_URL)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Works great with python-dotenv
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from dotenv import load_dotenv
|
|
137
|
+
load_dotenv() # loads .env file into environment
|
|
138
|
+
|
|
139
|
+
from env import env # envproof validates it
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Supported types
|
|
145
|
+
|
|
146
|
+
| Type | Example env value | Python value |
|
|
147
|
+
|---------|--------------------------------|---------------------|
|
|
148
|
+
| `str` | `hello` | `"hello"` |
|
|
149
|
+
| `int` | `8080` | `8080` |
|
|
150
|
+
| `float` | `3.14` | `3.14` |
|
|
151
|
+
| `bool` | `true`, `1`, `yes`, `on` | `True` |
|
|
152
|
+
| `bool` | `false`, `0`, `no`, `off` | `False` |
|
|
153
|
+
| `list` | `localhost,example.com` | `["localhost", "example.com"]` |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Why envproof?
|
|
158
|
+
|
|
159
|
+
| | envproof | `os.environ` | `pydantic-settings` |
|
|
160
|
+
|---|---|---|---|
|
|
161
|
+
| Type coercion | Yes | No | Yes |
|
|
162
|
+
| Clear error messages | Yes | No | Partial |
|
|
163
|
+
| Zero dependencies | Yes | Yes | No (~2MB) |
|
|
164
|
+
| Fail fast at startup | Yes | No | Yes |
|
|
165
|
+
| Works anywhere | Yes | Yes | Pydantic only |
|
|
166
|
+
|
|
167
|
+
**vs `pydantic-settings`:** great library, but pulls in Pydantic (~2MB). envproof is a single file with zero dependencies — ideal for serverless functions, lightweight containers, or any project that doesn't already use Pydantic.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Production use
|
|
172
|
+
|
|
173
|
+
envproof is designed for production. The "fail fast" pattern — crashing at startup rather than during a request — is standard practice in production backend systems. Your app either starts correctly or doesn't start at all. No surprises mid-flight.
|
|
174
|
+
|
|
175
|
+
Used in: FastAPI apps, Flask apps, Django projects, CLI tools, serverless functions, Docker containers.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Open Source
|
|
180
|
+
|
|
181
|
+
envproof is MIT licensed and open for contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get started.
|
|
182
|
+
|
|
183
|
+
Things we'd love help with:
|
|
184
|
+
- New types: `dict`, `Path`, `Enum`
|
|
185
|
+
- Framework integrations (FastAPI lifespan hooks, Django `AppConfig.ready()`)
|
|
186
|
+
- Better error formatting
|
|
187
|
+
- More test coverage
|
|
188
|
+
|
|
189
|
+
[Open an issue](https://github.com/CoderSufiyan/envguard/issues) or submit a PR — all contributions welcome.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
[MIT](LICENSE) © Sufiyan Khan
|
envproof-0.2.0/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# envproof
|
|
2
|
+
|
|
3
|
+
> Validate environment variables at startup with typed, clear error messages. Zero dependencies. Production-ready.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/envproof/)
|
|
6
|
+
[](https://pypi.org/project/envproof/)
|
|
7
|
+
[](https://github.com/CoderSufiyan/envguard/actions)
|
|
8
|
+
[](https://github.com/CoderSufiyan/envguard/blob/main/LICENSE)
|
|
9
|
+
[](https://github.com/CoderSufiyan/envguard)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Every backend app reads environment variables. The standard way fails silently or crashes with a cryptic error deep inside your app at runtime:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# Crashes 10 minutes in, on an obscure code path
|
|
17
|
+
db = connect(os.environ["DATABASE_URL"]) # KeyError: DATABASE_URL
|
|
18
|
+
|
|
19
|
+
# PORT is a string — breaks math silently
|
|
20
|
+
port = os.environ["PORT"] # "8080", not 8080
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**envproof catches all of this at startup**, before your app serves a single request:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from envproof import EnvGuard
|
|
27
|
+
|
|
28
|
+
class Env(EnvGuard):
|
|
29
|
+
DATABASE_URL: str
|
|
30
|
+
PORT: int = 8080
|
|
31
|
+
DEBUG: bool = False
|
|
32
|
+
ALLOWED_HOSTS: list = []
|
|
33
|
+
|
|
34
|
+
env = Env()
|
|
35
|
+
print(env.PORT) # 8080 — already an int
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If anything is wrong, you get a clear error immediately:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
EnvProofError:
|
|
42
|
+
Missing required environment variables:
|
|
43
|
+
- DATABASE_URL (str): not set
|
|
44
|
+
|
|
45
|
+
Invalid environment variable values:
|
|
46
|
+
- PORT: expected int, got 'abc'
|
|
47
|
+
- DEBUG: expected bool, got 'maybe'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
All errors reported at once — not one at a time.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install envproof
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
### Recommended: one `env.py` file in your project
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# env.py
|
|
68
|
+
from envproof import EnvGuard
|
|
69
|
+
|
|
70
|
+
class Env(EnvGuard):
|
|
71
|
+
# Required — raises EnvProofError at startup if not set
|
|
72
|
+
DATABASE_URL: str
|
|
73
|
+
SECRET_KEY: str
|
|
74
|
+
API_KEY: str
|
|
75
|
+
|
|
76
|
+
# Optional — uses default if not set
|
|
77
|
+
PORT: int = 8080
|
|
78
|
+
DEBUG: bool = False
|
|
79
|
+
LOG_LEVEL: str = "INFO"
|
|
80
|
+
ALLOWED_HOSTS: list = []
|
|
81
|
+
|
|
82
|
+
env = Env()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then import `env` anywhere in your app:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# app.py
|
|
89
|
+
from env import env
|
|
90
|
+
|
|
91
|
+
app.run(port=env.PORT, debug=env.DEBUG)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# database.py
|
|
96
|
+
from env import env
|
|
97
|
+
|
|
98
|
+
db = connect(env.DATABASE_URL)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### One-liner style (no subclass needed)
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from envproof import guard
|
|
105
|
+
|
|
106
|
+
env = guard(DATABASE_URL=str, PORT=int, DEBUG=bool)
|
|
107
|
+
print(env.DATABASE_URL)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Works great with python-dotenv
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from dotenv import load_dotenv
|
|
114
|
+
load_dotenv() # loads .env file into environment
|
|
115
|
+
|
|
116
|
+
from env import env # envproof validates it
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Supported types
|
|
122
|
+
|
|
123
|
+
| Type | Example env value | Python value |
|
|
124
|
+
|---------|--------------------------------|---------------------|
|
|
125
|
+
| `str` | `hello` | `"hello"` |
|
|
126
|
+
| `int` | `8080` | `8080` |
|
|
127
|
+
| `float` | `3.14` | `3.14` |
|
|
128
|
+
| `bool` | `true`, `1`, `yes`, `on` | `True` |
|
|
129
|
+
| `bool` | `false`, `0`, `no`, `off` | `False` |
|
|
130
|
+
| `list` | `localhost,example.com` | `["localhost", "example.com"]` |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Why envproof?
|
|
135
|
+
|
|
136
|
+
| | envproof | `os.environ` | `pydantic-settings` |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| Type coercion | Yes | No | Yes |
|
|
139
|
+
| Clear error messages | Yes | No | Partial |
|
|
140
|
+
| Zero dependencies | Yes | Yes | No (~2MB) |
|
|
141
|
+
| Fail fast at startup | Yes | No | Yes |
|
|
142
|
+
| Works anywhere | Yes | Yes | Pydantic only |
|
|
143
|
+
|
|
144
|
+
**vs `pydantic-settings`:** great library, but pulls in Pydantic (~2MB). envproof is a single file with zero dependencies — ideal for serverless functions, lightweight containers, or any project that doesn't already use Pydantic.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Production use
|
|
149
|
+
|
|
150
|
+
envproof is designed for production. The "fail fast" pattern — crashing at startup rather than during a request — is standard practice in production backend systems. Your app either starts correctly or doesn't start at all. No surprises mid-flight.
|
|
151
|
+
|
|
152
|
+
Used in: FastAPI apps, Flask apps, Django projects, CLI tools, serverless functions, Docker containers.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Open Source
|
|
157
|
+
|
|
158
|
+
envproof is MIT licensed and open for contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get started.
|
|
159
|
+
|
|
160
|
+
Things we'd love help with:
|
|
161
|
+
- New types: `dict`, `Path`, `Enum`
|
|
162
|
+
- Framework integrations (FastAPI lifespan hooks, Django `AppConfig.ready()`)
|
|
163
|
+
- Better error formatting
|
|
164
|
+
- More test coverage
|
|
165
|
+
|
|
166
|
+
[Open an issue](https://github.com/CoderSufiyan/envguard/issues) or submit a PR — all contributions welcome.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
[MIT](LICENSE) © Sufiyan Khan
|
|
@@ -4,11 +4,11 @@ from typing import Any, get_type_hints
|
|
|
4
4
|
_MISSING = object()
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class EnvProofError(Exception):
|
|
8
8
|
pass
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class EnvProof:
|
|
12
12
|
"""
|
|
13
13
|
Subclass this and declare your env vars as type-annotated class attributes.
|
|
14
14
|
|
|
@@ -54,7 +54,7 @@ class EnvGuard:
|
|
|
54
54
|
errors.append(f" - {name}: expected {type_name}, got '{raw}'")
|
|
55
55
|
|
|
56
56
|
if errors:
|
|
57
|
-
raise
|
|
57
|
+
raise EnvProofError("\n" + "\n".join(errors))
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def _coerce(value: str, type_: type) -> Any:
|
|
@@ -77,7 +77,7 @@ def guard(**schema) -> Any:
|
|
|
77
77
|
print(env.DATABASE_URL)
|
|
78
78
|
"""
|
|
79
79
|
|
|
80
|
-
class _Env(
|
|
80
|
+
class _Env(EnvProof):
|
|
81
81
|
pass
|
|
82
82
|
|
|
83
83
|
_Env.__annotations__ = schema
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "envproof"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Validate environment variables at startup with typed, clear error messages. Zero dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -30,7 +30,7 @@ Repository = "https://github.com/CoderSufiyan/envguard"
|
|
|
30
30
|
Issues = "https://github.com/CoderSufiyan/envguard/issues"
|
|
31
31
|
|
|
32
32
|
[tool.hatch.build.targets.wheel]
|
|
33
|
-
packages = ["
|
|
33
|
+
packages = ["envproof"]
|
|
34
34
|
|
|
35
35
|
[tool.pytest.ini_options]
|
|
36
36
|
testpaths = ["tests"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import pytest
|
|
3
|
-
from
|
|
3
|
+
from envproof import EnvProof, EnvProofError, guard
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def set_env(**kwargs):
|
|
@@ -13,13 +13,13 @@ def clear_env(*keys):
|
|
|
13
13
|
os.environ.pop(k, None)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
# ---
|
|
16
|
+
# --- EnvProof subclass style ---
|
|
17
17
|
|
|
18
18
|
class TestRequired:
|
|
19
19
|
def test_loads_required_str(self):
|
|
20
20
|
set_env(APP_NAME="myapp")
|
|
21
21
|
|
|
22
|
-
class Env(
|
|
22
|
+
class Env(EnvProof):
|
|
23
23
|
APP_NAME: str
|
|
24
24
|
|
|
25
25
|
env = Env()
|
|
@@ -29,7 +29,7 @@ class TestRequired:
|
|
|
29
29
|
def test_loads_required_int(self):
|
|
30
30
|
set_env(PORT="9000")
|
|
31
31
|
|
|
32
|
-
class Env(
|
|
32
|
+
class Env(EnvProof):
|
|
33
33
|
PORT: int
|
|
34
34
|
|
|
35
35
|
env = Env()
|
|
@@ -39,7 +39,7 @@ class TestRequired:
|
|
|
39
39
|
def test_loads_required_float(self):
|
|
40
40
|
set_env(RATE="3.14")
|
|
41
41
|
|
|
42
|
-
class Env(
|
|
42
|
+
class Env(EnvProof):
|
|
43
43
|
RATE: float
|
|
44
44
|
|
|
45
45
|
env = Env()
|
|
@@ -49,10 +49,10 @@ class TestRequired:
|
|
|
49
49
|
def test_raises_on_missing_required(self):
|
|
50
50
|
clear_env("DATABASE_URL")
|
|
51
51
|
|
|
52
|
-
class Env(
|
|
52
|
+
class Env(EnvProof):
|
|
53
53
|
DATABASE_URL: str
|
|
54
54
|
|
|
55
|
-
with pytest.raises(
|
|
55
|
+
with pytest.raises(EnvProofError) as exc:
|
|
56
56
|
Env()
|
|
57
57
|
assert "DATABASE_URL" in str(exc.value)
|
|
58
58
|
assert "Missing" in str(exc.value)
|
|
@@ -60,11 +60,11 @@ class TestRequired:
|
|
|
60
60
|
def test_raises_on_multiple_missing(self):
|
|
61
61
|
clear_env("DB_URL", "API_KEY")
|
|
62
62
|
|
|
63
|
-
class Env(
|
|
63
|
+
class Env(EnvProof):
|
|
64
64
|
DB_URL: str
|
|
65
65
|
API_KEY: str
|
|
66
66
|
|
|
67
|
-
with pytest.raises(
|
|
67
|
+
with pytest.raises(EnvProofError) as exc:
|
|
68
68
|
Env()
|
|
69
69
|
assert "DB_URL" in str(exc.value)
|
|
70
70
|
assert "API_KEY" in str(exc.value)
|
|
@@ -74,7 +74,7 @@ class TestOptional:
|
|
|
74
74
|
def test_uses_default_when_not_set(self):
|
|
75
75
|
clear_env("PORT")
|
|
76
76
|
|
|
77
|
-
class Env(
|
|
77
|
+
class Env(EnvProof):
|
|
78
78
|
PORT: int = 8080
|
|
79
79
|
|
|
80
80
|
env = Env()
|
|
@@ -83,7 +83,7 @@ class TestOptional:
|
|
|
83
83
|
def test_overrides_default_when_set(self):
|
|
84
84
|
set_env(PORT="9000")
|
|
85
85
|
|
|
86
|
-
class Env(
|
|
86
|
+
class Env(EnvProof):
|
|
87
87
|
PORT: int = 8080
|
|
88
88
|
|
|
89
89
|
env = Env()
|
|
@@ -93,7 +93,7 @@ class TestOptional:
|
|
|
93
93
|
def test_optional_str_default(self):
|
|
94
94
|
clear_env("LOG_LEVEL")
|
|
95
95
|
|
|
96
|
-
class Env(
|
|
96
|
+
class Env(EnvProof):
|
|
97
97
|
LOG_LEVEL: str = "INFO"
|
|
98
98
|
|
|
99
99
|
env = Env()
|
|
@@ -105,7 +105,7 @@ class TestBoolCoercion:
|
|
|
105
105
|
def test_truthy_values(self, value):
|
|
106
106
|
set_env(DEBUG=value)
|
|
107
107
|
|
|
108
|
-
class Env(
|
|
108
|
+
class Env(EnvProof):
|
|
109
109
|
DEBUG: bool
|
|
110
110
|
|
|
111
111
|
assert Env().DEBUG is True
|
|
@@ -115,7 +115,7 @@ class TestBoolCoercion:
|
|
|
115
115
|
def test_falsy_values(self, value):
|
|
116
116
|
set_env(DEBUG=value)
|
|
117
117
|
|
|
118
|
-
class Env(
|
|
118
|
+
class Env(EnvProof):
|
|
119
119
|
DEBUG: bool
|
|
120
120
|
|
|
121
121
|
assert Env().DEBUG is False
|
|
@@ -124,10 +124,10 @@ class TestBoolCoercion:
|
|
|
124
124
|
def test_invalid_bool_raises(self):
|
|
125
125
|
set_env(DEBUG="maybe")
|
|
126
126
|
|
|
127
|
-
class Env(
|
|
127
|
+
class Env(EnvProof):
|
|
128
128
|
DEBUG: bool
|
|
129
129
|
|
|
130
|
-
with pytest.raises(
|
|
130
|
+
with pytest.raises(EnvProofError) as exc:
|
|
131
131
|
Env()
|
|
132
132
|
assert "DEBUG" in str(exc.value)
|
|
133
133
|
clear_env("DEBUG")
|
|
@@ -137,7 +137,7 @@ class TestListCoercion:
|
|
|
137
137
|
def test_comma_separated_list(self):
|
|
138
138
|
set_env(ALLOWED_HOSTS="localhost,example.com,api.example.com")
|
|
139
139
|
|
|
140
|
-
class Env(
|
|
140
|
+
class Env(EnvProof):
|
|
141
141
|
ALLOWED_HOSTS: list
|
|
142
142
|
|
|
143
143
|
env = Env()
|
|
@@ -147,7 +147,7 @@ class TestListCoercion:
|
|
|
147
147
|
def test_list_strips_whitespace(self):
|
|
148
148
|
set_env(HOSTS=" a , b , c ")
|
|
149
149
|
|
|
150
|
-
class Env(
|
|
150
|
+
class Env(EnvProof):
|
|
151
151
|
HOSTS: list
|
|
152
152
|
|
|
153
153
|
env = Env()
|
|
@@ -159,10 +159,10 @@ class TestInvalidType:
|
|
|
159
159
|
def test_invalid_int_raises(self):
|
|
160
160
|
set_env(PORT="not-a-number")
|
|
161
161
|
|
|
162
|
-
class Env(
|
|
162
|
+
class Env(EnvProof):
|
|
163
163
|
PORT: int
|
|
164
164
|
|
|
165
|
-
with pytest.raises(
|
|
165
|
+
with pytest.raises(EnvProofError) as exc:
|
|
166
166
|
Env()
|
|
167
167
|
assert "PORT" in str(exc.value)
|
|
168
168
|
assert "int" in str(exc.value)
|
|
@@ -171,11 +171,11 @@ class TestInvalidType:
|
|
|
171
171
|
def test_reports_all_invalid_at_once(self):
|
|
172
172
|
set_env(PORT="bad", WORKERS="also-bad")
|
|
173
173
|
|
|
174
|
-
class Env(
|
|
174
|
+
class Env(EnvProof):
|
|
175
175
|
PORT: int
|
|
176
176
|
WORKERS: int
|
|
177
177
|
|
|
178
|
-
with pytest.raises(
|
|
178
|
+
with pytest.raises(EnvProofError) as exc:
|
|
179
179
|
Env()
|
|
180
180
|
assert "PORT" in str(exc.value)
|
|
181
181
|
assert "WORKERS" in str(exc.value)
|
|
@@ -193,7 +193,7 @@ class TestGuard:
|
|
|
193
193
|
|
|
194
194
|
def test_guard_raises_on_missing(self):
|
|
195
195
|
clear_env("SECRET_KEY")
|
|
196
|
-
with pytest.raises(
|
|
196
|
+
with pytest.raises(EnvProofError):
|
|
197
197
|
guard(SECRET_KEY=str)
|
|
198
198
|
|
|
199
199
|
def test_guard_int(self):
|
envproof-0.1.0/PKG-INFO
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
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
|
envproof-0.1.0/README.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# envguard
|
|
2
|
-
|
|
3
|
-
Validate environment variables at startup with typed, clear error messages. Zero dependencies.
|
|
4
|
-
|
|
5
|
-
```python
|
|
6
|
-
from envguard import EnvGuard
|
|
7
|
-
|
|
8
|
-
class Env(EnvGuard):
|
|
9
|
-
DATABASE_URL: str
|
|
10
|
-
PORT: int = 8080
|
|
11
|
-
DEBUG: bool = False
|
|
12
|
-
ALLOWED_HOSTS: list = []
|
|
13
|
-
|
|
14
|
-
env = Env()
|
|
15
|
-
print(env.PORT) # 8080 (int, not string)
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
If `DATABASE_URL` is missing, you get this instead of a cryptic `KeyError` somewhere deep in your app:
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
EnvGuardError:
|
|
22
|
-
Missing required environment variables:
|
|
23
|
-
- DATABASE_URL (str): not set
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Install
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pip install envguard
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Usage
|
|
33
|
-
|
|
34
|
-
### Subclass style (recommended)
|
|
35
|
-
|
|
36
|
-
```python
|
|
37
|
-
from envguard import EnvGuard
|
|
38
|
-
|
|
39
|
-
class Env(EnvGuard):
|
|
40
|
-
# Required — raises if not set
|
|
41
|
-
DATABASE_URL: str
|
|
42
|
-
API_KEY: str
|
|
43
|
-
|
|
44
|
-
# Optional — uses default if not set
|
|
45
|
-
PORT: int = 8080
|
|
46
|
-
DEBUG: bool = False
|
|
47
|
-
LOG_LEVEL: str = "INFO"
|
|
48
|
-
ALLOWED_HOSTS: list = []
|
|
49
|
-
|
|
50
|
-
env = Env()
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### One-liner style
|
|
54
|
-
|
|
55
|
-
```python
|
|
56
|
-
from envguard import guard
|
|
57
|
-
|
|
58
|
-
env = guard(DATABASE_URL=str, PORT=int, DEBUG=bool)
|
|
59
|
-
print(env.DATABASE_URL)
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Supported types
|
|
63
|
-
|
|
64
|
-
| Type | Example env value | Python value |
|
|
65
|
-
|---------|---------------------------|--------------------------|
|
|
66
|
-
| `str` | `"hello"` | `"hello"` |
|
|
67
|
-
| `int` | `"8080"` | `8080` |
|
|
68
|
-
| `float` | `"3.14"` | `3.14` |
|
|
69
|
-
| `bool` | `"true"`, `"1"`, `"yes"` | `True` |
|
|
70
|
-
| `bool` | `"false"`, `"0"`, `"no"` | `False` |
|
|
71
|
-
| `list` | `"a,b,c"` | `["a", "b", "c"]` |
|
|
72
|
-
|
|
73
|
-
## Error messages
|
|
74
|
-
|
|
75
|
-
All errors are collected and reported together — you won't fix one missing var only to discover another:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
EnvGuardError:
|
|
79
|
-
Missing required environment variables:
|
|
80
|
-
- DATABASE_URL (str): not set
|
|
81
|
-
- API_KEY (str): not set
|
|
82
|
-
|
|
83
|
-
Invalid environment variable values:
|
|
84
|
-
- PORT: expected int, got 'abc'
|
|
85
|
-
- DEBUG: expected bool, got 'maybe'
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Why not pydantic-settings?
|
|
89
|
-
|
|
90
|
-
`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.
|
|
91
|
-
|
|
92
|
-
## License
|
|
93
|
-
|
|
94
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|