prodkit 0.2.0__tar.gz → 0.3.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.
Files changed (54) hide show
  1. {prodkit-0.2.0 → prodkit-0.3.0}/CHANGELOG.md +32 -0
  2. prodkit-0.2.0/README.md → prodkit-0.3.0/PKG-INFO +120 -15
  3. prodkit-0.2.0/PKG-INFO → prodkit-0.3.0/README.md +50 -62
  4. {prodkit-0.2.0 → prodkit-0.3.0}/pyproject.toml +18 -1
  5. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/__init__.py +7 -1
  6. prodkit-0.3.0/src/prodkit/contracts/__init__.py +5 -0
  7. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/contracts/plugin.py +8 -2
  8. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/config.py +34 -1
  9. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/production.py +3 -0
  10. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/__init__.py +12 -0
  11. prodkit-0.3.0/src/prodkit/plugins/_redis.py +34 -0
  12. prodkit-0.3.0/src/prodkit/plugins/cache/__init__.py +150 -0
  13. prodkit-0.3.0/src/prodkit/plugins/metrics/__init__.py +173 -0
  14. prodkit-0.3.0/src/prodkit/plugins/rate_limit/__init__.py +169 -0
  15. prodkit-0.3.0/src/prodkit/plugins/rate_limit/backends.py +117 -0
  16. prodkit-0.3.0/src/prodkit/plugins/tracing/__init__.py +231 -0
  17. prodkit-0.3.0/tests/unit/test_cache.py +151 -0
  18. {prodkit-0.2.0 → prodkit-0.3.0}/tests/unit/test_config.py +44 -0
  19. {prodkit-0.2.0 → prodkit-0.3.0}/tests/unit/test_doctor.py +5 -2
  20. prodkit-0.3.0/tests/unit/test_metrics.py +184 -0
  21. prodkit-0.3.0/tests/unit/test_rate_limit.py +216 -0
  22. prodkit-0.3.0/tests/unit/test_tracing.py +179 -0
  23. prodkit-0.2.0/src/prodkit/plugins/rate_limit/__init__.py +0 -147
  24. prodkit-0.2.0/tests/unit/__init__.py +0 -0
  25. prodkit-0.2.0/tests/unit/test_rate_limit.py +0 -97
  26. {prodkit-0.2.0 → prodkit-0.3.0}/.gitignore +0 -0
  27. {prodkit-0.2.0 → prodkit-0.3.0}/LICENSE +0 -0
  28. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/cli/__init__.py +0 -0
  29. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/cli/app.py +0 -0
  30. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/cli/loader.py +0 -0
  31. {prodkit-0.2.0/src/prodkit/contracts → prodkit-0.3.0/src/prodkit/core}/__init__.py +0 -0
  32. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/context.py +0 -0
  33. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/doctor.py +0 -0
  34. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/event_bus.py +0 -0
  35. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/exceptions.py +0 -0
  36. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/lifecycle.py +0 -0
  37. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/plugin_manager.py +0 -0
  38. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/core/registry.py +0 -0
  39. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/compression/__init__.py +0 -0
  40. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/cors/__init__.py +0 -0
  41. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/errors/__init__.py +0 -0
  42. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/health/__init__.py +0 -0
  43. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/logging/__init__.py +0 -0
  44. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/request_id/__init__.py +0 -0
  45. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/plugins/security/__init__.py +0 -0
  46. {prodkit-0.2.0 → prodkit-0.3.0}/src/prodkit/py.typed +0 -0
  47. {prodkit-0.2.0/src/prodkit/core → prodkit-0.3.0/tests}/__init__.py +0 -0
  48. {prodkit-0.2.0 → prodkit-0.3.0}/tests/conftest.py +0 -0
  49. {prodkit-0.2.0/tests → prodkit-0.3.0/tests/integration}/__init__.py +0 -0
  50. {prodkit-0.2.0 → prodkit-0.3.0}/tests/integration/test_cli.py +0 -0
  51. {prodkit-0.2.0 → prodkit-0.3.0}/tests/integration/test_logging.py +0 -0
  52. {prodkit-0.2.0 → prodkit-0.3.0}/tests/integration/test_production.py +0 -0
  53. {prodkit-0.2.0/tests/integration → prodkit-0.3.0/tests/unit}/__init__.py +0 -0
  54. {prodkit-0.2.0 → prodkit-0.3.0}/tests/unit/test_kernel.py +0 -0
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-08-14
11
+
12
+ ### Added
13
+ - **Prometheus metrics plugin** (`pip install prodkit[metrics]`): request
14
+ count, latency histogram, in-flight gauge. Exposed at `/metrics` (or a
15
+ custom path). Metrics use route templates as labels (bounded cardinality).
16
+ Dedicated `CollectorRegistry` per instance — no global-state collisions.
17
+ - **Cache plugin** (`cache={...}`): a named cache service published in the
18
+ registry under `"cache"`. Memory (per-process LRU with TTL) and Redis
19
+ (shared across workers) backends. Values must be JSON-serializable.
20
+ - **OpenTelemetry tracing plugin** (`pip install prodkit[otel]`): automatic
21
+ request spans with W3C `traceparent` propagation, configurable sampler
22
+ (`sample_rate`), and OTLP/console/none exporters. The `TracerProvider` is
23
+ published in the registry as `"tracer"`.
24
+ - **Redis backend for rate limiting** (`rate_limit.backend="redis"`): shared
25
+ aligned fixed-window counters across all workers and hosts. Fails open on
26
+ Redis errors (availability > strict limiting). Readiness check via `/ready`.
27
+
28
+ ### Changed
29
+ - CI installs `otel` and `metrics` extras for full test coverage.
30
+ - `TracingMiddleware` now uses `context.attach()`/`detach()` for correct W3C
31
+ trace context propagation (previously passed unsupported `context` kwarg).
32
+ - Dev dependencies now include `anyio` and `pytest-anyio` for async tests.
33
+
34
+
35
+ ## [0.2.1] - 2026-07-23
36
+
37
+ ### Changed
38
+ - README polish: PyPI/Python badges, table of contents, `prodkit[cli]` install
39
+ note, rate-limiting shown in the config examples, and a `doctor()` hook in the
40
+ plugin-authoring example. Docs-only release (no code changes).
41
+
10
42
  ## [0.2.0] - 2026-07-22
