prodkit 0.1.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.
- prodkit-0.1.0/.gitignore +27 -0
- prodkit-0.1.0/CHANGELOG.md +22 -0
- prodkit-0.1.0/LICENSE +21 -0
- prodkit-0.1.0/PKG-INFO +264 -0
- prodkit-0.1.0/README.md +222 -0
- prodkit-0.1.0/pyproject.toml +111 -0
- prodkit-0.1.0/src/prodkit/__init__.py +57 -0
- prodkit-0.1.0/src/prodkit/contracts/__init__.py +0 -0
- prodkit-0.1.0/src/prodkit/contracts/plugin.py +61 -0
- prodkit-0.1.0/src/prodkit/core/__init__.py +0 -0
- prodkit-0.1.0/src/prodkit/core/config.py +245 -0
- prodkit-0.1.0/src/prodkit/core/context.py +53 -0
- prodkit-0.1.0/src/prodkit/core/event_bus.py +42 -0
- prodkit-0.1.0/src/prodkit/core/exceptions.py +23 -0
- prodkit-0.1.0/src/prodkit/core/lifecycle.py +54 -0
- prodkit-0.1.0/src/prodkit/core/plugin_manager.py +52 -0
- prodkit-0.1.0/src/prodkit/core/production.py +123 -0
- prodkit-0.1.0/src/prodkit/core/registry.py +38 -0
- prodkit-0.1.0/src/prodkit/plugins/__init__.py +54 -0
- prodkit-0.1.0/src/prodkit/plugins/compression/__init__.py +22 -0
- prodkit-0.1.0/src/prodkit/plugins/cors/__init__.py +38 -0
- prodkit-0.1.0/src/prodkit/plugins/errors/__init__.py +98 -0
- prodkit-0.1.0/src/prodkit/plugins/health/__init__.py +72 -0
- prodkit-0.1.0/src/prodkit/plugins/logging/__init__.py +116 -0
- prodkit-0.1.0/src/prodkit/plugins/request_id/__init__.py +61 -0
- prodkit-0.1.0/src/prodkit/plugins/security/__init__.py +78 -0
- prodkit-0.1.0/src/prodkit/py.typed +0 -0
- prodkit-0.1.0/tests/__init__.py +0 -0
- prodkit-0.1.0/tests/conftest.py +35 -0
- prodkit-0.1.0/tests/integration/__init__.py +0 -0
- prodkit-0.1.0/tests/integration/test_logging.py +81 -0
- prodkit-0.1.0/tests/integration/test_production.py +232 -0
- prodkit-0.1.0/tests/unit/__init__.py +0 -0
- prodkit-0.1.0/tests/unit/test_config.py +124 -0
- prodkit-0.1.0/tests/unit/test_kernel.py +116 -0
prodkit-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Byte-compiled / caches
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.mypy_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.coverage
|
|
8
|
+
htmlcov/
|
|
9
|
+
|
|
10
|
+
# Environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
.env
|
|
14
|
+
|
|
15
|
+
# Build artifacts
|
|
16
|
+
build/
|
|
17
|
+
dist/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
|
|
20
|
+
# Editors / OS
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
LI.MD
|
|
26
|
+
PUB.MD
|
|
27
|
+
EX.MD
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-07-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Kernel: layered configuration (args > env > `prodkit.toml` > profile > defaults),
|
|
14
|
+
plugin manager with dependency topological sort, service registry, event bus,
|
|
15
|
+
lifespan composition, fail-fast config validation.
|
|
16
|
+
- Plugin contract with async startup/shutdown hooks and prioritized middleware
|
|
17
|
+
registration.
|
|
18
|
+
- Environment profiles: `development`, `staging`, `production`.
|
|
19
|
+
- Built-in plugins: `request-id`, `logging` (structured JSON/console), `errors`
|
|
20
|
+
(RFC 9457 problem+json), `health` (`/health`, `/ready`, `/live`), `security`
|
|
21
|
+
(security headers, trusted hosts, HTTPS redirect), `cors`, `compression` (gzip).
|
|
22
|
+
- `Production(app)` one-line entrypoint.
|
prodkit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pushkar Pant
|
|
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.
|
prodkit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prodkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The production framework for FastAPI. One line. Production ready.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Pushkarpant/PRODKIT
|
|
6
|
+
Project-URL: Documentation, https://github.com/Pushkarpant/PRODKIT#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/Pushkarpant/PRODKIT
|
|
8
|
+
Project-URL: Changelog, https://github.com/Pushkarpant/PRODKIT/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/Pushkarpant/PRODKIT/issues
|
|
10
|
+
Author: ProdKit Contributors
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: fastapi,health-check,logging,middleware,observability,production,security
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: fastapi>=0.110
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.1
|
|
29
|
+
Requires-Dist: pydantic>=2.5
|
|
30
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
31
|
+
Provides-Extra: brotli
|
|
32
|
+
Requires-Dist: brotli-asgi>=1.4; extra == 'brotli'
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
35
|
+
Requires-Dist: import-linter>=2.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
40
|
+
Requires-Dist: tomli>=2.0; extra == 'dev'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# ProdKit
|
|
44
|
+
|
|
45
|
+
> **One line. Production ready.**
|
|
46
|
+
|
|
47
|
+
The production framework for [FastAPI](https://fastapi.tiangolo.com/).
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from fastapi import FastAPI
|
|
51
|
+
from prodkit import Production
|
|
52
|
+
|
|
53
|
+
app = FastAPI()
|
|
54
|
+
Production(app)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it. Your app now has security headers, structured JSON logging with
|
|
58
|
+
request-ID correlation, RFC 9457 error responses, Kubernetes-ready health
|
|
59
|
+
endpoints, and gzip compression — configured to current best practice,
|
|
60
|
+
hardened for production, and pleasant in development.
|
|
61
|
+
|
|
62
|
+
[](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
|
|
63
|
+
[](https://pypi.org/project/prodkit/)
|
|
64
|
+
[](https://pypi.org/project/prodkit/)
|
|
65
|
+
[](LICENSE)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Why
|
|
70
|
+
|
|
71
|
+
Every production FastAPI service re-implements the same ~500 lines of glue:
|
|
72
|
+
middleware ordering, security headers, structured logging, health checks,
|
|
73
|
+
error normalization, graceful shutdown. FastAPI deliberately doesn't ship
|
|
74
|
+
this — it's a micro framework. **ProdKit is the batteries.**
|
|
75
|
+
|
|
76
|
+
And unlike a project template, ProdKit is a library: when best practices
|
|
77
|
+
evolve, `pip install -U prodkit` updates every app you own.
|
|
78
|
+
|
|
79
|
+
## Installation
|
|
80
|
+
|
|
81
|
+
[**`pip install prodkit`**](https://pypi.org/project/prodkit/)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install prodkit
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Requires Python 3.10+ and FastAPI 0.110+. The base install depends only on
|
|
88
|
+
FastAPI and Pydantic — nothing else.
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from fastapi import FastAPI
|
|
94
|
+
from prodkit import Production
|
|
95
|
+
|
|
96
|
+
app = FastAPI()
|
|
97
|
+
Production(app) # production profile by default
|
|
98
|
+
|
|
99
|
+
@app.get("/hello")
|
|
100
|
+
def hello():
|
|
101
|
+
return {"message": "hello"}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
uvicorn main:app
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
$ curl -i localhost:8000/hello
|
|
110
|
+
HTTP/1.1 200 OK
|
|
111
|
+
x-request-id: 26fdc49565614c2a9ef1a3b8d4e0f712
|
|
112
|
+
x-content-type-options: nosniff
|
|
113
|
+
x-frame-options: DENY
|
|
114
|
+
strict-transport-security: max-age=63072000; includeSubDomains
|
|
115
|
+
referrer-policy: strict-origin-when-cross-origin
|
|
116
|
+
...
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For local development, flip the profile — pretty console logs, debug error
|
|
120
|
+
details, no HSTS:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
Production(app, environment="development")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## What You Get
|
|
127
|
+
|
|
128
|
+
| Feature | Details |
|
|
129
|
+
|---|---|
|
|
130
|
+
| 🆔 **Request IDs** | `X-Request-ID` on every response, propagated into every log line. Inbound IDs untrusted by default. |
|
|
131
|
+
| 📋 **Structured logging** | One JSON object per request in production (Datadog/Loki/CloudWatch-ready); pretty console logs in development. |
|
|
132
|
+
| 🛡️ **Security headers** | OWASP-aligned: `nosniff`, `X-Frame-Options`, HSTS, `Referrer-Policy`, `Permissions-Policy`. Your own headers always win. |
|
|
133
|
+
| 🚨 **Error normalization** | [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) `problem+json` responses. Unhandled 500s are **opaque to clients** — the traceback goes to logs, correlated by request ID. |
|
|
134
|
+
| ❤️ **Health endpoints** | `/health`, `/live` (liveness) and `/ready` (readiness — aggregates checks from every plugin, 503 until all pass). Kubernetes-native. |
|
|
135
|
+
| 🌐 **CORS** | Explicit origins only; the wildcard-with-credentials footgun is refused at boot. |
|
|
136
|
+
| 📦 **Compression** | Gzip for responses over 500 bytes. |
|
|
137
|
+
| 🔌 **Plugin system** | Every feature above is a plugin. Write your own with 6 optional hooks. |
|
|
138
|
+
|
|
139
|
+
## Configuration
|
|
140
|
+
|
|
141
|
+
Everything is configurable through four layers (highest wins):
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Python args > environment variables > prodkit.toml > profile defaults
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Python:**
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
Production(
|
|
151
|
+
app,
|
|
152
|
+
environment="production",
|
|
153
|
+
cors={"origins": ["https://app.example.com"]}, # dict = configure & enable
|
|
154
|
+
compression=False, # bool = toggle
|
|
155
|
+
security={"trusted_hosts": ["api.example.com"]},
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Environment variables** (`__` descends into sections):
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
PRODKIT_ENVIRONMENT=production
|
|
163
|
+
PRODKIT_LOGGING__LEVEL=WARNING
|
|
164
|
+
PRODKIT_SECURITY__TRUSTED_HOSTS=api.example.com,admin.example.com
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**`prodkit.toml`:**
|
|
168
|
+
|
|
169
|
+
```toml
|
|
170
|
+
[prodkit]
|
|
171
|
+
environment = "production"
|
|
172
|
+
|
|
173
|
+
[logging]
|
|
174
|
+
level = "INFO"
|
|
175
|
+
|
|
176
|
+
[cors]
|
|
177
|
+
enabled = true
|
|
178
|
+
origins = ["https://app.example.com"]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Fail-fast, refuse-unsafe
|
|
182
|
+
|
|
183
|
+
Misconfiguration fails **at startup with a named key**, never silently:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
ProdKitConfigError: Invalid ProdKit configuration:
|
|
187
|
+
- logging.levle: Extra inputs are not permitted
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
And configurations that would weaken a production deployment are refused,
|
|
191
|
+
not warned about:
|
|
192
|
+
|
|
193
|
+
- `debug=True` in production
|
|
194
|
+
- error responses that would leak tracebacks in production
|
|
195
|
+
- CORS `origins=["*"]` combined with `allow_credentials=True`
|
|
196
|
+
|
|
197
|
+
## Writing a Plugin
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from prodkit import Check, Plugin, Production
|
|
201
|
+
|
|
202
|
+
class DatabasePlugin(Plugin):
|
|
203
|
+
name = "database"
|
|
204
|
+
|
|
205
|
+
async def startup(self, ctx):
|
|
206
|
+
self.pool = await create_pool(...)
|
|
207
|
+
ctx.registry.provide("db", self.pool)
|
|
208
|
+
|
|
209
|
+
async def shutdown(self, ctx):
|
|
210
|
+
await self.pool.close()
|
|
211
|
+
|
|
212
|
+
def checks(self, ctx):
|
|
213
|
+
return [Check(name="database", passed=self.pool.is_alive())]
|
|
214
|
+
|
|
215
|
+
Production(app, plugins=[DatabasePlugin()])
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Your check now shows up in `/ready` automatically. Plugins can declare
|
|
219
|
+
`requires = ("other-plugin",)` and the kernel activates them in dependency
|
|
220
|
+
order — cycles and missing dependencies fail at boot.
|
|
221
|
+
|
|
222
|
+
Middleware registered by plugins carries an explicit integer priority, so
|
|
223
|
+
the middleware onion is always correctly ordered no matter what order
|
|
224
|
+
plugins load in (request-id outermost, compression innermost).
|
|
225
|
+
|
|
226
|
+
## Plays Nice With Your App
|
|
227
|
+
|
|
228
|
+
- **Same app object.** Routes, dependencies, and existing middleware keep
|
|
229
|
+
working. Remove `Production(app)` and you have a plain FastAPI app again.
|
|
230
|
+
- **Your lifespan survives.** ProdKit *composes* with an existing `lifespan`:
|
|
231
|
+
plugin startup → your lifespan → plugin shutdown (LIFO).
|
|
232
|
+
- **Your headers win.** Security headers use set-if-absent semantics.
|
|
233
|
+
- **Every feature can be turned off.** `Production(app, security=False, ...)`
|
|
234
|
+
|
|
235
|
+
## Project Status
|
|
236
|
+
|
|
237
|
+
**v0.1.0 — alpha.** Core kernel and seven built-in plugins, 60 tests, 98%
|
|
238
|
+
coverage, strict mypy, CI across Python 3.10–3.13.
|
|
239
|
+
|
|
240
|
+
Roadmap: `prodkit doctor` CLI with a production-readiness score (v0.2),
|
|
241
|
+
Prometheus metrics + Redis backends (v0.3), Dockerfile/nginx/CI generators
|
|
242
|
+
(v0.4), public plugin SDK (v0.5), auth helpers (v0.6), stable API (v1.0).
|
|
243
|
+
Full details in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
244
|
+
|
|
245
|
+
## Contributing
|
|
246
|
+
|
|
247
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
248
|
+
Security reports: see [SECURITY.md](SECURITY.md) (never open a public issue).
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
git clone https://github.com/Pushkarpant/PRODKIT
|
|
252
|
+
cd PRODKIT
|
|
253
|
+
python -m venv .venv && source .venv/bin/activate
|
|
254
|
+
pip install -e ".[dev]"
|
|
255
|
+
pytest
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
[MIT](LICENSE)
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
*FastAPI builds APIs. ProdKit makes them production-ready.*
|
prodkit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# ProdKit
|
|
2
|
+
|
|
3
|
+
> **One line. Production ready.**
|
|
4
|
+
|
|
5
|
+
The production framework for [FastAPI](https://fastapi.tiangolo.com/).
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
from prodkit import Production
|
|
10
|
+
|
|
11
|
+
app = FastAPI()
|
|
12
|
+
Production(app)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
That's it. Your app now has security headers, structured JSON logging with
|
|
16
|
+
request-ID correlation, RFC 9457 error responses, Kubernetes-ready health
|
|
17
|
+
endpoints, and gzip compression — configured to current best practice,
|
|
18
|
+
hardened for production, and pleasant in development.
|
|
19
|
+
|
|
20
|
+
[](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
|
|
21
|
+
[](https://pypi.org/project/prodkit/)
|
|
22
|
+
[](https://pypi.org/project/prodkit/)
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
Every production FastAPI service re-implements the same ~500 lines of glue:
|
|
30
|
+
middleware ordering, security headers, structured logging, health checks,
|
|
31
|
+
error normalization, graceful shutdown. FastAPI deliberately doesn't ship
|
|
32
|
+
this — it's a micro framework. **ProdKit is the batteries.**
|
|
33
|
+
|
|
34
|
+
And unlike a project template, ProdKit is a library: when best practices
|
|
35
|
+
evolve, `pip install -U prodkit` updates every app you own.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
[**`pip install prodkit`**](https://pypi.org/project/prodkit/)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install prodkit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.10+ and FastAPI 0.110+. The base install depends only on
|
|
46
|
+
FastAPI and Pydantic — nothing else.
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from fastapi import FastAPI
|
|
52
|
+
from prodkit import Production
|
|
53
|
+
|
|
54
|
+
app = FastAPI()
|
|
55
|
+
Production(app) # production profile by default
|
|
56
|
+
|
|
57
|
+
@app.get("/hello")
|
|
58
|
+
def hello():
|
|
59
|
+
return {"message": "hello"}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uvicorn main:app
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
$ curl -i localhost:8000/hello
|
|
68
|
+
HTTP/1.1 200 OK
|
|
69
|
+
x-request-id: 26fdc49565614c2a9ef1a3b8d4e0f712
|
|
70
|
+
x-content-type-options: nosniff
|
|
71
|
+
x-frame-options: DENY
|
|
72
|
+
strict-transport-security: max-age=63072000; includeSubDomains
|
|
73
|
+
referrer-policy: strict-origin-when-cross-origin
|
|
74
|
+
...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For local development, flip the profile — pretty console logs, debug error
|
|
78
|
+
details, no HSTS:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
Production(app, environment="development")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## What You Get
|
|
85
|
+
|
|
86
|
+
| Feature | Details |
|
|
87
|
+
|---|---|
|
|
88
|
+
| 🆔 **Request IDs** | `X-Request-ID` on every response, propagated into every log line. Inbound IDs untrusted by default. |
|
|
89
|
+
| 📋 **Structured logging** | One JSON object per request in production (Datadog/Loki/CloudWatch-ready); pretty console logs in development. |
|
|
90
|
+
| 🛡️ **Security headers** | OWASP-aligned: `nosniff`, `X-Frame-Options`, HSTS, `Referrer-Policy`, `Permissions-Policy`. Your own headers always win. |
|
|
91
|
+
| 🚨 **Error normalization** | [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) `problem+json` responses. Unhandled 500s are **opaque to clients** — the traceback goes to logs, correlated by request ID. |
|
|
92
|
+
| ❤️ **Health endpoints** | `/health`, `/live` (liveness) and `/ready` (readiness — aggregates checks from every plugin, 503 until all pass). Kubernetes-native. |
|
|
93
|
+
| 🌐 **CORS** | Explicit origins only; the wildcard-with-credentials footgun is refused at boot. |
|
|
94
|
+
| 📦 **Compression** | Gzip for responses over 500 bytes. |
|
|
95
|
+
| 🔌 **Plugin system** | Every feature above is a plugin. Write your own with 6 optional hooks. |
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
Everything is configurable through four layers (highest wins):
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Python args > environment variables > prodkit.toml > profile defaults
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Python:**
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
Production(
|
|
109
|
+
app,
|
|
110
|
+
environment="production",
|
|
111
|
+
cors={"origins": ["https://app.example.com"]}, # dict = configure & enable
|
|
112
|
+
compression=False, # bool = toggle
|
|
113
|
+
security={"trusted_hosts": ["api.example.com"]},
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Environment variables** (`__` descends into sections):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
PRODKIT_ENVIRONMENT=production
|
|
121
|
+
PRODKIT_LOGGING__LEVEL=WARNING
|
|
122
|
+
PRODKIT_SECURITY__TRUSTED_HOSTS=api.example.com,admin.example.com
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**`prodkit.toml`:**
|
|
126
|
+
|
|
127
|
+
```toml
|
|
128
|
+
[prodkit]
|
|
129
|
+
environment = "production"
|
|
130
|
+
|
|
131
|
+
[logging]
|
|
132
|
+
level = "INFO"
|
|
133
|
+
|
|
134
|
+
[cors]
|
|
135
|
+
enabled = true
|
|
136
|
+
origins = ["https://app.example.com"]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Fail-fast, refuse-unsafe
|
|
140
|
+
|
|
141
|
+
Misconfiguration fails **at startup with a named key**, never silently:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
ProdKitConfigError: Invalid ProdKit configuration:
|
|
145
|
+
- logging.levle: Extra inputs are not permitted
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
And configurations that would weaken a production deployment are refused,
|
|
149
|
+
not warned about:
|
|
150
|
+
|
|
151
|
+
- `debug=True` in production
|
|
152
|
+
- error responses that would leak tracebacks in production
|
|
153
|
+
- CORS `origins=["*"]` combined with `allow_credentials=True`
|
|
154
|
+
|
|
155
|
+
## Writing a Plugin
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from prodkit import Check, Plugin, Production
|
|
159
|
+
|
|
160
|
+
class DatabasePlugin(Plugin):
|
|
161
|
+
name = "database"
|
|
162
|
+
|
|
163
|
+
async def startup(self, ctx):
|
|
164
|
+
self.pool = await create_pool(...)
|
|
165
|
+
ctx.registry.provide("db", self.pool)
|
|
166
|
+
|
|
167
|
+
async def shutdown(self, ctx):
|
|
168
|
+
await self.pool.close()
|
|
169
|
+
|
|
170
|
+
def checks(self, ctx):
|
|
171
|
+
return [Check(name="database", passed=self.pool.is_alive())]
|
|
172
|
+
|
|
173
|
+
Production(app, plugins=[DatabasePlugin()])
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Your check now shows up in `/ready` automatically. Plugins can declare
|
|
177
|
+
`requires = ("other-plugin",)` and the kernel activates them in dependency
|
|
178
|
+
order — cycles and missing dependencies fail at boot.
|
|
179
|
+
|
|
180
|
+
Middleware registered by plugins carries an explicit integer priority, so
|
|
181
|
+
the middleware onion is always correctly ordered no matter what order
|
|
182
|
+
plugins load in (request-id outermost, compression innermost).
|
|
183
|
+
|
|
184
|
+
## Plays Nice With Your App
|
|
185
|
+
|
|
186
|
+
- **Same app object.** Routes, dependencies, and existing middleware keep
|
|
187
|
+
working. Remove `Production(app)` and you have a plain FastAPI app again.
|
|
188
|
+
- **Your lifespan survives.** ProdKit *composes* with an existing `lifespan`:
|
|
189
|
+
plugin startup → your lifespan → plugin shutdown (LIFO).
|
|
190
|
+
- **Your headers win.** Security headers use set-if-absent semantics.
|
|
191
|
+
- **Every feature can be turned off.** `Production(app, security=False, ...)`
|
|
192
|
+
|
|
193
|
+
## Project Status
|
|
194
|
+
|
|
195
|
+
**v0.1.0 — alpha.** Core kernel and seven built-in plugins, 60 tests, 98%
|
|
196
|
+
coverage, strict mypy, CI across Python 3.10–3.13.
|
|
197
|
+
|
|
198
|
+
Roadmap: `prodkit doctor` CLI with a production-readiness score (v0.2),
|
|
199
|
+
Prometheus metrics + Redis backends (v0.3), Dockerfile/nginx/CI generators
|
|
200
|
+
(v0.4), public plugin SDK (v0.5), auth helpers (v0.6), stable API (v1.0).
|
|
201
|
+
Full details in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
206
|
+
Security reports: see [SECURITY.md](SECURITY.md) (never open a public issue).
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone https://github.com/Pushkarpant/PRODKIT
|
|
210
|
+
cd PRODKIT
|
|
211
|
+
python -m venv .venv && source .venv/bin/activate
|
|
212
|
+
pip install -e ".[dev]"
|
|
213
|
+
pytest
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
[MIT](LICENSE)
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
*FastAPI builds APIs. ProdKit makes them production-ready.*
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "prodkit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The production framework for FastAPI. One line. Production ready."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "ProdKit Contributors" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"fastapi",
|
|
16
|
+
"production",
|
|
17
|
+
"middleware",
|
|
18
|
+
"security",
|
|
19
|
+
"observability",
|
|
20
|
+
"logging",
|
|
21
|
+
"health-check",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Framework :: FastAPI",
|
|
33
|
+
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"fastapi>=0.110",
|
|
39
|
+
"pydantic>=2.5",
|
|
40
|
+
"pydantic-settings>=2.1",
|
|
41
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
brotli = ["brotli-asgi>=1.4"]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8.0",
|
|
48
|
+
"pytest-cov>=5.0",
|
|
49
|
+
"httpx>=0.27",
|
|
50
|
+
"ruff>=0.6",
|
|
51
|
+
"mypy>=1.11",
|
|
52
|
+
"import-linter>=2.0",
|
|
53
|
+
"tomli>=2.0", # so mypy (python_version=3.10) can type-check the fallback import
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.urls]
|
|
57
|
+
Homepage = "https://github.com/Pushkarpant/PRODKIT"
|
|
58
|
+
Documentation = "https://github.com/Pushkarpant/PRODKIT#readme"
|
|
59
|
+
Repository = "https://github.com/Pushkarpant/PRODKIT"
|
|
60
|
+
Changelog = "https://github.com/Pushkarpant/PRODKIT/blob/main/CHANGELOG.md"
|
|
61
|
+
Issues = "https://github.com/Pushkarpant/PRODKIT/issues"
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel]
|
|
64
|
+
packages = ["src/prodkit"]
|
|
65
|
+
|
|
66
|
+
[tool.hatch.build.targets.sdist]
|
|
67
|
+
include = ["src/prodkit", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
target-version = "py310"
|
|
71
|
+
line-length = 99
|
|
72
|
+
src = ["src", "tests"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = [
|
|
76
|
+
"E", # pycodestyle errors
|
|
77
|
+
"W", # pycodestyle warnings
|
|
78
|
+
"F", # pyflakes
|
|
79
|
+
"I", # isort
|
|
80
|
+
"B", # bugbear
|
|
81
|
+
"UP", # pyupgrade
|
|
82
|
+
"S", # bandit (security)
|
|
83
|
+
"N", # pep8-naming
|
|
84
|
+
"C4", # comprehensions
|
|
85
|
+
"SIM", # simplify
|
|
86
|
+
"RUF", # ruff-specific
|
|
87
|
+
]
|
|
88
|
+
[tool.ruff.lint.per-file-ignores]
|
|
89
|
+
"tests/**" = ["S101", "S106"] # asserts and test credentials are fine in tests
|
|
90
|
+
|
|
91
|
+
[tool.mypy]
|
|
92
|
+
python_version = "3.10"
|
|
93
|
+
strict = true
|
|
94
|
+
packages = ["prodkit"]
|
|
95
|
+
mypy_path = "src"
|
|
96
|
+
|
|
97
|
+
[tool.pytest.ini_options]
|
|
98
|
+
testpaths = ["tests"]
|
|
99
|
+
addopts = "--cov=prodkit --cov-report=term-missing --cov-fail-under=90"
|
|
100
|
+
|
|
101
|
+
[tool.coverage.run]
|
|
102
|
+
source = ["src/prodkit"]
|
|
103
|
+
|
|
104
|
+
[tool.importlinter]
|
|
105
|
+
root_package = "prodkit"
|
|
106
|
+
|
|
107
|
+
[[tool.importlinter.contracts]]
|
|
108
|
+
name = "Kernel must not depend on plugins"
|
|
109
|
+
type = "forbidden"
|
|
110
|
+
source_modules = ["prodkit.core", "prodkit.contracts"]
|
|
111
|
+
forbidden_modules = ["prodkit.plugins"]
|