omgkit 2.1.1 → 2.2.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 (50) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  5. package/plugin/skills/databases/redis/SKILL.md +885 -25
  6. package/plugin/skills/devops/aws/SKILL.md +686 -28
  7. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  8. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  9. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  10. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  11. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  12. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  13. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  14. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  15. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  16. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  17. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  18. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  19. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  20. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  21. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  22. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  23. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  24. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  25. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  26. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  27. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  28. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  29. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  30. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  31. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  32. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  33. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  34. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  35. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  36. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  37. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  38. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  39. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  40. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  41. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  42. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  43. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  44. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  45. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  46. package/plugin/skills/security/oauth/SKILL.md +968 -31
  47. package/plugin/skills/security/owasp/SKILL.md +894 -33
  48. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  49. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  50. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,58 +1,1285 @@
1
1
  ---
2
2
  name: fastapi
3
- description: FastAPI development. Use for FastAPI projects, async APIs, Pydantic models.
3
+ description: Enterprise FastAPI development with async patterns, Pydantic v2, dependency injection, and production APIs
4
+ category: frameworks
5
+ triggers:
6
+ - fastapi
7
+ - fast api
8
+ - python api
9
+ - pydantic
10
+ - starlette
11
+ - async python
12
+ - python rest api
13
+ - uvicorn
4
14
  ---
5
15
 