11
43
 
12
44
  ### Added
@@ -1,3 +1,73 @@
1
+ Metadata-Version: 2.5
2
+ Name: prodkit
3
+ Version: 0.3.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-email: Pushkar Pant <pantpushkar4@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: fastapi,health-check,logging,metrics,middleware,observability,opentelemetry,production,security,tracing
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: all
32
+ Requires-Dist: brotli-asgi>=1.4; extra == 'all'
33
+ Requires-Dist: opentelemetry-api>=1.20; extra == 'all'
34
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == 'all'
35
+ Requires-Dist: opentelemetry-sdk>=1.20; extra == 'all'
36
+ Requires-Dist: prometheus-client>=0.20; extra == 'all'
37
+ Requires-Dist: redis>=5.0; extra == 'all'
38
+ Requires-Dist: rich>=13; extra == 'all'
39
+ Requires-Dist: typer>=0.12; extra == 'all'
40
+ Provides-Extra: brotli
41
+ Requires-Dist: brotli-asgi>=1.4; extra == 'brotli'
42
+ Provides-Extra: cli
43
+ Requires-Dist: rich>=13; extra == 'cli'
44
+ Requires-Dist: typer>=0.12; extra == 'cli'
45
+ Provides-Extra: dev
46
+ Requires-Dist: anyio[trio]>=4.0; extra == 'dev'
47
+ Requires-Dist: fakeredis[lua]>=2.21; extra == 'dev'
48
+ Requires-Dist: httpx>=0.27; extra == 'dev'
49
+ Requires-Dist: import-linter>=2.0; extra == 'dev'
50
+ Requires-Dist: mypy>=1.11; extra == 'dev'
51
+ Requires-Dist: opentelemetry-api>=1.20; extra == 'dev'
52
+ Requires-Dist: opentelemetry-sdk>=1.20; extra == 'dev'
53
+ Requires-Dist: prometheus-client>=0.20; extra == 'dev'
54
+ Requires-Dist: pytest-anyio>=0.0.0; extra == 'dev'
55
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
56
+ Requires-Dist: pytest>=8.0; extra == 'dev'
57
+ Requires-Dist: rich>=13; extra == 'dev'
58
+ Requires-Dist: ruff>=0.6; extra == 'dev'
59
+ Requires-Dist: tomli>=2.0; extra == 'dev'
60
+ Requires-Dist: typer>=0.12; extra == 'dev'
61
+ Provides-Extra: metrics
62
+ Requires-Dist: prometheus-client>=0.20; extra == 'metrics'
63
+ Provides-Extra: otel
64
+ Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
65
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == 'otel'
66
+ Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
67
+ Provides-Extra: redis
68
+ Requires-Dist: redis>=5.0; extra == 'redis'
69
+ Description-Content-Type: text/markdown
70
+
1
71
  # ProdKit
