Przejdź do głównej zawartości

Przepisy na backend w Pythonie dla agentów AI to prompty i bramki weryfikacyjne, dzięki którym Claude Code, Codex i Cursor tworzą produkcyjne usługi FastAPI i Django: walidowaną konfigurację, schematy Pydantic v2, asynchroniczne SQLAlchemy 2.1 odporne na współbieżność, idempotentne zadania Celery, odporne klienty HTTP i logi z redakcją sekretów. Kod przyjmujesz na podstawie wyników bramek, a nie lektury linijka po linijce.

Twoja usługa FastAPI przechodzi wszystkie testy napisane przez agenta. Potem pierwszy test obciążeniowy zamraża pętlę zdarzeń, bo handler wywołuje requests.get(), zapytanie asynchroniczne rzuca MissingGreenlet na relacji, której nikt nie załadował, a ponowione zadanie Celery obciąża klienta dwa razy. Kod wyglądał idiomatycznie, a testy działały na SQLite z mockami, więc nie miały szansy niczego z tego wyłapać.

Ta strona jest dla programistów, którzy budują backendy w Pythonie z agentem i chcą przestać przeglądać każdy handler wzrokiem. Produkcyjne szczegóły trafiają do promptów, a dowód dostarcza maszyna.

  • Blok reguł AGENTS.md dla typowanego, asynchronicznego Pythona, którego przestrzegają wszystkie trzy narzędzia, oraz bramkę make check, którą agent uruchamia, zanim powie „gotowe”
  • Osiem promptów do skopiowania: szkielet FastAPI z lifespanem i konfiguracją, walidacja Pydantic v2, asynchroniczne repozytorium SQLAlchemy, które nie sprzeda więcej, niż ma na stanie, idempotentne zadania Celery, klient httpx z timeoutami i ponowieniami, structlog z redakcją, API w Django REST Framework z izolacją tenantów oraz sprzątanie przestarzałych idiomów
  • Tabelę weryfikacji, która mówi, która bramka łapie który błąd, więc czytasz wynik bramek zamiast diffa
  • Kroki naprawcze dla siedmiu najczęstszych awarii Pythona napisanego przez agenta

Przygotuj konwencje Pythona i bramkę, którą agent musi przejść

Dział zatytułowany „Przygotuj konwencje Pythona i bramkę, którą agent musi przejść”

