claudient 0.1.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/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# FastAPI Skill
|
|
2
|
+
|
|
3
|
+
## When to activate
|
|
4
|
+
- Building a Python REST or async API with FastAPI
|
|
5
|
+
- Defining Pydantic request/response models
|
|
6
|
+
- Setting up dependency injection with `Depends`
|
|
7
|
+
- Writing async route handlers with SQLAlchemy or async DB drivers
|
|
8
|
+
- Adding middleware (CORS, auth, logging, rate limiting)
|
|
9
|
+
- Configuring background tasks or Celery workers
|
|
10
|
+
- Customizing OpenAPI docs (tags, descriptions, response schemas)
|
|
11
|
+
- Writing integration tests with `TestClient` or `AsyncClient`
|
|
12
|
+
- Structuring a multi-module FastAPI project
|
|
13
|
+
|
|
14
|
+
## When NOT to use
|
|
15
|
+
- Django/DRF projects — use the Django skill
|
|
16
|
+
- Synchronous-only codebases where async overhead is not justified
|
|
17
|
+
- Simple scripts that don't need HTTP — use plain Python
|
|
18
|
+
- gRPC or GraphQL APIs — different transport and schema layer
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
### Project structure
|
|
23
|
+
```
|
|
24
|
+
app/
|
|
25
|
+
├── main.py # FastAPI app factory
|
|
26
|
+
├── core/
|
|
27
|
+
│ ├── config.py # Settings via pydantic-settings
|
|
28
|
+
│ └── security.py # JWT, hashing utilities
|
|
29
|
+
├── api/
|
|
30
|
+
│ ├── deps.py # Shared Depends() functions
|
|
31
|
+
│ └── v1/
|
|
32
|
+
│ ├── router.py # APIRouter aggregator
|
|
33
|
+
│ └── endpoints/
|
|
34
|
+
│ ├── users.py
|
|
35
|
+
│ └── items.py
|
|
36
|
+
├── models/ # SQLAlchemy ORM models
|
|
37
|
+
├── schemas/ # Pydantic request/response schemas
|
|
38
|
+
├── crud/ # DB operation functions (not ORM, not HTTP)
|
|
39
|
+
└── db/
|
|
40
|
+
├── session.py # AsyncSession factory
|
|
41
|
+
└── base.py # Declarative base import
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### App factory
|
|
45
|
+
```python
|
|
46
|
+
# main.py
|
|
47
|
+
from fastapi import FastAPI
|
|
48
|
+
from app.api.v1.router import api_router
|
|
49
|
+
from app.core.config import settings
|
|
50
|
+
|
|
51
|
+
def create_app() -> FastAPI:
|
|
52
|
+
app = FastAPI(
|
|
53
|
+
title=settings.PROJECT_NAME,
|
|
54
|
+
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
|
55
|
+
docs_url="/docs" if settings.ENVIRONMENT != "production" else None,
|
|
56
|
+
)
|
|
57
|
+
app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
58
|
+
return app
|
|
59
|
+
|
|
60
|
+
app = create_app()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Settings with pydantic-settings
|
|
64
|
+
```python
|
|
65
|
+
# core/config.py
|
|
66
|
+
from pydantic_settings import BaseSettings
|
|
67
|
+
|
|
68
|
+
class Settings(BaseSettings):
|
|
69
|
+
PROJECT_NAME: str = "MyAPI"
|
|
70
|
+
API_V1_STR: str = "/api/v1"
|
|
71
|
+
DATABASE_URL: str
|
|
72
|
+
SECRET_KEY: str
|
|
73
|
+
ENVIRONMENT: str = "development"
|
|
74
|
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
|
75
|
+
|
|
76
|
+
class Config:
|
|
77
|
+
env_file = ".env"
|
|
78
|
+
case_sensitive = True
|
|
79
|
+
|
|
80
|
+
settings = Settings()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Async SQLAlchemy session dependency
|
|
84
|
+
```python
|
|
85
|
+
# db/session.py
|
|
86
|
+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
|
87
|
+
|
|
88
|
+
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
|
89
|
+
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
|
90
|
+
|
|
91
|
+
# api/deps.py
|
|
92
|
+
async def get_db() -> AsyncIterator[AsyncSession]:
|
|
93
|
+
async with AsyncSessionLocal() as session:
|
|
94
|
+
try:
|
|
95
|
+
yield session
|
|
96
|
+
await session.commit()
|
|
97
|
+
except Exception:
|
|
98
|
+
await session.rollback()
|
|
99
|
+
raise
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Route handlers
|
|
103
|
+
```python
|
|
104
|
+
# api/v1/endpoints/users.py
|
|
105
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
106
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
107
|
+
from app.api.deps import get_db, get_current_user
|
|
108
|
+
from app.crud import user as crud_user
|
|
109
|
+
from app.schemas.user import UserCreate, UserResponse
|
|
110
|
+
|
|
111
|
+
router = APIRouter(prefix="/users", tags=["users"])
|
|
112
|
+
|
|
113
|
+
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
114
|
+
async def create_user(
|
|
115
|
+
payload: UserCreate,
|
|
116
|
+
db: AsyncSession = Depends(get_db),
|
|
117
|
+
) -> UserResponse:
|
|
118
|
+
if await crud_user.get_by_email(db, email=payload.email):
|
|
119
|
+
raise HTTPException(status_code=400, detail="Email already registered")
|
|
120
|
+
return await crud_user.create(db, obj_in=payload)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Dependency injection for auth
|
|
124
|
+
```python
|
|
125
|
+
# api/deps.py
|
|
126
|
+
from fastapi import Depends, HTTPException, status
|
|
127
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
128
|
+
from jose import JWTError, jwt
|
|
129
|
+
|
|
130
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
|
|
131
|
+
|
|
132
|
+
async def get_current_user(
|
|
133
|
+
token: str = Depends(oauth2_scheme),
|
|
134
|
+
db: AsyncSession = Depends(get_db),
|
|
135
|
+
) -> User:
|
|
136
|
+
try:
|
|
137
|
+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
|
138
|
+
user_id: str = payload.get("sub")
|
|
139
|
+
if user_id is None:
|
|
140
|
+
raise credentials_exception
|
|
141
|
+
except JWTError:
|
|
142
|
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
143
|
+
user = await crud_user.get(db, id=user_id)
|
|
144
|
+
if user is None:
|
|
145
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
146
|
+
return user
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Background tasks
|
|
150
|
+
```python
|
|
151
|
+
# Use FastAPI's BackgroundTasks for lightweight fire-and-forget (no result needed)
|
|
152
|
+
@router.post("/send-email")
|
|
153
|
+
async def send_email_endpoint(
|
|
154
|
+
payload: EmailPayload,
|
|
155
|
+
background_tasks: BackgroundTasks,
|
|
156
|
+
):
|
|
157
|
+
background_tasks.add_task(send_email, payload.to, payload.subject, payload.body)
|
|
158
|
+
return {"status": "queued"}
|
|
159
|
+
|
|
160
|
+
# Use Celery for: retries, result tracking, scheduling, cross-service tasks
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### CORS middleware
|
|
164
|
+
```python
|
|
165
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
166
|
+
|
|
167
|
+
app.add_middleware(
|
|
168
|
+
CORSMiddleware,
|
|
169
|
+
allow_origins=settings.ALLOWED_ORIGINS, # Never ["*"] in production
|
|
170
|
+
allow_credentials=True,
|
|
171
|
+
allow_methods=["*"],
|
|
172
|
+
allow_headers=["*"],
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Custom exception handlers
|
|
177
|
+
```python
|
|
178
|
+
from fastapi.responses import JSONResponse
|
|
179
|
+
|
|
180
|
+
@app.exception_handler(ValueError)
|
|
181
|
+
async def value_error_handler(request: Request, exc: ValueError) -> JSONResponse:
|
|
182
|
+
return JSONResponse(status_code=422, content={"detail": str(exc)})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Testing
|
|
186
|
+
```python
|
|
187
|
+
# tests/conftest.py
|
|
188
|
+
import pytest
|
|
189
|
+
from httpx import AsyncClient, ASGITransport
|
|
190
|
+
from app.main import app
|
|
191
|
+
|
|
192
|
+
@pytest.fixture
|
|
193
|
+
async def client() -> AsyncIterator[AsyncClient]:
|
|
194
|
+
async with AsyncClient(
|
|
195
|
+
transport=ASGITransport(app=app), base_url="http://test"
|
|
196
|
+
) as ac:
|
|
197
|
+
yield ac
|
|
198
|
+
|
|
199
|
+
# tests/test_users.py
|
|
200
|
+
@pytest.mark.asyncio
|
|
201
|
+
async def test_create_user(client: AsyncClient, db_session):
|
|
202
|
+
resp = await client.post("/api/v1/users/", json={"email": "a@b.com", "password": "secret"})
|
|
203
|
+
assert resp.status_code == 201
|
|
204
|
+
assert resp.json()["email"] == "a@b.com"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Common Pydantic patterns
|
|
208
|
+
```python
|
|
209
|
+
from pydantic import BaseModel, EmailStr, field_validator, model_validator
|
|
210
|
+
|
|
211
|
+
class UserCreate(BaseModel):
|
|
212
|
+
email: EmailStr
|
|
213
|
+
password: str
|
|
214
|
+
|
|
215
|
+
@field_validator("password")
|
|
216
|
+
@classmethod
|
|
217
|
+
def password_strength(cls, v: str) -> str:
|
|
218
|
+
if len(v) < 8:
|
|
219
|
+
raise ValueError("Password must be at least 8 characters")
|
|
220
|
+
return v
|
|
221
|
+
|
|
222
|
+
class UserResponse(BaseModel):
|
|
223
|
+
id: int
|
|
224
|
+
email: EmailStr
|
|
225
|
+
|
|
226
|
+
model_config = {"from_attributes": True} # replaces orm_mode = True
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Example
|
|
230
|
+
|
|
231
|
+
**User:** Build a FastAPI endpoint to create a blog post, authenticated with JWT, saving to PostgreSQL with SQLAlchemy async.
|
|
232
|
+
|
|
233
|
+
**Expected structure:**
|
|
234
|
+
- `schemas/post.py` — `PostCreate(BaseModel)`, `PostResponse(BaseModel)` with `from_attributes=True`
|
|
235
|
+
- `models/post.py` — `Post` ORM model with `id`, `title`, `body`, `author_id` (FK to User), `created_at`
|
|
236
|
+
- `crud/post.py` — `create(db, *, obj_in, author_id)` async function
|
|
237
|
+
- `api/v1/endpoints/posts.py` — `POST /posts/` with `Depends(get_current_user)` and `Depends(get_db)`
|
|
238
|
+
- `api/v1/router.py` — include posts router
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
> **Work with us:** Claudient is backed by [Uitbreiden](https://uitbreiden.com/) — we build AI products and B2B solutions with developer communities. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../django.md).
|
|
2
|
+
|
|
3
|
+
# Compétence Django
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Construire un projet Django avec des modèles ORM, des migrations et des vues
|
|
7
|
+
- Configurer les sérialiseurs, viewsets et routers Django REST Framework (DRF)
|
|
8
|
+
- Rédiger des managers de modèles personnalisés ou des méthodes QuerySet
|
|
9
|
+
- Utiliser les signaux Django pour des effets secondaires découplés
|
|
10
|
+
- Configurer Celery pour les tâches async dans un projet Django
|
|
11
|
+
- Personnaliser l'admin Django
|
|
12
|
+
- Rédiger des tests avec `django.test.TestCase` ou `pytest-django`
|
|
13
|
+
|
|
14
|
+
## Quand NE PAS utiliser
|
|
15
|
+
- APIs async en priorité — utiliser la compétence FastAPI à la place
|
|
16
|
+
- Microservices qui n'ont pas besoin de l'ORM ou de l'admin Django
|
|
17
|
+
- Scripts ou CLIs simples — Python simple ou Typer
|
|
18
|
+
- Si le projet utilise déjà FastAPI ou Flask
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
### Structure du projet
|
|
23
|
+
```
|
|
24
|
+
project_name/
|
|
25
|
+
├── manage.py
|
|
26
|
+
├── config/
|
|
27
|
+
│ ├── settings/
|
|
28
|
+
│ │ ├── base.py
|
|
29
|
+
│ │ ├── development.py
|
|
30
|
+
│ │ └── production.py
|
|
31
|
+
│ ├── urls.py
|
|
32
|
+
│ └── wsgi.py
|
|
33
|
+
├── apps/
|
|
34
|
+
│ └── users/
|
|
35
|
+
│ ├── models.py
|
|
36
|
+
│ ├── serializers.py
|
|
37
|
+
│ ├── views.py
|
|
38
|
+
│ ├── urls.py
|
|
39
|
+
│ ├── admin.py
|
|
40
|
+
│ ├── managers.py
|
|
41
|
+
│ └── tests/
|
|
42
|
+
└── requirements/
|
|
43
|
+
├── base.txt
|
|
44
|
+
├── development.txt
|
|
45
|
+
└── production.txt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Séparation des paramètres
|
|
49
|
+
```python
|
|
50
|
+
# config/settings/base.py
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
53
|
+
|
|
54
|
+
INSTALLED_APPS = [
|
|
55
|
+
"django.contrib.admin",
|
|
56
|
+
"django.contrib.auth",
|
|
57
|
+
"django.contrib.contenttypes",
|
|
58
|
+
"rest_framework",
|
|
59
|
+
"apps.users",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
AUTH_USER_MODEL = "users.User" # Toujours définir un modèle utilisateur personnalisé dès le début
|
|
63
|
+
|
|
64
|
+
# config/settings/production.py
|
|
65
|
+
from .base import *
|
|
66
|
+
DEBUG = False
|
|
67
|
+
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
|
|
68
|
+
DATABASES = {"default": env.db("DATABASE_URL")}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Modèle User personnalisé
|
|
72
|
+
```python
|
|
73
|
+
# apps/users/models.py — à configurer avant la première migration, ne jamais changer ensuite
|
|
74
|
+
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
|
75
|
+
from django.db import models
|
|
76
|
+
from .managers import UserManager
|
|
77
|
+
|
|
78
|
+
class User(AbstractBaseUser, PermissionsMixin):
|
|
79
|
+
email = models.EmailField(unique=True)
|
|
80
|
+
is_staff = models.BooleanField(default=False)
|
|
81
|
+
is_active = models.BooleanField(default=True)
|
|
82
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
83
|
+
|
|
84
|
+
USERNAME_FIELD = "email"
|
|
85
|
+
REQUIRED_FIELDS = []
|
|
86
|
+
objects = UserManager()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Manager personnalisé
|
|
90
|
+
```python
|
|
91
|
+
# apps/users/managers.py
|
|
92
|
+
from django.contrib.auth.base_user import BaseUserManager
|
|
93
|
+
|
|
94
|
+
class UserManager(BaseUserManager):
|
|
95
|
+
def create_user(self, email: str, password: str, **extra_fields):
|
|
96
|
+
if not email:
|
|
97
|
+
raise ValueError("Email is required")
|
|
98
|
+
email = self.normalize_email(email)
|
|
99
|
+
user = self.model(email=email, **extra_fields)
|
|
100
|
+
user.set_password(password)
|
|
101
|
+
user.save()
|
|
102
|
+
return user
|
|
103
|
+
|
|
104
|
+
def create_superuser(self, email: str, password: str, **extra_fields):
|
|
105
|
+
extra_fields.setdefault("is_staff", True)
|
|
106
|
+
extra_fields.setdefault("is_superuser", True)
|
|
107
|
+
return self.create_user(email, password, **extra_fields)
|
|
108
|
+
|
|
109
|
+
def active(self):
|
|
110
|
+
return self.get_queryset().filter(is_active=True)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Sérialiseurs DRF
|
|
114
|
+
```python
|
|
115
|
+
# apps/users/serializers.py
|
|
116
|
+
from rest_framework import serializers
|
|
117
|
+
from .models import User
|
|
118
|
+
|
|
119
|
+
class UserCreateSerializer(serializers.ModelSerializer):
|
|
120
|
+
password = serializers.CharField(write_only=True, min_length=8)
|
|
121
|
+
|
|
122
|
+
class Meta:
|
|
123
|
+
model = User
|
|
124
|
+
fields = ["id", "email", "password"]
|
|
125
|
+
|
|
126
|
+
def create(self, validated_data: dict) -> User:
|
|
127
|
+
return User.objects.create_user(**validated_data)
|
|
128
|
+
|
|
129
|
+
class UserSerializer(serializers.ModelSerializer):
|
|
130
|
+
class Meta:
|
|
131
|
+
model = User
|
|
132
|
+
fields = ["id", "email", "created_at"]
|
|
133
|
+
read_only_fields = ["id", "created_at"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ViewSets DRF
|
|
137
|
+
```python
|
|
138
|
+
# apps/users/views.py
|
|
139
|
+
from rest_framework import viewsets, permissions, status
|
|
140
|
+
from rest_framework.decorators import action
|
|
141
|
+
from rest_framework.response import Response
|
|
142
|
+
from .models import User
|
|
143
|
+
from .serializers import UserSerializer, UserCreateSerializer
|
|
144
|
+
|
|
145
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
146
|
+
queryset = User.objects.active()
|
|
147
|
+
permission_classes = [permissions.IsAuthenticated]
|
|
148
|
+
|
|
149
|
+
def get_serializer_class(self):
|
|
150
|
+
if self.action == "create":
|
|
151
|
+
return UserCreateSerializer
|
|
152
|
+
return UserSerializer
|
|
153
|
+
|
|
154
|
+
def get_permissions(self):
|
|
155
|
+
if self.action == "create":
|
|
156
|
+
return [permissions.AllowAny()]
|
|
157
|
+
return super().get_permissions()
|
|
158
|
+
|
|
159
|
+
@action(detail=False, methods=["get"])
|
|
160
|
+
def me(self, request):
|
|
161
|
+
return Response(UserSerializer(request.user).data)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Configuration du Router
|
|
165
|
+
```python
|
|
166
|
+
# apps/users/urls.py
|
|
167
|
+
from rest_framework.routers import DefaultRouter
|
|
168
|
+
from .views import UserViewSet
|
|
169
|
+
|
|
170
|
+
router = DefaultRouter()
|
|
171
|
+
router.register("users", UserViewSet, basename="user")
|
|
172
|
+
urlpatterns = router.urls
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Signaux
|
|
176
|
+
```python
|
|
177
|
+
# apps/users/signals.py — utiliser les signaux uniquement pour des effets secondaires vraiment découplés
|
|
178
|
+
from django.db.models.signals import post_save
|
|
179
|
+
from django.dispatch import receiver
|
|
180
|
+
from .models import User
|
|
181
|
+
|
|
182
|
+
@receiver(post_save, sender=User)
|
|
183
|
+
def send_welcome_email(sender, instance: User, created: bool, **kwargs):
|
|
184
|
+
if created:
|
|
185
|
+
send_email_task.delay(instance.email, "welcome")
|
|
186
|
+
|
|
187
|
+
# apps/users/apps.py
|
|
188
|
+
class UsersConfig(AppConfig):
|
|
189
|
+
name = "apps.users"
|
|
190
|
+
def ready(self):
|
|
191
|
+
import apps.users.signals # noqa: F401
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Celery
|
|
195
|
+
```python
|
|
196
|
+
# config/celery.py
|
|
197
|
+
from celery import Celery
|
|
198
|
+
import os
|
|
199
|
+
|
|
200
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
|
|
201
|
+
app = Celery("project_name")
|
|
202
|
+
app.config_from_object("django.conf:settings", namespace="CELERY")
|
|
203
|
+
app.autodiscover_tasks()
|
|
204
|
+
|
|
205
|
+
# apps/users/tasks.py
|
|
206
|
+
from config.celery import app
|
|
207
|
+
|
|
208
|
+
@app.task(bind=True, max_retries=3)
|
|
209
|
+
def send_email_task(self, to_email: str, template: str):
|
|
210
|
+
try:
|
|
211
|
+
# envoyer l'email
|
|
212
|
+
pass
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
raise self.retry(exc=exc, countdown=60)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Personnalisation de l'admin
|
|
218
|
+
```python
|
|
219
|
+
# apps/users/admin.py
|
|
220
|
+
from django.contrib import admin
|
|
221
|
+
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
|
222
|
+
from .models import User
|
|
223
|
+
|
|
224
|
+
@admin.register(User)
|
|
225
|
+
class UserAdmin(BaseUserAdmin):
|
|
226
|
+
list_display = ["email", "is_active", "is_staff", "created_at"]
|
|
227
|
+
list_filter = ["is_active", "is_staff"]
|
|
228
|
+
search_fields = ["email"]
|
|
229
|
+
ordering = ["-created_at"]
|
|
230
|
+
fieldsets = (
|
|
231
|
+
(None, {"fields": ("email", "password")}),
|
|
232
|
+
("Permissions", {"fields": ("is_active", "is_staff", "is_superuser", "groups")}),
|
|
233
|
+
)
|
|
234
|
+
add_fieldsets = (
|
|
235
|
+
(None, {"fields": ("email", "password1", "password2")}),
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Optimisation des QuerySet
|
|
240
|
+
```python
|
|
241
|
+
# Toujours select_related pour les champs FK, prefetch_related pour M2M/FK inverse
|
|
242
|
+
posts = Post.objects.select_related("author").prefetch_related("tags").filter(published=True)
|
|
243
|
+
|
|
244
|
+
# Utiliser only() ou defer() pour les grands modèles quand vous n'avez besoin que de champs spécifiques
|
|
245
|
+
emails = User.objects.filter(is_active=True).only("email")
|
|
246
|
+
|
|
247
|
+
# Utiliser values() pour les agrégations en lecture seule — évite la construction d'objets ORM
|
|
248
|
+
counts = Order.objects.values("status").annotate(count=Count("id"))
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Tests
|
|
252
|
+
```python
|
|
253
|
+
# Style pytest-django
|
|
254
|
+
import pytest
|
|
255
|
+
from rest_framework.test import APIClient
|
|
256
|
+
|
|
257
|
+
@pytest.fixture
|
|
258
|
+
def api_client():
|
|
259
|
+
return APIClient()
|
|
260
|
+
|
|
261
|
+
@pytest.fixture
|
|
262
|
+
def authenticated_client(api_client, user):
|
|
263
|
+
api_client.force_authenticate(user=user)
|
|
264
|
+
return api_client
|
|
265
|
+
|
|
266
|
+
@pytest.mark.django_db
|
|
267
|
+
def test_create_user(api_client):
|
|
268
|
+
resp = api_client.post("/api/users/", {"email": "a@b.com", "password": "strongpass"})
|
|
269
|
+
assert resp.status_code == 201
|
|
270
|
+
assert resp.data["email"] == "a@b.com"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Exemple
|
|
274
|
+
|
|
275
|
+
**Utilisateur :** Ajouter un modèle `Post` à un projet Django avec DRF, incluant des endpoints liste/création/récupération, des résultats paginés et un filtre par `published=True`.
|
|
276
|
+
|
|
277
|
+
**Sortie attendue :**
|
|
278
|
+
- `models.py` — `Post` avec `title`, `body`, `author` (FK vers User), `published`, `created_at`
|
|
279
|
+
- `serializers.py` — `PostSerializer` avec `author` en lecture seule (imbriqué), `title`/`body`/`published` modifiables
|
|
280
|
+
- `views.py` — `PostViewSet` avec `queryset` filtré à `published=True` pour les utilisateurs non authentifiés, permission `IsAuthenticatedOrReadOnly`, `PageNumberPagination`
|
|
281
|
+
- `urls.py` — router enregistré à `/api/posts/`
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
> **Travaillez avec nous :** Claudient est soutenu par [Uitbreiden](https://uitbreiden.com/) — nous construisons des produits IA et des solutions B2B avec des communautés de développeurs. [uitbreiden.com](https://uitbreiden.com/)
|