Przejdź do treści
Biblioteka skilli
Programowanie · Budowanie i kod

python-backend-design

/python-backend-design

Pomaga w architekturze usługi CDN w FastAPI, w tym w puli połączeń psycopg, uwierzytelnianiu, konfiguracji i uploadzie.

Repozytorium
agents ↗
Licencja
MIT
Ostatnia zmiana repozytorium
09.11.2026

Ocena i bezpieczeństwo

100/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść35 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
1
Skrypty
0
Komentarze HTML
0
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 09.11.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co robi i kiedy

Pomaga w architekturze usługi CDN w FastAPI, w tym w puli połączeń psycopg, uwierzytelnianiu, konfiguracji i uploadzie.

Oryginalny opis (EN)

Architecture and data-access patterns for the CDN service — FastAPI app lifespan, psycopg async pool, the auth dependency, pydantic-settings config, static file serving, upload validation, and rate limiting. Use when adding CDN endpoints, wiring the DB pool, config, or upload handling.

Jak działa

  1. When to activate
  2. Request flow
  3. App structure & lifespan
  4. Database access (psycopg, no ORM)
  5. Auth dependency
  6. Config (pydantic-settings)
  7. Errors
  8. Static files & uploads
  9. Rate limiting (mechanism)
  10. Logging
Sekcje z nagłówków SKILL.md, w oryginale · 211 linii, 970 słów
Podgląd SKILL.md
---
name: python-backend-design
description: Architecture and data-access patterns for the CDN service — FastAPI app lifespan, psycopg async pool, the auth dependency, pydantic-settings config, static file serving, upload validation, and rate limiting. Use when adding CDN endpoints, wiring the DB pool, config, or upload handling.
license: MIT
metadata:
  author: rainingdaemons/agents
  version: "1.0"
---

# Backend Patterns (FastAPI)

How this backend is structured and how to add to it without fighting its design. It covers the **architecture and data access** of this service; FastAPI framework mechanics are in `fastapi`, Python language idioms in `python-patterns`, HTTP contract design in `api-design`, and SQL/schema review in `postgresql-patterns` / `postgresql-optimization` / `database-design`.

## When to activate

- Adding or changing a endpoint in `app/main.py`
- Touching the database pool, auth dependency, config, or upload handling
- Reviewing the backend structure, error handling, or rate limiting

## Request flow

```
HTTP → FastAPI router → Depends(require_admin) [auth + RBAC] → handler
```

- Public endpoints: `GET /images/{filename}`, `GET /health`.
- Mutation endpoints (`POST`, `PUT`, `DELETE` `/images`) require the admin role.
- Rate limiting runs per-IP inside mutation handlers.
- Errors are `HTTPException(status_code, detail)`.
- Handlers are thin: validate → act → respond. Keep business logic in the handler; no service/repository layer.

## App structure & lifespan

The pool is created and closed via the FastAPI lifespan, not per request:

```python
@asynccontextmanager
async def lifespan(app: FastAPI):
    await init_pool()
    logger.info("CDN started — serving %s on %s:%s", settings.public_dir, settings.host, settings.port)
    yield
    await close_pool()

app = FastAPI(title="SLEG CDN", lifespan=lifespan)
```

The static mount is registered **last** so API routes match first:

```python
app.mount("/", StaticFiles(directory=settings.public_dir, html=True), name="static")
```

## Database access (psycopg, no ORM)

One module-level async pool in `app/database.py`; create it in the lifespan, borrow connections with `async with`:

```python
pool: psycopg_pool.AsyncConnectionPool | None = None

async def init_pool() -> None:  # AsyncConnectionPool(conninfo=settings.dsn, min_size=1, max_size=5, timeout=10, open=False); await pool.open()
async def close_pool() -> None: ...
def get_pool() -> psycopg_pool.AsyncConnectionPool:
    if pool is None:
        raise RuntimeError("Database pool not initialized")
    return pool
```

```python
db = get_pool()
async with db.connection() as conn:
    await conn.execute("SELECT 1")
```

- No ORM. Do not add SQLAlchemy — use the psycopg pool.
- Use parameterized queries (`%s` placeholders); never format values into SQL.
- The CDN only reads shared tables (schema is owned by `database/migrations/`); do not manage migrations here.

## Auth dependency

`app/auth.py` implements `require_admin` as a FastAPI dependency. It decodes an **HS256 JWT** (signed with `settings.jwt_secret`) and checks the `role` claim:

```python
@dataclass
class AdminUser:
    user_id: UUID

async def require_admin(
    credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(_bearer_scheme)],
) -> AdminUser:
    if credentials is None:
        raise HTTPException(status_code=401, detail="Missing authorization")
    try:
        payload = jwt.decode(credentials.credentials, settings.jwt_secret, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")
    if payload.get("role", "") != "admin":
        raise HTTPException(status_code=403, detail="Admin access required")
    return AdminUser(user_id=UUID(payload.get("sub") or payload.get("user_id")))
```

Attach it per route; never re-implement auth checks in handlers:

```python
@app.delete("/images/{filename}")
async def delete_image(
    filename: str,
    admin: Annotated[AdminUser, Depends(require_admin)],
    request: Request,
):
    ...
```

> Doc drift: `agentwork/docs/architecture.md` describes an opaque-token lookup against `login_tokens`; the code decodes a JWT. Treat the code as the source of truth and fix the doc when you touch auth.

## Config (pydantic-settings)

`app/config.py` declares one `Settings(BaseSettings)` read from `.env`; expose derived values as properties:

