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.
Files changed (44) hide show
  1. {prodkit-0.1.3 → prodkit-0.2.1}/.gitignore +2 -1
  2. prodkit-0.2.1/CHANGELOG.md +72 -0
  3. {prodkit-0.1.3 → prodkit-0.2.1}/PKG-INFO +85 -15
  4. {prodkit-0.1.3 → prodkit-0.2.1}/README.md +79 -14
  5. {prodkit-0.1.3 → prodkit-0.2.1}/pyproject.toml +18 -1
  6. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/__init__.py +5 -2
  7. prodkit-0.2.1/src/prodkit/cli/__init__.py +28 -0
  8. prodkit-0.2.1/src/prodkit/cli/app.py +260 -0
  9. prodkit-0.2.1/src/prodkit/cli/loader.py +75 -0
  10. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/contracts/plugin.py +38 -2
  11. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/config.py +8 -0
  12. prodkit-0.2.1/src/prodkit/core/doctor.py +143 -0
  13. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/production.py +1 -0
  14. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/__init__.py +4 -0
  15. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/compression/__init__.py +11 -1
  16. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/cors/__init__.py +19 -1
  17. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/errors/__init__.py +54 -10
  18. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/health/__init__.py +12 -1
  19. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/logging/__init__.py +19 -1
  20. prodkit-0.2.1/src/prodkit/plugins/rate_limit/__init__.py +147 -0
  21. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/request_id/__init__.py +20 -1
  22. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/plugins/security/__init__.py +47 -1
  23. prodkit-0.2.1/tests/integration/test_cli.py +132 -0
  24. prodkit-0.2.1/tests/unit/test_doctor.py +91 -0
  25. prodkit-0.2.1/tests/unit/test_rate_limit.py +97 -0
  26. prodkit-0.1.3/CHANGELOG.md +0 -39
  27. {prodkit-0.1.3 → prodkit-0.2.1}/LICENSE +0 -0
  28. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/contracts/__init__.py +0 -0
  29. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/__init__.py +0 -0
  30. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/context.py +0 -0
  31. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/event_bus.py +0 -0
  32. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/exceptions.py +0 -0
  33. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/lifecycle.py +0 -0
  34. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/plugin_manager.py +0 -0
  35. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/core/registry.py +0 -0
  36. {prodkit-0.1.3 → prodkit-0.2.1}/src/prodkit/py.typed +0 -0
  37. {prodkit-0.1.3 → prodkit-0.2.1}/tests/__init__.py +0 -0
  38. {prodkit-0.1.3 → prodkit-0.2.1}/tests/conftest.py +0 -0
  39. {prodkit-0.1.3 → prodkit-0.2.1}/tests/integration/__init__.py +0 -0
  40. {prodkit-0.1.3 → prodkit-0.2.1}/tests/integration/test_logging.py +0 -0
  41. {prodkit-0.1.3 → prodkit-0.2.1}/tests/integration/test_production.py +0 -0
  42. {prodkit-0.1.3 → prodkit-0.2.1}/tests/unit/__init__.py +0 -0
  43. {prodkit-0.1.3 → prodkit-0.2.1}/tests/unit/test_config.py +0 -0
  44. {prodkit-0.1.3 → prodkit-0.2.1}/tests/unit/test_kernel.py +0 -0
@@ -24,4 +24,5 @@ dist/
24
24
  Thumbs.db
25
25
  LI.MD
26
26
  PUB.MD
27
- EX.MD
27
+ EX.MD
28
+ ss.md
@@ -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
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, and gzip compression — configured to current best practice,
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
+ [![PyPI](https://img.shields.io/pypi/v/prodkit.svg)](https://pypi.org/project/prodkit/)
69
+ [![Python](https://img.shields.io/pypi/pyversions/prodkit.svg)](https://pypi.org/project/prodkit/)
62
70
  [![CI](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml/badge.svg)](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
63
71
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- | 🔌 **Plugin system** | Every feature above is a plugin. Write your own with 6 optional hooks. |
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 check now shows up in `/ready` automatically. Plugins can declare
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.0 — alpha.** Core kernel and seven built-in plugins, 60 tests, 98%
236
- coverage, strict mypy, CI across Python 3.10–3.13.
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 with a production-readiness score (v0.2),
239
- Prometheus metrics + Redis backends (v0.3), Dockerfile/nginx/CI generators
240
- (v0.4), public plugin SDK (v0.5), auth helpers (v0.6), stable API (v1.0).
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, and gzip compression — configured to current best practice,
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
+ [![PyPI](https://img.shields.io/pypi/v/prodkit.svg)](https://pypi.org/project/prodkit/)
22
+ [![Python](https://img.shields.io/pypi/pyversions/prodkit.svg)](https://pypi.org/project/prodkit/)
20
23
  [![CI](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml/badge.svg)](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
21
24
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- | 🔌 **Plugin system** | Every feature above is a plugin. Write your own with 6 optional hooks. |
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 check now shows up in `/ready` automatically. Plugins can declare
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.0 — alpha.** Core kernel and seven built-in plugins, 60 tests, 98%
194
- coverage, strict mypy, CI across Python 3.10–3.13.
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 with a production-readiness score (v0.2),
197
- Prometheus metrics + Redis backends (v0.3), Dockerfile/nginx/CI generators
198
- (v0.4), public plugin SDK (v0.5), auth helpers (v0.6), stable API (v1.0).
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.3"
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.3"
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()