@malamute/ai-rules 1.0.0 → 1.3.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.
Files changed (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -0,0 +1,254 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # FastAPI Background Tasks
7
+
8
+ ## Simple Background Tasks
9
+
10
+ ```python
11
+ from fastapi import BackgroundTasks
12
+
13
+ def write_log(message: str):
14
+ with open("log.txt", "a") as f:
15
+ f.write(f"{datetime.now()}: {message}\n")
16
+
17
+ @router.post("/items")
18
+ async def create_item(
19
+ item: ItemCreate,
20
+ background_tasks: BackgroundTasks,
21
+ ) -> ItemResponse:
22
+ # Process item
23
+ created_item = await item_service.create(item)
24
+
25
+ # Queue background task
26
+ background_tasks.add_task(write_log, f"Item created: {created_item.id}")
27
+
28
+ # Return immediately
29
+ return created_item
30
+ ```
31
+
32
+ ## Async Background Tasks
33
+
34
+ ```python
35
+ async def send_notification(user_id: int, message: str):
36
+ async with httpx.AsyncClient() as client:
37
+ await client.post(
38
+ "https://notification-service/send",
39
+ json={"user_id": user_id, "message": message},
40
+ )
41
+
42
+ @router.post("/orders")
43
+ async def create_order(
44
+ order: OrderCreate,
45
+ background_tasks: BackgroundTasks,
46
+ db: DbSession,
47
+ ) -> OrderResponse:
48
+ created_order = await order_service.create(db, order)
49
+
50
+ # Async task
51
+ background_tasks.add_task(
52
+ send_notification,
53
+ order.user_id,
54
+ f"Order {created_order.id} confirmed",
55
+ )
56
+
57
+ return created_order
58
+ ```
59
+
60
+ ## Multiple Background Tasks
61
+
62
+ ```python
63
+ @router.post("/signup")
64
+ async def signup(
65
+ user: UserCreate,
66
+ background_tasks: BackgroundTasks,
67
+ ) -> UserResponse:
68
+ created_user = await user_service.create(user)
69
+
70
+ # Queue multiple tasks
71
+ background_tasks.add_task(send_welcome_email, created_user.email)
72
+ background_tasks.add_task(notify_admin, created_user.id)
73
+ background_tasks.add_task(update_analytics, "new_signup")
74
+
75
+ return created_user
76
+ ```
77
+
78
+ ## Background Tasks in Dependencies
79
+
80
+ ```python
81
+ async def log_request(
82
+ request: Request,
83
+ background_tasks: BackgroundTasks,
84
+ ):
85
+ background_tasks.add_task(
86
+ log_to_database,
87
+ path=request.url.path,
88
+ method=request.method,
89
+ timestamp=datetime.now(),
90
+ )
91
+
92
+ @router.get("/data", dependencies=[Depends(log_request)])
93
+ async def get_data() -> dict:
94
+ return {"data": "value"}
95
+ ```
96
+
97
+ ## Long-Running Tasks with Celery
98
+
99
+ ```python
100
+ # tasks.py
101
+ from celery import Celery
102
+
103
+ celery_app = Celery(
104
+ "tasks",
105
+ broker="redis://localhost:6379/0",
106
+ backend="redis://localhost:6379/0",
107
+ )
108
+
109
+ @celery_app.task
110
+ def process_large_file(file_path: str) -> dict:
111
+ # Long-running process
112
+ result = heavy_computation(file_path)
113
+ return {"status": "completed", "result": result}
114
+
115
+ # router.py
116
+ from tasks import process_large_file
117
+
118
+ @router.post("/process")
119
+ async def process_file(file: UploadFile) -> dict:
120
+ # Save file
121
+ file_path = f"/tmp/{file.filename}"
122
+ async with aiofiles.open(file_path, "wb") as f:
123
+ await f.write(await file.read())
124
+
125
+ # Queue Celery task
126
+ task = process_large_file.delay(file_path)
127
+
128
+ return {"task_id": task.id, "status": "processing"}
129
+
130
+ @router.get("/process/{task_id}")
131
+ async def get_task_status(task_id: str) -> dict:
132
+ task = process_large_file.AsyncResult(task_id)
133
+
134
+ if task.ready():
135
+ return {"status": "completed", "result": task.result}
136
+
137
+ return {"status": task.status}
138
+ ```
139
+
140
+ ## ARQ (Async Redis Queue)
141
+
142
+ ```python
143
+ # tasks.py
144
+ from arq import create_pool
145
+ from arq.connections import RedisSettings
146
+
147
+ async def send_email_task(ctx, email: str, subject: str, body: str):
148
+ await email_service.send(email, subject, body)
149
+
150
+ class WorkerSettings:
151
+ functions = [send_email_task]
152
+ redis_settings = RedisSettings()
153
+
154
+ # router.py
155
+ from arq import create_pool
156
+
157
+ @router.post("/send-email")
158
+ async def send_email(email: EmailSchema) -> dict:
159
+ redis = await create_pool(RedisSettings())
160
+
161
+ job = await redis.enqueue_job(
162
+ "send_email_task",
163
+ email.to,
164
+ email.subject,
165
+ email.body,
166
+ )
167
+
168
+ return {"job_id": job.job_id, "status": "queued"}
169
+ ```
170
+
171
+ ## Periodic Tasks
172
+
173
+ ```python
174
+ # Using APScheduler
175
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
176
+
177
+ scheduler = AsyncIOScheduler()
178
+
179
+ async def cleanup_expired_sessions():
180
+ async with async_session() as db:
181
+ await db.execute(
182
+ delete(Session).where(Session.expires_at < datetime.utcnow())
183
+ )
184
+ await db.commit()
185
+
186
+ @asynccontextmanager
187
+ async def lifespan(app: FastAPI):
188
+ # Start scheduler
189
+ scheduler.add_job(
190
+ cleanup_expired_sessions,
191
+ "interval",
192
+ hours=1,
193
+ )
194
+ scheduler.start()
195
+ yield
196
+ # Shutdown
197
+ scheduler.shutdown()
198
+
199
+ app = FastAPI(lifespan=lifespan)
200
+ ```
201
+
202
+ ## Task Status Tracking
203
+
204
+ ```python
205
+ from enum import Enum
206
+ import uuid
207
+
208
+ class TaskStatus(str, Enum):
209
+ PENDING = "pending"
210
+ RUNNING = "running"
211
+ COMPLETED = "completed"
212
+ FAILED = "failed"
213
+
214
+ # In-memory store (use Redis in production)
215
+ tasks_store: dict[str, dict] = {}
216
+
217
+ async def process_with_tracking(task_id: str, data: dict):
218
+ tasks_store[task_id]["status"] = TaskStatus.RUNNING
219
+
220
+ try:
221
+ result = await heavy_process(data)
222
+ tasks_store[task_id].update({
223
+ "status": TaskStatus.COMPLETED,
224
+ "result": result,
225
+ })
226
+ except Exception as e:
227
+ tasks_store[task_id].update({
228
+ "status": TaskStatus.FAILED,
229
+ "error": str(e),
230
+ })
231
+
232
+ @router.post("/process")
233
+ async def start_processing(
234
+ data: ProcessData,
235
+ background_tasks: BackgroundTasks,
236
+ ) -> dict:
237
+ task_id = str(uuid.uuid4())
238
+
239
+ tasks_store[task_id] = {
240
+ "status": TaskStatus.PENDING,
241
+ "created_at": datetime.utcnow(),
242
+ }
243
+
244
+ background_tasks.add_task(process_with_tracking, task_id, data.dict())
245
+
246
+ return {"task_id": task_id}
247
+
248
+ @router.get("/process/{task_id}")
249
+ async def get_status(task_id: str) -> dict:
250
+ if task_id not in tasks_store:
251
+ raise HTTPException(404, "Task not found")
252
+
253
+ return tasks_store[task_id]
254
+ ```
@@ -0,0 +1,170 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # FastAPI Dependency Injection
7
+
8
+ ## Basic Dependencies
9
+
10
+ ```python
11
+ # GOOD - reusable dependency
12
+ from typing import Annotated
13
+ from fastapi import Depends
14
+
15
+ async def get_db():
16
+ async with async_session() as session:
17
+ yield session
18
+
19
+ DbSession = Annotated[AsyncSession, Depends(get_db)]
20
+
21
+ @router.get("/users/{user_id}")
22
+ async def get_user(user_id: int, db: DbSession) -> User:
23
+ return await db.get(User, user_id)
24
+ ```
25
+
26
+ ## Dependency with Parameters
27
+
28
+ ```python
29
+ # GOOD - parameterized dependency
30
+ def get_pagination(
31
+ page: int = Query(1, ge=1),
32
+ size: int = Query(20, ge=1, le=100),
33
+ ) -> Pagination:
34
+ return Pagination(page=page, size=size)
35
+
36
+ Paginated = Annotated[Pagination, Depends(get_pagination)]
37
+
38
+ @router.get("/users")
39
+ async def list_users(pagination: Paginated) -> Page[User]:
40
+ ...
41
+ ```
42
+
43
+ ## Class-Based Dependencies
44
+
45
+ ```python
46
+ # GOOD - stateful dependency
47
+ class RateLimiter:
48
+ def __init__(self, requests_per_minute: int):
49
+ self.rpm = requests_per_minute
50
+ self.requests: dict[str, list[float]] = {}
51
+
52
+ async def __call__(self, request: Request) -> None:
53
+ client_ip = request.client.host
54
+ now = time.time()
55
+
56
+ # Clean old requests
57
+ self.requests[client_ip] = [
58
+ t for t in self.requests.get(client_ip, [])
59
+ if now - t < 60
60
+ ]
61
+
62
+ if len(self.requests[client_ip]) >= self.rpm:
63
+ raise HTTPException(429, "Rate limit exceeded")
64
+
65
+ self.requests[client_ip].append(now)
66
+
67
+ rate_limiter = RateLimiter(requests_per_minute=60)
68
+
69
+ @router.get("/api/data", dependencies=[Depends(rate_limiter)])
70
+ async def get_data():
71
+ ...
72
+ ```
73
+
74
+ ## Authentication Dependencies
75
+
76
+ ```python
77
+ # GOOD - reusable auth dependency
78
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
79
+
80
+ async def get_current_user(
81
+ token: Annotated[str, Depends(oauth2_scheme)],
82
+ db: DbSession,
83
+ ) -> User:
84
+ payload = verify_token(token)
85
+ if not payload:
86
+ raise HTTPException(401, "Invalid token")
87
+
88
+ user = await db.get(User, payload["sub"])
89
+ if not user:
90
+ raise HTTPException(401, "User not found")
91
+
92
+ return user
93
+
94
+ CurrentUser = Annotated[User, Depends(get_current_user)]
95
+
96
+ # Role-based dependency
97
+ def require_role(role: str):
98
+ async def check_role(user: CurrentUser) -> User:
99
+ if user.role != role:
100
+ raise HTTPException(403, "Insufficient permissions")
101
+ return user
102
+ return check_role
103
+
104
+ AdminUser = Annotated[User, Depends(require_role("admin"))]
105
+ ```
106
+
107
+ ## Dependency Chaining
108
+
109
+ ```python
110
+ # Dependencies can depend on other dependencies
111
+ async def get_user_service(db: DbSession) -> UserService:
112
+ return UserService(db)
113
+
114
+ async def get_current_user_with_service(
115
+ token: Annotated[str, Depends(oauth2_scheme)],
116
+ service: Annotated[UserService, Depends(get_user_service)],
117
+ ) -> User:
118
+ return await service.get_by_token(token)
119
+ ```
120
+
121
+ ## Global Dependencies
122
+
123
+ ```python
124
+ # Apply to all routes in router
125
+ router = APIRouter(
126
+ prefix="/admin",
127
+ dependencies=[Depends(require_role("admin"))],
128
+ )
129
+
130
+ # Apply to entire app
131
+ app = FastAPI(dependencies=[Depends(verify_api_key)])
132
+ ```
133
+
134
+ ## Yield Dependencies (Context Managers)
135
+
136
+ ```python
137
+ # GOOD - cleanup after request
138
+ async def get_db_transaction():
139
+ async with async_session() as session:
140
+ async with session.begin():
141
+ yield session
142
+ # Commit happens automatically if no exception
143
+ # Rollback happens automatically on exception
144
+
145
+ # GOOD - resource cleanup
146
+ async def get_temp_file():
147
+ path = Path(tempfile.mktemp())
148
+ try:
149
+ yield path
150
+ finally:
151
+ path.unlink(missing_ok=True)
152
+ ```
153
+
154
+ ## Testing Dependencies
155
+
156
+ ```python
157
+ # Override dependencies in tests
158
+ from fastapi.testclient import TestClient
159
+
160
+ def get_test_db():
161
+ return TestDatabase()
162
+
163
+ app.dependency_overrides[get_db] = get_test_db
164
+
165
+ with TestClient(app) as client:
166
+ response = client.get("/users")
167
+
168
+ # Clean up
169
+ app.dependency_overrides.clear()
170
+ ```
@@ -1,6 +1,11 @@
1
1
  ---
2
2
  paths:
3
- - "**/*.py"
3
+ - "**/routers/**/*.py"
4
+ - "**/routes/**/*.py"
5
+ - "**/api/**/*.py"
6
+ - "**/endpoints/**/*.py"
7
+ - "**/main.py"
8
+ - "**/app.py"
4
9
  ---
5
10
 
6
11
  # FastAPI Rules
@@ -270,3 +275,58 @@ app = FastAPI(
270
275
  if settings.environment == "production":
271
276
  app = FastAPI(docs_url=None, redoc_url=None)
272
277
  ```
278
+
279
+ ## Type Hints
280
+
281
+ Always use modern syntax (Python 3.10+):
282
+
283
+ ```python
284
+ # Modern type hints
285
+ async def get_user(user_id: int) -> User | None:
286
+ ...
287
+
288
+ def process_items(items: list[str]) -> dict[str, int]:
289
+ ...
290
+
291
+ # Annotated for dependency injection
292
+ DbSession = Annotated[AsyncSession, Depends(get_db)]
293
+ CurrentUser = Annotated[User, Depends(get_current_user)]
294
+
295
+ @router.get("/{user_id}")
296
+ async def get_user(
297
+ user_id: int,
298
+ db: DbSession,
299
+ current_user: CurrentUser,
300
+ ) -> UserResponse:
301
+ ...
302
+ ```
303
+
304
+ ## Custom Exception Handling
305
+
306
+ ```python
307
+ # Custom exceptions
308
+ class NotFoundError(Exception):
309
+ def __init__(self, resource: str, id: int):
310
+ self.resource = resource
311
+ self.id = id
312
+
313
+ class BusinessError(Exception):
314
+ def __init__(self, message: str, code: str):
315
+ self.message = message
316
+ self.code = code
317
+
318
+ # FastAPI exception handlers
319
+ @app.exception_handler(NotFoundError)
320
+ async def not_found_handler(request: Request, exc: NotFoundError):
321
+ return JSONResponse(
322
+ status_code=404,
323
+ content={"detail": f"{exc.resource} {exc.id} not found"}
324
+ )
325
+
326
+ @app.exception_handler(BusinessError)
327
+ async def business_error_handler(request: Request, exc: BusinessError):
328
+ return JSONResponse(
329
+ status_code=422,
330
+ content={"detail": exc.message, "code": exc.code}
331
+ )
332
+ ```