Skip to content
Back to skills

Tpl Backend Python Fastapi

ASecurity

Template do pack (backend/02-python-fastapi.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.

  • 11 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added September 8, 2026
databasespythongosqlexpressfastapigitapidatabasebackendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-backend-python-fastapi --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tpl Backend Python Fastapi?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tpl Backend Python Fastapi
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/artubss-tpl-backend-python-fastapi/badge)](https://www.skillsdirectory.com/skills/artubss-tpl-backend-python-fastapi)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tpl-backend-python-fastapi
description: Template do pack (backend/02-python-fastapi.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.
metadata:
  version: 1.0.0
  source_template: backend/02-python-fastapi.md
  generated_by: install_pack_templates_as_claude_skills
---

# PROJECT: Python REST API (FastAPI)

Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `backend/02-python-fastapi.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.

## Conteudo do template

## STACK

| Technology | Version | Purpose |
|------------|---------|---------|
| Python | 3.12 | Runtime |
| FastAPI | 0.111+ | HTTP framework |
| SQLAlchemy | 2.0 async | ORM |
| asyncpg | 0.29+ | Async PostgreSQL driver |
| Alembic | 1.13+ | Database migrations |
| Pydantic | v2 | Request/response validation + settings |
| pytest | 8.x | Test runner |
| httpx | 0.27+ | Async HTTP client for tests |
| ruff | 0.4+ | Linting + formatting |
| mypy | 1.10+ | Static type checking |
| python-jose | 3.x | JWT tokens |
| passlib[bcrypt] | 1.7+ | Password hashing |

---

## PROJECT STRUCTURE

```
app/
├── core/
│   ├── config.py         # Pydantic BaseSettings — env vars
│   ├── security.py       # JWT creation/verification, password hashing
│   ├── database.py       # Async engine + session factory
│   └── exceptions.py     # Custom HTTP exceptions + handlers
├── modules/
│   └── users/
│       ├── router.py          # APIRouter, endpoints only
│       ├── service.py         # Business logic
│       ├── repository.py      # DB queries (SQLAlchemy)
│       ├── models.py          # SQLAlchemy ORM models
│       ├── schemas.py         # Pydantic request/response schemas
│       └── dependencies.py    # FastAPI Depends() factories
├── middlewares/
│   ├── logging.py        # Request/response logging
│   └── cors.py           # CORS config
├── tasks/
│   └── email.py          # Background tasks (send email, etc.)
├── migrations/
│   ├── env.py            # Alembic async env config
│   └── versions/         # Auto-generated migration files
└── main.py               # App factory, include_router, lifespan
alembic.ini
pyproject.toml
tests/
├── conftest.py           # Fixtures: async client, test DB, test user
├── unit/
│   └── users/
│       └── test_service.py
└── integration/
    └── users/
        └── test_router.py
```

---

## ARCHITECTURE RULES

- **Router → Service → Repository** — strict, no skipping layers
- Routers contain ONLY path declarations and dependency injection
- Services contain business logic — never import `Request` or `Response`
- Repositories contain ONLY SQLAlchemy queries — no `if/else` logic
- All endpoint functions are `async def`; synchronous endpoints use `run_in_executor`
- Models (SQLAlchemy) and Schemas (Pydantic) are SEPARATE — never expose ORM objects directly
- All schemas use `model_config = ConfigDict(from_attributes=True)` for ORM conversion

---

## DEPENDENCY INJECTION PATTERN

### Async Session Dependency

```python
# app/core/database.py
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.core.config import settings

engine = create_async_engine(
    settings.DATABASE_URL,
    pool_size=10,
    max_overflow=20,
    echo=settings.DEBUG,
)

AsyncSessionLocal = async_sessionmaker(
    engine,
    expire_on_commit=False,
    class_=AsyncSession,
)

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise
```

### Current User Dependency

```python
# app/modules/users/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import decode_access_token
from app.modules.users.repository import UserRepository
from app.modules.users.models import User

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db),
) -> User:
    payload = decode_access_token(token)  # raises 401 if invalid
    user = await UserRepository(db).get_by_id(payload["sub"])
    if not user:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
    return user

