prodkit 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,78 @@
1
+ """Security plugin: response security headers, trusted hosts, HTTPS redirect.
2
+
3
+ Headers follow current OWASP Secure Headers recommendations. CSP is opt-in
4
+ because a wrong default CSP breaks apps; everything else is safe universally.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import ClassVar
10
+
11
+ from starlette.datastructures import MutableHeaders
12
+ from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
13
+ from starlette.middleware.trustedhost import TrustedHostMiddleware
14
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
15
+
16
+ from prodkit.contracts.plugin import PRIORITY_SECURITY, Plugin
17
+ from prodkit.core.context import Context
18
+
19
+
20
+ class SecurityHeadersMiddleware:
21
+ """Pure-ASGI middleware (no BaseHTTPMiddleware overhead) that stamps
22
+ security headers on every response."""
23
+
24
+ def __init__(self, app: ASGIApp, headers: dict[str, str]) -> None:
25
+ self.app = app
26
+ self.headers = headers
27
+
28
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
29
+ if scope["type"] != "http":
30
+ await self.app(scope, receive, send)
31
+ return
32
+
33
+ async def send_with_headers(message: Message) -> None:
34
+ if message["type"] == "http.response.start":
35
+ headers = MutableHeaders(scope=message)
36
+ for name, value in self.headers.items():
37
+ headers.setdefault(name, value)
38
+ await send(message)
39
+
40
+ await self.app(scope, receive, send_with_headers)
41
+
42
+
43
+ def build_security_headers(ctx: Context) -> dict[str, str]:
44
+ cfg = ctx.config.security
45
+ headers = {
46
+ "X-Content-Type-Options": "nosniff",
47
+ "X-Frame-Options": cfg.frame_options,
48
+ "Referrer-Policy": cfg.referrer_policy,
49
+ "Permissions-Policy": cfg.permissions_policy,
50
+ # Kill legacy XSS auditor behavior explicitly (OWASP recommendation).
51
+ "X-XSS-Protection": "0",
52
+ }
53
+ if cfg.hsts:
54
+ headers["Strict-Transport-Security"] = f"max-age={cfg.hsts_max_age}; includeSubDomains"
55
+ if cfg.content_security_policy:
56
+ headers["Content-Security-Policy"] = cfg.content_security_policy
57
+ return headers
58
+
59
+
60
+ class SecurityPlugin(Plugin):
61
+ name: ClassVar[str] = "security"
62
+
63
+ def register_middleware(self, ctx: Context) -> None:
64
+ cfg = ctx.config.security
65
+ ctx.add_middleware(
66
+ SecurityHeadersMiddleware,
67
+ priority=PRIORITY_SECURITY,
68
+ headers=build_security_headers(ctx),
69
+ )
70
+ if cfg.trusted_hosts:
71
+ # Slightly outside the headers middleware: reject bad hosts early.
72
+ ctx.add_middleware(
73
+ TrustedHostMiddleware,
74
+ priority=PRIORITY_SECURITY - 10,
75
+ allowed_hosts=cfg.trusted_hosts,
76
+ )
77
+ if cfg.https_redirect:
78
+ ctx.add_middleware(HTTPSRedirectMiddleware, priority=PRIORITY_SECURITY - 20)
prodkit/py.typed ADDED
File without changes
@@ -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
+ [![CI](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml/badge.svg)](https://github.com/Pushkarpant/PRODKIT/actions/workflows/ci.yml)
63
+ [![PyPI](https://img.shields.io/pypi/v/prodkit)](https://pypi.org/project/prodkit/)
64
+ [![Python](https://img.shields.io/pypi/pyversions/prodkit)](https://pypi.org/project/prodkit/)
65
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.*
@@ -0,0 +1,25 @@
1
+ prodkit/__init__.py,sha256=cU6DON8Gq38mV0vGsqOOfNVzGisyPjoFMsS4AFSisYI,1286
2
+ prodkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ prodkit/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ prodkit/contracts/plugin.py,sha256=DZntTf1fq97nuWTUXEbSHaxgjrT_JFOvQmADryBbcEo,1713
5
+ prodkit/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ prodkit/core/config.py,sha256=AVNTbSZlSyjevGvEZBa4Bl7RJx_vwVVyZTkzVOvlwwg,8491
7
+ prodkit/core/context.py,sha256=8pQHY2XpbQAOpxz3mfYmWkq2mCDa6Nmka2SkthPG09s,1644
8
+ prodkit/core/event_bus.py,sha256=SptknC8po7wP9NYK5zRqBbWJe0W8BmSt6KNV7UKxih4,1485
9
+ prodkit/core/exceptions.py,sha256=AdujJIe5YjEE-C5p9mYQIEH5x6bxYe2vZf0Ac0l5bqA,598
10
+ prodkit/core/lifecycle.py,sha256=imHauQNQQechsXsGjE1SwliQlEUHBVZQ088XZkJ3zJQ,1716
11
+ prodkit/core/plugin_manager.py,sha256=Hm3MWDGWxuJiBU_oyuYhD1donUytR-3OJjc6FqktwG4,2099
12
+ prodkit/core/production.py,sha256=12BnsGqs6PD9NG6DGJ5HNPNpiXNr0V-smRZuFxdQeLM,4316
13
+ prodkit/core/registry.py,sha256=hxv6gpS659nlwzaFdelXbZCVkBV-kaGb_eLa-Hlr5Mg,1206
14
+ prodkit/plugins/__init__.py,sha256=ucOkndUtRS0KZ-mJQ9erjI_DCADZYpe-k4Pb5F0N9j4,1685
15
+ prodkit/plugins/compression/__init__.py,sha256=ikhnES9z7TdZuF6KmFsmlT2p5S8TCZUJ3H8053QTb-g,654
16
+ prodkit/plugins/cors/__init__.py,sha256=lB1NjtDdCgZfQM9WfbuBiDCgDE5JA-p0xTnhxMJU6YY,1234
17
+ prodkit/plugins/errors/__init__.py,sha256=RtiXpBw1T5Gx7s-zg78OPHsYiE8kjAkrOf4wcT_I0ms,3936
18
+ prodkit/plugins/health/__init__.py,sha256=p6nOQRArHmPF3QSLqxFkyazH9wjgMh92HkOpI6dTvQM,2695
19
+ prodkit/plugins/logging/__init__.py,sha256=9MjwvvICQMZSBRtpGnVIwEGQt8MKqGr5tvQyVWHI_kE,4080
20
+ prodkit/plugins/request_id/__init__.py,sha256=5hLVjlKJJQVz4xV1CJL0c589tx5BfIHxNJG47YfobJA,2140
21
+ prodkit/plugins/security/__init__.py,sha256=KKTA3Gx9ELOhjBTjRvJUOKVkU1mgo1pFa_clSLgQabg,2903
22
+ prodkit-0.1.0.dist-info/METADATA,sha256=VyBQ5SW1MxJdRQPUqam1UqQybh7CfogceoAOhlFSgqU,8698
23
+ prodkit-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
24
+ prodkit-0.1.0.dist-info/licenses/LICENSE,sha256=Qvw6r3jRq2Oobj1LnjqC_3V7T46qCLcE69JXpxPKVt4,1068
25
+ prodkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.