Większość pracy wykonują dwa artefakty: plik reguł, który agent czyta przy każdym zadaniu, i jedno polecenie, które kończy się błędem, gdy kod nie jest gotowy. Przygotuj oba, zanim poprosisz o jakikolwiek kod.

  1. Przypnij interpreter i narzędzia. Aktualną stabilną linią jest Python 3.14, a PEP 790 planuje wydanie Pythona 3.15.0 na 2026-10-01. Zostań przy 3.14, dopóki twoje zależności nie zadeklarują wsparcia dla 3.15. Z uv (terminal):

    Okno terminala
    uv python pin 3.14
    uv add fastapi "pydantic-settings" "sqlalchemy[postgresql-asyncpg]" alembic structlog httpx
    uv add --dev pytest pytest-asyncio pytest-cov "testcontainers[postgres]" respx ruff mypy

    Aktualne wydania na PyPI 2026-09-26: fastapi 0.141.1, pydantic 2.13.5, pydantic-settings 2.15.0, sqlalchemy 2.1.1, alembic 1.20.0, pytest 9.1.1, pytest-asyncio 1.4.0, ruff 0.16.9, mypy 2.3.1, testcontainers 4.15.0, celery 5.6.3, django 6.1.1. Dodatek [postgresql-asyncpg] ma znaczenie: SQLAlchemy 2.1 nie instaluje już domyślnie greenlet, a rozszerzenie asynchroniczne bez niego nie działa.

  2. Skonfiguruj narzędzia w pyproject.toml. Najwięcej łapią dwa ustawienia: reguły ASYNC wskazują blokujące wywołania wewnątrz async def, a filterwarnings = ["error"] zamienia każde ostrzeżenie o deprecjacji, w tym idiomy z Pydantic v1, w nieudany test.

    [tool.ruff]
    target-version = "py314"
    [tool.ruff.lint]
    select = ["E", "F", "I", "B", "UP", "ASYNC", "S", "RUF"]
    [tool.ruff.lint.per-file-ignores]
    "tests/**" = ["S101"]
    [tool.mypy]
    strict = true
    plugins = ["pydantic.mypy"]
    [tool.pytest.ini_options]
    asyncio_mode = "auto"
    asyncio_default_fixture_loop_scope = "function"
    filterwarnings = ["error"]
  3. Dodaj bramkę. To definicja „gotowe” dla każdego promptu na tej stronie. W pliku Makefile (linie receptury zaczynają się od tabulatora):

    check:
    uv run ruff format --check .
    uv run ruff check .
    uv run mypy app
    uv run pytest --cov=app --cov-fail-under=80
    uv export --locked --no-dev --no-emit-project --format requirements.txt -o .audit.txt
    uvx pip-audit --disable-pip -r .audit.txt

    Dwie ostatnie linie audytują zablokowane zależności runtime bez ich instalowania. Dodaj .audit.txt do .gitignore.

  4. Zapisz reguły Pythona w AGENTS.md w katalogu głównym repozytorium. Codex czyta AGENTS.md natywnie; zakładki niżej pokazują, jak ten sam tekst trafia do Claude Code i Cursora.

    ## Python conventions
    - Python 3.14, uv, fully typed. mypy --strict must pass; no bare `Any`,
    no `# type: ignore` without a reason in the same comment.
    - Pydantic v2 only: model_config = ConfigDict(...), @field_validator,
    @model_validator, model_dump(). Never class Config, @validator, or .dict().
    - Settings come from one pydantic-settings class in app/core/config.py.
    No os.getenv or os.environ anywhere else.
    - Inside async def: no requests, no time.sleep, no sync SQLAlchemy sessions,
    no blocking file I/O. Use httpx.AsyncClient, asyncio.sleep, AsyncSession.
    - SQLAlchemy 2.x style: select(), Mapped[], mapped_column(). Relationships
    use lazy="raise"; load them explicitly with selectinload().
    - Every outbound HTTP call has an explicit timeout.
    - Tests: pytest with pytest-asyncio in auto mode. Database tests use
    Testcontainers Postgres, not SQLite and not mocks.
    - Never edit an existing test assertion, add pytest.mark.skip or xfail, or
    loosen filterwarnings to make a test pass. If a test looks wrong, stop and
    say why.
    - Done means `make check` passes. Paste its last 20 lines in your final message.

Najważniejsze są dwie ostatnie reguły. Pierwsza nie pozwala agentowi osłabiać testów, żeby dojść do zielonego wyniku; strona chroń wyrocznię wyjaśnia, dlaczego to najczęstszy sposób, w jaki praca agenta udaje sukces. Druga zamienia „chyba działa” w dowód, który przeczytasz w dziesięć sekund.

Reguły i prompty są identyczne we wszystkich narzędziach. Różni się to, jak każde narzędzie wczytuje reguły, formatuje zmiany i recenzuje wynik.

Claude Code czyta najpierw CLAUDE.md, więc utwórz plik, który importuje wspólne reguły. Składnia @path wstawia zawartość pliku do kontekstu przy starcie:

@AGENTS.md

Formatuj i automatycznie poprawiaj każdy plik Pythona edytowany przez agenta hookiem PostToolUse w .claude/settings.json:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | grep -E '\\.py$' | xargs -r sh -c 'uv run ruff format \"$@\" && uv run ruff check --fix \"$@\"' _"
}
]
}
]
}
}

Przejście do definicji i diagnostykę typów daje agentowi wtyczka pyright-lsp z oficjalnego marketplace’u Anthropic. Wtyczka rejestruje tylko serwer, więc najpierw zainstaluj Pyright (terminal):

Okno terminala
uv tool install pyright
claude plugin install pyright-lsp@claude-plugins-official

Zanim otworzysz pull request, uruchom w sesji /code-review, żeby dostać drugą opinię o diffie.

Zacznij w każdym narzędziu od domyślnego modelu i najpierw zwiększ ustawienie effort, a dopiero potem zmień model; aktualne domyślne modele znajdziesz w hubie modeli.

Stos Pythona z tej strony zmienił się szybciej niż większość danych treningowych: FastAPI porzuciło wsparcie pydantic.v1 w wersji 0.128.0 (2025-12-27), pytest-asyncio 1.0 usunęło fixture event_loop, a SQLAlchemy 2.1 zmieniło sposób instalacji wsparcia async. Serwer MCP Context7 dostarcza agentowi dokumentację dla konkretnych wersji. Klucz API jest opcjonalny, więc wystarczy sam zdalny URL (terminal):

Okno terminala
claude mcp add --transport http context7 https://mcp.context7.com/mcp

