prodkit 0.1.3__tar.gz → 0.2.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.
- {prodkit-0.1.3 → prodkit-0.2.1}/.gitignore +2 -1
- prodkit-0.2.1/CHANGELOG.md +72 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/PKG-INFO +85 -15
- {prodkit-0.1.3 → prodkit-0.2.1}/README.md +79 -14
- {prodkit-0.1.3 → prodkit-0.2.1}/pyproject.toml +18 -1
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/__init__.py +5 -2
- prodkit-0.2.1/src/prodkit/cli/__init__.py +28 -0
- prodkit-0.2.1/src/prodkit/cli/app.py +260 -0
- prodkit-0.2.1/src/prodkit/cli/loader.py +75 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/contracts/plugin.py +38 -2
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/config.py +8 -0
- prodkit-0.2.1/src/prodkit/core/doctor.py +143 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/production.py +1 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/__init__.py +4 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/compression/__init__.py +11 -1
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/cors/__init__.py +19 -1
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/errors/__init__.py +54 -10
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/health/__init__.py +12 -1
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/logging/__init__.py +19 -1
- prodkit-0.2.1/src/prodkit/plugins/rate_limit/__init__.py +147 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/request_id/__init__.py +20 -1
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/security/__init__.py +47 -1
- prodkit-0.2.1/tests/integration/test_cli.py +132 -0
- prodkit-0.2.1/tests/unit/test_doctor.py +91 -0
- prodkit-0.2.1/tests/unit/test_rate_limit.py +97 -0
- prodkit-0.1.3/CHANGELOG.md +0 -39
- {prodkit-0.1.3 → prodkit-0.2.1}/LICENSE +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/contracts/__init__.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/__init__.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/context.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/event_bus.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/exceptions.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/lifecycle.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/plugin_manager.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/registry.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/py.typed +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/__init__.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/conftest.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/integration/__init__.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/integration/test_logging.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/integration/test_production.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/unit/__init__.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/unit/test_config.py +0 -0
- {prodkit-0.1.3 → prodkit-0.2.1}/tests/unit/test_kernel.py +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
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.2.1] - 2026-07-23
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- README polish: PyPI/Python badges, table of contents, `prodkit[cli]` install
|
|
14
|
+
note, rate-limiting shown in the config examples, and a `doctor()` hook in the
|
|
15
|
+
plugin-authoring example. Docs-only release (no code changes).
|
|
16
|
+
|
|
17
|
+
## [0.2.0] - 2026-07-22
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **`prodkit` CLI** (install with `pip install prodkit[cli]`):
|
|
21
|
+
- `prodkit doctor` — production-readiness audit with a weighted 0–100 score;
|
|
22
|
+
`--strict --min-score N` turns it into a CI gate (non-zero exit below the
|
|
23
|
+
threshold).
|
|
24
|
+
- `prodkit inspect` — resolved config, active plugins, and middleware order.
|
|
25
|
+
- `prodkit plugins` — active plugins and the hooks each implements.
|
|
26
|
+
- `prodkit init [--example]` — scaffold a `prodkit.toml` (and starter app).
|
|
27
|
+
- **`Plugin.doctor(ctx)` hook** returning `Audit` findings; implemented by every
|
|
28
|
+
built-in plugin. `Audit` (name, status `ok`/`warn`/`fail`, detail,
|
|
29
|
+
recommendation, weight) is exported from the top-level package.
|
|
30
|
+
- Kernel doctor engine (`prodkit.core.doctor`) aggregating plugin audits with
|
|
31
|
+
config-level checks (environment, debug, disabled rate-limiting, low-entropy
|
|
32
|
+
secrets in the environment) into a score.
|
|
33
|
+
- **Rate-limiting plugin** (`rate-limit`, off by default): in-memory
|
|
34
|
+
fixed-window per-IP limiting (`rate_limit={"default": "100/minute"}`),
|
|
35
|
+
returning `429 application/problem+json` with a `Retry-After` header. Logs a
|
|
36
|
+
per-process-backend warning (shared Redis backend arrives in v0.3).
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
- Error responses now include an `instance` member (the request path). All
|
|
40
|
+
framework errors — including the rate limiter's 429 — share one
|
|
41
|
+
`problem_response` builder for a consistent RFC 9457 shape.
|
|
42
|
+
|
|
43
|
+
## [0.1.3] - 2026-07-20
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- README updated (badge cleanup); republished so the PyPI project page shows
|
|
47
|
+
the current README.
|
|
48
|
+
|
|
49
|
+
## [0.1.1] - 2026-07-20
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- README links that were relative (LICENSE, docs/ARCHITECTURE.md,
|
|
53
|
+
CONTRIBUTING.md, SECURITY.md) now use absolute GitHub URLs so they work on
|
|
54
|
+
the PyPI project page.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
- Package author set to Pushkar Pant with contact email; author section added
|
|
58
|
+
to the README.
|
|
59
|
+
|
|
60
|
+
## [0.1.0] - 2026-07-16
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Kernel: layered configuration (args > env > `prodkit.toml` > profile > defaults),
|
|
64
|
+
plugin manager with dependency topological sort, service registry, event bus,
|
|
65
|
+
lifespan composition, fail-fast config validation.
|
|
66
|
+
- Plugin contract with async startup/shutdown hooks and prioritized middleware
|
|
67
|
+
registration.
|
|
68
|
+
- Environment profiles: `development`, `staging`, `production`.
|
|
69
|
+
- Built-in plugins: `request-id`, `logging` (structured JSON/console), `errors`
|
|
70
|
+
(RFC 9457 problem+json), `health` (`/health`, `/ready`, `/live`), `security`
|
|
71
|
+
(security headers, trusted hosts, HTTPS redirect), `cors`, `compression` (gzip).
|
|
72
|
+
- `Production(app)` one-line entrypoint.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prodkit
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: The production framework for FastAPI. One line. Production ready.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Pushkarpant/PRODKIT
|
|
6
6
|
Project-URL: Documentation, https://github.com/Pushkarpant/PRODKIT#readme
|
|
@@ -30,14 +30,19 @@ Requires-Dist: pydantic>=2.5
|
|
|
30
30
|
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
31
31
|
Provides-Extra: brotli
|
|
32
32
|
Requires-Dist: brotli-asgi>=1.4; extra == 'brotli'
|
|
33
|
+
Provides-Extra: cli
|
|
34
|
+
Requires-Dist: rich>=13; extra == 'cli'
|
|
35
|
+
Requires-Dist: typer>=0.12; extra == 'cli'
|
|
33
36
|
Provides-Extra: dev
|
|
34
37
|
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
35
38
|
Requires-Dist: import-linter>=2.0; extra == 'dev'
|
|
36
39
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
37
40
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
38
41
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: rich>=13; extra == 'dev'
|
|
39
43
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
40
44
|
Requires-Dist: tomli>=2.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: typer>=0.12; extra == 'dev'
|
|
41
46
|
Description-Content-Type: text/markdown
|
|
42
47
|
|
|
43
48
|
# ProdKit
|
|
@@ -56,14 +61,24 @@ Production(app)
|
|
|
56
61
|
|
|
57
62
|
That's it. Your app now has security headers, structured JSON logging with
|
|
58
63
|
request-ID correlation, RFC 9457 error responses, Kubernetes-ready health
|
|
59
|
-
endpoints,
|
|
60
|
-
hardened for production, and pleasant in development.
|
|
64
|
+
endpoints, gzip compression, and opt-in rate limiting — configured to current
|
|
65
|
+
best practice, hardened for production, and pleasant in development. Then run
|
|
66
|
+
[`prodkit doctor`](#cli--prodkit-doctor) to score how production-ready it is.
|
|
61
67
|
|
|
68
|
+
[](https://pypi.org/project/prodkit/)
|
|
69
|
+
[](https://pypi.org/project/prodkit/)
|
|
62
70
|
[](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
|
|
63
71
|
[](https://github.com/Pushkarpant/PRODKIT/blob/main/LICENSE)
|
|
64
72
|
|
|
65
73
|
---
|
|
66
74
|
|
|
75
|
+
**Contents:** [Why](#why) · [Install](#installation) · [Quick Start](#quick-start) ·
|
|
76
|
+
[What You Get](#what-you-get) · [Configuration](#configuration) ·
|
|
77
|
+
[Plugins](#writing-a-plugin) · [CLI / doctor](#cli--prodkit-doctor) ·
|
|
78
|
+
[Status & Roadmap](#project-status)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
67
82
|
## Why
|
|
68
83
|
|
|
69
84
|
Every production FastAPI service re-implements the same ~500 lines of glue:
|
|
@@ -79,11 +94,13 @@ evolve, `pip install -U prodkit` updates every app you own.
|
|
|
79
94
|
[**`pip install prodkit`**](https://pypi.org/project/prodkit/)
|
|
80
95
|
|
|
81
96
|
```bash
|
|
82
|
-
pip install prodkit
|
|
97
|
+
pip install prodkit # the library
|
|
98
|
+
pip install "prodkit[cli]" # + the `prodkit doctor` CLI (typer + rich)
|
|
83
99
|
```
|
|
84
100
|
|
|
85
101
|
Requires Python 3.10+ and FastAPI 0.110+. The base install depends only on
|
|
86
|
-
FastAPI and Pydantic — nothing else.
|
|
102
|
+
FastAPI and Pydantic — nothing else. Optional extras: `cli` (CLI),
|
|
103
|
+
`brotli` (Brotli compression).
|
|
87
104
|
|
|
88
105
|
## Quick Start
|
|
89
106
|
|
|
@@ -111,7 +128,9 @@ x-content-type-options: nosniff
|
|
|
111
128
|
x-frame-options: DENY
|
|
112
129
|
strict-transport-security: max-age=63072000; includeSubDomains
|
|
113
130
|
referrer-policy: strict-origin-when-cross-origin
|
|
114
|
-
|
|
131
|
+
permissions-policy: camera=(), microphone=(), geolocation=()
|
|
132
|
+
x-xss-protection: 0
|
|
133
|
+
content-type: application/json
|
|
115
134
|
```
|
|
116
135
|
|
|
117
136
|
For local development, flip the profile — pretty console logs, debug error
|
|
@@ -132,7 +151,9 @@ Production(app, environment="development")
|
|
|
132
151
|
| ❤️ **Health endpoints** | `/health`, `/live` (liveness) and `/ready` (readiness — aggregates checks from every plugin, 503 until all pass). Kubernetes-native. |
|
|
133
152
|
| 🌐 **CORS** | Explicit origins only; the wildcard-with-credentials footgun is refused at boot. |
|
|
134
153
|
| 📦 **Compression** | Gzip for responses over 500 bytes. |
|
|
135
|
-
|
|
|
154
|
+
| 🚦 **Rate limiting** | Opt-in per-IP limiting (`100/minute`), `429 problem+json` with `Retry-After`. In-memory backend (Redis in v0.3). |
|
|
155
|
+
| 🩺 **`prodkit doctor`** | CLI production-readiness audit with a 0–100 score. `--strict` gates CI. |
|
|
156
|
+
| 🔌 **Plugin system** | Every feature above is a plugin. Write your own with optional hooks incl. `doctor()`. |
|
|
136
157
|
|
|
137
158
|
## Configuration
|
|
138
159
|
|
|
@@ -151,6 +172,7 @@ Production(
|
|
|
151
172
|
cors={"origins": ["https://app.example.com"]}, # dict = configure & enable
|
|
152
173
|
compression=False, # bool = toggle
|
|
153
174
|
security={"trusted_hosts": ["api.example.com"]},
|
|
175
|
+
rate_limit={"default": "100/minute"}, # opt-in per-IP limiting
|
|
154
176
|
)
|
|
155
177
|
```
|
|
156
178
|
|
|
@@ -174,6 +196,10 @@ level = "INFO"
|
|
|
174
196
|
[cors]
|
|
175
197
|
enabled = true
|
|
176
198
|
origins = ["https://app.example.com"]
|
|
199
|
+
|
|
200
|
+
[rate_limit]
|
|
201
|
+
enabled = true
|
|
202
|
+
default = "100/minute"
|
|
177
203
|
```
|
|
178
204
|
|
|
179
205
|
### Fail-fast, refuse-unsafe
|
|
@@ -195,7 +221,7 @@ not warned about:
|
|
|
195
221
|
## Writing a Plugin
|
|
196
222
|
|
|
197
223
|
```python
|
|
198
|
-
from prodkit import Check, Plugin, Production
|
|
224
|
+
from prodkit import Audit, Check, Plugin, Production
|
|
199
225
|
|
|
200
226
|
class DatabasePlugin(Plugin):
|
|
201
227
|
name = "database"
|
|
@@ -207,13 +233,18 @@ class DatabasePlugin(Plugin):
|
|
|
207
233
|
async def shutdown(self, ctx):
|
|
208
234
|
await self.pool.close()
|
|
209
235
|
|
|
210
|
-
def checks(self, ctx):
|
|
236
|
+
def checks(self, ctx): # runtime readiness → /ready
|
|
211
237
|
return [Check(name="database", passed=self.pool.is_alive())]
|
|
212
238
|
|
|
239
|
+
def doctor(self, ctx): # static audit → prodkit doctor
|
|
240
|
+
return [Audit(name="Database pool",
|
|
241
|
+
status="ok", detail="connection pool configured")]
|
|
242
|
+
|
|
213
243
|
Production(app, plugins=[DatabasePlugin()])
|
|
214
244
|
```
|
|
215
245
|
|
|
216
|
-
Your
|
|
246
|
+
Your `checks()` result now shows up in `/ready` automatically, and your
|
|
247
|
+
`doctor()` findings roll into the `prodkit doctor` score. Plugins can declare
|
|
217
248
|
`requires = ("other-plugin",)` and the kernel activates them in dependency
|
|
218
249
|
order — cycles and missing dependencies fail at boot.
|
|
219
250
|
|
|
@@ -221,6 +252,44 @@ Middleware registered by plugins carries an explicit integer priority, so
|
|
|
221
252
|
the middleware onion is always correctly ordered no matter what order
|
|
222
253
|
plugins load in (request-id outermost, compression innermost).
|
|
223
254
|
|
|
255
|
+
## CLI — `prodkit doctor`
|
|
256
|
+
|
|
257
|
+
Install the CLI extra and audit any `Production`-configured app:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
pip install "prodkit[cli]"
|
|
261
|
+
prodkit doctor --app main:app
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```text
|
|
265
|
+
Production readiness
|
|
266
|
+
┌───┬───────────────────────┬─────────────────────┬─────────────────────────┐
|
|
267
|
+
│ ✔ │ Security headers │ nosniff, X-Frame ... │ │
|
|
268
|
+
│ ✔ │ Structured logging │ json @ INFO │ │
|
|
269
|
+
│ ✔ │ Error normalization │ 500s opaque │ │
|
|
270
|
+
│ ⚠ │ Content-Security-Po.. │ not set │ set a CSP to mitigate.. │
|
|
271
|
+
│ ⚠ │ Rate limiting │ disabled │ enable for public APIs │
|
|
272
|
+
└───┴───────────────────────┴─────────────────────┴─────────────────────────┘
|
|
273
|
+
Production score: 88/100 2 warning(s)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Make it a CI quality gate — fail the build below a threshold:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
prodkit doctor --app main:app --strict --min-score 90
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Other commands:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
prodkit inspect --app main:app # resolved config, plugins, middleware order
|
|
286
|
+
prodkit plugins --app main:app # active plugins and the hooks each implements
|
|
287
|
+
prodkit init --example # scaffold prodkit.toml (+ starter main.py)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Every plugin contributes findings via its `doctor(ctx)` hook, so your own
|
|
291
|
+
plugins score too.
|
|
292
|
+
|
|
224
293
|
## Plays Nice With Your App
|
|
225
294
|
|
|
226
295
|
- **Same app object.** Routes, dependencies, and existing middleware keep
|
|
@@ -232,12 +301,13 @@ plugins load in (request-id outermost, compression innermost).
|
|
|
232
301
|
|
|
233
302
|
## Project Status
|
|
234
303
|
|
|
235
|
-
**v0.1
|
|
236
|
-
|
|
304
|
+
**v0.2.1 — alpha.** Core kernel, eight built-in plugins (incl. rate-limiting),
|
|
305
|
+
the `prodkit doctor` CLI with a production-readiness score, strict mypy, CI
|
|
306
|
+
across Python 3.10–3.13.
|
|
237
307
|
|
|
238
|
-
Roadmap: `prodkit doctor` CLI
|
|
239
|
-
|
|
240
|
-
(v0.
|
|
308
|
+
Roadmap: ✅ `prodkit doctor` CLI + rate limiting (v0.2), Prometheus metrics +
|
|
309
|
+
Redis backends (v0.3), Dockerfile/nginx/CI generators (v0.4), public plugin SDK
|
|
310
|
+
(v0.5), auth helpers (v0.6), stable API (v1.0).
|
|
241
311
|
Full details in [docs/ARCHITECTURE.md](https://github.com/Pushkarpant/PRODKIT/blob/main/docs/ARCHITECTURE.md).
|
|
242
312
|
|
|
243
313
|
## Contributing
|
|
@@ -14,14 +14,24 @@ Production(app)
|
|
|
14
14
|
|
|
15
15
|
That's it. Your app now has security headers, structured JSON logging with
|
|
16
16
|
request-ID correlation, RFC 9457 error responses, Kubernetes-ready health
|
|
17
|
-
endpoints,
|
|
18
|
-
hardened for production, and pleasant in development.
|
|
17
|
+
endpoints, gzip compression, and opt-in rate limiting — configured to current
|
|
18
|
+
best practice, hardened for production, and pleasant in development. Then run
|
|
19
|
+
[`prodkit doctor`](#cli--prodkit-doctor) to score how production-ready it is.
|
|
19
20
|
|
|
21
|
+
[](https://pypi.org/project/prodkit/)
|
|
22
|
+
[](https://pypi.org/project/prodkit/)
|
|
20
23
|
[](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
|
|
21
24
|
[](https://github.com/Pushkarpant/PRODKIT/blob/main/LICENSE)
|
|
22
25
|
|
|
23
26
|
---
|
|
24
27
|
|
|
28
|
+
**Contents:** [Why](#why) · [Install](#installation) · [Quick Start](#quick-start) ·
|
|
29
|
+
[What You Get](#what-you-get) · [Configuration](#configuration) ·
|
|
30
|
+
[Plugins](#writing-a-plugin) · [CLI / doctor](#cli--prodkit-doctor) ·
|
|
31
|
+
[Status & Roadmap](#project-status)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
25
35
|
## Why
|
|
26
36
|
|
|
27
37
|
Every production FastAPI service re-implements the same ~500 lines of glue:
|
|
@@ -37,11 +47,13 @@ evolve, `pip install -U prodkit` updates every app you own.
|
|
|
37
47
|
[**`pip install prodkit`**](https://pypi.org/project/prodkit/)
|
|
38
48
|
|
|
39
49
|
```bash
|
|
40
|
-
pip install prodkit
|
|
50
|
+
pip install prodkit # the library
|
|
51
|
+
pip install "prodkit[cli]" # + the `prodkit doctor` CLI (typer + rich)
|
|
41
52
|
```
|
|
42
53
|
|
|
43
54
|
Requires Python 3.10+ and FastAPI 0.110+. The base install depends only on
|
|
44
|
-
FastAPI and Pydantic — nothing else.
|
|
55
|
+
FastAPI and Pydantic — nothing else. Optional extras: `cli` (CLI),
|
|
56
|
+
`brotli` (Brotli compression).
|
|
45
57
|
|
|
46
58
|
## Quick Start
|
|
47
59
|
|
|
@@ -69,7 +81,9 @@ x-content-type-options: nosniff
|
|
|
69
81
|
x-frame-options: DENY
|
|
70
82
|
strict-transport-security: max-age=63072000; includeSubDomains
|
|
71
83
|
referrer-policy: strict-origin-when-cross-origin
|
|
72
|
-
|
|
84
|
+
permissions-policy: camera=(), microphone=(), geolocation=()
|
|
85
|
+
x-xss-protection: 0
|
|
86
|
+
content-type: application/json
|
|
73
87
|
```
|
|
74
88
|
|
|
75
89
|
For local development, flip the profile — pretty console logs, debug error
|
|
@@ -90,7 +104,9 @@ Production(app, environment="development")
|
|
|
90
104
|
| ❤️ **Health endpoints** | `/health`, `/live` (liveness) and `/ready` (readiness — aggregates checks from every plugin, 503 until all pass). Kubernetes-native. |
|
|
91
105
|
| 🌐 **CORS** | Explicit origins only; the wildcard-with-credentials footgun is refused at boot. |
|
|
92
106
|
| 📦 **Compression** | Gzip for responses over 500 bytes. |
|
|
93
|
-
|
|
|
107
|
+
| 🚦 **Rate limiting** | Opt-in per-IP limiting (`100/minute`), `429 problem+json` with `Retry-After`. In-memory backend (Redis in v0.3). |
|
|
108
|
+
| 🩺 **`prodkit doctor`** | CLI production-readiness audit with a 0–100 score. `--strict` gates CI. |
|
|
109
|
+
| 🔌 **Plugin system** | Every feature above is a plugin. Write your own with optional hooks incl. `doctor()`. |
|
|
94
110
|
|
|
95
111
|
## Configuration
|
|
96
112
|
|
|
@@ -109,6 +125,7 @@ Production(
|
|
|
109
125
|
cors={"origins": ["https://app.example.com"]}, # dict = configure & enable
|
|
110
126
|
compression=False, # bool = toggle
|
|
111
127
|
security={"trusted_hosts": ["api.example.com"]},
|
|
128
|
+
rate_limit={"default": "100/minute"}, # opt-in per-IP limiting
|
|
112
129
|
)
|
|
113
130
|
```
|
|
114
131
|
|
|
@@ -132,6 +149,10 @@ level = "INFO"
|
|
|
132
149
|
[cors]
|
|
133
150
|
enabled = true
|
|
134
151
|
origins = ["https://app.example.com"]
|
|
152
|
+
|
|
153
|
+
[rate_limit]
|
|
154
|
+
enabled = true
|
|
155
|
+
default = "100/minute"
|
|
135
156
|
```
|
|
136
157
|
|
|
137
158
|
### Fail-fast, refuse-unsafe
|
|
@@ -153,7 +174,7 @@ not warned about:
|
|
|
153
174
|
## Writing a Plugin
|
|
154
175
|
|
|
155
176
|
```python
|
|
156
|
-
from prodkit import Check, Plugin, Production
|
|
177
|
+
from prodkit import Audit, Check, Plugin, Production
|
|
157
178
|
|
|
158
179
|
class DatabasePlugin(Plugin):
|
|
159
180
|
name = "database"
|
|
@@ -165,13 +186,18 @@ class DatabasePlugin(Plugin):
|
|
|
165
186
|
async def shutdown(self, ctx):
|
|
166
187
|
await self.pool.close()
|
|
167
188
|
|
|
168
|
-
def checks(self, ctx):
|
|
189
|
+
def checks(self, ctx): # runtime readiness → /ready
|
|
169
190
|
return [Check(name="database", passed=self.pool.is_alive())]
|
|
170
191
|
|
|
192
|
+
def doctor(self, ctx): # static audit → prodkit doctor
|
|
193
|
+
return [Audit(name="Database pool",
|
|
194
|
+
status="ok", detail="connection pool configured")]
|
|
195
|
+
|
|
171
196
|
Production(app, plugins=[DatabasePlugin()])
|
|
172
197
|
```
|
|
173
198
|
|
|
174
|
-
Your
|
|
199
|
+
Your `checks()` result now shows up in `/ready` automatically, and your
|
|
200
|
+
`doctor()` findings roll into the `prodkit doctor` score. Plugins can declare
|
|
175
201
|
`requires = ("other-plugin",)` and the kernel activates them in dependency
|
|
176
202
|
order — cycles and missing dependencies fail at boot.
|
|
177
203
|
|
|
@@ -179,6 +205,44 @@ Middleware registered by plugins carries an explicit integer priority, so
|
|
|
179
205
|
the middleware onion is always correctly ordered no matter what order
|
|
180
206
|
plugins load in (request-id outermost, compression innermost).
|
|
181
207
|
|
|
208
|
+
## CLI — `prodkit doctor`
|
|
209
|
+
|
|
210
|
+
Install the CLI extra and audit any `Production`-configured app:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pip install "prodkit[cli]"
|
|
214
|
+
prodkit doctor --app main:app
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
Production readiness
|
|
219
|
+
┌───┬───────────────────────┬─────────────────────┬─────────────────────────┐
|
|
220
|
+
│ ✔ │ Security headers │ nosniff, X-Frame ... │ │
|
|
221
|
+
│ ✔ │ Structured logging │ json @ INFO │ │
|
|
222
|
+
│ ✔ │ Error normalization │ 500s opaque │ │
|
|
223
|
+
│ ⚠ │ Content-Security-Po.. │ not set │ set a CSP to mitigate.. │
|
|
224
|
+
│ ⚠ │ Rate limiting │ disabled │ enable for public APIs │
|
|
225
|
+
└───┴───────────────────────┴─────────────────────┴─────────────────────────┘
|
|
226
|
+
Production score: 88/100 2 warning(s)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Make it a CI quality gate — fail the build below a threshold:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
prodkit doctor --app main:app --strict --min-score 90
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Other commands:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
prodkit inspect --app main:app # resolved config, plugins, middleware order
|
|
239
|
+
prodkit plugins --app main:app # active plugins and the hooks each implements
|
|
240
|
+
prodkit init --example # scaffold prodkit.toml (+ starter main.py)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Every plugin contributes findings via its `doctor(ctx)` hook, so your own
|
|
244
|
+
plugins score too.
|
|
245
|
+
|
|
182
246
|
## Plays Nice With Your App
|
|
183
247
|
|
|
184
248
|
- **Same app object.** Routes, dependencies, and existing middleware keep
|
|
@@ -190,12 +254,13 @@ plugins load in (request-id outermost, compression innermost).
|
|
|
190
254
|
|
|
191
255
|
## Project Status
|
|
192
256
|
|
|
193
|
-
**v0.1
|
|
194
|
-
|
|
257
|
+
**v0.2.1 — alpha.** Core kernel, eight built-in plugins (incl. rate-limiting),
|
|
258
|
+
the `prodkit doctor` CLI with a production-readiness score, strict mypy, CI
|
|
259
|
+
across Python 3.10–3.13.
|
|
195
260
|
|
|
196
|
-
Roadmap: `prodkit doctor` CLI
|
|
197
|
-
|
|
198
|
-
(v0.
|
|
261
|
+
Roadmap: ✅ `prodkit doctor` CLI + rate limiting (v0.2), Prometheus metrics +
|
|
262
|
+
Redis backends (v0.3), Dockerfile/nginx/CI generators (v0.4), public plugin SDK
|
|
263
|
+
(v0.5), auth helpers (v0.6), stable API (v1.0).
|
|
199
264
|
Full details in [docs/ARCHITECTURE.md](https://github.com/Pushkarpant/PRODKIT/blob/main/docs/ARCHITECTURE.md).
|
|
200
265
|
|
|
201
266
|
## Contributing
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "prodkit"
|
|
7
|
-
version = "0.1
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "The production framework for FastAPI. One line. Production ready."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -43,6 +43,7 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[project.optional-dependencies]
|
|
45
45
|
brotli = ["brotli-asgi>=1.4"]
|
|
46
|
+
cli = ["typer>=0.12", "rich>=13"]
|
|
46
47
|
dev = [
|
|
47
48
|
"pytest>=8.0",
|
|
48
49
|
"pytest-cov>=5.0",
|
|
@@ -51,8 +52,13 @@ dev = [
|
|
|
51
52
|
"mypy>=1.11",
|
|
52
53
|
"import-linter>=2.0",
|
|
53
54
|
"tomli>=2.0", # so mypy (python_version=3.10) can type-check the fallback import
|
|
55
|
+
"typer>=0.12", # CLI is type-checked and tested in CI
|
|
56
|
+
"rich>=13",
|
|
54
57
|
]
|
|
55
58
|
|
|
59
|
+
[project.scripts]
|
|
60
|
+
prodkit = "prodkit.cli:run"
|
|
61
|
+
|
|
56
62
|
[project.urls]
|
|
57
63
|
Homepage = "https://github.com/Pushkarpant/PRODKIT"
|
|
58
64
|
Documentation = "https://github.com/Pushkarpant/PRODKIT#readme"
|
|
@@ -88,12 +94,23 @@ select = [
|
|
|
88
94
|
[tool.ruff.lint.per-file-ignores]
|
|
89
95
|
"tests/**" = ["S101", "S106"] # asserts and test credentials are fine in tests
|
|
90
96
|
|
|
97
|
+
[tool.ruff.lint.flake8-bugbear]
|
|
98
|
+
# typer's API is built on function-call defaults (Option/Argument); this is the
|
|
99
|
+
# documented pattern, not the B008 footgun.
|
|
100
|
+
extend-immutable-calls = ["typer.Option", "typer.Argument"]
|
|
101
|
+
|
|
91
102
|
[tool.mypy]
|
|
92
103
|
python_version = "3.10"
|
|
93
104
|
strict = true
|
|
94
105
|
packages = ["prodkit"]
|
|
95
106
|
mypy_path = "src"
|
|
96
107
|
|
|
108
|
+
[[tool.mypy.overrides]]
|
|
109
|
+
# typer's decorator-driven option defaults don't play well with strict mode.
|
|
110
|
+
module = "prodkit.cli.app"
|
|
111
|
+
disallow_untyped_decorators = false
|
|
112
|
+
disallow_any_decorated = false
|
|
113
|
+
|
|
97
114
|
[tool.pytest.ini_options]
|
|
98
115
|
testpaths = ["tests"]
|
|
99
116
|
addopts = "--cov=prodkit --cov-report=term-missing --cov-fail-under=90"
|
|
@@ -7,7 +7,7 @@ app = FastAPI()
|
|
|
7
7
|
Production(app)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from prodkit.contracts.plugin import Check, Plugin
|
|
10
|
+
from prodkit.contracts.plugin import Audit, Check, Plugin
|
|
11
11
|
from prodkit.core.config import (
|
|
12
12
|
CompressionConfig,
|
|
13
13
|
CORSConfig,
|
|
@@ -15,6 +15,7 @@ from prodkit.core.config import (
|
|
|
15
15
|
HealthConfig,
|
|
16
16
|
LoggingConfig,
|
|
17
17
|
ProdKitConfig,
|
|
18
|
+
RateLimitConfig,
|
|
18
19
|
RequestIDConfig,
|
|
19
20
|
SecurityConfig,
|
|
20
21
|
)
|
|
@@ -33,9 +34,10 @@ from prodkit.plugins import builtin_plugins
|
|
|
33
34
|
# root — the kernel itself never imports from prodkit.plugins.
|
|
34
35
|
set_builtin_factory(builtin_plugins)
|
|
35
36
|
|
|
36
|
-
__version__ = "0.1
|
|
37
|
+
__version__ = "0.2.1"
|
|
37
38
|
|
|
38
39
|
__all__ = [
|
|
40
|
+
"Audit",
|
|
39
41
|
"CORSConfig",
|
|
40
42
|
"Check",
|
|
41
43
|
"CompressionConfig",
|
|
@@ -50,6 +52,7 @@ __all__ = [
|
|
|
50
52
|
"ProdKitConfigError",
|
|
51
53
|
"ProdKitError",
|
|
52
54
|
"Production",
|
|
55
|
+
"RateLimitConfig",
|
|
53
56
|
"RequestIDConfig",
|
|
54
57
|
"SecurityConfig",
|
|
55
58
|
"ServiceNotFoundError",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""ProdKit command-line interface.
|
|
2
|
+
|
|
3
|
+
The heavy CLI (typer + rich) lives in :mod:`prodkit.cli.app`; this module keeps
|
|
4
|
+
only a thin ``run()`` entry point so that a base install without the ``cli``
|
|
5
|
+
extra fails with an actionable message instead of an import traceback.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
_CLI_DEPS = {"typer", "rich", "click"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run() -> None:
|
|
16
|
+
"""Console-script entry point (``prodkit``)."""
|
|
17
|
+
try:
|
|
18
|
+
from prodkit.cli.app import app
|
|
19
|
+
except ModuleNotFoundError as exc: # pragma: no cover - exercised via packaging
|
|
20
|
+
if exc.name in _CLI_DEPS:
|
|
21
|
+
print(
|
|
22
|
+
"The prodkit CLI requires extra dependencies.\n"
|
|
23
|
+
" Install them with: pip install 'prodkit[cli]'",
|
|
24
|
+
file=sys.stderr,
|
|
25
|
+
)
|
|
26
|
+
raise SystemExit(1) from None
|
|
27
|
+
raise
|
|
28
|
+
app()
|