```python
class Settings(BaseSettings):
    host: str = "0.0.0.0"
    public_dir: str = "./public"
    jwt_secret: str = ""
    max_upload_size_mb: int = 10
    allowed_image_types: str = "image/jpeg,image/png,image/webp"

    @property
    def max_upload_bytes(self) -> int:
        return self.max_upload_size_mb * 1024 * 1024

    @property
    def dsn(self) -> str: ...

    model_config = {"env_file": ".env", "extra": "ignore"}

settings = Settings()
```

- Read secrets from the environment via `settings`; never hardcode them.
- Import the singleton: `from .config import settings`.

## Errors

- Raise `HTTPException(status_code=..., detail="...")` with a meaningful status (see `api-design` for status semantics).
- Log unexpected errors with `logging.error()` at the handler boundary.
- Never leak internals (DB errors, stack traces) in `detail`.

## Static files & uploads

Serving and writing files must stay inside `public/`:

- **Path traversal:** `validate_filename()` rejects `\x00`, `/`, `\`, `..`, and names not matching `^[a-zA-Z0-9][a-zA-Z0-9._\-]*$`. `_resolve_image()` re-checks the resolved path starts with `settings.public_path`.
- **Magic bytes:** validate uploads by content (`detect_mime_from_bytes` via `puremagic` + `validate_image_contents` via Pillow), never by the `Content-Type` header alone; reject mismatched extensions with 415.
- **Atomic writes:** write to a temp file in `public/`, then `os.replace()`; clean up the temp file on failure.
- **Size limit:** reject bodies over `settings.max_upload_bytes` (413) before and after reading.

```python
fd, tmp_path = tempfile.mkstemp(dir=str(settings.public_path), suffix=".tmp")
try:
    with os.fdopen(fd, "wb") as f:
        f.write(contents)
    os.replace(tmp_path, str(target_path))
except Exception:
    if os.path.exists(tmp_path):
        os.unlink(tmp_path)
    raise
```

## Rate limiting (mechanism)

In-memory per-IP sliding window on mutation endpoints (30 requests / 60 s). Keep the policy in one helper; the HTTP semantics and headers are `api-design`'s concern:

```python
_rate_buckets: dict[str, list[float]] = defaultdict(list)

def _check_rate_limit(key: str) -> None:
    now = time.monotonic()
    _rate_buckets[key] = [t for t in _rate_buckets[key] if now - t < _RATE_LIMIT_WINDOW]
    if len(_rate_buckets[key]) >= _RATE_LIMIT_MAX:
        raise HTTPException(status_code=429, detail="Rate limit exceeded")
    _rate_buckets[key].append(now)
```

Call it inside the handler: `_check_rate_limit(request.client.host if request.client else "unknown")`.

## Logging

- Use the `logging` module (`logging.getLogger("cdn")`), never `print()`.
- Configure once (`logging.basicConfig(level=logging.INFO, ...)`).
- Log lifecycle events (startup), warnings, and unexpected errors; do not log tokens or secrets.

## Testing

`pytest` + `pytest-asyncio`, driving the app with `httpx.AsyncClient` over `ASGITransport`; mock the pool and settings to isolate DB calls. Tests live in `tests/`, named `test_<action>_<scenario>`. See `cdn/agentwork/docs/conventions.md` for the exact commands.

## Module layout

```
app/main.py       FastAPI app, lifespan, routes, rate limit, upload/static logic
app/config.py     Settings (pydantic-settings) + derived properties
app/database.py   AsyncConnectionPool lifecycle, magic-byte detection, MIME maps
app/auth.py       AdminUser, require_admin, validate_filename
public/           served static files (images)
tests/            pytest + httpx tests
```

## Checklist

- [ ] Pool created/closed in the lifespan; handlers borrow via `get_pool()`
- [ ] No ORM; parameterized psycopg queries only
- [ ] Mutations depend on `require_admin`; auth not re-implemented in handlers
- [ ] Config via `settings`; no hardcoded secrets
- [ ] Errors are `HTTPException`; internals logged, not returned
- [ ] Filenames validated and resolved inside `public/`; uploads validated by magic bytes; writes atomic
- [ ] Mutation endpoints rate-limited via `_check_rate_limit`
- [ ] `logging`, not `print`; no tokens/secrets in logs

Wersje

W bazie nie ma innych skilli o nazwie python-backend-design.

Migawka bazy z 13.11.2026 · wersja paczki 89794e288c76 (skrót SHA-256 całej paczki)

Pobrania w ostatnich 30 dniach: 0 · ranking

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 100fastapi-patterns
    Buduje i przegląda usługi FastAPI: układ projektu, konfigurację, schematy, zależności, uwierzytelnianie 401 i 403 oraz testy.
  • 100fastapi-async-microservices
    Projektuje produkcyjne asynchroniczne mikrousługi w FastAPI i Pythonie 3.12+: Pydantic v2, SQLAlchemy 2.0 async, stan lifespan, logi JSON i pule połączeń.
  • 100fastapi-pro
    Podaje produkcyjne wzorce FastAPI dla Pythona 3.11+: endpointy async, SQLAlchemy 2.0 async, Pydantic V2, wstrzykiwanie zależności, JWT i testy.
  • 100scaffold-fastapi-service
    Zakłada serwis HTTP w FastAPI z uv: ustawienia pydantic-settings, ruff, mypy, pytest, health checki i błędy w formacie problem+json.
  • 100python-fastapi-pydantic
    Buduje asynchroniczne REST API w FastAPI z walidacją Pydantic i dokumentacją OpenAPI.
  • 81python-backend-fastapi-docker-agents
    Pomaga założyć projekt w Pythonie, zbudować usługę FastAPI, utwardzić obraz Docker i stworzyć agenta AI w Pydantic AI.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.