6
- # FastAPI Skill
16
+ # FastAPI
17
+
18
+ Enterprise-grade **FastAPI development** following industry best practices. This skill covers async programming, Pydantic v2 validation, dependency injection, authentication, background tasks, testing patterns, and production deployment configurations used by top engineering teams.
19
+
20
+ ## Purpose
21
+
22
+ Build high-performance Python APIs with confidence:
23
+
24
+ - Design async API architectures for maximum throughput
25
+ - Implement comprehensive request validation with Pydantic
26
+ - Use dependency injection for clean, testable code
27
+ - Handle authentication and authorization securely
28
+ - Write comprehensive tests with pytest
29
+ - Deploy production-ready applications
30
+ - Leverage automatic OpenAPI documentation
31
+
32
+ ## Features
33
+
34
+ ### 1. Application Setup and Configuration
35
+
36
+ ```python
37
+ # app/main.py
38
+ from contextlib import asynccontextmanager
39
+ from fastapi import FastAPI
40
+ from fastapi.middleware.cors import CORSMiddleware
41
+ from fastapi.middleware.gzip import GZipMiddleware
42
+ import uvicorn
43
+
44
+ from app.api.v1 import router as api_v1_router
45
+ from app.core.config import settings
46
+ from app.core.logging import setup_logging
47
+ from app.db.session import engine, async_session_maker
48
+ from app.db.base import Base
49
+
50
+
51
+ @asynccontextmanager
52
+ async def lifespan(app: FastAPI):
53
+ """Application lifespan events."""
54
+ # Startup
55
+ setup_logging()
56
+ async with engine.begin() as conn:
57
+ await conn.run_sync(Base.metadata.create_all)
58
+ yield
59
+ # Shutdown
60
+ await engine.dispose()
61
+
62
+
63
+ def create_app() -> FastAPI:
64
+ app = FastAPI(
65
+ title=settings.PROJECT_NAME,
66
+ description="Enterprise API",
67
+ version="1.0.0",
68
+ openapi_url=f"{settings.API_V1_PREFIX}/openapi.json",
69
+ docs_url=f"{settings.API_V1_PREFIX}/docs",
70
+ redoc_url=f"{settings.API_V1_PREFIX}/redoc",
71
+ lifespan=lifespan,
72
+ )
73
+
74
+ # Middleware
75
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
76
+ app.add_middleware(
77
+ CORSMiddleware,
78
+ allow_origins=settings.CORS_ORIGINS,
79
+ allow_credentials=True,
80
+ allow_methods=["*"],
81
+ allow_headers=["*"],
82
+ )
83
+
84
+ # Routes
85
+ app.include_router(api_v1_router, prefix=settings.API_V1_PREFIX)
86
+
87
+ @app.get("/health")
88
+ async def health_check():
89
+ return {"status": "healthy"}
90
+
91
+ return app
92
+
93
+
94
+ app = create_app()
95
+
96
+ if __name__ == "__main__":
97
+ uvicorn.run(
98
+ "app.main:app",
99
+ host="0.0.0.0",
100
+ port=8000,
101
+ reload=settings.DEBUG,
102
+ workers=settings.WORKERS,
103
+ )
104
+
105
+
106
+ # app/core/config.py
107
+ from functools import lru_cache
108
+ from typing import List, Optional
109
+ from pydantic import Field, PostgresDsn, field_validator
110
+ from pydantic_settings import BaseSettings, SettingsConfigDict
111
+
112
+
113
+ class Settings(BaseSettings):
114
+ model_config = SettingsConfigDict(
115
+ env_file=".env",
116
+ env_file_encoding="utf-8",
117
+ case_sensitive=True,
118
+ )
119
+
120
+ # Application
121
+ PROJECT_NAME: str = "FastAPI App"
122
+ DEBUG: bool = False
123
+ WORKERS: int = 4
124
+ API_V1_PREFIX: str = "/api/v1"
125
+
126
+ # Database
127
+ DATABASE_URL: PostgresDsn
128
+ DATABASE_POOL_SIZE: int = 5
129
+ DATABASE_MAX_OVERFLOW: int = 10
130
+
131
+ # Redis
132
+ REDIS_URL: str = "redis://localhost:6379/0"
133
+
134
+ # Security
135
+ SECRET_KEY: str
136
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
137
+ REFRESH_TOKEN_EXPIRE_DAYS: int = 7
138
+ ALGORITHM: str = "HS256"
139
+
140
+ # CORS
141
+ CORS_ORIGINS: List[str] = ["http://localhost:3000"]
142
+
143
+ @field_validator("CORS_ORIGINS", mode="before")
144
+ @classmethod
145
+ def parse_cors_origins(cls, v: str | List[str]) -> List[str]:
146
+ if isinstance(v, str):
147
+ return [origin.strip() for origin in v.split(",")]
148
+ return v
149
+
150
+
151
+ @lru_cache
152
+ def get_settings() -> Settings:
153
+ return Settings()
154
+
155
+
156
+ settings = get_settings()
157
+ ```
158
+
159
+ ### 2. Pydantic Schemas and Validation
160
+
161
+ ```python
162
+ # app/schemas/user.py
163
+ from datetime import datetime
164
+ from typing import Optional, List
165
+ from uuid import UUID
166
+ from pydantic import BaseModel, EmailStr, Field, field_validator, ConfigDict
167
+
168
+
169
+ class UserBase(BaseModel):
170
+ email: EmailStr
171
+ name: str = Field(..., min_length=2, max_length=100)
172
+ is_active: bool = True
173
+
174
+
175
+ class UserCreate(UserBase):
176
+ password: str = Field(..., min_length=8, max_length=128)
177
+
178
+ @field_validator("password")
179
+ @classmethod
180
+ def validate_password(cls, v: str) -> str:
181
+ if not any(c.isupper() for c in v):
182
+ raise ValueError("Password must contain uppercase letter")
183
+ if not any(c.isdigit() for c in v):
184
+ raise ValueError("Password must contain a digit")
185
+ if not any(c in "!@#$%^&*" for c in v):
186
+ raise ValueError("Password must contain a special character")
187
+ return v
188
+
189
+
190
+ class UserUpdate(BaseModel):
191
+ email: Optional[EmailStr] = None
192
+ name: Optional[str] = Field(None, min_length=2, max_length=100)
193
+ is_active: Optional[bool] = None
194
+
195
+
196
+ class UserInDB(UserBase):
197
+ model_config = ConfigDict(from_attributes=True)
198
+
199
+ id: UUID
200
+ role: str
201
+ created_at: datetime
202
+ updated_at: datetime
203
+
204
+
205
+ class UserResponse(UserInDB):
206
+ """Response model excluding sensitive fields."""
207
+ pass
208
+
209
+
210
+ class UserWithOrganizations(UserResponse):
211
+ organizations: List["OrganizationSummary"] = []
212
+
213
+
214
+ # app/schemas/organization.py
215
+ from datetime import datetime
216
+ from typing import Optional, List
217
+ from uuid import UUID
218
+ from pydantic import BaseModel, Field, ConfigDict
219
+ from enum import Enum
220
+
221
+
222
+ class MemberRole(str, Enum):
223
+ OWNER = "owner"
224
+ ADMIN = "admin"
225
+ MEMBER = "member"
226
+ VIEWER = "viewer"
227
+
228
+
229
+ class OrganizationCreate(BaseModel):
230
+ name: str = Field(..., min_length=2, max_length=255)
231
+ slug: str = Field(..., min_length=2, max_length=100, pattern=r"^[a-z0-9-]+$")
232
+
233
+
234
+ class OrganizationUpdate(BaseModel):
235
+ name: Optional[str] = Field(None, min_length=2, max_length=255)
236
+
237
+
238
+ class OrganizationSummary(BaseModel):
239
+ model_config = ConfigDict(from_attributes=True)
240
+
241
+ id: UUID
242
+ name: str
243
+ slug: str
244
+
245
+
246
+ class OrganizationResponse(OrganizationSummary):
247
+ owner_id: UUID
248
+ member_count: int = 0
249
+ created_at: datetime
250
+
251
+
252
+ class MembershipResponse(BaseModel):
253
+ model_config = ConfigDict(from_attributes=True)
254
+
255
+ user_id: UUID
256
+ organization_id: UUID
257
+ role: MemberRole
258
+ joined_at: datetime
259
+
260
+
261
+ # app/schemas/common.py
262
+ from typing import Generic, TypeVar, List, Optional
263
+ from pydantic import BaseModel, Field
264
+
265
+ T = TypeVar("T")
266
+
267
+
268
+ class PaginationParams(BaseModel):
269
+ page: int = Field(1, ge=1)
270
+ limit: int = Field(20, ge=1, le=100)
271
+
272
+ @property
273
+ def offset(self) -> int:
274
+ return (self.page - 1) * self.limit
275
+
276
+
277
+ class PaginatedResponse(BaseModel, Generic[T]):
278
+ data: List[T]
279
+ total: int
280
+ page: int
281
+ limit: int
282
+ total_pages: int
283
+ has_more: bool
284
+
285
+ @classmethod
286
+ def create(
287
+ cls,
288
+ data: List[T],
289
+ total: int,
290
+ page: int,
291
+ limit: int,
292
+ ) -> "PaginatedResponse[T]":
293
+ total_pages = (total + limit - 1) // limit
294
+ return cls(
295
+ data=data,
296
+ total=total,
297
+ page=page,
298
+ limit=limit,
299
+ total_pages=total_pages,
300
+ has_more=page < total_pages,
301
+ )
302
+ ```
303
+
304
+ ### 3. Dependency Injection
7
305
 