Potem zakończ prompt słowami „use context7 for SQLAlchemy 2.1 and pytest-asyncio 1.x”. Każdy serwer MCP dokłada do kontekstu definicje swoich narzędzi, więc podłączaj go w projektach, które go potrzebują, a nie globalnie.

Zbuduj produkcyjną usługę FastAPI z agentem, krok po kroku

Dział zatytułowany „Zbuduj produkcyjną usługę FastAPI z agentem, krok po kroku”

Każdy krok to jeden prompt i sprawdzenie, które go dowodzi. Wykonuj je po kolei: późniejsze prompty zakładają, że szkielet, moduł konfiguracji i moduł bazy danych już istnieją.

Jak postawić usługę FastAPI, która czysto startuje i się zamyka?

Dział zatytułowany „Jak postawić usługę FastAPI, która czysto startuje i się zamyka?”

Najczęstsze braki w pierwszym podejściu to walidacja przy starcie i zamykanie. Agent czyta zmienne środowiskowe tam, gdzie ich potrzebuje, i tworzy silnik bazy danych przy imporcie, więc brakująca zmienna wychodzi jako 500 przy pierwszym żądaniu, a silnik nigdy nie jest zwalniany.

Dobra odpowiedź ma taki rdzeń:

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
settings = get_settings()
engine = create_async_engine(str(settings.DATABASE_URL), pool_pre_ping=True)
app.state.sessionmaker = async_sessionmaker(engine, expire_on_commit=False)
yield
await engine.dispose()

Sam ASGITransport nie uruchamia lifespanu, dlatego prompt wymienia LifespanManager (pakiet asgi-lifespan na PyPI). Bez niego testy mogą przechodzić, choć nigdy nie utworzyły silnika, który rzekomo sprawdzają.

Jak dostać walidację Pydantic v2, która precyzyjnie odrzuca złe dane?

Dział zatytułowany „Jak dostać walidację Pydantic v2, która precyzyjnie odrzuca złe dane?”

Agenci wytrenowani na latach kodu z Pydantic v1 wciąż piszą class Config, @validator i .dict(). Ustawienie filterwarnings = ["error"] zamienia każdy z tych idiomów w nieudany test, bo Pydantic v2 emituje dla nich ostrzeżenie o deprecjacji.

Asercje na loc i type zamiast na treści komunikatu sprawiają, że testy są stabilne między wydaniami Pydantic, a mimo to padają, gdy zniknie jakaś reguła.

Jak dostać asynchroniczne zapytania SQLAlchemy, które wytrzymają równoległe zamówienia?

Dział zatytułowany „Jak dostać asynchroniczne zapytania SQLAlchemy, które wytrzymają równoległe zamówienia?”

Pierwsze podejście agenta do „sprawdź stan, potem zmniejsz” ładuje produkt, porównuje wartości w Pythonie i zapisuje wynik. Dwa równoległe zamówienia czytają stock = 1 i oba się udają. SQLite z jednym połączeniem nie pokaże tego błędu; pokaże go tylko Postgres z równoległymi sesjami.

lazy="raise" zamienia klasyczny błąd async, czyli niejawne leniwe ładowanie, które na produkcji kończy się MissingGreenlet, w natychmiastowy błąd w pierwszym teście, który dotknie relacji. Liczy się też ścieżka importu: w testcontainers 4.15.0 stary moduł testcontainers.postgres emituje ostrzeżenie o deprecjacji, które twoje ustawienie filterwarnings zamienia w porażkę.

Żeby agent czytał prawdziwy schemat zamiast zgadywać nazwy kolumn, podłącz bazodanowy serwer MCP do deweloperskiej bazy przez rolę tylko do odczytu. Przewodnik po bazodanowych serwerach MCP porównuje serwery i ich przełączniki trybu tylko do odczytu. O bezpiecznych migracjach dużych tabel przeczytasz we wzorcach migracji baz danych.

Jak przenieść wolną pracę do Celery bez zdublowanych efektów ubocznych?

Dział zatytułowany „Jak przenieść wolną pracę do Celery bez zdublowanych efektów ubocznych?”

Ponowienia w Celery są bezpieczne tylko wtedy, gdy zadanie jest idempotentne. Przy acks_late awaria workera albo zadanie, które trwa dłużej niż visibility timeout brokera Redis, zostaje dostarczone ponownie, a drugie wykonanie powtarza każdy efekt uboczny.

