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
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../fastapi.md).
|
|
2
|
+
|
|
3
|
+
# FastAPI Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Eine Python REST- oder asynchrone API mit FastAPI bauen
|
|
7
|
+
- Pydantic-Anfrage-/Antwortmodelle definieren
|
|
8
|
+
- Dependency Injection mit `Depends` einrichten
|
|
9
|
+
- Asynchrone Route-Handler mit SQLAlchemy oder asynchronen DB-Treibern schreiben
|
|
10
|
+
- Middleware hinzufügen (CORS, Auth, Logging, Rate Limiting)
|
|
11
|
+
- Hintergrundaufgaben oder Celery-Worker konfigurieren
|
|
12
|
+
- OpenAPI-Docs anpassen (Tags, Beschreibungen, Antwort-Schemas)
|
|
13
|
+
- Integrationstests mit `TestClient` oder `AsyncClient` schreiben
|
|
14
|
+
- Ein Multi-Modul-FastAPI-Projekt strukturieren
|
|
15
|
+
|
|
16
|
+
## Wann NICHT verwenden
|
|
17
|
+
- Django/DRF-Projekte — Django Skill verwenden
|
|
18
|
+
- Nur-synchrone Codebasen, bei denen Async-Overhead nicht gerechtfertigt ist
|
|
19
|
+
- Einfache Skripte, die kein HTTP benötigen — einfaches Python verwenden
|
|
20
|
+
- gRPC- oder GraphQL-APIs — anderer Transport und Schema-Layer
|
|
21
|
+
|
|
22
|
+
## Anweisungen
|
|
23
|
+
|
|
24
|
+
### Projektstruktur
|
|
25
|
+
```
|
|
26
|
+
app/
|
|
27
|
+
├── main.py # FastAPI App-Factory
|
|
28
|
+
├── core/
|
|
29
|
+
│ ├── config.py # Einstellungen über pydantic-settings
|
|
30
|
+
│ └── security.py # JWT, Hashing-Utilities
|
|
31
|
+
├── api/
|
|
32
|
+
│ ├── deps.py # Gemeinsame Depends()-Funktionen
|
|
33
|
+
│ └── v1/
|
|
34
|
+
│ ├── router.py # APIRouter-Aggregator
|
|
35
|
+
│ └── endpoints/
|
|
36
|
+
│ ├── users.py
|
|
37
|
+
│ └── items.py
|
|
38
|
+
├── models/ # SQLAlchemy ORM-Modelle
|
|
39
|
+
├── schemas/ # Pydantic Anfrage-/Antwort-Schemas
|
|
40
|
+
├── crud/ # DB-Operationsfunktionen (kein ORM, kein HTTP)
|
|
41
|
+
└── db/
|
|
42
|
+
├── session.py # AsyncSession-Factory
|
|
43
|
+
└── base.py # Deklarative Basis-Import
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### App-Factory
|
|
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
|
+
### Einstellungen mit 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
|
+
### Asynchrone SQLAlchemy-Session-Abhängigkeit
|
|
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
|
+
### Route-Handler
|
|
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
|
+
### Dependency Injection für 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
|
+
### Hintergrundaufgaben
|
|
152
|
+
```python
|
|
153
|
+
# FastAPI's BackgroundTasks für leichtgewichtiges Fire-and-Forget verwenden (kein Ergebnis benötigt)
|
|
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
|
+
# Celery verwenden für: Wiederholungen, Ergebnisverfolgung, Planung, dienstübergreifende Aufgaben
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### CORS-Middleware
|
|
166
|
+
```python
|
|
167
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
168
|
+
|
|
169
|
+
app.add_middleware(
|
|
170
|
+
CORSMiddleware,
|
|
171
|
+
allow_origins=settings.ALLOWED_ORIGINS, # Niemals ["*"] in der Produktion
|
|
172
|
+
allow_credentials=True,
|
|
173
|
+
allow_methods=["*"],
|
|
174
|
+
allow_headers=["*"],
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Benutzerdefinierte Exception-Handler
|
|
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
|
+
### Häufige Pydantic-Muster
|
|
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} # ersetzt orm_mode = True
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Beispiel
|
|
232
|
+
|
|
233
|
+
**Benutzer:** Einen FastAPI-Endpunkt zum Erstellen eines Blog-Posts bauen, mit JWT authentifiziert und in PostgreSQL mit asynchronem SQLAlchemy gespeichert.
|
|
234
|
+
|
|
235
|
+
**Erwartete Struktur:**
|
|
236
|
+
- `schemas/post.py` — `PostCreate(BaseModel)`, `PostResponse(BaseModel)` mit `from_attributes=True`
|
|
237
|
+
- `models/post.py` — `Post` ORM-Modell mit `id`, `title`, `body`, `author_id` (FK zu User), `created_at`
|
|
238
|
+
- `crud/post.py` — `create(db, *, obj_in, author_id)` asynchrone Funktion
|
|
239
|
+
- `api/v1/endpoints/posts.py` — `POST /posts/` mit `Depends(get_current_user)` und `Depends(get_db)`
|
|
240
|
+
- `api/v1/router.py` — Posts-Router einschließen
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Django Skill
|
|
2
|
+
|
|
3
|
+
## When to activate
|
|
4
|
+
- Building a Django project with ORM models, migrations, and views
|
|
5
|
+
- Setting up Django REST Framework (DRF) serializers, viewsets, and routers
|
|
6
|
+
- Writing custom model managers or QuerySet methods
|
|
7
|
+
- Using Django signals for decoupled side effects
|
|
8
|
+
- Setting up Celery for async tasks in a Django project
|
|
9
|
+
- Customizing the Django admin
|
|
10
|
+
- Writing tests with `django.test.TestCase` or `pytest-django`
|
|
11
|
+
|
|
12
|
+
## When NOT to use
|
|
13
|
+
- Async-first APIs — use FastAPI skill instead
|
|
14
|
+
- Microservices that don't need Django's ORM or admin
|
|
15
|
+
- Simple scripts or CLIs — plain Python or Typer
|
|
16
|
+
- If the project already uses FastAPI or Flask
|
|
17
|
+
|
|
18
|
+
## Instructions
|
|
19
|
+
|
|
20
|
+
### Project layout
|
|
21
|
+
```
|
|
22
|
+
project_name/
|
|
23
|
+
├── manage.py
|
|
24
|
+
├── config/
|
|
25
|
+
│ ├── settings/
|
|
26
|
+
│ │ ├── base.py
|
|
27
|
+
│ │ ├── development.py
|
|
28
|
+
│ │ └── production.py
|
|
29
|
+
│ ├── urls.py
|
|
30
|
+
│ └── wsgi.py
|
|
31
|
+
├── apps/
|
|
32
|
+
│ └── users/
|
|
33
|
+
│ ├── models.py
|
|
34
|
+
│ ├── serializers.py
|
|
35
|
+
│ ├── views.py
|
|
36
|
+
│ ├── urls.py
|
|
37
|
+
│ ├── admin.py
|
|
38
|
+
│ ├── managers.py
|
|
39
|
+
│ └── tests/
|
|
40
|
+
└── requirements/
|
|
41
|
+
├── base.txt
|
|
42
|
+
├── development.txt
|
|
43
|
+
└── production.txt
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Settings split
|
|
47
|
+
```python
|
|
48
|
+
# config/settings/base.py
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
51
|
+
|
|
52
|
+
INSTALLED_APPS = [
|
|
53
|
+
"django.contrib.admin",
|
|
54
|
+
"django.contrib.auth",
|
|
55
|
+
"django.contrib.contenttypes",
|
|
56
|
+
"rest_framework",
|
|
57
|
+
"apps.users",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
AUTH_USER_MODEL = "users.User" # Always set a custom user model from day one
|
|
61
|
+
|
|
62
|
+
# config/settings/production.py
|
|
63
|
+
from .base import *
|
|
64
|
+
DEBUG = False
|
|
65
|
+
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
|
|
66
|
+
DATABASES = {"default": env.db("DATABASE_URL")}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Custom User model
|
|
70
|
+
```python
|
|
71
|
+
# apps/users/models.py — set up before first migration, never change afterwards
|
|
72
|
+
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
|
73
|
+
from django.db import models
|
|
74
|
+
from .managers import UserManager
|
|
75
|
+
|
|
76
|
+
class User(AbstractBaseUser, PermissionsMixin):
|
|
77
|
+
email = models.EmailField(unique=True)
|
|
78
|
+
is_staff = models.BooleanField(default=False)
|
|
79
|
+
is_active = models.BooleanField(default=True)
|
|
80
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
81
|
+
|
|
82
|
+
USERNAME_FIELD = "email"
|
|
83
|
+
REQUIRED_FIELDS = []
|
|
84
|
+
objects = UserManager()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Custom Manager
|
|
88
|
+
```python
|
|
89
|
+
# apps/users/managers.py
|
|
90
|
+
from django.contrib.auth.base_user import BaseUserManager
|
|
91
|
+
|
|
92
|
+
class UserManager(BaseUserManager):
|
|
93
|
+
def create_user(self, email: str, password: str, **extra_fields):
|
|
94
|
+
if not email:
|
|
95
|
+
raise ValueError("Email is required")
|
|
96
|
+
email = self.normalize_email(email)
|
|
97
|
+
user = self.model(email=email, **extra_fields)
|
|
98
|
+
user.set_password(password)
|
|
99
|
+
user.save()
|
|
100
|
+
return user
|
|
101
|
+
|
|
102
|
+
def create_superuser(self, email: str, password: str, **extra_fields):
|
|
103
|
+
extra_fields.setdefault("is_staff", True)
|
|
104
|
+
extra_fields.setdefault("is_superuser", True)
|
|
105
|
+
return self.create_user(email, password, **extra_fields)
|
|
106
|
+
|
|
107
|
+
def active(self):
|
|
108
|
+
return self.get_queryset().filter(is_active=True)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### DRF Serializers
|
|
112
|
+
```python
|
|
113
|
+
# apps/users/serializers.py
|
|
114
|
+
from rest_framework import serializers
|
|
115
|
+
from .models import User
|
|
116
|
+
|
|
117
|
+
class UserCreateSerializer(serializers.ModelSerializer):
|
|
118
|
+
password = serializers.CharField(write_only=True, min_length=8)
|
|
119
|
+
|
|
120
|
+
class Meta:
|
|
121
|
+
model = User
|
|
122
|
+
fields = ["id", "email", "password"]
|
|
123
|
+
|
|
124
|
+
def create(self, validated_data: dict) -> User:
|
|
125
|
+
return User.objects.create_user(**validated_data)
|
|
126
|
+
|
|
127
|
+
class UserSerializer(serializers.ModelSerializer):
|
|
128
|
+
class Meta:
|
|
129
|
+
model = User
|
|
130
|
+
fields = ["id", "email", "created_at"]
|
|
131
|
+
read_only_fields = ["id", "created_at"]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### DRF ViewSets
|
|
135
|
+
```python
|
|
136
|
+
# apps/users/views.py
|
|
137
|
+
from rest_framework import viewsets, permissions, status
|
|
138
|
+
from rest_framework.decorators import action
|
|
139
|
+
from rest_framework.response import Response
|
|
140
|
+
from .models import User
|
|
141
|
+
from .serializers import UserSerializer, UserCreateSerializer
|
|
142
|
+
|
|
143
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
144
|
+
queryset = User.objects.active()
|
|
145
|
+
permission_classes = [permissions.IsAuthenticated]
|
|
146
|
+
|
|
147
|
+
def get_serializer_class(self):
|
|
148
|
+
if self.action == "create":
|
|
149
|
+
return UserCreateSerializer
|
|
150
|
+
return UserSerializer
|
|
151
|
+
|
|
152
|
+
def get_permissions(self):
|
|
153
|
+
if self.action == "create":
|
|
154
|
+
return [permissions.AllowAny()]
|
|
155
|
+
return super().get_permissions()
|
|
156
|
+
|
|
157
|
+
@action(detail=False, methods=["get"])
|
|
158
|
+
def me(self, request):
|
|
159
|
+
return Response(UserSerializer(request.user).data)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Router setup
|
|
163
|
+
```python
|
|
164
|
+
# apps/users/urls.py
|
|
165
|
+
from rest_framework.routers import DefaultRouter
|
|
166
|
+
from .views import UserViewSet
|
|
167
|
+
|
|
168
|
+
router = DefaultRouter()
|
|
169
|
+
router.register("users", UserViewSet, basename="user")
|
|
170
|
+
urlpatterns = router.urls
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Signals
|
|
174
|
+
```python
|
|
175
|
+
# apps/users/signals.py — use signals for truly decoupled side effects only
|
|
176
|
+
from django.db.models.signals import post_save
|
|
177
|
+
from django.dispatch import receiver
|
|
178
|
+
from .models import User
|
|
179
|
+
|
|
180
|
+
@receiver(post_save, sender=User)
|
|
181
|
+
def send_welcome_email(sender, instance: User, created: bool, **kwargs):
|
|
182
|
+
if created:
|
|
183
|
+
send_email_task.delay(instance.email, "welcome")
|
|
184
|
+
|
|
185
|
+
# apps/users/apps.py
|
|
186
|
+
class UsersConfig(AppConfig):
|
|
187
|
+
name = "apps.users"
|
|
188
|
+
def ready(self):
|
|
189
|
+
import apps.users.signals # noqa: F401
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Celery
|
|
193
|
+
```python
|
|
194
|
+
# config/celery.py
|
|
195
|
+
from celery import Celery
|
|
196
|
+
import os
|
|
197
|
+
|
|
198
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
|
|
199
|
+
app = Celery("project_name")
|
|
200
|
+
app.config_from_object("django.conf:settings", namespace="CELERY")
|
|
201
|
+
app.autodiscover_tasks()
|
|
202
|
+
|
|
203
|
+
# apps/users/tasks.py
|
|
204
|
+
from config.celery import app
|
|
205
|
+
|
|
206
|
+
@app.task(bind=True, max_retries=3)
|
|
207
|
+
def send_email_task(self, to_email: str, template: str):
|
|
208
|
+
try:
|
|
209
|
+
# send email
|
|
210
|
+
pass
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
raise self.retry(exc=exc, countdown=60)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Admin customization
|
|
216
|
+
```python
|
|
217
|
+
# apps/users/admin.py
|
|
218
|
+
from django.contrib import admin
|
|
219
|
+
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
|
220
|
+
from .models import User
|
|
221
|
+
|
|
222
|
+
@admin.register(User)
|
|
223
|
+
class UserAdmin(BaseUserAdmin):
|
|
224
|
+
list_display = ["email", "is_active", "is_staff", "created_at"]
|
|
225
|
+
list_filter = ["is_active", "is_staff"]
|
|
226
|
+
search_fields = ["email"]
|
|
227
|
+
ordering = ["-created_at"]
|
|
228
|
+
fieldsets = (
|
|
229
|
+
(None, {"fields": ("email", "password")}),
|
|
230
|
+
("Permissions", {"fields": ("is_active", "is_staff", "is_superuser", "groups")}),
|
|
231
|
+
)
|
|
232
|
+
add_fieldsets = (
|
|
233
|
+
(None, {"fields": ("email", "password1", "password2")}),
|
|
234
|
+
)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### QuerySet optimization
|
|
238
|
+
```python
|
|
239
|
+
# Always select_related for FK fields, prefetch_related for M2M/reverse FK
|
|
240
|
+
posts = Post.objects.select_related("author").prefetch_related("tags").filter(published=True)
|
|
241
|
+
|
|
242
|
+
# Use only() or defer() for large models when you only need specific fields
|
|
243
|
+
emails = User.objects.filter(is_active=True).only("email")
|
|
244
|
+
|
|
245
|
+
# Use values() for read-only aggregations — skips ORM object construction
|
|
246
|
+
counts = Order.objects.values("status").annotate(count=Count("id"))
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Testing
|
|
250
|
+
```python
|
|
251
|
+
# pytest-django style
|
|
252
|
+
import pytest
|
|
253
|
+
from rest_framework.test import APIClient
|
|
254
|
+
|
|
255
|
+
@pytest.fixture
|
|
256
|
+
def api_client():
|
|
257
|
+
return APIClient()
|
|
258
|
+
|
|
259
|
+
@pytest.fixture
|
|
260
|
+
def authenticated_client(api_client, user):
|
|
261
|
+
api_client.force_authenticate(user=user)
|
|
262
|
+
return api_client
|
|
263
|
+
|
|
264
|
+
@pytest.mark.django_db
|
|
265
|
+
def test_create_user(api_client):
|
|
266
|
+
resp = api_client.post("/api/users/", {"email": "a@b.com", "password": "strongpass"})
|
|
267
|
+
assert resp.status_code == 201
|
|
268
|
+
assert resp.data["email"] == "a@b.com"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Example
|
|
272
|
+
|
|
273
|
+
**User:** Add a `Post` model to a Django project with DRF, including list/create/retrieve endpoints, paginated results, and filter by `published=True`.
|
|
274
|
+
|
|
275
|
+
**Expected output:**
|
|
276
|
+
- `models.py` — `Post` with `title`, `body`, `author` (FK to User), `published`, `created_at`
|
|
277
|
+
- `serializers.py` — `PostSerializer` with read-only `author` (nested), writable `title`/`body`/`published`
|
|
278
|
+
- `views.py` — `PostViewSet` with `queryset` filtered to `published=True` for unauthenticated users, `IsAuthenticatedOrReadOnly` permission, `PageNumberPagination`
|
|
279
|
+
- `urls.py` — router registered at `/api/posts/`
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
> **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/)
|