8
- ## Setup
9
306
  ```python
10
- from fastapi import FastAPI, HTTPException, Depends
11
- from pydantic import BaseModel
307
+ # app/api/deps.py
308
+ from typing import Annotated, AsyncGenerator, Optional
309
+ from fastapi import Depends, HTTPException, status, Query
310
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
311
+ from sqlalchemy.ext.asyncio import AsyncSession
312
+ from jose import JWTError, jwt
313
+ from uuid import UUID
314
+
315
+ from app.core.config import settings
316
+ from app.db.session import async_session_maker
317
+ from app.models.user import User
318
+ from app.schemas.common import PaginationParams
319
+ from app.services.user import UserService
320
+ from app.services.organization import OrganizationService
321
+ from app.core.redis import redis_client
322
+
323
+
324
+ security = HTTPBearer()
325
+
326
+
327
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
328
+ """Database session dependency."""
329
+ async with async_session_maker() as session:
330
+ try:
331
+ yield session
332
+ finally:
333
+ await session.close()
334
+
335
+
336
+ async def get_redis():
337
+ """Redis client dependency."""
338
+ return redis_client
339
+
340
+
341
+ def get_pagination(
342
+ page: int = Query(1, ge=1, description="Page number"),
343
+ limit: int = Query(20, ge=1, le=100, description="Items per page"),
344
+ ) -> PaginationParams:
345
+ """Pagination parameters dependency."""
346
+ return PaginationParams(page=page, limit=limit)
347
+
348
+
349
+ async def get_current_user(
350
+ credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)],
351
+ db: Annotated[AsyncSession, Depends(get_db)],
352
+ ) -> User:
353
+ """Get current authenticated user."""
354
+ credentials_exception = HTTPException(
355
+ status_code=status.HTTP_401_UNAUTHORIZED,
356
+ detail="Could not validate credentials",
357
+ headers={"WWW-Authenticate": "Bearer"},
358
+ )
359
+
360
+ try:
361
+ payload = jwt.decode(
362
+ credentials.credentials,
363
+ settings.SECRET_KEY,
364
+ algorithms=[settings.ALGORITHM],
365
+ )
366
+ user_id: str = payload.get("sub")
367
+ if user_id is None:
368
+ raise credentials_exception
369
+ except JWTError:
370
+ raise credentials_exception
371
+
372
+ user_service = UserService(db)
373
+ user = await user_service.get_by_id(UUID(user_id))
374
+
375
+ if user is None:
376
+ raise credentials_exception
377
+ if not user.is_active:
378
+ raise HTTPException(
379
+ status_code=status.HTTP_403_FORBIDDEN,
380
+ detail="Inactive user",
381
+ )
382
+
383
+ return user
384
+
385
+
386
+ async def get_current_active_user(
387
+ current_user: Annotated[User, Depends(get_current_user)],
388
+ ) -> User:
389
+ """Get current active user."""
390
+ if not current_user.is_active:
391
+ raise HTTPException(status_code=400, detail="Inactive user")
392
+ return current_user
393
+
394
+
395
+ def require_role(*roles: str):
396
+ """Role-based access control dependency factory."""
397
+ async def role_checker(
398
+ current_user: Annotated[User, Depends(get_current_user)],
399
+ ) -> User:
400
+ if current_user.role not in roles:
401
+ raise HTTPException(
402
+ status_code=status.HTTP_403_FORBIDDEN,
403
+ detail="Insufficient permissions",
404
+ )
405
+ return current_user
406
+ return role_checker
407
+
408
+
409
+ async def get_organization_member(
410
+ org_slug: str,
411
+ current_user: Annotated[User, Depends(get_current_user)],
412
+ db: Annotated[AsyncSession, Depends(get_db)],
413
+ ):
414
+ """Verify user is a member of the organization."""
415
+ org_service = OrganizationService(db)
416
+ organization = await org_service.get_by_slug(org_slug)
417
+
418
+ if not organization:
419
+ raise HTTPException(
420
+ status_code=status.HTTP_404_NOT_FOUND,
421
+ detail="Organization not found",
422
+ )
423
+
424
+ membership = await org_service.get_membership(organization.id, current_user.id)
425
+ if not membership:
426
+ raise HTTPException(
427
+ status_code=status.HTTP_403_FORBIDDEN,
428
+ detail="Not a member of this organization",
429
+ )
430
+
431
+ return {"organization": organization, "membership": membership}
12
432
 
13
- app = FastAPI()
433
+
434
+ # Type aliases for cleaner signatures
435
+ DB = Annotated[AsyncSession, Depends(get_db)]
436
+ CurrentUser = Annotated[User, Depends(get_current_user)]
437
+ Pagination = Annotated[PaginationParams, Depends(get_pagination)]
438
+ AdminUser = Annotated[User, Depends(require_role("admin"))]
14
439
  ```
15
440
 
16
- ## Patterns
441
+ ### 4. Routes and Endpoints
17
442
 