async def require_admin(
    current_user: User = Depends(get_current_user),
) -> User:
    if current_user.role != "admin":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin required")
    return current_user
```

### OAuth2 Password Flow (Login endpoint)

```python
# app/modules/auth/router.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import verify_password, create_access_token, create_refresh_token
from app.modules.users.repository import UserRepository

router = APIRouter(prefix="/auth", tags=["auth"])

@router.post("/login")
async def login(
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: AsyncSession = Depends(get_db),
):
    repo = UserRepository(db)
    user = await repo.get_by_email(form_data.username)
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
        )
    return {
        "access_token": create_access_token({"sub": str(user.id), "role": user.role}),
        "refresh_token": create_refresh_token(str(user.id)),
        "token_type": "bearer",
    }
```

### Background Tasks

```python
# app/tasks/email.py
from fastapi import BackgroundTasks
import smtplib
from app.core.config import settings

def send_welcome_email(email: str, name: str) -> None:
    # This runs in a thread pool — do NOT use async here
    print(f"Sending welcome email to {email}")

# In router:
@router.post("/register", status_code=201)
async def register(
    body: UserCreateSchema,
    db: AsyncSession = Depends(get_db),
    background_tasks: BackgroundTasks = BackgroundTasks(),
):
    user = await UserService(db).create(body)
    background_tasks.add_task(send_welcome_email, user.email, user.name)
    return user
```

---

## SETTINGS PATTERN

```python
# app/core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")

    DATABASE_URL: str
    SECRET_KEY: str
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
    REFRESH_TOKEN_EXPIRE_DAYS: int = 7
    DEBUG: bool = False
    CORS_ORIGINS: list[str] = ["http://localhost:5173"]

settings = Settings()
```

---

## ROUTING TABLE

| Trigger | Method | Route | Auth | Handler |
|---------|--------|-------|------|---------|
| Register | POST | `/api/v1/auth/register` | Public | auth.register |
| Login | POST | `/api/v1/auth/login` | Public (form) | auth.login |
| Refresh | POST | `/api/v1/auth/refresh` | Refresh token | auth.refresh |
| Get profile | GET | `/api/v1/users/me` | Bearer | users.me |
| Update profile | PATCH | `/api/v1/users/me` | Bearer | users.update_me |
| List all users | GET | `/api/v1/users` | Bearer + admin | users.list |
| Get user by ID | GET | `/api/v1/users/{id}` | Bearer + admin | users.get |
| Delete user | DELETE | `/api/v1/users/{id}` | Bearer + admin | users.delete |
| Health check | GET | `/health` | Public | health.check |

---

## QUALITY GATES

Before opening a PR, verify ALL of the following:

- [ ] `ruff check . && ruff format --check .` passes with zero issues
- [ ] `mypy app/` passes — no `type: ignore` without a comment explaining why
- [ ] `pytest -x --tb=short` passes with zero failures
- [ ] Coverage ≥ 80% for new service/repository code
- [ ] New Alembic migration generated for any model changes: `alembic revision --autogenerate`
- [ ] Migration tested: `alembic upgrade head && alembic downgrade -1`
- [ ] No synchronous DB calls inside `async def` functions — always `await`
- [ ] No ORM model instances returned directly from endpoints — always use Pydantic schemas
- [ ] All new endpoints have a type annotation for the response model
- [ ] Secrets not hardcoded — sourced from `Settings`

---

## FORBIDDEN

- **NEVER** use `session.execute(text("raw SQL"))` — use SQLAlchemy ORM expressions
- **NEVER** define `async def` route handlers that block on sync I/O without `run_in_executor`
- **NEVER** share a single `AsyncSession` across multiple requests
- **NEVER** return SQLAlchemy model objects directly — always serialize through Pydantic
- **NEVER** use `from __future__ import annotations` — it breaks Pydantic v2 at runtime
- **NEVER** commit secrets to git — use `.env` (gitignored) and `.env.example`
- **NEVER** use `Union[X, None]` — use `X | None` (Python 3.10+ syntax)
- **NEVER** put business logic in Pydantic validators — keep them in services
- **NEVER** use `global` variables for shared state — use dependency injection
- **NEVER** ignore mypy errors with blanket `# type: ignore` — fix the types

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in databases →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.