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,244 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../fastapi.md).
|
|
2
|
+
|
|
3
|
+
# Compétence FastAPI
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Construire une API Python REST ou async avec FastAPI
|
|
7
|
+
- Définir des modèles Pydantic de requête/réponse
|
|
8
|
+
- Configurer l'injection de dépendances avec `Depends`
|
|
9
|
+
- Rédiger des gestionnaires de routes async avec SQLAlchemy ou des drivers DB async
|
|
10
|
+
- Ajouter des middlewares (CORS, auth, logging, rate limiting)
|
|
11
|
+
- Configurer des tâches en arrière-plan ou des workers Celery
|
|
12
|
+
- Personnaliser les docs OpenAPI (tags, descriptions, schémas de réponse)
|
|
13
|
+
- Rédiger des tests d'intégration avec `TestClient` ou `AsyncClient`
|
|
14
|
+
- Structurer un projet FastAPI multi-modules
|
|
15
|
+
|
|
16
|
+
## Quand NE PAS utiliser
|
|
17
|
+
- Projets Django/DRF — utiliser la compétence Django
|
|
18
|
+
- Bases de code uniquement synchrones où le surcoût async n'est pas justifié
|
|
19
|
+
- Scripts simples qui n'ont pas besoin de HTTP — utiliser du Python simple
|
|
20
|
+
- APIs gRPC ou GraphQL — transport et couche de schéma différents
|
|
21
|
+
|
|
22
|
+
## Instructions
|
|
23
|
+
|
|
24
|
+
### Structure du projet
|
|
25
|
+
```
|
|
26
|
+
app/
|
|
27
|
+
├── main.py # Fabrique d'application FastAPI
|
|
28
|
+
├── core/
|
|
29
|
+
│ ├── config.py # Paramètres via pydantic-settings
|
|
30
|
+
│ └── security.py # JWT, utilitaires de hachage
|
|
31
|
+
├── api/
|
|
32
|
+
│ ├── deps.py # Fonctions Depends() partagées
|
|
33
|
+
│ └── v1/
|
|
34
|
+
│ ├── router.py # Agrégateur APIRouter
|
|
35
|
+
│ └── endpoints/
|
|
36
|
+
│ ├── users.py
|
|
37
|
+
│ └── items.py
|
|
38
|
+
├── models/ # Modèles ORM SQLAlchemy
|
|
39
|
+
├── schemas/ # Schémas Pydantic de requête/réponse
|
|
40
|
+
├── crud/ # Fonctions d'opérations DB (pas ORM, pas HTTP)
|
|
41
|
+
└── db/
|
|
42
|
+
├── session.py # Fabrique AsyncSession
|
|
43
|
+
└── base.py # Import de la base déclarative
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Fabrique d'application
|
|
47
|
+
```python
|
|
48
|
+
# main.py
|
|
49
|
+
from fastapi import FastAPI
|
|
50
|
+
from app.api.v1.router import api_router
|
|
51
|
+
from app.core.config import settings
|
|
52
|
+
|
|
53
|
+
def create_app() -> FastAPI:
|
|
54
|
+
app = FastAPI(
|
|
55
|
+
title=settings.PROJECT_NAME,
|
|
56
|
+
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
|
57
|
+
docs_url="/docs" if settings.ENVIRONMENT != "production" else None,
|
|
58
|
+
)
|
|
59
|
+
app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
60
|
+
return app
|
|
61
|
+
|
|
62
|
+
app = create_app()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Paramètres avec pydantic-settings
|
|
66
|
+
```python
|
|
67
|
+
# core/config.py
|
|
68
|
+
from pydantic_settings import BaseSettings
|
|
69
|
+
|
|
70
|
+
class Settings(BaseSettings):
|
|
71
|
+
PROJECT_NAME: str = "MyAPI"
|
|
72
|
+
API_V1_STR: str = "/api/v1"
|
|
73
|
+
DATABASE_URL: str
|
|
74
|
+
SECRET_KEY: str
|
|
75
|
+
ENVIRONMENT: str = "development"
|
|
76
|
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
|
77
|
+
|
|
78
|
+
class Config:
|
|
79
|
+
env_file = ".env"
|
|
80
|
+
case_sensitive = True
|
|
81
|
+
|
|
82
|
+
settings = Settings()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Dépendance de session SQLAlchemy async
|
|
86
|
+
```python
|
|
87
|
+
# db/session.py
|
|
88
|
+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
|
89
|
+
|
|
90
|
+
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
|
91
|
+
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
|
|
92
|
+
|
|
93
|
+
# api/deps.py
|
|
94
|
+
async def get_db() -> AsyncIterator[AsyncSession]:
|
|
95
|
+
async with AsyncSessionLocal() as session:
|
|
96
|
+
try:
|
|
97
|
+
yield session
|
|
98
|
+
await session.commit()
|
|
99
|
+
except Exception:
|
|
100
|
+
await session.rollback()
|
|
101
|
+
raise
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Gestionnaires de routes
|
|
105
|
+
```python
|
|
106
|
+
# api/v1/endpoints/users.py
|
|
107
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
108
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
109
|
+
from app.api.deps import get_db, get_current_user
|
|
110
|
+
from app.crud import user as crud_user
|
|
111
|
+
from app.schemas.user import UserCreate, UserResponse
|
|
112
|
+
|
|
113
|
+
router = APIRouter(prefix="/users", tags=["users"])
|
|
114
|
+
|
|
115
|
+
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
|
116
|
+
async def create_user(
|
|
117
|
+
payload: UserCreate,
|
|
118
|
+
db: AsyncSession = Depends(get_db),
|
|
119
|
+
) -> UserResponse:
|
|
120
|
+
if await crud_user.get_by_email(db, email=payload.email):
|
|
121
|
+
raise HTTPException(status_code=400, detail="Email already registered")
|
|
122
|
+
return await crud_user.create(db, obj_in=payload)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Injection de dépendances pour l'auth
|
|
126
|
+
```python
|
|
127
|
+
# api/deps.py
|
|
128
|
+
from fastapi import Depends, HTTPException, status
|
|
129
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
130
|
+
from jose import JWTError, jwt
|
|
131
|
+
|
|
132
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
|
|
133
|
+
|
|
134
|
+
async def get_current_user(
|
|
135
|
+
token: str = Depends(oauth2_scheme),
|
|
136
|
+
db: AsyncSession = Depends(get_db),
|
|
137
|
+
) -> User:
|
|
138
|
+
try:
|
|
139
|
+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
|
140
|
+
user_id: str = payload.get("sub")
|
|
141
|
+
if user_id is None:
|
|
142
|
+
raise credentials_exception
|
|
143
|
+
except JWTError:
|
|
144
|
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
145
|
+
user = await crud_user.get(db, id=user_id)
|
|
146
|
+
if user is None:
|
|
147
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
148
|
+
return user
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Tâches en arrière-plan
|
|
152
|
+
```python
|
|
153
|
+
# Utiliser BackgroundTasks de FastAPI pour les tâches légères fire-and-forget (pas de résultat nécessaire)
|
|
154
|
+
@router.post("/send-email")
|
|
155
|
+
async def send_email_endpoint(
|
|
156
|
+
payload: EmailPayload,
|
|
157
|
+
background_tasks: BackgroundTasks,
|
|
158
|
+
):
|
|
159
|
+
background_tasks.add_task(send_email, payload.to, payload.subject, payload.body)
|
|
160
|
+
return {"status": "queued"}
|
|
161
|
+
|
|
162
|
+
# Utiliser Celery pour : les réessais, le suivi des résultats, la planification, les tâches inter-services
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Middleware CORS
|
|
166
|
+
```python
|
|
167
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
168
|
+
|
|
169
|
+
app.add_middleware(
|
|
170
|
+
CORSMiddleware,
|
|
171
|
+
allow_origins=settings.ALLOWED_ORIGINS, # Jamais ["*"] en production
|
|
172
|
+
allow_credentials=True,
|
|
173
|
+
allow_methods=["*"],
|
|
174
|
+
allow_headers=["*"],
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Gestionnaires d'exceptions personnalisés
|
|
179
|
+
```python
|
|
180
|
+
from fastapi.responses import JSONResponse
|
|
181
|
+
|
|
182
|
+
@app.exception_handler(ValueError)
|
|
183
|
+
async def value_error_handler(request: Request, exc: ValueError) -> JSONResponse:
|
|
184
|
+
return JSONResponse(status_code=422, content={"detail": str(exc)})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Tests
|
|
188
|
+
```python
|
|
189
|
+
# tests/conftest.py
|
|
190
|
+
import pytest
|
|
191
|
+
from httpx import AsyncClient, ASGITransport
|
|
192
|
+
from app.main import app
|
|
193
|
+
|
|
194
|
+
@pytest.fixture
|
|
195
|
+
async def client() -> AsyncIterator[AsyncClient]:
|
|
196
|
+
async with AsyncClient(
|
|
197
|
+
transport=ASGITransport(app=app), base_url="http://test"
|
|
198
|
+
) as ac:
|
|
199
|
+
yield ac
|
|
200
|
+
|
|
201
|
+
# tests/test_users.py
|
|
202
|
+
@pytest.mark.asyncio
|
|
203
|
+
async def test_create_user(client: AsyncClient, db_session):
|
|
204
|
+
resp = await client.post("/api/v1/users/", json={"email": "a@b.com", "password": "secret"})
|
|
205
|
+
assert resp.status_code == 201
|
|
206
|
+
assert resp.json()["email"] == "a@b.com"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Patterns Pydantic courants
|
|
210
|
+
```python
|
|
211
|
+
from pydantic import BaseModel, EmailStr, field_validator, model_validator
|
|
212
|
+
|
|
213
|
+
class UserCreate(BaseModel):
|
|
214
|
+
email: EmailStr
|
|
215
|
+
password: str
|
|
216
|
+
|
|
217
|
+
@field_validator("password")
|
|
218
|
+
@classmethod
|
|
219
|
+
def password_strength(cls, v: str) -> str:
|
|
220
|
+
if len(v) < 8:
|
|
221
|
+
raise ValueError("Password must be at least 8 characters")
|
|
222
|
+
return v
|
|
223
|
+
|
|
224
|
+
class UserResponse(BaseModel):
|
|
225
|
+
id: int
|
|
226
|
+
email: EmailStr
|
|
227
|
+
|
|
228
|
+
model_config = {"from_attributes": True} # remplace orm_mode = True
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Exemple
|
|
232
|
+
|
|
233
|
+
**Utilisateur :** Construire un endpoint FastAPI pour créer un article de blog, authentifié avec JWT, sauvegardant dans PostgreSQL avec SQLAlchemy async.
|
|
234
|
+
|
|
235
|
+
**Structure attendue :**
|
|
236
|
+
- `schemas/post.py` — `PostCreate(BaseModel)`, `PostResponse(BaseModel)` avec `from_attributes=True`
|
|
237
|
+
- `models/post.py` — modèle ORM `Post` avec `id`, `title`, `body`, `author_id` (FK vers User), `created_at`
|
|
238
|
+
- `crud/post.py` — fonction async `create(db, *, obj_in, author_id)`
|
|
239
|
+
- `api/v1/endpoints/posts.py` — `POST /posts/` avec `Depends(get_current_user)` et `Depends(get_db)`
|
|
240
|
+
- `api/v1/router.py` — inclure le router posts
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
> **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/)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
> 🇳🇱 Dit is de Nederlandse vertaling. [Engelse versie](../django.md).
|
|
2
|
+
|
|
3
|
+
# Django Skill
|
|
4
|
+
|
|
5
|
+
## Wanneer te activeren
|
|
6
|
+
- Een Django-project bouwen met ORM-modellen, migraties en views
|
|
7
|
+
- Django REST Framework (DRF) serializers, viewsets en routers instellen
|
|
8
|
+
- Aangepaste model-managers of QuerySet-methoden schrijven
|
|
9
|
+
- Django-signalen gebruiken voor ontkoppelde neveneffecten
|
|
10
|
+
- Celery instellen voor async taken in een Django-project
|
|
11
|
+
- De Django-admin aanpassen
|
|
12
|
+
- Tests schrijven met `django.test.TestCase` of `pytest-django`
|
|
13
|
+
|
|
14
|
+
## Wanneer NIET te gebruiken
|
|
15
|
+
- Async-first API's — gebruik in plaats daarvan de FastAPI skill
|
|
16
|
+
- Microservices die Django's ORM of admin niet nodig hebben
|
|
17
|
+
- Eenvoudige scripts of CLI's — gewone Python of Typer
|
|
18
|
+
- Als het project al FastAPI of Flask gebruikt
|
|
19
|
+
|
|
20
|
+
## Instructies
|
|
21
|
+
|
|
22
|
+
### Projectindeling
|
|
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
|
+
### Instellingen splitsen
|
|
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" # Stel altijd een aangepast gebruikersmodel in vanaf dag één
|
|
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
|
+
### Aangepast User-model
|
|
72
|
+
```python
|
|
73
|
+
# apps/users/models.py — instellen vóór eerste migratie, daarna nooit meer wijzigen
|
|
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
|
+
### Aangepaste Manager
|
|
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
|
+
### DRF Serializers
|
|
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
|
+
### DRF ViewSets
|
|
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
|
+
### Router-instelling
|
|
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
|
+
### Signalen
|
|
176
|
+
```python
|
|
177
|
+
# apps/users/signals.py — gebruik signalen alleen voor werkelijk ontkoppelde neveneffecten
|
|
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
|
+
# e-mail verzenden
|
|
212
|
+
pass
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
raise self.retry(exc=exc, countdown=60)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Admin-aanpassing
|
|
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
|
+
### QuerySet-optimalisatie
|
|
240
|
+
```python
|
|
241
|
+
# Gebruik altijd select_related voor FK-velden, prefetch_related voor M2M/reverse FK
|
|
242
|
+
posts = Post.objects.select_related("author").prefetch_related("tags").filter(published=True)
|
|
243
|
+
|
|
244
|
+
# Gebruik only() of defer() voor grote modellen wanneer je alleen specifieke velden nodig hebt
|
|
245
|
+
emails = User.objects.filter(is_active=True).only("email")
|
|
246
|
+
|
|
247
|
+
# Gebruik values() voor alleen-lezen aggregaties — slaat ORM-objectconstructie over
|
|
248
|
+
counts = Order.objects.values("status").annotate(count=Count("id"))
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Testen
|
|
252
|
+
```python
|
|
253
|
+
# pytest-django-stijl
|
|
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
|
+
## Voorbeeld
|
|
274
|
+
|
|
275
|
+
**Gebruiker:** Voeg een `Post`-model toe aan een Django-project met DRF, inclusief lijst/aanmaken/ophalen-endpoints, gepagineerde resultaten en filter op `published=True`.
|
|
276
|
+
|
|
277
|
+
**Verwachte output:**
|
|
278
|
+
- `models.py` — `Post` met `title`, `body`, `author` (FK naar User), `published`, `created_at`
|
|
279
|
+
- `serializers.py` — `PostSerializer` met alleen-lezen `author` (genest), beschrijfbare `title`/`body`/`published`
|
|
280
|
+
- `views.py` — `PostViewSet` met `queryset` gefilterd op `published=True` voor niet-geauthenticeerde gebruikers, `IsAuthenticatedOrReadOnly`-toestemming, `PageNumberPagination`
|
|
281
|
+
- `urls.py` — router geregistreerd op `/api/posts/`
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
> **Werk met ons:** Claudient wordt ondersteund door [Uitbreiden](https://uitbreiden.com/) — we bouwen AI-producten en B2B-oplossingen met ontwikkelaarsgemeenschappen. [uitbreiden.com](https://uitbreiden.com/)
|