18
- ### Route with Pydantic
19
443
  ```python
20
- class UserCreate(BaseModel):
21
- email: str
444
+ # app/api/v1/__init__.py
445
+ from fastapi import APIRouter
446
+ from app.api.v1 import auth, users, organizations, projects
447
+
448
+ router = APIRouter()
449
+
450
+ router.include_router(auth.router, prefix="/auth", tags=["auth"])
451
+ router.include_router(users.router, prefix="/users", tags=["users"])
452
+ router.include_router(organizations.router, prefix="/organizations", tags=["organizations"])
453
+ router.include_router(projects.router, prefix="/projects", tags=["projects"])
454
+
455
+
456
+ # app/api/v1/users.py
457
+ from typing import Optional
458
+ from uuid import UUID
459
+ from fastapi import APIRouter, HTTPException, status, Query
460
+
461
+ from app.api.deps import DB, CurrentUser, Pagination, AdminUser
462
+ from app.schemas.user import UserCreate, UserUpdate, UserResponse, UserWithOrganizations
463
+ from app.schemas.common import PaginatedResponse
464
+ from app.services.user import UserService
465
+
466
+ router = APIRouter()
467
+
468
+
469
+ @router.get("/", response_model=PaginatedResponse[UserResponse])
470
+ async def list_users(
471
+ db: DB,
472
+ current_user: AdminUser,
473
+ pagination: Pagination,
474
+ search: Optional[str] = Query(None, min_length=2),
475
+ role: Optional[str] = None,
476
+ is_active: Optional[bool] = None,
477
+ ):
478
+ """List all users (admin only)."""
479
+ service = UserService(db)
480
+ users, total = await service.list(
481
+ offset=pagination.offset,
482
+ limit=pagination.limit,
483
+ search=search,
484
+ role=role,
485
+ is_active=is_active,
486
+ )
487
+ return PaginatedResponse.create(
488
+ data=users,
489
+ total=total,
490
+ page=pagination.page,
491
+ limit=pagination.limit,
492
+ )
493
+
494
+
495
+ @router.get("/me", response_model=UserWithOrganizations)
496
+ async def get_current_user(db: DB, current_user: CurrentUser):
497
+ """Get current user profile."""
498
+ service = UserService(db)
499
+ return await service.get_with_organizations(current_user.id)
500
+
501
+
502
+ @router.patch("/me", response_model=UserResponse)
503
+ async def update_current_user(
504
+ db: DB,
505
+ current_user: CurrentUser,
506
+ user_in: UserUpdate,
507
+ ):
508
+ """Update current user profile."""
509
+ service = UserService(db)
510
+ return await service.update(current_user.id, user_in)
511
+
512
+
513
+ @router.get("/{user_id}", response_model=UserResponse)
514
+ async def get_user(db: DB, current_user: AdminUser, user_id: UUID):
515
+ """Get user by ID (admin only)."""
516
+ service = UserService(db)
517
+ user = await service.get_by_id(user_id)
518
+ if not user:
519
+ raise HTTPException(
520
+ status_code=status.HTTP_404_NOT_FOUND,
521
+ detail="User not found",
522
+ )
523
+ return user
524
+
525
+
526
+ @router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
527
+ async def create_user(db: DB, current_user: AdminUser, user_in: UserCreate):
528
+ """Create new user (admin only)."""
529
+ service = UserService(db)
530
+ existing = await service.get_by_email(user_in.email)
531
+ if existing:
532
+ raise HTTPException(
533
+ status_code=status.HTTP_409_CONFLICT,
534
+ detail="Email already registered",
535
+ )
536
+ return await service.create(user_in)
537
+
538
+
539
+ @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
540
+ async def delete_user(db: DB, current_user: AdminUser, user_id: UUID):
541
+ """Delete user (admin only)."""
542
+ service = UserService(db)
543
+ user = await service.get_by_id(user_id)
544
+ if not user:
545
+ raise HTTPException(
546
+ status_code=status.HTTP_404_NOT_FOUND,
547
+ detail="User not found",
548
+ )
549
+ await service.delete(user_id)
550
+
551
+
552
+ # app/api/v1/auth.py
553
+ from datetime import timedelta
554
+ from fastapi import APIRouter, HTTPException, status, BackgroundTasks
555
+ from pydantic import BaseModel, EmailStr
556
+
557
+ from app.api.deps import DB
558
+ from app.core.config import settings
559
+ from app.core.security import create_access_token, create_refresh_token, verify_password
560
+ from app.services.user import UserService
561
+ from app.schemas.user import UserCreate, UserResponse
562
+
563
+ router = APIRouter()
564
+
565
+
566
+ class LoginRequest(BaseModel):
567
+ email: EmailStr
22
568
  password: str
23
569
 
24
- class User(BaseModel):
25
- id: str
26
- email: str
27
570
 
28
- @app.post("/users", response_model=User)
29
- async def create_user(user: UserCreate):
30
- return await db.create_user(user)
571
+ class TokenResponse(BaseModel):
572
+ access_token: str
573
+ refresh_token: str
574
+ token_type: str = "bearer"
575
+ user: UserResponse
576
+
577
+
578
+ class RefreshRequest(BaseModel):
579
+ refresh_token: str
580
+
581
+
582
+ @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
583
+ async def register(
584
+ db: DB,
585
+ user_in: UserCreate,
586
+ background_tasks: BackgroundTasks,
587
+ ):
588
+ """Register a new user."""
589
+ service = UserService(db)
590
+ existing = await service.get_by_email(user_in.email)
591
+ if existing:
592
+ raise HTTPException(
593
+ status_code=status.HTTP_409_CONFLICT,
594
+ detail="Email already registered",
595
+ )
596
+
597
+ user = await service.create(user_in)
598
+
599
+ # Send welcome email in background
600
+ background_tasks.add_task(send_welcome_email, user.email, user.name)
601
+
602
+ return user
603
+
604
+
605
+ @router.post("/login", response_model=TokenResponse)
606
+ async def login(db: DB, login_data: LoginRequest):
607
+ """Authenticate user and return tokens."""
608
+ service = UserService(db)
609
+ user = await service.get_by_email(login_data.email)
610
+
611
+ if not user or not verify_password(login_data.password, user.hashed_password):
612
+ raise HTTPException(
613
+ status_code=status.HTTP_401_UNAUTHORIZED,
614
+ detail="Incorrect email or password",
615
+ )
616
+
617
+ if not user.is_active:
618
+ raise HTTPException(
619
+ status_code=status.HTTP_403_FORBIDDEN,
620
+ detail="Inactive account",
621
+ )
622
+
623
+ access_token = create_access_token(
624
+ subject=str(user.id),
625
+ expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
626
+ )
627
+ refresh_token = create_refresh_token(
628
+ subject=str(user.id),
629
+ expires_delta=timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS),
630
+ )
631
+
632
+ return TokenResponse(
633
+ access_token=access_token,
634
+ refresh_token=refresh_token,
635
+ user=user,
636
+ )
637
+
638
+
639
+ @router.post("/refresh", response_model=TokenResponse)
640
+ async def refresh_token(db: DB, refresh_data: RefreshRequest):
641
+ """Refresh access token."""
642
+ # Verify and decode refresh token
643
+ # Generate new access token
644
+ pass
645
+
646
+
647
+ async def send_welcome_email(email: str, name: str):
648
+ """Background task to send welcome email."""
649
+ # Implement email sending logic
650
+ pass
31
651
  ```
