@martinffx/atelier 1.4.0 → 3.0.0
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.
- package/CHANGELOG.md +38 -0
- package/README.md +95 -220
- package/agents/architect.md +6 -6
- package/agents/oracle.md +1 -1
- package/agents/{recon.md → sentinel.md} +3 -3
- package/dist/atelier.js +167 -26
- package/package.json +2 -1
- package/skills/atelier-orchestrator/SKILL.md +180 -0
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/SKILL.md +2 -2
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +9 -9
- package/skills/code-subagents/SKILL.md +44 -75
- package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
- package/skills/code-subagents/references/implementor-prompt.md +10 -11
- package/skills/spec-brainstorm/SKILL.md +21 -16
- package/skills/spec-finish/SKILL.md +14 -14
- package/skills/spec-implement/SKILL.md +42 -42
- package/skills/spec-plan/SKILL.md +72 -26
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
- package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/spec-orchestrator/SKILL.md +0 -145
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
# Middleware in FastAPI
|
|
2
|
-
|
|
3
|
-
Middleware runs before and after each request, useful for cross-cutting concerns like logging, timing, CORS, authentication.
|
|
4
|
-
|
|
5
|
-
## Built-in Middleware
|
|
6
|
-
|
|
7
|
-
### CORS Middleware
|
|
8
|
-
|
|
9
|
-
```python
|
|
10
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
-
|
|
12
|
-
app.add_middleware(
|
|
13
|
-
CORSMiddleware,
|
|
14
|
-
allow_origins=["https://example.com"], # Allowed origins
|
|
15
|
-
allow_credentials=True,
|
|
16
|
-
allow_methods=["*"], # All HTTP methods
|
|
17
|
-
allow_headers=["*"], # All headers
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
# Development: allow all
|
|
21
|
-
app.add_middleware(
|
|
22
|
-
CORSMiddleware,
|
|
23
|
-
allow_origins=["*"],
|
|
24
|
-
allow_credentials=True,
|
|
25
|
-
allow_methods=["*"],
|
|
26
|
-
allow_headers=["*"],
|
|
27
|
-
)
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Trusted Host Middleware
|
|
31
|
-
|
|
32
|
-
```python
|
|
33
|
-
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
34
|
-
|
|
35
|
-
app.add_middleware(
|
|
36
|
-
TrustedHostMiddleware,
|
|
37
|
-
allowed_hosts=["example.com", "*.example.com"],
|
|
38
|
-
)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### GZip Middleware
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
from fastapi.middleware.gzip import GZipMiddleware
|
|
45
|
-
|
|
46
|
-
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Custom Middleware
|
|
50
|
-
|
|
51
|
-
### Function-Based Middleware
|
|
52
|
-
|
|
53
|
-
```python
|
|
54
|
-
from fastapi import Request
|
|
55
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
56
|
-
from time import time
|
|
57
|
-
|
|
58
|
-
class TimingMiddleware(BaseHTTPMiddleware):
|
|
59
|
-
async def dispatch(self, request: Request, call_next):
|
|
60
|
-
start_time = time()
|
|
61
|
-
|
|
62
|
-
# Process request
|
|
63
|
-
response = await call_next(request)
|
|
64
|
-
|
|
65
|
-
# Add timing header
|
|
66
|
-
process_time = time() - start_time
|
|
67
|
-
response.headers["X-Process-Time"] = str(process_time)
|
|
68
|
-
|
|
69
|
-
return response
|
|
70
|
-
|
|
71
|
-
app.add_middleware(TimingMiddleware)
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Request ID Middleware
|
|
75
|
-
|
|
76
|
-
```python
|
|
77
|
-
import uuid
|
|
78
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
79
|
-
|
|
80
|
-
class RequestIDMiddleware(BaseHTTPMiddleware):
|
|
81
|
-
async def dispatch(self, request: Request, call_next):
|
|
82
|
-
# Generate request ID
|
|
83
|
-
request_id = str(uuid.uuid4())
|
|
84
|
-
|
|
85
|
-
# Add to request state
|
|
86
|
-
request.state.request_id = request_id
|
|
87
|
-
|
|
88
|
-
# Process request
|
|
89
|
-
response = await call_next(request)
|
|
90
|
-
|
|
91
|
-
# Add to response headers
|
|
92
|
-
response.headers["X-Request-ID"] = request_id
|
|
93
|
-
|
|
94
|
-
return response
|
|
95
|
-
|
|
96
|
-
app.add_middleware(RequestIDMiddleware)
|
|
97
|
-
|
|
98
|
-
# Access in route
|
|
99
|
-
@app.get("/")
|
|
100
|
-
def read_root(request: Request):
|
|
101
|
-
request_id = request.state.request_id
|
|
102
|
-
return {"request_id": request_id}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Logging Middleware
|
|
106
|
-
|
|
107
|
-
```python
|
|
108
|
-
import logging
|
|
109
|
-
from time import time
|
|
110
|
-
|
|
111
|
-
logger = logging.getLogger(__name__)
|
|
112
|
-
|
|
113
|
-
class LoggingMiddleware(BaseHTTPMiddleware):
|
|
114
|
-
async def dispatch(self, request: Request, call_next):
|
|
115
|
-
# Log request
|
|
116
|
-
logger.info(
|
|
117
|
-
f"Request: {request.method} {request.url.path}",
|
|
118
|
-
extra={
|
|
119
|
-
"method": request.method,
|
|
120
|
-
"path": request.url.path,
|
|
121
|
-
"client": request.client.host,
|
|
122
|
-
},
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
start_time = time()
|
|
126
|
-
|
|
127
|
-
# Process request
|
|
128
|
-
response = await call_next(request)
|
|
129
|
-
|
|
130
|
-
# Log response
|
|
131
|
-
process_time = time() - start_time
|
|
132
|
-
logger.info(
|
|
133
|
-
f"Response: {response.status_code}",
|
|
134
|
-
extra={
|
|
135
|
-
"status_code": response.status_code,
|
|
136
|
-
"process_time": process_time,
|
|
137
|
-
},
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
return response
|
|
141
|
-
|
|
142
|
-
app.add_middleware(LoggingMiddleware)
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Error Handling Middleware
|
|
146
|
-
|
|
147
|
-
```python
|
|
148
|
-
import traceback
|
|
149
|
-
from fastapi.responses import JSONResponse
|
|
150
|
-
|
|
151
|
-
class ErrorHandlingMiddleware(BaseHTTPMiddleware):
|
|
152
|
-
async def dispatch(self, request: Request, call_next):
|
|
153
|
-
try:
|
|
154
|
-
return await call_next(request)
|
|
155
|
-
except Exception as exc:
|
|
156
|
-
# Log error
|
|
157
|
-
logger.error(
|
|
158
|
-
f"Unhandled exception: {exc}",
|
|
159
|
-
exc_info=True,
|
|
160
|
-
extra={"traceback": traceback.format_exc()},
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
# Return error response
|
|
164
|
-
return JSONResponse(
|
|
165
|
-
status_code=500,
|
|
166
|
-
content={
|
|
167
|
-
"error": "Internal server error",
|
|
168
|
-
"detail": str(exc),
|
|
169
|
-
},
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
app.add_middleware(ErrorHandlingMiddleware)
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Authentication Middleware
|
|
176
|
-
|
|
177
|
-
```python
|
|
178
|
-
from fastapi import HTTPException
|
|
179
|
-
|
|
180
|
-
class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
181
|
-
async def dispatch(self, request: Request, call_next):
|
|
182
|
-
# Skip auth for public paths
|
|
183
|
-
if request.url.path in ["/health", "/docs", "/openapi.json"]:
|
|
184
|
-
return await call_next(request)
|
|
185
|
-
|
|
186
|
-
# Check auth token
|
|
187
|
-
auth_header = request.headers.get("Authorization")
|
|
188
|
-
if not auth_header or not auth_header.startswith("Bearer "):
|
|
189
|
-
return JSONResponse(
|
|
190
|
-
status_code=401,
|
|
191
|
-
content={"error": "Missing or invalid authorization header"},
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
token = auth_header[7:] # Remove "Bearer "
|
|
195
|
-
|
|
196
|
-
try:
|
|
197
|
-
# Verify token
|
|
198
|
-
user = verify_token(token)
|
|
199
|
-
request.state.user = user
|
|
200
|
-
except Exception:
|
|
201
|
-
return JSONResponse(
|
|
202
|
-
status_code=401,
|
|
203
|
-
content={"error": "Invalid token"},
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
return await call_next(request)
|
|
207
|
-
|
|
208
|
-
app.add_middleware(AuthenticationMiddleware)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Rate Limiting Middleware
|
|
212
|
-
|
|
213
|
-
```python
|
|
214
|
-
from collections import defaultdict
|
|
215
|
-
from datetime import datetime, timedelta
|
|
216
|
-
|
|
217
|
-
class RateLimitMiddleware(BaseHTTPMiddleware):
|
|
218
|
-
def __init__(self, app, calls: int = 100, period: int = 60):
|
|
219
|
-
super().__init__(app)
|
|
220
|
-
self.calls = calls
|
|
221
|
-
self.period = period
|
|
222
|
-
self.clients = defaultdict(list)
|
|
223
|
-
|
|
224
|
-
async def dispatch(self, request: Request, call_next):
|
|
225
|
-
client = request.client.host
|
|
226
|
-
|
|
227
|
-
# Clean old timestamps
|
|
228
|
-
now = datetime.now()
|
|
229
|
-
cutoff = now - timedelta(seconds=self.period)
|
|
230
|
-
self.clients[client] = [
|
|
231
|
-
ts for ts in self.clients[client] if ts > cutoff
|
|
232
|
-
]
|
|
233
|
-
|
|
234
|
-
# Check rate limit
|
|
235
|
-
if len(self.clients[client]) >= self.calls:
|
|
236
|
-
return JSONResponse(
|
|
237
|
-
status_code=429,
|
|
238
|
-
content={"error": "Rate limit exceeded"},
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
# Record this request
|
|
242
|
-
self.clients[client].append(now)
|
|
243
|
-
|
|
244
|
-
return await call_next(request)
|
|
245
|
-
|
|
246
|
-
app.add_middleware(RateLimitMiddleware, calls=100, period=60)
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
## Database Session Middleware
|
|
250
|
-
|
|
251
|
-
```python
|
|
252
|
-
from sqlalchemy.orm import Session
|
|
253
|
-
|
|
254
|
-
class DatabaseSessionMiddleware(BaseHTTPMiddleware):
|
|
255
|
-
async def dispatch(self, request: Request, call_next):
|
|
256
|
-
# Create session
|
|
257
|
-
db = SessionLocal()
|
|
258
|
-
request.state.db = db
|
|
259
|
-
|
|
260
|
-
try:
|
|
261
|
-
response = await call_next(request)
|
|
262
|
-
# Commit on success
|
|
263
|
-
db.commit()
|
|
264
|
-
return response
|
|
265
|
-
except Exception:
|
|
266
|
-
# Rollback on error
|
|
267
|
-
db.rollback()
|
|
268
|
-
raise
|
|
269
|
-
finally:
|
|
270
|
-
db.close()
|
|
271
|
-
|
|
272
|
-
app.add_middleware(DatabaseSessionMiddleware)
|
|
273
|
-
|
|
274
|
-
# Access in route
|
|
275
|
-
@app.get("/users")
|
|
276
|
-
def list_users(request: Request):
|
|
277
|
-
db: Session = request.state.db
|
|
278
|
-
return db.query(User).all()
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## Middleware Order
|
|
282
|
-
|
|
283
|
-
Middleware runs in reverse order of addition:
|
|
284
|
-
|
|
285
|
-
```python
|
|
286
|
-
# Execution order: Error → Logging → Timing → CORS → Route
|
|
287
|
-
|
|
288
|
-
app.add_middleware(CORSMiddleware, ...) # 4. CORS (innermost)
|
|
289
|
-
app.add_middleware(TimingMiddleware) # 3. Timing
|
|
290
|
-
app.add_middleware(LoggingMiddleware) # 2. Logging
|
|
291
|
-
app.add_middleware(ErrorHandlingMiddleware) # 1. Error (outermost)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
**Request flow:**
|
|
295
|
-
1. ErrorHandlingMiddleware (enter)
|
|
296
|
-
2. LoggingMiddleware (enter)
|
|
297
|
-
3. TimingMiddleware (enter)
|
|
298
|
-
4. CORSMiddleware (enter)
|
|
299
|
-
5. Route handler
|
|
300
|
-
6. CORSMiddleware (exit)
|
|
301
|
-
7. TimingMiddleware (exit)
|
|
302
|
-
8. LoggingMiddleware (exit)
|
|
303
|
-
9. ErrorHandlingMiddleware (exit)
|
|
304
|
-
|
|
305
|
-
## Pure ASGI Middleware
|
|
306
|
-
|
|
307
|
-
For maximum performance, use pure ASGI middleware:
|
|
308
|
-
|
|
309
|
-
```python
|
|
310
|
-
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
311
|
-
|
|
312
|
-
class SimpleMiddleware:
|
|
313
|
-
def __init__(self, app: ASGIApp):
|
|
314
|
-
self.app = app
|
|
315
|
-
|
|
316
|
-
async def __call__(self, scope: Scope, receive: Receive, send: Send):
|
|
317
|
-
if scope["type"] != "http":
|
|
318
|
-
await self.app(scope, receive, send)
|
|
319
|
-
return
|
|
320
|
-
|
|
321
|
-
# Wrap send to modify response
|
|
322
|
-
async def send_wrapper(message):
|
|
323
|
-
if message["type"] == "http.response.start":
|
|
324
|
-
headers = list(message["headers"])
|
|
325
|
-
headers.append((b"x-custom-header", b"value"))
|
|
326
|
-
message["headers"] = headers
|
|
327
|
-
await send(message)
|
|
328
|
-
|
|
329
|
-
await self.app(scope, receive, send_wrapper)
|
|
330
|
-
|
|
331
|
-
app.add_middleware(SimpleMiddleware)
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
## Request State
|
|
335
|
-
|
|
336
|
-
Share data between middleware and routes:
|
|
337
|
-
|
|
338
|
-
```python
|
|
339
|
-
class ContextMiddleware(BaseHTTPMiddleware):
|
|
340
|
-
async def dispatch(self, request: Request, call_next):
|
|
341
|
-
# Set request state
|
|
342
|
-
request.state.request_id = str(uuid.uuid4())
|
|
343
|
-
request.state.start_time = time()
|
|
344
|
-
|
|
345
|
-
response = await call_next(request)
|
|
346
|
-
|
|
347
|
-
# Access request state
|
|
348
|
-
duration = time() - request.state.start_time
|
|
349
|
-
response.headers["X-Duration"] = str(duration)
|
|
350
|
-
|
|
351
|
-
return response
|
|
352
|
-
|
|
353
|
-
@app.get("/")
|
|
354
|
-
def read_root(request: Request):
|
|
355
|
-
# Access state set by middleware
|
|
356
|
-
request_id = request.state.request_id
|
|
357
|
-
return {"request_id": request_id}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
## Conditional Middleware
|
|
361
|
-
|
|
362
|
-
Apply middleware conditionally:
|
|
363
|
-
|
|
364
|
-
```python
|
|
365
|
-
class ConditionalMiddleware(BaseHTTPMiddleware):
|
|
366
|
-
async def dispatch(self, request: Request, call_next):
|
|
367
|
-
# Skip for specific paths
|
|
368
|
-
if request.url.path.startswith("/static"):
|
|
369
|
-
return await call_next(request)
|
|
370
|
-
|
|
371
|
-
# Skip for specific methods
|
|
372
|
-
if request.method == "OPTIONS":
|
|
373
|
-
return await call_next(request)
|
|
374
|
-
|
|
375
|
-
# Apply middleware logic
|
|
376
|
-
# ...
|
|
377
|
-
|
|
378
|
-
return await call_next(request)
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
## Response Modification
|
|
382
|
-
|
|
383
|
-
Modify response before returning:
|
|
384
|
-
|
|
385
|
-
```python
|
|
386
|
-
class ResponseModificationMiddleware(BaseHTTPMiddleware):
|
|
387
|
-
async def dispatch(self, request: Request, call_next):
|
|
388
|
-
response = await call_next(request)
|
|
389
|
-
|
|
390
|
-
# Add security headers
|
|
391
|
-
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
392
|
-
response.headers["X-Frame-Options"] = "DENY"
|
|
393
|
-
response.headers["X-XSS-Protection"] = "1; mode=block"
|
|
394
|
-
|
|
395
|
-
return response
|
|
396
|
-
|
|
397
|
-
app.add_middleware(ResponseModificationMiddleware)
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
## Testing Middleware
|
|
401
|
-
|
|
402
|
-
```python
|
|
403
|
-
from fastapi.testclient import TestClient
|
|
404
|
-
|
|
405
|
-
def test_timing_middleware():
|
|
406
|
-
client = TestClient(app)
|
|
407
|
-
response = client.get("/")
|
|
408
|
-
|
|
409
|
-
assert "X-Process-Time" in response.headers
|
|
410
|
-
assert float(response.headers["X-Process-Time"]) > 0
|
|
411
|
-
|
|
412
|
-
def test_request_id_middleware():
|
|
413
|
-
client = TestClient(app)
|
|
414
|
-
response = client.get("/")
|
|
415
|
-
|
|
416
|
-
assert "X-Request-ID" in response.headers
|
|
417
|
-
assert len(response.headers["X-Request-ID"]) == 36 # UUID length
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
## Best Practices
|
|
421
|
-
|
|
422
|
-
1. **Order matters**: Add middleware in correct order (error handling first)
|
|
423
|
-
2. **Use request.state**: Share data between middleware and routes
|
|
424
|
-
3. **Handle errors**: Catch exceptions in middleware
|
|
425
|
-
4. **Performance**: Keep middleware fast (runs on every request)
|
|
426
|
-
5. **Skip when needed**: Don't apply to health checks, static files
|
|
427
|
-
6. **Type hints**: Use proper types for Request, Response
|
|
428
|
-
7. **Pure ASGI for performance**: Use ASGI middleware for hot paths
|
|
429
|
-
|
|
430
|
-
## Common Middleware Stack
|
|
431
|
-
|
|
432
|
-
Recommended middleware order:
|
|
433
|
-
|
|
434
|
-
```python
|
|
435
|
-
# 1. Error handling (outermost)
|
|
436
|
-
app.add_middleware(ErrorHandlingMiddleware)
|
|
437
|
-
|
|
438
|
-
# 2. Logging
|
|
439
|
-
app.add_middleware(LoggingMiddleware)
|
|
440
|
-
|
|
441
|
-
# 3. Timing
|
|
442
|
-
app.add_middleware(TimingMiddleware)
|
|
443
|
-
|
|
444
|
-
# 4. Rate limiting
|
|
445
|
-
app.add_middleware(RateLimitMiddleware)
|
|
446
|
-
|
|
447
|
-
# 5. Authentication
|
|
448
|
-
app.add_middleware(AuthenticationMiddleware)
|
|
449
|
-
|
|
450
|
-
# 6. Database session
|
|
451
|
-
app.add_middleware(DatabaseSessionMiddleware)
|
|
452
|
-
|
|
453
|
-
# 7. CORS (innermost)
|
|
454
|
-
app.add_middleware(CORSMiddleware, ...)
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
## Summary
|
|
458
|
-
|
|
459
|
-
**Built-in middleware:**
|
|
460
|
-
- CORSMiddleware (cross-origin requests)
|
|
461
|
-
- TrustedHostMiddleware (host validation)
|
|
462
|
-
- GZipMiddleware (compression)
|
|
463
|
-
|
|
464
|
-
**Custom middleware:**
|
|
465
|
-
- Timing (performance monitoring)
|
|
466
|
-
- Logging (request/response logging)
|
|
467
|
-
- Error handling (catch all exceptions)
|
|
468
|
-
- Authentication (verify tokens)
|
|
469
|
-
- Rate limiting (prevent abuse)
|
|
470
|
-
|
|
471
|
-
**Key concepts:**
|
|
472
|
-
- Middleware runs on every request
|
|
473
|
-
- Order matters (reverse order of addition)
|
|
474
|
-
- Use `request.state` for sharing data
|
|
475
|
-
- Keep middleware fast
|