Agent zwykle pomija dwa szczegóły. celery.contrib.pytest nie rejestruje się jako wtyczka pytest automatycznie, więc jego fixture’y nie istnieją, dopóki conftest.py go nie wskaże. A task_always_eager uruchamia zadanie w procesie testu, więc nie pokaże ponownego dostarczenia przez broker ani utraty workera; tryb eager zostaw wyłącznie do testów jednostkowych samej treści zadania. W Django 6 wbudowany framework django.tasks definiuje i kolejkuje zadania, ale do ich wykonania w tle nadal potrzebny jest zewnętrzny backend z workerem; wbudowane backendy wykonują zadania tylko natychmiast albo je odrzucają.

Jak wołać zewnętrzne API bez zamrażania pętli zdarzeń?

Dział zatytułowany „Jak wołać zewnętrzne API bez zamrażania pętli zdarzeń?”

Klienty pisane przez agenta psują się na trzy sposoby: synchroniczne wywołanie requests wewnątrz async def, brak timeoutu albo pętla ponowień, która w nieskończoność ponawia 400. Pierwszy przypadek łapią w bramce reguły ASYNC Ruffa; dwa pozostałe załatwia prompt.

Najważniejsze są przypadki „bez ponowienia”. Implementacja, która ponawia wszystko, przechodzi każdy test ścieżki sukcesu, a na produkcji mnoży zwroty pieniędzy.

Jak dostać ustrukturyzowane logi, które nigdy nie ujawniają sekretów?

Dział zatytułowany „Jak dostać ustrukturyzowane logi, które nigdy nie ujawniają sekretów?”

Asercja z dwoma równoległymi żądaniami dowodzi, że zmienne kontekstowe są czyszczone i przypisane do jednego żądania. Nieprzetestowany procesor redakcji psuje się po cichu, gdy nazwy kluczy się rozjadą.

Jak utrzymać izolację tenantów w API Django REST Framework?

Dział zatytułowany „Jak utrzymać izolację tenantów w API Django REST Framework?”

W wielodostępnej aplikacji Django groźny błąd to jeden viewset, który zapomina filtrować po organizacji. Przechodzi każdy test pisany z perspektywy właściciela.

Test przechodzący po wszystkich trasach to bramka, która się skaluje: gdy agent za miesiąc doda nowy viewset i zapomni o mixinie, zestaw testów padnie, zanim ktokolwiek przeczyta nowy plik.

Jak posprzątać przestarzałe idiomy Pythona, które agent wciąż przywraca?

Dział zatytułowany „Jak posprzątać przestarzałe idiomy Pythona, które agent wciąż przywraca?”

Uruchom ten prompt raz na istniejącym kodzie, zanim zaczniesz przepisy, i ponownie za każdym razem, gdy bramka deprecjacji zrobi się czerwona po aktualizacji zależności.

Jak zweryfikować Python napisany przez agenta bez czytania każdej linii?

Dział zatytułowany „Jak zweryfikować Python napisany przez agenta bez czytania każdej linii?”

Czytaj wynik bramek i listy przypadków testowych, nie diff. Każda bramka poniżej łapie klasę błędów, która w recenzji wygląda dobrze.

BramkaPolecenieCo łapie
Format i lintruff format --check, ruff check z ASYNC, S, B, UPBlokujące wywołania w async def, zaszyte sekrety, SQL sklejany ze stringów, przestarzałą składnię
Typymypy --strict z wtyczką PydanticNiezgodne kształty danych między warstwami, nieobsłużone None, brakujące await na typowanych korutynach
Deprecjacjefilterwarnings = ["error"]Idiomy Pydantic v1, usunięte wzorce pytest-asyncio, przestarzałe importy
Testy jednostkowe i HTTPpytest z httpx.AsyncClient i respxKody statusu, treść błędów, walidację, ponowienia, redakcję
Testy integracyjneTestcontainers z Postgresem i RedisemWyścigi, np. sprzedaż ponad stan, brakujące ograniczenia, ponownie dostarczone zadania Celery, wycieki między tenantami
Próg pokryciapytest --cov-fail-under=80Całe moduły, których agent nie przetestował
Zależnościpip-audit na wyeksportowanym lockfile’uZnane podatności w zależnościach runtime
Agent recenzujący/code-review (Claude Code), codex review --base main (Codex), Bugbot (Cursor)Błędy logiczne i brakujące przypadki, których bramki nie wyrażą