32
652
 
33
- ### Dependency Injection
653
+ ### 5. Service Layer
654
+
34
655
  ```python
35
- async def get_db():
36
- db = Database()
656
+ # app/services/user.py
657
+ from typing import Optional, List, Tuple
658
+ from uuid import UUID
659
+ from sqlalchemy import select, func, or_
660
+ from sqlalchemy.ext.asyncio import AsyncSession
661
+ from sqlalchemy.orm import selectinload
662
+
663
+ from app.models.user import User
664
+ from app.models.membership import Membership
665
+ from app.schemas.user import UserCreate, UserUpdate
666
+ from app.core.security import get_password_hash
667
+
668
+
669
+ class UserService:
670
+ def __init__(self, db: AsyncSession):
671
+ self.db = db
672
+
673
+ async def get_by_id(self, user_id: UUID) -> Optional[User]:
674
+ result = await self.db.execute(
675
+ select(User).where(User.id == user_id, User.deleted_at.is_(None))
676
+ )
677
+ return result.scalar_one_or_none()
678
+
679
+ async def get_by_email(self, email: str) -> Optional[User]:
680
+ result = await self.db.execute(
681
+ select(User).where(User.email == email, User.deleted_at.is_(None))
682
+ )
683
+ return result.scalar_one_or_none()
684
+
685
+ async def get_with_organizations(self, user_id: UUID) -> Optional[User]:
686
+ result = await self.db.execute(
687
+ select(User)
688
+ .options(selectinload(User.memberships).selectinload(Membership.organization))
689
+ .where(User.id == user_id, User.deleted_at.is_(None))
690
+ )
691
+ return result.scalar_one_or_none()
692
+
693
+ async def list(
694
+ self,
695
+ offset: int = 0,
696
+ limit: int = 20,
697
+ search: Optional[str] = None,
698
+ role: Optional[str] = None,
699
+ is_active: Optional[bool] = None,
700
+ ) -> Tuple[List[User], int]:
701
+ query = select(User).where(User.deleted_at.is_(None))
702
+
703
+ if search:
704
+ query = query.where(
705
+ or_(
706
+ User.name.ilike(f"%{search}%"),
707
+ User.email.ilike(f"%{search}%"),
708
+ )
709
+ )
710
+
711
+ if role:
712
+ query = query.where(User.role == role)
713
+
714
+ if is_active is not None:
715
+ query = query.where(User.is_active == is_active)
716
+
717
+ # Get total count
718
+ count_query = select(func.count()).select_from(query.subquery())
719
+ total_result = await self.db.execute(count_query)
720
+ total = total_result.scalar()
721
+
722
+ # Get paginated results
723
+ query = query.order_by(User.created_at.desc()).offset(offset).limit(limit)
724
+ result = await self.db.execute(query)
725
+ users = list(result.scalars().all())
726
+
727
+ return users, total
728
+
729
+ async def create(self, user_in: UserCreate) -> User:
730
+ user = User(
731
+ email=user_in.email,
732
+ name=user_in.name,
733
+ hashed_password=get_password_hash(user_in.password),
734
+ role="user",
735
+ is_active=True,
736
+ )
737
+ self.db.add(user)
738
+ await self.db.commit()
739
+ await self.db.refresh(user)
740
+ return user
741
+
742
+ async def update(self, user_id: UUID, user_in: UserUpdate) -> Optional[User]:
743
+ user = await self.get_by_id(user_id)
744
+ if not user:
745
+ return None
746
+
747
+ update_data = user_in.model_dump(exclude_unset=True)
748
+ for field, value in update_data.items():
749
+ setattr(user, field, value)
750
+
751
+ await self.db.commit()
752
+ await self.db.refresh(user)
753
+ return user
754
+
755
+ async def delete(self, user_id: UUID) -> bool:
756
+ user = await self.get_by_id(user_id)
757
+ if not user:
758
+ return False
759
+
760
+ # Soft delete
761
+ from datetime import datetime
762
+ user.deleted_at = datetime.utcnow()
763
+ await self.db.commit()
764
+ return True
765
+
766
+
767
+ # app/services/organization.py
768
+ from typing import Optional, List, Tuple
769
+ from uuid import UUID
770
+ from sqlalchemy import select, func
771
+ from sqlalchemy.ext.asyncio import AsyncSession
772
+ from sqlalchemy.orm import selectinload
773
+
774
+ from app.models.organization import Organization
775
+ from app.models.membership import Membership
776
+ from app.schemas.organization import OrganizationCreate, OrganizationUpdate, MemberRole
777
+
778
+
779
+ class OrganizationService:
780
+ def __init__(self, db: AsyncSession):
781
+ self.db = db
782
+
783
+ async def get_by_id(self, org_id: UUID) -> Optional[Organization]:
784
+ result = await self.db.execute(
785
+ select(Organization).where(Organization.id == org_id)
786
+ )
787
+ return result.scalar_one_or_none()
788
+
789
+ async def get_by_slug(self, slug: str) -> Optional[Organization]:
790
+ result = await self.db.execute(
791
+ select(Organization).where(Organization.slug == slug)
792
+ )
793
+ return result.scalar_one_or_none()
794
+
795
+ async def get_membership(
796
+ self, org_id: UUID, user_id: UUID
797
+ ) -> Optional[Membership]:
798
+ result = await self.db.execute(
799
+ select(Membership).where(
800
+ Membership.organization_id == org_id,
801
+ Membership.user_id == user_id,
802
+ )
803
+ )
804
+ return result.scalar_one_or_none()
805
+
806
+ async def list_for_user(
807
+ self, user_id: UUID, offset: int = 0, limit: int = 20
808
+ ) -> Tuple[List[Organization], int]:
809
+ query = (
810
+ select(Organization)
811
+ .join(Membership)
812
+ .where(Membership.user_id == user_id)
813
+ )
814
+
815
+ count_result = await self.db.execute(
816
+ select(func.count()).select_from(query.subquery())
817
+ )
818
+ total = count_result.scalar()
819
+
820
+ result = await self.db.execute(
821
+ query.order_by(Organization.created_at.desc())
822
+ .offset(offset)
823
+ .limit(limit)
824
+ )
825
+ organizations = list(result.scalars().all())
826
+
827
+ return organizations, total
828
+
829
+ async def create(
830
+ self, org_in: OrganizationCreate, owner_id: UUID
831
+ ) -> Organization:
832
+ org = Organization(
833
+ name=org_in.name,
834
+ slug=org_in.slug,
835
+ owner_id=owner_id,
836
+ )
837
+ self.db.add(org)
838
+ await self.db.flush()
839
+
840
+ # Create owner membership
841
+ membership = Membership(
842
+ user_id=owner_id,
843
+ organization_id=org.id,
844
+ role=MemberRole.OWNER,
845
+ )
846
+ self.db.add(membership)
847
+
848
+ await self.db.commit()
849
+ await self.db.refresh(org)
850
+ return org
851
+
852
+ async def add_member(
853
+ self, org_id: UUID, user_id: UUID, role: MemberRole = MemberRole.MEMBER
854
+ ) -> Membership:
855
+ membership = Membership(
856
+ user_id=user_id,
857
+ organization_id=org_id,
858
+ role=role,
859
+ )
860
+ self.db.add(membership)
861
+ await self.db.commit()
862
+ await self.db.refresh(membership)
863
+ return membership
864
+ ```
865
+
866
+ ### 6. Database Models
867
+
868
+ ```python
869
+ # app/models/user.py
870
+ from datetime import datetime
871
+ from typing import List, TYPE_CHECKING
872
+ from uuid import uuid4
873
+ from sqlalchemy import String, Boolean, DateTime
874
+ from sqlalchemy.dialects.postgresql import UUID
875
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
876
+
877
+ from app.db.base import Base
878
+
879
+ if TYPE_CHECKING:
880
+ from app.models.membership import Membership
881
+ from app.models.organization import Organization
882
+
883
+
884
+ class User(Base):
885
+ __tablename__ = "users"
886
+
887
+ id: Mapped[UUID] = mapped_column(
888
+ UUID(as_uuid=True), primary_key=True, default=uuid4
889
+ )
890
+ email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
891
+ name: Mapped[str] = mapped_column(String(100))
892
+ hashed_password: Mapped[str] = mapped_column(String(255))
893
+ role: Mapped[str] = mapped_column(String(50), default="user")
894
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True)
895
+ created_at: Mapped[datetime] = mapped_column(
896
+ DateTime, default=datetime.utcnow
897
+ )
898
+ updated_at: Mapped[datetime] = mapped_column(
899
+ DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
900
+ )
901
+ deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
902
+
903
+ # Relationships
904
+ memberships: Mapped[List["Membership"]] = relationship(
905
+ "Membership", back_populates="user", lazy="selectin"
906
+ )
907
+ owned_organizations: Mapped[List["Organization"]] = relationship(
908
+ "Organization", back_populates="owner", foreign_keys="Organization.owner_id"
909
+ )
910
+
911
+
912
+ # app/models/organization.py
913
+ from datetime import datetime
914
+ from typing import List, TYPE_CHECKING
915
+ from uuid import uuid4
916
+ from sqlalchemy import String, DateTime, ForeignKey
917
+ from sqlalchemy.dialects.postgresql import UUID
918
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
919
+
920
+ from app.db.base import Base
921
+
922
+ if TYPE_CHECKING:
923
+ from app.models.user import User
924
+ from app.models.membership import Membership
925
+
926
+
927
+ class Organization(Base):
928
+ __tablename__ = "organizations"
929
+
930
+ id: Mapped[UUID] = mapped_column(
931
+ UUID(as_uuid=True), primary_key=True, default=uuid4
932
+ )
933
+ name: Mapped[str] = mapped_column(String(255))
934
+ slug: Mapped[str] = mapped_column(String(100), unique=True, index=True)
935
+ owner_id: Mapped[UUID] = mapped_column(
936
+ UUID(as_uuid=True), ForeignKey("users.id")
937
+ )
938
+ created_at: Mapped[datetime] = mapped_column(
939
+ DateTime, default=datetime.utcnow
940
+ )
941
+
942
+ # Relationships
943
+ owner: Mapped["User"] = relationship(
944
+ "User", back_populates="owned_organizations", foreign_keys=[owner_id]
945
+ )
946
+ memberships: Mapped[List["Membership"]] = relationship(
947
+ "Membership", back_populates="organization"
948
+ )
949
+
950
+
951
+ # app/models/membership.py
952
+ from datetime import datetime
953
+ from uuid import uuid4
954
+ from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint
955
+ from sqlalchemy.dialects.postgresql import UUID
956
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
957
+
958
+ from app.db.base import Base
959
+ from app.models.user import User
960
+ from app.models.organization import Organization
961
+
962
+
963
+ class Membership(Base):
964
+ __tablename__ = "memberships"
965
+ __table_args__ = (
966
+ UniqueConstraint("user_id", "organization_id", name="uq_user_org"),
967
+ )
968
+
969
+ id: Mapped[UUID] = mapped_column(
970
+ UUID(as_uuid=True), primary_key=True, default=uuid4
971
+ )
972
+ user_id: Mapped[UUID] = mapped_column(
973
+ UUID(as_uuid=True), ForeignKey("users.id")
974
+ )
975
+ organization_id: Mapped[UUID] = mapped_column(
976
+ UUID(as_uuid=True), ForeignKey("organizations.id")
977
+ )
978
+ role: Mapped[str] = mapped_column(String(50), default="member")
979
+ joined_at: Mapped[datetime] = mapped_column(
980
+ DateTime, default=datetime.utcnow
981
+ )
982
+
983
+ # Relationships
984
+ user: Mapped["User"] = relationship("User", back_populates="memberships")
985
+ organization: Mapped["Organization"] = relationship(
986
+ "Organization", back_populates="memberships"
987
+ )
988
+ ```
989
+
990
+ ### 7. Testing Patterns
991
+
992
+ ```python
993
+ # tests/conftest.py
994
+ import asyncio
995
+ from typing import AsyncGenerator
996
+ import pytest
997
+ from httpx import AsyncClient, ASGITransport
998
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
999
+ from sqlalchemy.orm import sessionmaker
1000
+
1001
+ from app.main import app
1002
+ from app.db.base import Base
1003
+ from app.api.deps import get_db
1004
+ from app.core.security import create_access_token
1005
+
1006
+ TEST_DATABASE_URL = "sqlite+aiosqlite:///./test.db"
1007
+
1008
+ engine = create_async_engine(TEST_DATABASE_URL, echo=True)
1009
+ TestingSessionLocal = sessionmaker(
1010
+ engine, class_=AsyncSession, expire_on_commit=False
1011
+ )
1012
+
1013
+
1014
+ @pytest.fixture(scope="session")
1015
+ def event_loop():
1016
+ loop = asyncio.get_event_loop_policy().new_event_loop()
1017
+ yield loop
1018
+ loop.close()
1019
+
1020
+
1021
+ @pytest.fixture(scope="function")
1022
+ async def db_session() -> AsyncGenerator[AsyncSession, None]:
1023
+ async with engine.begin() as conn:
1024
+ await conn.run_sync(Base.metadata.create_all)
1025
+
1026
+ async with TestingSessionLocal() as session:
1027
+ yield session
1028
+
1029
+ async with engine.begin() as conn:
1030
+ await conn.run_sync(Base.metadata.drop_all)
1031
+
1032
+
1033
+ @pytest.fixture(scope="function")
1034
+ async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
1035
+ async def override_get_db():
1036
+ yield db_session
1037
+
1038
+ app.dependency_overrides[get_db] = override_get_db
1039
+
1040
+ async with AsyncClient(
1041
+ transport=ASGITransport(app=app),
1042
+ base_url="http://test",
1043
+ ) as ac:
1044
+ yield ac
1045
+
1046
+ app.dependency_overrides.clear()
1047
+
1048
+
1049
+ @pytest.fixture
1050
+ async def test_user(db_session: AsyncSession):
1051
+ from app.models.user import User
1052
+ from app.core.security import get_password_hash
1053
+
1054
+ user = User(
1055
+ email="test@example.com",
1056
+ name="Test User",
1057
+ hashed_password=get_password_hash("TestPass123!"),
1058
+ role="user",
1059
+ is_active=True,
1060
+ )
1061
+ db_session.add(user)
1062
+ await db_session.commit()
1063
+ await db_session.refresh(user)
1064
+ return user
1065
+
1066
+
1067
+ @pytest.fixture
1068
+ def auth_headers(test_user):
1069
+ token = create_access_token(subject=str(test_user.id))
1070
+ return {"Authorization": f"Bearer {token}"}
1071
+
1072
+
1073
+ # tests/test_users.py
1074
+ import pytest
1075
+ from httpx import AsyncClient
1076
+
1077
+
1078
+ @pytest.mark.asyncio
1079
+ async def test_get_current_user(client: AsyncClient, auth_headers: dict):
1080
+ response = await client.get("/api/v1/users/me", headers=auth_headers)
1081
+ assert response.status_code == 200
1082
+ data = response.json()
1083
+ assert data["email"] == "test@example.com"
1084
+ assert "hashed_password" not in data
1085
+
1086
+
1087
+ @pytest.mark.asyncio
1088
+ async def test_update_current_user(client: AsyncClient, auth_headers: dict):
1089
+ response = await client.patch(
1090
+ "/api/v1/users/me",
1091
+ headers=auth_headers,
1092
+ json={"name": "Updated Name"},
1093
+ )
1094
+ assert response.status_code == 200
1095
+ assert response.json()["name"] == "Updated Name"
1096
+
1097
+
1098
+ @pytest.mark.asyncio
1099
+ async def test_unauthorized_access(client: AsyncClient):
1100
+ response = await client.get("/api/v1/users/me")
1101
+ assert response.status_code == 403
1102
+
1103
+
1104
+ @pytest.mark.asyncio
1105
+ async def test_register_user(client: AsyncClient):
1106
+ response = await client.post(
1107
+ "/api/v1/auth/register",
1108
+ json={
1109
+ "email": "new@example.com",
1110
+ "name": "New User",
1111
+ "password": "SecurePass123!",
1112
+ },
1113
+ )
1114
+ assert response.status_code == 201
1115
+ data = response.json()
1116
+ assert data["email"] == "new@example.com"
1117
+ assert "password" not in data
1118
+
1119
+
1120
+ @pytest.mark.asyncio
1121
+ async def test_register_duplicate_email(client: AsyncClient, test_user):
1122
+ response = await client.post(
1123
+ "/api/v1/auth/register",
1124
+ json={
1125
+ "email": "test@example.com",
1126
+ "name": "Duplicate User",
1127
+ "password": "SecurePass123!",
1128
+ },
1129
+ )
1130
+ assert response.status_code == 409
1131
+
1132
+
1133
+ @pytest.mark.asyncio
1134
+ async def test_login(client: AsyncClient, test_user):
1135
+ response = await client.post(
1136
+ "/api/v1/auth/login",
1137
+ json={
1138
+ "email": "test@example.com",
1139
+ "password": "TestPass123!",
1140
+ },
1141
+ )
1142
+ assert response.status_code == 200
1143
+ data = response.json()
1144
+ assert "access_token" in data
1145
+ assert "refresh_token" in data
1146
+
1147
+
1148
+ @pytest.mark.asyncio
1149
+ async def test_login_wrong_password(client: AsyncClient, test_user):
1150
+ response = await client.post(
1151
+ "/api/v1/auth/login",
1152
+ json={
1153
+ "email": "test@example.com",
1154
+ "password": "WrongPassword123!",
1155
+ },
1156
+ )
1157
+ assert response.status_code == 401
1158
+ ```
1159
+
1160
+ ## Use Cases
1161
+
1162
+ ### Background Task Processing
1163
+
1164
+ ```python
1165
+ # app/tasks/email.py
1166
+ from celery import Celery
1167
+ from app.core.config import settings
1168
+
1169
+ celery = Celery("tasks", broker=settings.REDIS_URL)
1170
+
1171
+
1172
+ @celery.task(bind=True, max_retries=3)
1173
+ def send_email_task(self, to: str, subject: str, body: str):
37
1174
  try:
38
- yield db
39
- finally:
40
- await db.close()
1175
+ # Send email logic
1176
+ pass
1177
+ except Exception as exc:
1178
+ raise self.retry(exc=exc, countdown=60)
41
1179
 
42
- @app.get("/users/{id}")
43
- async def get_user(id: str, db: Database = Depends(get_db)):
44
- return await db.get_user(id)
1180
+
1181
+ # Using with FastAPI background tasks
1182
+ from fastapi import BackgroundTasks
1183
+
1184
+ @router.post("/invite")
1185
+ async def invite_user(
1186
+ email: str,
1187
+ background_tasks: BackgroundTasks,
1188
+ current_user: CurrentUser,
1189
+ ):
1190
+ # Add task to background
1191
+ background_tasks.add_task(
1192
+ send_invitation_email,
1193
+ email=email,
1194
+ inviter_name=current_user.name,
1195
+ )
1196
+ return {"message": "Invitation sent"}
45
1197
  ```