2
72
 
3
73
  > **One line. Production ready.**
@@ -14,14 +84,24 @@ Production(app)
14
84
 
15
85
  That's it. Your app now has security headers, structured JSON logging with
16
86
  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.
87
+ endpoints, gzip compression, and opt-in rate limiting — configured to current
88
+ best practice, hardened for production, and pleasant in development. Then run
89
+ [`prodkit doctor`](#cli--prodkit-doctor) to score how production-ready it is.
19
90
 
91
+ [![PyPI](https://img.shields.io/pypi/v/prodkit.svg)](https://pypi.org/project/prodkit/)
92
+ [![Python](https://img.shields.io/pypi/pyversions/prodkit.svg)](https://pypi.org/project/prodkit/)
20
93
  [![CI](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml/badge.svg)](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
21
94
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Pushkarpant/PRODKIT/blob/main/LICENSE)
22
95
 
23
96
  ---
24
97
 
98
+ **Contents:** [Why](#why) · [Install](#installation) · [Quick Start](#quick-start) ·
99
+ [What You Get](#what-you-get) · [Configuration](#configuration) ·
100
+ [Plugins](#writing-a-plugin) · [CLI / doctor](#cli--prodkit-doctor) ·
101
+ [Status & Roadmap](#project-status)
102
+
103
+ ---
104
+
25
105
  ## Why
26
106
 
27
107
  Every production FastAPI service re-implements the same ~500 lines of glue:
@@ -37,11 +117,21 @@ evolve, `pip install -U prodkit` updates every app you own.
37
117
  [**`pip install prodkit`**](https://pypi.org/project/prodkit/)
38
118
 
39
119
  ```bash
40
- pip install prodkit
120
+ pip install prodkit # the library
121
+ pip install "prodkit[cli]" # + the `prodkit doctor` CLI (typer + rich)
41
122
  ```
42
123
 
43
124
  Requires Python 3.10+ and FastAPI 0.110+. The base install depends only on
44
- FastAPI and Pydantic — nothing else.
125
+ FastAPI and Pydantic — nothing else. Optional extras:
126
+
127
+ | Extra | Adds |
128
+ |---|---|
129
+ | `cli` | `prodkit doctor` CLI (typer + rich) |
130
+ | `metrics` | Prometheus `/metrics` endpoint |
131
+ | `otel` | OpenTelemetry tracing (OTLP export) |
132
+ | `redis` | Shared Redis backends (rate-limit, cache) |
133
+ | `brotli` | Brotli compression |
134
+ | `all` | Everything above |
45
135
 
46
136
  ## Quick Start
47
137
 
@@ -69,7 +159,9 @@ x-content-type-options: nosniff
69
159
  x-frame-options: DENY
70
160
  strict-transport-security: max-age=63072000; includeSubDomains
71
161
  referrer-policy: strict-origin-when-cross-origin
72
- ...
162
+ permissions-policy: camera=(), microphone=(), geolocation=()
163
+ x-xss-protection: 0
164
+ content-type: application/json
73
165
  ```
74
166
 
75
167
  For local development, flip the profile — pretty console logs, debug error
@@ -90,7 +182,10 @@ Production(app, environment="development")
90
182
  | ❤️ **Health endpoints** | `/health`, `/live` (liveness) and `/ready` (readiness — aggregates checks from every plugin, 503 until all pass). Kubernetes-native. |
91
183
  | 🌐 **CORS** | Explicit origins only; the wildcard-with-credentials footgun is refused at boot. |
92
184
  | 📦 **Compression** | Gzip for responses over 500 bytes. |
93
- | 🚦 **Rate limiting** | Opt-in per-IP limiting (`100/minute`), `429 problem+json` with `Retry-After`. In-memory backend (Redis in v0.3). |
185
+ | 🚦 **Rate limiting** | Opt-in per-IP limiting (`100/minute`), `429 problem+json` with `Retry-After`. Memory or Redis backend (shared across workers). |
186
+ | 📊 **Prometheus metrics** | Request count, latency histogram, in-flight gauge at `/metrics`. Route-template labels (bounded cardinality). `pip install prodkit[metrics]` |
187
+ | 🗄️ **Cache service** | Memory (LRU + TTL) or Redis backend, published in the registry. `await cache.get(key)` / `.set(key, value, ttl=60)`. |
188
+ | 🔭 **OpenTelemetry tracing** | Automatic request spans with W3C `traceparent` propagation. OTLP, console, or none exporter. `pip install prodkit[otel]` |
94
189
  | 🩺 **`prodkit doctor`** | CLI production-readiness audit with a 0–100 score. `--strict` gates CI. |
95
190
  | 🔌 **Plugin system** | Every feature above is a plugin. Write your own with optional hooks incl. `doctor()`. |
96
191
 
@@ -111,6 +206,7 @@ Production(
111
206
  cors={"origins": ["https://app.example.com"]}, # dict = configure & enable
112
207
  compression=False, # bool = toggle
113
208
  security={"trusted_hosts": ["api.example.com"]},
209
+ rate_limit={"default": "100/minute"}, # opt-in per-IP limiting
114
210
  )
115
211
  ```
116
212
 
@@ -134,6 +230,10 @@ level = "INFO"
134
230
  [cors]
135
231
  enabled = true
136
232
  origins = ["https://app.example.com"]
233
+
234
+ [rate_limit]
235
+ enabled = true
236
+ default = "100/minute"
137
237
  ```
138
238
 
139
239
  ### Fail-fast, refuse-unsafe
@@ -155,7 +255,7 @@ not warned about:
155
255
  ## Writing a Plugin
156
256
 
157
257
  ```python
158
- from prodkit import Check, Plugin, Production
258
+ from prodkit import Audit, Check, Plugin, Production
159
259
 
160
260
  class DatabasePlugin(Plugin):
161
261
  name = "database"
@@ -167,13 +267,18 @@ class DatabasePlugin(Plugin):
167
267
  async def shutdown(self, ctx):
168
268
  await self.pool.close()
169
269
 
170
- def checks(self, ctx):
270
+ def checks(self, ctx): # runtime readiness → /ready
171
271
  return [Check(name="database", passed=self.pool.is_alive())]
172
272
 
273
+ def doctor(self, ctx): # static audit → prodkit doctor
274
+ return [Audit(name="Database pool",
275
+ status="ok", detail="connection pool configured")]
276
+
173
277
  Production(app, plugins=[DatabasePlugin()])
174
278
  ```
175
279
 
176
- Your check now shows up in `/ready` automatically. Plugins can declare
280
+ Your `checks()` result now shows up in `/ready` automatically, and your
281
+ `doctor()` findings roll into the `prodkit doctor` score. Plugins can declare
177
282
  `requires = ("other-plugin",)` and the kernel activates them in dependency
178
283
  order — cycles and missing dependencies fail at boot.
179
284
 
@@ -230,13 +335,13 @@ plugins score too.
230
335
 
231
336
  ## Project Status
232
337
 
233
- **v0.2.0 — alpha.** Core kernel, eight built-in plugins (incl. rate-limiting),
234
- the `prodkit doctor` CLI with a production-readiness score, strict mypy, CI
235
- across Python 3.10–3.13.
338
+ **v0.3.0 — alpha.** Core kernel, eleven built-in plugins (metrics, cache, tracing,
339
+ rate-limiting with Redis), the `prodkit doctor` CLI, strict mypy, CI across
340
+ Python 3.10–3.13.
236
341
 
237
- Roadmap: ✅ `prodkit doctor` CLI + rate limiting (v0.2), Prometheus metrics +
238
- Redis backends (v0.3), Dockerfile/nginx/CI generators (v0.4), public plugin SDK
239
- (v0.5), auth helpers (v0.6), stable API (v1.0).
342
+ Roadmap: ✅ `prodkit doctor` CLI + rate limiting (v0.2), ✅ Prometheus metrics +
343
+ Redis backends + OpenTelemetry tracing (v0.3), Dockerfile/nginx/CI generators
344
+ (v0.4), public plugin SDK (v0.5), auth helpers (v0.6), stable API (v1.0).
240
345
  Full details in [docs/ARCHITECTURE.md](https://github.com/Pushkarpant/PRODKIT/blob/main/docs/ARCHITECTURE.md).
241
346
 
242
347
  ## Contributing
@@ -1,50 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: prodkit
3
- Version: 0.2.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-email: Pushkar Pant <pantpushkar4@gmail.com>
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: cli
34
- Requires-Dist: rich>=13; extra == 'cli'
35
- Requires-Dist: typer>=0.12; extra == 'cli'
36
- Provides-Extra: dev
37
- Requires-Dist: httpx>=0.27; extra == 'dev'
38
- Requires-Dist: import-linter>=2.0; extra == 'dev'
39
- Requires-Dist: mypy>=1.11; extra == 'dev'
40
- Requires-Dist: pytest-cov>=5.0; extra == 'dev'
41
- Requires-Dist: pytest>=8.0; extra == 'dev'
42
- Requires-Dist: rich>=13; extra == 'dev'
43
- Requires-Dist: ruff>=0.6; extra == 'dev'
44
- Requires-Dist: tomli>=2.0; extra == 'dev'
45
- Requires-Dist: typer>=0.12; extra == 'dev'
46
- Description-Content-Type: text/markdown
47
-
48
1
  # ProdKit
49
2
 
50
3
  > **One line. Production ready.**
@@ -61,14 +14,24 @@ Production(app)
61
14
 
62
15
  That's it. Your app now has security headers, structured JSON logging with
63
16
  request-ID correlation, RFC 9457 error responses, Kubernetes-ready health
64
- endpoints, and gzip compression — configured to current best practice,
65
- 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.
66
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/)
67
23
  [![CI](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml/badge.svg)](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
68
24
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Pushkarpant/PRODKIT/blob/main/LICENSE)
69
25
 
70
26
  ---
71
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
+
72
35
  ## Why
73
36
 
74
37
  Every production FastAPI service re-implements the same ~500 lines of glue:
@@ -84,11 +47,21 @@ evolve, `pip install -U prodkit` updates every app you own.
84
47
  [**`pip install prodkit`**](https://pypi.org/project/prodkit/)
85
48
 
86
49
  ```bash
87
- pip install prodkit
50
+ pip install prodkit # the library
51
+ pip install "prodkit[cli]" # + the `prodkit doctor` CLI (typer + rich)
88
52
  ```
89
53
 
90
54
  Requires Python 3.10+ and FastAPI 0.110+. The base install depends only on
91
- FastAPI and Pydantic — nothing else.
55
+ FastAPI and Pydantic — nothing else. Optional extras:
56
+
57
+ | Extra | Adds |
58
+ |---|---|
59
+ | `cli` | `prodkit doctor` CLI (typer + rich) |
60
+ | `metrics` | Prometheus `/metrics` endpoint |
61
+ | `otel` | OpenTelemetry tracing (OTLP export) |
62
+ | `redis` | Shared Redis backends (rate-limit, cache) |
63
+ | `brotli` | Brotli compression |
64
+ | `all` | Everything above |
92
65
 
93
66
  ## Quick Start
94
67
 
@@ -116,7 +89,9 @@ x-content-type-options: nosniff
116
89
  x-frame-options: DENY
117
90
  strict-transport-security: max-age=63072000; includeSubDomains
118
91
  referrer-policy: strict-origin-when-cross-origin
119
- ...
92
+ permissions-policy: camera=(), microphone=(), geolocation=()
93
+ x-xss-protection: 0
94
+ content-type: application/json
120
95
  ```
121
96
 
122
97
  For local development, flip the profile — pretty console logs, debug error
@@ -137,7 +112,10 @@ Production(app, environment="development")
137
112
  | ❤️ **Health endpoints** | `/health`, `/live` (liveness) and `/ready` (readiness — aggregates checks from every plugin, 503 until all pass). Kubernetes-native. |
138
113
  | 🌐 **CORS** | Explicit origins only; the wildcard-with-credentials footgun is refused at boot. |
139
114
  | 📦 **Compression** | Gzip for responses over 500 bytes. |
140
- | 🚦 **Rate limiting** | Opt-in per-IP limiting (`100/minute`), `429 problem+json` with `Retry-After`. In-memory backend (Redis in v0.3). |
115
+ | 🚦 **Rate limiting** | Opt-in per-IP limiting (`100/minute`), `429 problem+json` with `Retry-After`. Memory or Redis backend (shared across workers). |
116
+ | 📊 **Prometheus metrics** | Request count, latency histogram, in-flight gauge at `/metrics`. Route-template labels (bounded cardinality). `pip install prodkit[metrics]` |
117
+ | 🗄️ **Cache service** | Memory (LRU + TTL) or Redis backend, published in the registry. `await cache.get(key)` / `.set(key, value, ttl=60)`. |
118
+ | 🔭 **OpenTelemetry tracing** | Automatic request spans with W3C `traceparent` propagation. OTLP, console, or none exporter. `pip install prodkit[otel]` |
141
119
  | 🩺 **`prodkit doctor`** | CLI production-readiness audit with a 0–100 score. `--strict` gates CI. |
142
120
  | 🔌 **Plugin system** | Every feature above is a plugin. Write your own with optional hooks incl. `doctor()`. |
143
121
 
@@ -158,6 +136,7 @@ Production(
158
136
  cors={"origins": ["https://app.example.com"]}, # dict = configure & enable
159
137
  compression=False, # bool = toggle
160
138
  security={"trusted_hosts": ["api.example.com"]},
139
+ rate_limit={"default": "100/minute"}, # opt-in per-IP limiting
161
140
  )
162
141
  ```
163
142
 
@@ -181,6 +160,10 @@ level = "INFO"
181
160
  [cors]
182
161
  enabled = true
183
162
  origins = ["https://app.example.com"]
163
+
164
+ [rate_limit]
165
+ enabled = true
166
+ default = "100/minute"
184
167
  ```
185
168
 
186
169
  ### Fail-fast, refuse-unsafe
@@ -202,7 +185,7 @@ not warned about:
202
185
  ## Writing a Plugin
203
186
 
204
187
  ```python
205
- from prodkit import Check, Plugin, Production
188
+ from prodkit import Audit, Check, Plugin, Production
206
189
 
207
190
  class DatabasePlugin(Plugin):
208
191
  name = "database"
@@ -214,13 +197,18 @@ class DatabasePlugin(Plugin):
214
197
  async def shutdown(self, ctx):
215
198
  await self.pool.close()
216
199
 
217
- def checks(self, ctx):
200
+ def checks(self, ctx): # runtime readiness → /ready
218
201
  return [Check(name="database", passed=self.pool.is_alive())]
219
202
 
203
+ def doctor(self, ctx): # static audit → prodkit doctor
204
+ return [Audit(name="Database pool",
205
+ status="ok", detail="connection pool configured")]
206
+
220
207
  Production(app, plugins=[DatabasePlugin()])
221
208
  ```
222
209
 
223
- Your check now shows up in `/ready` automatically. Plugins can declare
210
+ Your `checks()` result now shows up in `/ready` automatically, and your
211
+ `doctor()` findings roll into the `prodkit doctor` score. Plugins can declare
224
212
  `requires = ("other-plugin",)` and the kernel activates them in dependency
225
213
  order — cycles and missing dependencies fail at boot.
226
214
 
@@ -277,13 +265,13 @@ plugins score too.
277
265
 
278
266
  ## Project Status
279
267
 
280
- **v0.2.0 — alpha.** Core kernel, eight built-in plugins (incl. rate-limiting),
281
- the `prodkit doctor` CLI with a production-readiness score, strict mypy, CI
282
- across Python 3.10–3.13.
268
+ **v0.3.0 — alpha.** Core kernel, eleven built-in plugins (metrics, cache, tracing,
269
+ rate-limiting with Redis), the `prodkit doctor` CLI, strict mypy, CI across
270
+ Python 3.10–3.13.
283
271
 
284
- Roadmap: ✅ `prodkit doctor` CLI + rate limiting (v0.2), Prometheus metrics +
285
- Redis backends (v0.3), Dockerfile/nginx/CI generators (v0.4), public plugin SDK
286
- (v0.5), auth helpers (v0.6), stable API (v1.0).
272
+ Roadmap: ✅ `prodkit doctor` CLI + rate limiting (v0.2), ✅ Prometheus metrics +
273
+ Redis backends + OpenTelemetry tracing (v0.3), Dockerfile/nginx/CI generators
274
+ (v0.4), public plugin SDK (v0.5), auth helpers (v0.6), stable API (v1.0).
287
275
  Full details in [docs/ARCHITECTURE.md](https://github.com/Pushkarpant/PRODKIT/blob/main/docs/ARCHITECTURE.md).
288
276
 
289
277
  ## Contributing
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "prodkit"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "The production framework for FastAPI. One line. Production ready."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -19,6 +19,9 @@ keywords = [
19
19
  "observability",
20
20
  "logging",
21
21
  "health-check",
22
+ "metrics",
23
+ "tracing",
24
+ "opentelemetry",
22
25
  ]
23
26
  classifiers = [
24
27
  "Development Status :: 3 - Alpha",
@@ -44,9 +47,19 @@ dependencies = [
44
47
  [project.optional-dependencies]
45
48
  brotli = ["brotli-asgi>=1.4"]
46
49
  cli = ["typer>=0.12", "rich>=13"]
50
+ metrics = ["prometheus-client>=0.20"]
51
+ redis = ["redis>=5.0"]
52
+ otel = [
53
+ "opentelemetry-api>=1.20",
54
+ "opentelemetry-sdk>=1.20",
55
+ "opentelemetry-exporter-otlp-proto-grpc>=1.20",
56
+ ]
57
+ all = ["prodkit[metrics,redis,otel,brotli,cli]"]
47
58
  dev = [
48
59
  "pytest>=8.0",
49
60
  "pytest-cov>=5.0",
61
+ "anyio[trio]>=4.0",
62
+ "pytest-anyio>=0.0.0",
50
63
  "httpx>=0.27",
51
64
  "ruff>=0.6",
52
65
  "mypy>=1.11",
@@ -54,6 +67,10 @@ dev = [
54
67
  "tomli>=2.0", # so mypy (python_version=3.10) can type-check the fallback import
55
68
  "typer>=0.12", # CLI is type-checked and tested in CI
56
69
  "rich>=13",
70
+ "prometheus-client>=0.20",
71
+ "fakeredis[lua]>=2.21",
72
+ "opentelemetry-api>=1.20",
73
+ "opentelemetry-sdk>=1.20",
57
74
  ]
58
75
 
59
76
  [project.scripts]
@@ -9,15 +9,18 @@ Production(app)
9
9
 
10
10
  from prodkit.contracts.plugin import Audit, Check, Plugin
11
11
  from prodkit.core.config import (
12
+ CacheConfig,
12
13
  CompressionConfig,
13
14
  CORSConfig,
14
15
  ErrorsConfig,
15
16
  HealthConfig,
16
17
  LoggingConfig,
18
+ MetricsConfig,
17
19
  ProdKitConfig,
18
20
  RateLimitConfig,
19
21
  RequestIDConfig,
20
22
  SecurityConfig,
23
+ TracingConfig,
21
24
  )
22
25
  from prodkit.core.context import Context
23
26
  from prodkit.core.exceptions import (
@@ -34,17 +37,19 @@ from prodkit.plugins import builtin_plugins
34
37
  # root — the kernel itself never imports from prodkit.plugins.
35
38
  set_builtin_factory(builtin_plugins)
36
39
 
37
- __version__ = "0.2.0"
40
+ __version__ = "0.3.0"
38
41
 
39
42
  __all__ = [
40
43
  "Audit",
41
44
  "CORSConfig",
45
+ "CacheConfig",
42
46
  "Check",
43
47
  "CompressionConfig",
44
48
  "Context",
45
49
  "ErrorsConfig",
46
50
  "HealthConfig",
47
51
  "LoggingConfig",
52
+ "MetricsConfig",
48
53
  "Plugin",
49
54
  "PluginDependencyError",
50
55
  "PluginError",
@@ -56,5 +61,6 @@ __all__ = [
56
61
  "RequestIDConfig",
57
62
  "SecurityConfig",
58
63
  "ServiceNotFoundError",
64
+ "TracingConfig",
59
65
  "__version__",
60
66
  ]
@@ -0,0 +1,5 @@
1
+ """ProdKit plugin contract and supporting types."""
2
+
3
+ from prodkit.contracts.plugin import Audit, AuditStatus, Check, Plugin
4
+
5
+ __all__ = ["Audit", "AuditStatus", "Check", "Plugin"]
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Awaitable
5
6
  from dataclasses import dataclass
6
7
  from typing import TYPE_CHECKING, ClassVar, Literal
7
8
 
@@ -72,8 +73,12 @@ class Plugin:
72
73
  async def shutdown(self, ctx: Context) -> None:
73
74
  """Release resources gracefully."""
74
75
 
75
- def checks(self, ctx: Context) -> list[Check]:
76
- """Readiness checks, aggregated by the health plugin's /ready."""
76
+ def checks(self, ctx: Context) -> list[Check] | Awaitable[list[Check]]:
77
+ """Readiness checks, aggregated by the health plugin's /ready.
78
+
79
+ May be overridden as ``async def`` (e.g. to ping a backend); the
80
+ health plugin awaits awaitable results.
81
+ """
77
82
  return []
78
83
 
79
84
  def doctor(self, ctx: Context) -> list[Audit]:
@@ -89,6 +94,7 @@ class Plugin:
89
94
  PRIORITY_REQUEST_ID = 100
90
95
  PRIORITY_LOGGING = 200
91
96
  PRIORITY_ERRORS = 250
97
+ PRIORITY_TRACING = 290
92
98
  PRIORITY_METRICS = 300
93
99
  PRIORITY_SECURITY = 400
94
100
  PRIORITY_CORS = 500
@@ -93,7 +93,37 @@ class RateLimitConfig(_Section):
93
93
  enabled: bool = False # opt-in: an unexpected 429 is worse than no limit
94
94
  # "<count>/<second|minute|hour>", parsed and validated by the plugin.
95
95
  default: str = "100/minute"
96
- by: Literal["ip"] = "ip" # v0.2 keys on client IP; per-user/route land later
96
+ by: Literal["ip"] = "ip" # keys on client IP; per-user/route land later
97
+ # "memory" is per-process; "redis" shares the limit across workers/hosts.
98
+ backend: Literal["memory", "redis"] = "memory"
99
+ redis_url: str = "redis://localhost:6379/0"
100
+ key_prefix: str = "prodkit:ratelimit:"
101
+
102
+
103
+ class MetricsConfig(_Section):
104
+ enabled: bool = False # opt-in: needs prodkit[metrics]
105
+ path: str = "/metrics"
106
+ buckets: list[float] | None = None # None -> prometheus-client defaults
107
+ # Paths never measured (the metrics path itself is always excluded).
108
+ exclude_paths: list[str] = Field(default_factory=list)
109
+
110
+
111
+ class CacheConfig(_Section):
112
+ enabled: bool = False # opt-in: a service other code asks for, not middleware
113
+ backend: Literal["memory", "redis"] = "memory"
114
+ redis_url: str = "redis://localhost:6379/0"
115
+ default_ttl: int = 300 # seconds; 0 = no expiry
116
+ key_prefix: str = "prodkit:cache:"
117
+ max_entries: int = 1024 # memory backend LRU bound
118
+
119
+
120
+ class TracingConfig(_Section):
121
+ enabled: bool = False # opt-in: needs prodkit[otel]
122
+ service_name: str | None = None # None -> the FastAPI app's title
123
+ exporter: Literal["otlp", "console", "none"] = "otlp"
124
+ # None defers to the standard OTEL_EXPORTER_OTLP_* environment variables.
125
+ endpoint: str | None = None
126
+ sample_rate: float = Field(default=1.0, ge=0.0, le=1.0)
97
127
 
98
128
 
99
129
  class ProdKitConfig(_Section):
@@ -109,6 +139,9 @@ class ProdKitConfig(_Section):
109
139
  cors: CORSConfig = Field(default_factory=CORSConfig)
110
140
  compression: CompressionConfig = Field(default_factory=CompressionConfig)
111
141
  rate_limit: RateLimitConfig = Field(default_factory=RateLimitConfig)
142
+ metrics: MetricsConfig = Field(default_factory=MetricsConfig)
143
+ cache: CacheConfig = Field(default_factory=CacheConfig)
144
+ tracing: TracingConfig = Field(default_factory=TracingConfig)
112
145
 
113
146
 
114
147
  # Profile defaults: applied beneath toml/env/args. The one-liner must be
@@ -45,6 +45,9 @@ _TOGGLEABLE = (
45
45
  "cors",
46
46
  "compression",
47
47
  "rate_limit",
48
+ "metrics",
49
+ "cache",
50
+ "tracing",
48
51
  )
49
52
 
50
53