Pokrycie dowodzi, że linie się wykonały, a nie że asercje są mocne. Mocniejszy dowód jakości samych testów opisuje strona o testach mutacyjnych i sile wyroczni.

Uruchamiaj bramkę w CI przy każdym pull requeście; plik workflow znajdziesz we wzorcach potoków CI/CD. Inżynier odpowiedzialny za usługę zatwierdza trzy rzeczy: listy przypadków testowych, które agent napisał przed kodem, zieloną bramkę oraz wyniki testów współbieżności i macierzy uprawnień. Ludzka lektura trafia do fragmentów wskazanych przez agenta recenzującego i do kodu, który dotyka uwierzytelniania, pieniędzy albo granic między tenantami. Jak podzielić tę pracę w zespole, opisuje recenzja PR-ów agenta, a bramki bezpieczeństwa dla agentów dodają niezależnego recenzenta bezpieczeństwa.

Kiedy Python napisany przez agenta psuje się na produkcji

Dział zatytułowany „Kiedy Python napisany przez agenta psuje się na produkcji”

Każda z poniższych awarii przechodzi zestaw testów happy path. Każda ma objaw, który da się wykryć, i sposób naprawy.

  • Pętla zdarzeń staje pod obciążeniem. Objaw: p99 opóźnień skacze dla wszystkich endpointów, gdy wywoływany jest jeden wolny upstream, a CPU pozostaje niskie. Przyczyna: blokujące wywołanie (requests, time.sleep, synchroniczna sesja ORM, open()) wewnątrz async def. Naprawa: włącz reguły ASYNC Ruffa, jeśli są wyłączone, a potem wydaj prompt: „Fix every ASYNC finding with an async equivalent, or move the call to asyncio.to_thread with a comment saying why. Done means make check passes.”
  • MissingGreenlet tylko na produkcji. Objaw: sqlalchemy.exc.MissingGreenlet na endpoincie, który w testach działał. Przyczyna: niejawne leniwe ładowanie relacji w kodzie async, często w serializatorze odpowiedzi. Naprawa: ustaw lazy="raise" na każdej relacji, uruchom testy i dodaj selectinload() tam, gdzie teraz coś pada.
  • Async SQLAlchemy nie startuje po aktualizacji. Objaw: błąd informujący, że wymagana jest biblioteka greenlet. Przyczyna: SQLAlchemy 2.1 przeniosło greenlet do dodatku [asyncio]. Naprawa: zadeklaruj zależność sqlalchemy[postgresql-asyncpg] (albo sqlalchemy[asyncio]) i zacommituj zaktualizowany uv.lock.
  • Sprzedaż ponad stan albo podwójne efekty uboczne. Objaw: ujemny stan magazynu, zdublowane raporty albo zdublowane zwroty po skoku ruchu lub restarcie workera. Przyczyna: logika „odczytaj, a potem zapisz” w Pythonie albo nieidempotentne zadanie dostarczone ponownie. Naprawa: dodaj współbieżny test na Testcontainers z przepisu na zamówienia, a potem wydaj prompt: „Find every place that reads a value, decides in Python, and writes it back. Replace each with a conditional UPDATE or a row lock, and add a concurrent test for each.”
  • Zestaw testów pada po aktualizacji pytest-asyncio. Objaw: błędy wspominające fixture event_loop. Przyczyna: agent użył wzorca sprzed wersji 1.0, czyli nadpisania fixture’a event_loop, a pytest-asyncio 1.0 ten fixture usunęło. Naprawa: uruchom prompt sprzątający przestarzałe idiomy; zastępuje ten fixture ustawieniem loop_scope.
  • Jeden tenant widzi dane innego. Objaw: zgłoszenie do supportu albo log dostępu z identyfikatorami innej organizacji. Przyczyna: nowy viewset lub zapytanie bez filtra tenanta. Naprawa: dodaj test mixinu przechodzący po wszystkich trasach z przepisu na Django, żeby luka nie wróciła, i przejrzyj logi z okresu, którego dotyczy problem.
  • Agent osłabia test, żeby dojść do zielonego wyniku. Objaw: poluzowana asercja, usunięty przypadek, dodany skip lub xfail albo zmienione filterwarnings w tym samym diffie co poprawka. Naprawa: przywróć plik testu, powtórz regułę z AGENTS.md i spraw, żeby CI padało, gdy pull request zmienia istniejący plik test_*.py albo konfigurację pytest bez etykiety test-change.

Czy ten artykuł Ci pomógł?

Jedno kliknięcie, bez logowania.

wcale bardzo