46
1198
 
47
- ### Error Handling
1199
+ ### WebSocket for Real-time Updates
1200
+
48
1201
  ```python
49
- @app.exception_handler(ValueError)
50
- async def value_error_handler(request, exc):
51
- return JSONResponse(status_code=400, content={"error": str(exc)})
1202
+ # app/api/v1/websocket.py
1203
+ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
1204
+ from typing import Dict, Set
1205
+ from uuid import UUID
1206
+ import json
1207
+
1208
+ router = APIRouter()
1209
+
1210
+
1211
+ class ConnectionManager:
1212
+ def __init__(self):
1213
+ self.active_connections: Dict[str, Set[WebSocket]] = {}
1214
+
1215
+ async def connect(self, websocket: WebSocket, room: str):
1216
+ await websocket.accept()
1217
+ if room not in self.active_connections:
1218
+ self.active_connections[room] = set()
1219
+ self.active_connections[room].add(websocket)
1220
+
1221
+ def disconnect(self, websocket: WebSocket, room: str):
1222
+ if room in self.active_connections:
1223
+ self.active_connections[room].discard(websocket)
1224
+
1225
+ async def broadcast(self, room: str, message: dict):
1226
+ if room in self.active_connections:
1227
+ for connection in self.active_connections[room]:
1228
+ await connection.send_json(message)
1229
+
1230
+
1231
+ manager = ConnectionManager()
1232
+
1233
+
1234
+ @router.websocket("/ws/projects/{project_id}")
1235
+ async def project_websocket(
1236
+ websocket: WebSocket,
1237
+ project_id: UUID,
1238
+ ):
1239
+ room = f"project:{project_id}"
1240
+ await manager.connect(websocket, room)
1241
+
1242
+ try:
1243
+ while True:
1244
+ data = await websocket.receive_text()
1245
+ message = json.loads(data)
1246
+ await manager.broadcast(room, message)
1247
+ except WebSocketDisconnect:
1248
+ manager.disconnect(websocket, room)
52
1249
  ```
