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,285 @@
|
|
|
1
|
+
> 🇪🇸 Esta es la traducción en español. [Versión en inglés](../django.md).
|
|
2
|
+
|
|
3
|
+
# Skill de Django
|
|
4
|
+
|
|
5
|
+
## Cuándo activar
|
|
6
|
+
- Construir un proyecto Django con modelos ORM, migraciones y vistas
|
|
7
|
+
- Configurar serializadores, viewsets y routers de Django REST Framework (DRF)
|
|
8
|
+
- Escribir managers de modelos personalizados o métodos QuerySet
|
|
9
|
+
- Usar señales de Django para efectos secundarios desacoplados
|
|
10
|
+
- Configurar Celery para tareas asíncronas en un proyecto Django
|
|
11
|
+
- Personalizar el admin de Django
|
|
12
|
+
- Escribir pruebas con `django.test.TestCase` o `pytest-django`
|
|
13
|
+
|
|
14
|
+
## Cuándo NO usar
|
|
15
|
+
- APIs async-first — usar el skill de FastAPI en su lugar
|
|
16
|
+
- Microservicios que no necesitan el ORM o el admin de Django
|
|
17
|
+
- Scripts o CLIs simples — Python puro o Typer
|
|
18
|
+
- Si el proyecto ya usa FastAPI o Flask
|
|
19
|
+
|
|
20
|
+
## Instrucciones
|
|
21
|
+
|
|
22
|
+
### Estructura del proyecto
|
|
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
|
+
### División de configuración
|
|
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" # Siempre establece un modelo de usuario personalizado desde el primer día
|
|
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
|
+
### Modelo de usuario personalizado
|
|
72
|
+
```python
|
|
73
|
+
# apps/users/models.py — configurar antes de la primera migración, nunca cambiar después
|
|
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 personalizado
|
|
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
|
+
### Serializadores 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
|
+
### Configuración del 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
|
+
### Señales
|
|
176
|
+
```python
|
|
177
|
+
# apps/users/signals.py — usa señales solo para efectos secundarios verdaderamente desacoplados
|
|
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
|
+
# enviar correo
|
|
212
|
+
pass
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
raise self.retry(exc=exc, countdown=60)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Personalización del 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
|
+
("Permisos", {"fields": ("is_active", "is_staff", "is_superuser", "groups")}),
|
|
233
|
+
)
|
|
234
|
+
add_fieldsets = (
|
|
235
|
+
(None, {"fields": ("email", "password1", "password2")}),
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Optimización de QuerySet
|
|
240
|
+
```python
|
|
241
|
+
# Siempre select_related para campos FK, prefetch_related para M2M/FK inversa
|
|
242
|
+
posts = Post.objects.select_related("author").prefetch_related("tags").filter(published=True)
|
|
243
|
+
|
|
244
|
+
# Usa only() o defer() para modelos grandes cuando solo necesitas campos específicos
|
|
245
|
+
emails = User.objects.filter(is_active=True).only("email")
|
|
246
|
+
|
|
247
|
+
# Usa values() para agregaciones de solo lectura — omite la construcción de objetos ORM
|
|
248
|
+
counts = Order.objects.values("status").annotate(count=Count("id"))
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Testing
|
|
252
|
+
```python
|
|
253
|
+
# Estilo 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
|
+
## Ejemplo
|
|
274
|
+
|
|
275
|
+
**Usuario:** Agregar un modelo `Post` a un proyecto Django con DRF, incluyendo endpoints de lista/create/retrieve, resultados paginados y filtro por `published=True`.
|
|
276
|
+
|
|
277
|
+
**Salida esperada:**
|
|
278
|
+
- `models.py` — `Post` con `title`, `body`, `author` (FK a User), `published`, `created_at`
|
|
279
|
+
- `serializers.py` — `PostSerializer` con `author` de solo lectura (anidado), `title`/`body`/`published` editables
|
|
280
|
+
- `views.py` — `PostViewSet` con `queryset` filtrado a `published=True` para usuarios no autenticados, permiso `IsAuthenticatedOrReadOnly`, `PageNumberPagination`
|
|
281
|
+
- `urls.py` — router registrado en `/api/posts/`
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
> **Trabaja con nosotros:** Claudient está respaldado por [Uitbreiden](https://uitbreiden.com/) — construimos productos de IA y soluciones B2B con comunidades de desarrolladores. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
> 🇪🇸 Esta es la traducción en español. [Versión en inglés](../fastapi.md).
|
|
2
|
+
|
|
3
|
+
# Skill de FastAPI
|
|
4
|
+
|
|
5
|
+
## Cuándo activar
|
|
6
|
+
- Construir una API REST o asíncrona en Python con FastAPI
|
|
7
|
+
- Definir modelos de solicitud/respuesta con Pydantic
|
|
8
|
+
- Configurar inyección de dependencias con `Depends`
|
|
9
|
+
- Escribir manejadores de rutas asíncronos con SQLAlchemy o drivers de BD asíncronos
|
|
10
|
+
- Agregar middleware (CORS, autenticación, logging, limitación de tasa)
|
|
11
|
+
- Configurar tareas en segundo plano o workers de Celery
|
|
12
|
+
- Personalizar la documentación OpenAPI (etiquetas, descripciones, esquemas de respuesta)
|
|
13
|
+
- Escribir pruebas de integración con `TestClient` o `AsyncClient`
|
|
14
|
+
- Estructurar un proyecto FastAPI multi-módulo
|
|
15
|
+
|
|
16
|
+
## Cuándo NO usar
|
|
17
|
+
- Proyectos Django/DRF — usar el skill de Django
|
|
18
|
+
- Codebases solo síncronos donde la sobrecarga asíncrona no está justificada
|
|
19
|
+
- Scripts simples que no necesitan HTTP — usar Python puro
|
|
20
|
+
- APIs gRPC o GraphQL — capa de transporte y esquema diferente
|
|
21
|
+
|
|
22
|
+
## Instrucciones
|
|
23
|
+
|
|
24
|
+
### Estructura del proyecto
|
|
25
|
+
```
|
|
26
|
+
app/
|
|
27
|
+
├── main.py # Factoría de la aplicación FastAPI
|
|
28
|
+
├── core/
|
|
29
|
+
│ ├── config.py # Configuración mediante pydantic-settings
|
|
30
|
+
│ └── security.py # Utilidades JWT y hashing
|
|
31
|
+
├── api/
|
|
32
|
+
│ ├── deps.py # Funciones Depends() compartidas
|
|
33
|
+
│ └── v1/
|
|
34
|
+
│ ├── router.py # Agregador APIRouter
|
|
35
|
+
│ └── endpoints/
|
|
36
|
+
│ ├── users.py
|
|
37
|
+
│ └── items.py
|
|
38
|
+
├── models/ # Modelos ORM de SQLAlchemy
|
|
39
|
+
├── schemas/ # Esquemas de solicitud/respuesta Pydantic
|
|
40
|
+
├── crud/ # Funciones de operación en BD (no ORM, no HTTP)
|
|
41
|
+
└── db/
|
|
42
|
+
├── session.py # Factoría AsyncSession
|
|
43
|
+
└── base.py # Importación de la base declarativa
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Factoría de la aplicación
|
|
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
|
+
### Configuración con 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
|
+
### Dependencia de sesión SQLAlchemy asíncrona
|
|
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
|
+
### Manejadores de rutas
|
|
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
|
+
### Inyección de dependencias para autenticación
|
|
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
|
+
### Tareas en segundo plano
|
|
152
|
+
```python
|
|
153
|
+
# Usa BackgroundTasks de FastAPI para tareas ligeras fire-and-forget (sin resultado necesario)
|
|
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
|
+
# Usa Celery para: reintentos, seguimiento de resultados, programación, tareas entre servicios
|
|
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, # Nunca ["*"] en producción
|
|
172
|
+
allow_credentials=True,
|
|
173
|
+
allow_methods=["*"],
|
|
174
|
+
allow_headers=["*"],
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Manejadores de excepciones personalizados
|
|
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
|
+
### Testing
|
|
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
|
+
### Patrones comunes de Pydantic
|
|
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} # reemplaza orm_mode = True
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Ejemplo
|
|
232
|
+
|
|
233
|
+
**Usuario:** Construir un endpoint FastAPI para crear una publicación de blog, autenticada con JWT, guardando en PostgreSQL con SQLAlchemy async.
|
|
234
|
+
|
|
235
|
+
**Estructura esperada:**
|
|
236
|
+
- `schemas/post.py` — `PostCreate(BaseModel)`, `PostResponse(BaseModel)` con `from_attributes=True`
|
|
237
|
+
- `models/post.py` — modelo ORM `Post` con `id`, `title`, `body`, `author_id` (FK a User), `created_at`
|
|
238
|
+
- `crud/post.py` — función asíncrona `create(db, *, obj_in, author_id)`
|
|
239
|
+
- `api/v1/endpoints/posts.py` — `POST /posts/` con `Depends(get_current_user)` y `Depends(get_db)`
|
|
240
|
+
- `api/v1/router.py` — incluir router de posts
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
> **Trabaja con nosotros:** Claudient está respaldado por [Uitbreiden](https://uitbreiden.com/) — construimos productos de IA y soluciones B2B con comunidades de desarrolladores. [uitbreiden.com](https://uitbreiden.com/)
|