envdrift 0.0.1__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.
- envdrift-0.0.1/.gitignore +79 -0
- envdrift-0.0.1/.pre-commit-config.yaml +23 -0
- envdrift-0.0.1/LICENSE +21 -0
- envdrift-0.0.1/Makefile +77 -0
- envdrift-0.0.1/PKG-INFO +146 -0
- envdrift-0.0.1/README.md +107 -0
- envdrift-0.0.1/pyproject.toml +124 -0
- envdrift-0.0.1/src/envdrift/__init__.py +16 -0
- envdrift-0.0.1/src/envdrift/cli.py +107 -0
- envdrift-0.0.1/src/envdrift/core.py +47 -0
- envdrift-0.0.1/src/envdrift/py.typed +0 -0
- envdrift-0.0.1/tests/__init__.py +1 -0
- envdrift-0.0.1/tests/test_cli.py +42 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.env.*
|
|
56
|
+
!.env.example
|
|
57
|
+
.venv
|
|
58
|
+
env/
|
|
59
|
+
venv/
|
|
60
|
+
ENV/
|
|
61
|
+
env.bak/
|
|
62
|
+
venv.bak/
|
|
63
|
+
|
|
64
|
+
# IDE
|
|
65
|
+
.idea/
|
|
66
|
+
.vscode/
|
|
67
|
+
*.swp
|
|
68
|
+
*.swo
|
|
69
|
+
*~
|
|
70
|
+
|
|
71
|
+
# Ruff
|
|
72
|
+
.ruff_cache/
|
|
73
|
+
|
|
74
|
+
# macOS
|
|
75
|
+
.DS_Store
|
|
76
|
+
|
|
77
|
+
# UV
|
|
78
|
+
.uv/
|
|
79
|
+
uv.lock
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.8.2
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/PyCQA/bandit
|
|
10
|
+
rev: 1.7.10
|
|
11
|
+
hooks:
|
|
12
|
+
- id: bandit
|
|
13
|
+
args: ["-c", "pyproject.toml", "-r", "src"]
|
|
14
|
+
additional_dependencies: ["bandit[toml]"]
|
|
15
|
+
|
|
16
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
17
|
+
rev: v5.0.0
|
|
18
|
+
hooks:
|
|
19
|
+
- id: trailing-whitespace
|
|
20
|
+
- id: end-of-file-fixer
|
|
21
|
+
- id: check-yaml
|
|
22
|
+
- id: check-added-large-files
|
|
23
|
+
- id: check-toml
|
envdrift-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jainal Gosaliya
|
|
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.
|
envdrift-0.0.1/Makefile
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
.PHONY: install dev lint format typecheck security test build clean publish help
|
|
2
|
+
|
|
3
|
+
# Default target
|
|
4
|
+
help:
|
|
5
|
+
@echo "envdrift - Prevent environment variable drift"
|
|
6
|
+
@echo ""
|
|
7
|
+
@echo "Usage: make [target]"
|
|
8
|
+
@echo ""
|
|
9
|
+
@echo "Targets:"
|
|
10
|
+
@echo " install Install production dependencies"
|
|
11
|
+
@echo " dev Install development dependencies"
|
|
12
|
+
@echo " lint Run linting with ruff"
|
|
13
|
+
@echo " format Format code with ruff"
|
|
14
|
+
@echo " typecheck Run type checking with pyrefly"
|
|
15
|
+
@echo " security Run security checks with bandit"
|
|
16
|
+
@echo " test Run tests with pytest"
|
|
17
|
+
@echo " check Run all checks (lint, typecheck, security, test)"
|
|
18
|
+
@echo " build Build package for distribution"
|
|
19
|
+
@echo " publish Publish to PyPI"
|
|
20
|
+
@echo " clean Remove build artifacts"
|
|
21
|
+
|
|
22
|
+
# Install production dependencies
|
|
23
|
+
install:
|
|
24
|
+
uv sync
|
|
25
|
+
|
|
26
|
+
# Install development dependencies
|
|
27
|
+
dev:
|
|
28
|
+
uv sync --all-extras
|
|
29
|
+
|
|
30
|
+
# Run linting
|
|
31
|
+
lint:
|
|
32
|
+
uv run ruff check src tests
|
|
33
|
+
|
|
34
|
+
# Format code
|
|
35
|
+
format:
|
|
36
|
+
uv run ruff check --fix src tests
|
|
37
|
+
uv run ruff format src tests
|
|
38
|
+
|
|
39
|
+
# Run type checking with pyrefly
|
|
40
|
+
typecheck:
|
|
41
|
+
uv run pyrefly check src
|
|
42
|
+
|
|
43
|
+
# Run security checks with bandit
|
|
44
|
+
security:
|
|
45
|
+
uv run bandit -r src -c pyproject.toml
|
|
46
|
+
|
|
47
|
+
# Run tests
|
|
48
|
+
test:
|
|
49
|
+
uv run pytest
|
|
50
|
+
|
|
51
|
+
# Run all checks
|
|
52
|
+
check: lint typecheck security test
|
|
53
|
+
|
|
54
|
+
# Build package
|
|
55
|
+
build: clean
|
|
56
|
+
uv build
|
|
57
|
+
|
|
58
|
+
# Publish to PyPI
|
|
59
|
+
publish: build
|
|
60
|
+
uv publish
|
|
61
|
+
|
|
62
|
+
# Publish to TestPyPI first (for testing)
|
|
63
|
+
publish-test: build
|
|
64
|
+
uv publish --index-url https://test.pypi.org/simple/
|
|
65
|
+
|
|
66
|
+
# Clean build artifacts
|
|
67
|
+
clean:
|
|
68
|
+
rm -rf dist/
|
|
69
|
+
rm -rf build/
|
|
70
|
+
rm -rf *.egg-info/
|
|
71
|
+
rm -rf src/*.egg-info/
|
|
72
|
+
rm -rf .pytest_cache/
|
|
73
|
+
rm -rf .ruff_cache/
|
|
74
|
+
rm -rf .coverage
|
|
75
|
+
rm -rf htmlcov/
|
|
76
|
+
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
|
77
|
+
find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
envdrift-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envdrift
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Prevent environment variable drift with Pydantic schema validation, pre-commit hooks, and dotenvx encryption
|
|
5
|
+
Project-URL: Homepage, https://github.com/jainal09/envdrift
|
|
6
|
+
Project-URL: Documentation, https://github.com/jainal09/envdrift#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/jainal09/envdrift
|
|
8
|
+
Project-URL: Issues, https://github.com/jainal09/envdrift/issues
|
|
9
|
+
Author-email: Jainal Gosaliya <gosaliya.jainal@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: config,dotenv,drift,environment,pre-commit,pydantic,schema,secrets,validation,variables
|
|
13
|
+
Classifier: Development Status :: 1 - Planning
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0
|
|
28
|
+
Requires-Dist: python-dotenv>=1.0
|
|
29
|
+
Requires-Dist: rich>=13.0
|
|
30
|
+
Requires-Dist: typer>=0.9
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pyrefly>=0.2.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# envdrift
|
|
41
|
+
|
|
42
|
+
[](https://badge.fury.io/py/envdrift)
|
|
43
|
+
[](https://www.python.org/downloads/)
|
|
44
|
+
[](https://opensource.org/licenses/MIT)
|
|
45
|
+
|
|
46
|
+
**Prevent environment variable drift between dev, staging, and production.**
|
|
47
|
+
|
|
48
|
+
> 🚧 **Under Active Development** - Core features coming in v0.1.0
|
|
49
|
+
|
|
50
|
+
## The Problem
|
|
51
|
+
|
|
52
|
+
Environment variable drift is a silent killer of deployments:
|
|
53
|
+
|
|
54
|
+
- A missing `DATABASE_URL` in production causes a 3am outage
|
|
55
|
+
- Staging has `NEW_FEATURE_FLAG=true` but production doesn't
|
|
56
|
+
- Someone copies the wrong `.env` file and chaos ensues
|
|
57
|
+
- "It works on my machine!" becomes your team's motto
|
|
58
|
+
|
|
59
|
+
**In 2024 alone, 24 million secrets were leaked on GitHub.** Knight Capital lost **$460 million in 45 minutes** due to a configuration deployment error.
|
|
60
|
+
|
|
61
|
+
## The Solution
|
|
62
|
+
|
|
63
|
+
`envdrift` treats your environment variables with the same rigor as your code:
|
|
64
|
+
|
|
65
|
+
- **Schema Validation**: Define expected variables with Pydantic, catch mismatches at startup
|
|
66
|
+
- **Drift Detection**: Compare `.env.dev` vs `.env.prod` and see exactly what differs
|
|
67
|
+
- **Pre-commit Hooks**: Block commits if your `.env` doesn't match your schema
|
|
68
|
+
- **CI/CD Integration**: Fail fast in pipelines before bad config reaches production
|
|
69
|
+
- **Encryption Support**: Works with dotenvx for secure, committable `.env` files
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install envdrift
|
|
75
|
+
# or
|
|
76
|
+
uv add envdrift
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
### Validate your .env against a schema
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
envdrift validate .env --schema myapp.config:Settings
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Compare environments
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
envdrift diff .env.dev .env.prod
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Generate a Settings class from existing .env
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
envdrift init .env --output settings.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Install pre-commit hook
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
envdrift hook --install
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Planned Features (v0.1.0)
|
|
106
|
+
|
|
107
|
+
- [ ] `envdrift validate` - Validate .env against Pydantic schema
|
|
108
|
+
- [ ] `envdrift diff` - Compare two .env files
|
|
109
|
+
- [ ] `envdrift init` - Generate Settings class from .env
|
|
110
|
+
- [ ] `envdrift hook` - Pre-commit hook integration
|
|
111
|
+
- [ ] Rich terminal output with clear error messages
|
|
112
|
+
- [ ] dotenvx encryption detection and support
|
|
113
|
+
- [ ] CI mode with proper exit codes
|
|
114
|
+
|
|
115
|
+
## Why envdrift?
|
|
116
|
+
|
|
117
|
+
| Feature | python-dotenv | dynaconf | pydantic-settings | **envdrift** |
|
|
118
|
+
|---------|---------------|----------|-------------------|--------------|
|
|
119
|
+
| Load .env | ✅ | ✅ | ✅ | ✅ |
|
|
120
|
+
| Type validation | ❌ | ⚠️ | ✅ | ✅ |
|
|
121
|
+
| Schema enforcement | ❌ | ⚠️ | ✅ | ✅ |
|
|
122
|
+
| Cross-env diff | ❌ | ❌ | ❌ | ✅ |
|
|
123
|
+
| Pre-commit hook | ❌ | ❌ | ❌ | ✅ |
|
|
124
|
+
| Encryption support | ❌ | ❌ | ❌ | ✅ |
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Clone the repo
|
|
130
|
+
git clone https://github.com/jainal09/envdrift.git
|
|
131
|
+
cd envdrift
|
|
132
|
+
|
|
133
|
+
# Install dev dependencies
|
|
134
|
+
make dev
|
|
135
|
+
|
|
136
|
+
# Run checks
|
|
137
|
+
make check
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
143
|
+
|
|
144
|
+
## Author
|
|
145
|
+
|
|
146
|
+
**Jainal Gosaliya** - [gosaliya.jainal@gmail.com](mailto:gosaliya.jainal@gmail.com)
|
envdrift-0.0.1/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# envdrift
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/envdrift)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
**Prevent environment variable drift between dev, staging, and production.**
|
|
8
|
+
|
|
9
|
+
> 🚧 **Under Active Development** - Core features coming in v0.1.0
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
Environment variable drift is a silent killer of deployments:
|
|
14
|
+
|
|
15
|
+
- A missing `DATABASE_URL` in production causes a 3am outage
|
|
16
|
+
- Staging has `NEW_FEATURE_FLAG=true` but production doesn't
|
|
17
|
+
- Someone copies the wrong `.env` file and chaos ensues
|
|
18
|
+
- "It works on my machine!" becomes your team's motto
|
|
19
|
+
|
|
20
|
+
**In 2024 alone, 24 million secrets were leaked on GitHub.** Knight Capital lost **$460 million in 45 minutes** due to a configuration deployment error.
|
|
21
|
+
|
|
22
|
+
## The Solution
|
|
23
|
+
|
|
24
|
+
`envdrift` treats your environment variables with the same rigor as your code:
|
|
25
|
+
|
|
26
|
+
- **Schema Validation**: Define expected variables with Pydantic, catch mismatches at startup
|
|
27
|
+
- **Drift Detection**: Compare `.env.dev` vs `.env.prod` and see exactly what differs
|
|
28
|
+
- **Pre-commit Hooks**: Block commits if your `.env` doesn't match your schema
|
|
29
|
+
- **CI/CD Integration**: Fail fast in pipelines before bad config reaches production
|
|
30
|
+
- **Encryption Support**: Works with dotenvx for secure, committable `.env` files
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install envdrift
|
|
36
|
+
# or
|
|
37
|
+
uv add envdrift
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### Validate your .env against a schema
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
envdrift validate .env --schema myapp.config:Settings
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Compare environments
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
envdrift diff .env.dev .env.prod
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Generate a Settings class from existing .env
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
envdrift init .env --output settings.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Install pre-commit hook
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
envdrift hook --install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Planned Features (v0.1.0)
|
|
67
|
+
|
|
68
|
+
- [ ] `envdrift validate` - Validate .env against Pydantic schema
|
|
69
|
+
- [ ] `envdrift diff` - Compare two .env files
|
|
70
|
+
- [ ] `envdrift init` - Generate Settings class from .env
|
|
71
|
+
- [ ] `envdrift hook` - Pre-commit hook integration
|
|
72
|
+
- [ ] Rich terminal output with clear error messages
|
|
73
|
+
- [ ] dotenvx encryption detection and support
|
|
74
|
+
- [ ] CI mode with proper exit codes
|
|
75
|
+
|
|
76
|
+
## Why envdrift?
|
|
77
|
+
|
|
78
|
+
| Feature | python-dotenv | dynaconf | pydantic-settings | **envdrift** |
|
|
79
|
+
|---------|---------------|----------|-------------------|--------------|
|
|
80
|
+
| Load .env | ✅ | ✅ | ✅ | ✅ |
|
|
81
|
+
| Type validation | ❌ | ⚠️ | ✅ | ✅ |
|
|
82
|
+
| Schema enforcement | ❌ | ⚠️ | ✅ | ✅ |
|
|
83
|
+
| Cross-env diff | ❌ | ❌ | ❌ | ✅ |
|
|
84
|
+
| Pre-commit hook | ❌ | ❌ | ❌ | ✅ |
|
|
85
|
+
| Encryption support | ❌ | ❌ | ❌ | ✅ |
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Clone the repo
|
|
91
|
+
git clone https://github.com/jainal09/envdrift.git
|
|
92
|
+
cd envdrift
|
|
93
|
+
|
|
94
|
+
# Install dev dependencies
|
|
95
|
+
make dev
|
|
96
|
+
|
|
97
|
+
# Run checks
|
|
98
|
+
make check
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
104
|
+
|
|
105
|
+
## Author
|
|
106
|
+
|
|
107
|
+
**Jainal Gosaliya** - [gosaliya.jainal@gmail.com](mailto:gosaliya.jainal@gmail.com)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "envdrift"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Prevent environment variable drift with Pydantic schema validation, pre-commit hooks, and dotenvx encryption"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jainal Gosaliya", email = "gosaliya.jainal@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"environment",
|
|
17
|
+
"variables",
|
|
18
|
+
"pydantic",
|
|
19
|
+
"validation",
|
|
20
|
+
"dotenv",
|
|
21
|
+
"config",
|
|
22
|
+
"drift",
|
|
23
|
+
"schema",
|
|
24
|
+
"secrets",
|
|
25
|
+
"pre-commit",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 1 - Planning",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
37
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
38
|
+
"Topic :: Security",
|
|
39
|
+
"Typing :: Typed",
|
|
40
|
+
]
|
|
41
|
+
dependencies = [
|
|
42
|
+
"pydantic>=2.0",
|
|
43
|
+
"pydantic-settings>=2.0",
|
|
44
|
+
"rich>=13.0",
|
|
45
|
+
"typer>=0.9",
|
|
46
|
+
"python-dotenv>=1.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.optional-dependencies]
|
|
50
|
+
dev = [
|
|
51
|
+
"ruff>=0.8.0",
|
|
52
|
+
"pyrefly>=0.2.0",
|
|
53
|
+
"bandit>=1.7.0",
|
|
54
|
+
"pytest>=8.0",
|
|
55
|
+
"pytest-cov>=4.0",
|
|
56
|
+
"pre-commit>=3.0",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.scripts]
|
|
60
|
+
envdrift = "envdrift.cli:app"
|
|
61
|
+
|
|
62
|
+
[project.urls]
|
|
63
|
+
Homepage = "https://github.com/jainal09/envdrift"
|
|
64
|
+
Documentation = "https://github.com/jainal09/envdrift#readme"
|
|
65
|
+
Repository = "https://github.com/jainal09/envdrift"
|
|
66
|
+
Issues = "https://github.com/jainal09/envdrift/issues"
|
|
67
|
+
|
|
68
|
+
[tool.hatch.build.targets.wheel]
|
|
69
|
+
packages = ["src/envdrift"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
target-version = "py311"
|
|
73
|
+
line-length = 100
|
|
74
|
+
src = ["src", "tests"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = [
|
|
78
|
+
"E", # pycodestyle errors
|
|
79
|
+
"W", # pycodestyle warnings
|
|
80
|
+
"F", # Pyflakes
|
|
81
|
+
"I", # isort
|
|
82
|
+
"N", # pep8-naming
|
|
83
|
+
"UP", # pyupgrade
|
|
84
|
+
"B", # flake8-bugbear
|
|
85
|
+
"C4", # flake8-comprehensions
|
|
86
|
+
"SIM", # flake8-simplify
|
|
87
|
+
"PTH", # flake8-use-pathlib
|
|
88
|
+
"RUF", # Ruff-specific rules
|
|
89
|
+
"S", # flake8-bandit (security)
|
|
90
|
+
]
|
|
91
|
+
ignore = [
|
|
92
|
+
"S101", # assert allowed in tests
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[tool.ruff.lint.per-file-ignores]
|
|
96
|
+
"tests/*" = ["S101"]
|
|
97
|
+
|
|
98
|
+
[tool.ruff.format]
|
|
99
|
+
quote-style = "double"
|
|
100
|
+
indent-style = "space"
|
|
101
|
+
|
|
102
|
+
[tool.pytest.ini_options]
|
|
103
|
+
testpaths = ["tests"]
|
|
104
|
+
pythonpath = ["src"]
|
|
105
|
+
addopts = [
|
|
106
|
+
"--strict-markers",
|
|
107
|
+
"--cov=envdrift",
|
|
108
|
+
"--cov-report=term-missing",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[tool.coverage.run]
|
|
112
|
+
source = ["src/envdrift"]
|
|
113
|
+
branch = true
|
|
114
|
+
|
|
115
|
+
[tool.coverage.report]
|
|
116
|
+
exclude_lines = [
|
|
117
|
+
"pragma: no cover",
|
|
118
|
+
"if TYPE_CHECKING:",
|
|
119
|
+
"raise NotImplementedError",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
[tool.bandit]
|
|
123
|
+
exclude_dirs = ["tests", ".venv"]
|
|
124
|
+
skips = ["B101"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Prevent environment variable drift with Pydantic schema validation.
|
|
2
|
+
|
|
3
|
+
envdrift helps you:
|
|
4
|
+
- Validate .env files against Pydantic schemas
|
|
5
|
+
- Detect drift between environments (dev, staging, prod)
|
|
6
|
+
- Integrate with pre-commit hooks and CI/CD pipelines
|
|
7
|
+
- Support dotenvx encryption for secure .env files
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.0.1"
|
|
11
|
+
__author__ = "Jainal Gosaliya"
|
|
12
|
+
__email__ = "gosaliya.jainal@gmail.com"
|
|
13
|
+
|
|
14
|
+
from envdrift.core import validate, diff, init
|
|
15
|
+
|
|
16
|
+
__all__ = ["validate", "diff", "init", "__version__"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Command-line interface for envdrift."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="envdrift",
|
|
12
|
+
help="Prevent environment variable drift with Pydantic schema validation.",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def validate(
|
|
20
|
+
env_file: Annotated[
|
|
21
|
+
Path, typer.Argument(help="Path to .env file to validate")
|
|
22
|
+
] = Path(".env"),
|
|
23
|
+
schema: Annotated[
|
|
24
|
+
Optional[str],
|
|
25
|
+
typer.Option("--schema", "-s", help="Dotted path to Settings class"),
|
|
26
|
+
] = None,
|
|
27
|
+
ci: Annotated[
|
|
28
|
+
bool, typer.Option("--ci", help="CI mode: exit with code 1 on failure")
|
|
29
|
+
] = False,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Validate an .env file against a Pydantic schema."""
|
|
32
|
+
console.print(
|
|
33
|
+
Panel(
|
|
34
|
+
"[yellow]Coming soon in v0.1.0[/yellow]\n\n"
|
|
35
|
+
"This command will validate your .env file against a Pydantic Settings schema.",
|
|
36
|
+
title="envdrift validate",
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
if ci:
|
|
40
|
+
raise typer.Exit(code=1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command()
|
|
44
|
+
def diff(
|
|
45
|
+
env1: Annotated[Path, typer.Argument(help="First .env file (e.g., .env.dev)")],
|
|
46
|
+
env2: Annotated[Path, typer.Argument(help="Second .env file (e.g., .env.prod)")],
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Compare two .env files and show differences."""
|
|
49
|
+
console.print(
|
|
50
|
+
Panel(
|
|
51
|
+
"[yellow]Coming soon in v0.1.0[/yellow]\n\n"
|
|
52
|
+
f"This command will compare [bold]{env1}[/bold] and [bold]{env2}[/bold]\n"
|
|
53
|
+
"and show missing, extra, and differing variables.",
|
|
54
|
+
title="envdrift diff",
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command()
|
|
60
|
+
def init(
|
|
61
|
+
env_file: Annotated[
|
|
62
|
+
Path, typer.Argument(help="Path to .env file to generate schema from")
|
|
63
|
+
] = Path(".env"),
|
|
64
|
+
output: Annotated[
|
|
65
|
+
Path, typer.Option("--output", "-o", help="Output file for Settings class")
|
|
66
|
+
] = Path("settings.py"),
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Generate a Pydantic Settings class from an existing .env file."""
|
|
69
|
+
console.print(
|
|
70
|
+
Panel(
|
|
71
|
+
"[yellow]Coming soon in v0.1.0[/yellow]\n\n"
|
|
72
|
+
f"This command will generate a Pydantic Settings class\n"
|
|
73
|
+
f"from [bold]{env_file}[/bold] and write it to [bold]{output}[/bold].",
|
|
74
|
+
title="envdrift init",
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
def hook(
|
|
81
|
+
install: Annotated[
|
|
82
|
+
bool, typer.Option("--install", "-i", help="Install pre-commit hook")
|
|
83
|
+
] = False,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Manage pre-commit hook integration."""
|
|
86
|
+
if install:
|
|
87
|
+
console.print(
|
|
88
|
+
Panel(
|
|
89
|
+
"[yellow]Coming soon in v0.1.0[/yellow]\n\n"
|
|
90
|
+
"This command will add envdrift to your .pre-commit-config.yaml",
|
|
91
|
+
title="envdrift hook",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
console.print("Use --install to add envdrift pre-commit hook")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.command()
|
|
99
|
+
def version() -> None:
|
|
100
|
+
"""Show envdrift version."""
|
|
101
|
+
from envdrift import __version__
|
|
102
|
+
|
|
103
|
+
console.print(f"envdrift [bold green]{__version__}[/bold green]")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
app()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Core functionality for envdrift."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def validate(env_file: Path | str = ".env", schema: str | None = None) -> bool:
|
|
7
|
+
"""Validate an .env file against a Pydantic schema.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
env_file: Path to the .env file to validate
|
|
11
|
+
schema: Dotted path to the Pydantic Settings class (e.g., 'app.config:Settings')
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
True if validation passes, False otherwise
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
NotImplementedError: This feature is coming soon
|
|
18
|
+
"""
|
|
19
|
+
raise NotImplementedError("Coming soon in v0.1.0")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def diff(env1: Path | str, env2: Path | str) -> dict[str, tuple[str | None, str | None]]:
|
|
23
|
+
"""Compare two .env files and return differences.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
env1: Path to first .env file
|
|
27
|
+
env2: Path to second .env file
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dictionary of differences: {key: (value_in_env1, value_in_env2)}
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
NotImplementedError: This feature is coming soon
|
|
34
|
+
"""
|
|
35
|
+
raise NotImplementedError("Coming soon in v0.1.0")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def init(output: Path | str = "settings.py") -> None:
|
|
39
|
+
"""Generate a Pydantic Settings class from an existing .env file.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
output: Path where to write the generated Settings class
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
NotImplementedError: This feature is coming soon
|
|
46
|
+
"""
|
|
47
|
+
raise NotImplementedError("Coming soon in v0.1.0")
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests for envdrift."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Tests for CLI commands."""
|
|
2
|
+
|
|
3
|
+
from typer.testing import CliRunner
|
|
4
|
+
|
|
5
|
+
from envdrift.cli import app
|
|
6
|
+
|
|
7
|
+
runner = CliRunner()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_version() -> None:
|
|
11
|
+
"""Test version command."""
|
|
12
|
+
result = runner.invoke(app, ["version"])
|
|
13
|
+
assert result.exit_code == 0
|
|
14
|
+
assert "0.0.1" in result.stdout
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_validate_coming_soon() -> None:
|
|
18
|
+
"""Test validate command shows coming soon message."""
|
|
19
|
+
result = runner.invoke(app, ["validate"])
|
|
20
|
+
assert result.exit_code == 0
|
|
21
|
+
assert "Coming soon" in result.stdout
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_diff_coming_soon() -> None:
|
|
25
|
+
"""Test diff command shows coming soon message."""
|
|
26
|
+
result = runner.invoke(app, ["diff", ".env.dev", ".env.prod"])
|
|
27
|
+
assert result.exit_code == 0
|
|
28
|
+
assert "Coming soon" in result.stdout
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_init_coming_soon() -> None:
|
|
32
|
+
"""Test init command shows coming soon message."""
|
|
33
|
+
result = runner.invoke(app, ["init"])
|
|
34
|
+
assert result.exit_code == 0
|
|
35
|
+
assert "Coming soon" in result.stdout
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_hook_without_install() -> None:
|
|
39
|
+
"""Test hook command without --install flag."""
|
|
40
|
+
result = runner.invoke(app, ["hook"])
|
|
41
|
+
assert result.exit_code == 0
|
|
42
|
+
assert "--install" in result.stdout
|