53
1250
 
54
1251
  ## Best Practices
55
- - Use Pydantic for validation
56
- - Use async/await
57
- - Use dependency injection
58
- - Document with OpenAPI
1252
+
1253
+ ### Do's
1254
+
1255
+ - Use Pydantic v2 for all validation
1256
+ - Use async/await consistently throughout
1257
+ - Use dependency injection for testability
1258
+ - Use proper type hints everywhere
1259
+ - Use background tasks for long operations
1260
+ - Use connection pooling for database
1261
+ - Use Redis for caching and rate limiting
1262
+ - Write comprehensive tests with pytest
1263
+ - Use proper error handling with HTTPException
1264
+ - Use environment variables for configuration
1265
+
1266
+ ### Don'ts
1267
+
1268
+ - Don't use sync operations in async functions
1269
+ - Don't skip input validation
1270
+ - Don't hardcode configuration values
1271
+ - Don't ignore database session management
1272
+ - Don't expose sensitive data in responses
1273
+ - Don't use `*` imports
1274
+ - Don't skip error handling
1275
+ - Don't forget to close database sessions
1276
+ - Don't use blocking I/O operations
1277
+ - Don't ignore type checker warnings
1278
+
1279
+ ## References
1280
+
1281
+ - [FastAPI Documentation](https://fastapi.tiangolo.com/)
1282
+ - [Pydantic v2 Documentation](https://docs.pydantic.dev/latest/)
1283
+ - [SQLAlchemy 2.0 Documentation](https://docs.sqlalchemy.org/en/20/)
1284
+ - [pytest-asyncio Documentation](https://pytest-asyncio.readthedocs.io/)
1285
+ - [Starlette Documentation](https://www.starlette.io/)