Fiszki Online FastAPI (Preview)
Darmowy podgląd 15 z 70 dostępnych pytań
Uwierzytelnianie i bezpieczeństwo
Jakie narzędzia bezpieczeństwa (security utilities) oferuje FastAPI?
Odpowiedź w 30 sekund:
FastAPI udostępnia gotowe klasy bezpieczeństwa w module fastapi.security, które integrują się z systemem zależności (Depends) i automatycznie dokumentują się w OpenAPI/Swagger UI. Są to m.in. OAuth2PasswordBearer, HTTPBasic, HTTPBearer, APIKeyHeader, APIKeyQuery i APIKeyCookie. Same w sobie wyłącznie wyciągają i opisują dane uwierzytelniające z żądania — logikę weryfikacji piszesz samodzielnie.
Odpowiedź w 2 minuty:
FastAPI nie narzuca jednego mechanizmu uwierzytelniania, lecz dostarcza zestaw klas-narzędzi (tzw. security utilities), które rozpoznają standardowe schematy bezpieczeństwa i wpinają się w system zależności. Dzięki temu w endpointcie deklarujesz po prostu zależność typu Annotated[str, Depends(oauth2_scheme)], a FastAPI zajmuje się odczytaniem nagłówka, zwróceniem 401 przy jego braku oraz wygenerowaniem odpowiedniego wpisu w schemacie OpenAPI (np. przycisk „Authorize" w Swagger UI).
Najczęściej używane narzędzia to: OAuth2PasswordBearer (token Bearer z przepływu OAuth2 „password"), HTTPBearer (dowolny token Bearer, np. JWT bez formularza logowania), HTTPBasic (uwierzytelnianie Basic z loginem i hasłem) oraz rodzina APIKey* do kluczy API przekazywanych w nagłówku, query stringu lub ciasteczku. Wszystkie dziedziczą po wspólnej bazie SecurityBase, dlatego działają spójnie i można je łączyć.
Kluczowa pułapka dla początkujących: te klasy tylko wydobywają poświadczenia z żądania — nie sprawdzają, czy hasło jest poprawne ani czy token jest ważny. To wciąż Twoja odpowiedzialność, by w zależności get_current_user zweryfikować token, odszukać użytkownika i odrzucić niepoprawne dane (zwracając HTTPException(status_code=401)). Dodatkowo OAuth2PasswordBearer w samym żądaniu nie wymusza HTTPS — szyfrowanie transportu konfigurujesz na poziomie serwera/reverse proxy.
Przykład kodu:
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, HTTPBasic, HTTPBasicCredentials, APIKeyHeader
import secrets
app = FastAPI()
# 1) OAuth2 z tokenem Bearer — wskazuje URL endpointu logowania
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 2) HTTP Basic — login + hasło w nagłówku Authorization
basic = HTTPBasic()
# 3) Klucz API w nagłówku
api_key_header = APIKeyHeader(name="X-API-Key")
@app.get("/oauth-protected")
async def oauth_protected(token: Annotated[str, Depends(oauth2_scheme)]):
# token to surowy ciąg z nagłówka "Authorization: Bearer ..."
# weryfikację (np. dekodowanie JWT) robisz samodzielnie
return {"token": token}
@app.get("/basic-protected")
async def basic_protected(creds: Annotated[HTTPBasicCredentials, Depends(basic)]):
# porównanie w czasie stałym chroni przed atakami czasowymi
poprawny_login = secrets.compare_digest(creds.username, "admin")
poprawne_haslo = secrets.compare_digest(creds.password, "tajne")
if not (poprawny_login and poprawne_haslo):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Błędne dane logowania",
headers={"WWW-Authenticate": "Basic"})
return {"user": creds.username}
@app.get("/key-protected")
async def key_protected(key: Annotated[str, Depends(api_key_header)]):
if key != "moj-tajny-klucz": # w realu: porównanie w czasie stałym + sekret z env
raise HTTPException(status.HTTP_403_FORBIDDEN, "Nieprawidłowy klucz API")
return {"ok": True}
Materiały
↑ Powrót na góręJak zaimplementować uwierzytelnianie OAuth2 z tokenem (OAuth2PasswordBearer)?
Odpowiedź w 30 sekund:
Tworzysz instancję OAuth2PasswordBearer(tokenUrl="token"), która wskazuje endpoint logowania i wyciąga token z nagłówka Authorization: Bearer. Endpoint /token przyjmuje OAuth2PasswordRequestForm (pola username/password), weryfikuje użytkownika i zwraca {"access_token": ..., "token_type": "bearer"}. Chronione endpointy zależą od get_current_user, która dekoduje token i zwraca użytkownika lub 401.
Odpowiedź w 2 minuty:
OAuth2PasswordBearer implementuje przepływ OAuth2 typu „password" (Resource Owner Password Credentials). Argument tokenUrl to względna ścieżka endpointu, na którym klient wymienia login i hasło na token — Swagger UI używa go, by przycisk „Authorize" wiedział, dokąd wysłać dane logowania. Sama klasa pełni rolę zależności: deklarujesz token: Annotated[str, Depends(oauth2_scheme)], a FastAPI odczyta nagłówek Authorization, a przy jego braku zwróci 401 z nagłówkiem WWW-Authenticate: Bearer.
Endpoint logowania przyjmuje OAuth2PasswordRequestForm jako zależność — to standardowy formularz application/x-www-form-urlencoded z polami username i password (a opcjonalnie scope, grant_type). To wymóg specyfikacji OAuth2: dane logowania idą jako form data, nie JSON. Po sprawdzeniu poświadczeń (hasło porównujesz z hashem, nigdy z plaintekstem) zwracasz słownik {"access_token": ..., "token_type": "bearer"}. W praktyce produkcyjnej access_token to zwykle podpisany JWT z polem exp, choć na początek może to być nawet sam login.
Logikę „kim jest użytkownik z tym tokenem" wydzielasz do zależności get_current_user — to ona dekoduje/weryfikuje token i pobiera użytkownika z bazy. Dzięki temu w chronionych endpointach piszesz tylko current_user: Annotated[User, Depends(get_current_user)] i masz czysty, wielokrotnego użytku mechanizm. Częsta pułapka: mylenie uwierzytelniania (authentication — „kim jesteś") z autoryzacją (authorization — „co możesz"); get_current_user odpowiada wyłącznie za to pierwsze. Pamiętaj też, że przepływ „password" działa bezpiecznie tylko po HTTPS.
Przykład kodu:
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Atrapa "bazy" użytkowników — hasło trzymamy jako hash (tu uproszczone)
fake_db = {"jan": {"username": "jan", "hashed_password": "haszowane:1234", "disabled": False}}
class User(BaseModel):
username: str
disabled: bool = False
def fake_hash(password: str) -> str:
return "haszowane:" + password # w produkcji: passlib/bcrypt
@app.post("/token")
async def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
user = fake_db.get(form.username)
# weryfikacja hasła przez porównanie hashy, nie plaintekstu
if not user or user["hashed_password"] != fake_hash(form.password):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Błędny login lub hasło")
# uproszczony token = login; w produkcji zwróć podpisany JWT z polem exp
return {"access_token": user["username"], "token_type": "bearer"}
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> User:
user = fake_db.get(token) # tu: dekodowanie i weryfikacja tokenu
if user is None:
raise HTTPException(
status.HTTP_401_UNAUTHORIZED, "Nieprawidłowe poświadczenia",
headers={"WWW-Authenticate": "Bearer"},
)
return User(**user)
# Dodatkowa zależność: sprawdza, czy konto nie jest zablokowane
async def get_current_active_user(
current_user: Annotated[User, Depends(get_current_user)],
) -> User:
if current_user.disabled:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Konto nieaktywne")
return current_user
@app.get("/users/me")
async def read_me(current_user: Annotated[User, Depends(get_current_active_user)]):
return current_user
Diagram:
sequenceDiagram
participant K as Klient
participant S as Serwer FastAPI
participant D as Baza danych
K->>S: POST /token ("username" + "password")
S->>D: Pobierz użytkownika i hash hasła
D-->>S: Dane użytkownika
S->>S: Zweryfikuj hasło (porównanie z hashem)
S-->>K: 200 {"access_token", "token_type": "bearer"}
K->>S: GET /users/me (Authorization: Bearer ...)
S->>S: get_current_user — zdekoduj i sprawdź token
S-->>K: 200 dane użytkownika (lub 401)
Materiały
↑ Powrót na góręPodstawy FastAPI
Czym jest FastAPI i jakie problemy rozwiązuje?
Odpowiedź w 30 sekund:
FastAPI to nowoczesny, wysokowydajny framework webowy do budowania API w Pythonie, oparty na standardowych adnotacjach typów. Rozwiązuje problem ręcznej walidacji danych, serializacji i braku dokumentacji — automatycznie waliduje żądania, serializuje odpowiedzi i generuje interaktywną dokumentację OpenAPI. Dzięki wsparciu dla async/await osiąga wydajność porównywalną z Node.js i Go.
Odpowiedź w 2 minuty: FastAPI powstał, aby wyeliminować powtarzalny i podatny na błędy kod, który wcześniej trzeba było pisać ręcznie w starszych frameworkach. W klasycznym Flasku programista sam parsuje ciało żądania, sprawdza typy, zwraca komunikaty błędów i osobno opisuje API w dokumentacji. FastAPI robi to wszystko automatycznie na podstawie deklaracji typów w sygnaturze funkcji obsługującej endpoint.
Główne problemy, które rozwiązuje, to: automatyczna walidacja danych wejściowych (przez Pydantic) wraz z czytelnymi, ustrukturyzowanymi błędami; automatyczna serializacja danych wyjściowych zgodnie z modelem odpowiedzi; samogenerująca się dokumentacja w standardzie OpenAPI (dostępna pod /docs jako Swagger UI oraz /redoc jako ReDoc); oraz natywne wsparcie dla programowania asynchronicznego, co pozwala obsługiwać wiele równoczesnych połączeń bez blokowania wątku.
Kolejną wartością jest doskonałe wsparcie edytora. Ponieważ wszystko opiera się na adnotacjach typów, IDE podpowiada pola, wykrywa błędy przed uruchomieniem i ułatwia refaktoryzację. FastAPI jest też świadomie zaprojektowany wokół standardów (OpenAPI, JSON Schema, OAuth2), więc integruje się z istniejącym ekosystemem narzędzi.
Typowa pułapka początkujących: traktowanie FastAPI jak frameworka pełnostackowego w stylu Django. FastAPI to framework do API — nie ma wbudowanego ORM, panelu administracyjnego ani systemu szablonów (choć można je dodać). Drugie błędne przekonanie to to, że "FastAPI jest szybki, więc wszystko piszę jako async" — jeśli wewnątrz endpointu wywołasz blokującą operację synchroniczną (np. zwykły sterownik bazy danych), zablokujesz całą pętlę zdarzeń.
Przykład kodu:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Model wejściowy — FastAPI automatycznie zwaliduje dane
class Produkt(BaseModel):
nazwa: str
cena: float
@app.post("/produkty/")
async def utworz_produkt(produkt: Produkt):
# Dane są już zwalidowane i poprawnie otypowane
return {"komunikat": f"Dodano {produkt.nazwa} za {produkt.cena} zł"}
Materiały
↑ Powrót na góręNa czym opiera się FastAPI (Starlette i Pydantic) i jaką rolę pełni każdy z tych komponentów?
Odpowiedź w 30 sekund: FastAPI stoi na dwóch filarach: Starlette odpowiada za warstwę webową (routing, obsługa żądań/odpowiedzi, ASGI, asynchroniczność), a Pydantic za walidację i serializację danych w oparciu o adnotacje typów. FastAPI sam jest cienką, ale potężną warstwą spajającą oba narzędzia i dodającą automatyczną dokumentację OpenAPI.
Odpowiedź w 2 minuty:
Starlette to lekki toolkit/framework ASGI, który dostarcza całą "mechanikę" serwera webowego: routing URL-i, obiekty Request i Response, middleware, obsługę WebSocketów, zadania w tle (background tasks), CORS oraz sesje. FastAPI dziedziczy po Starlette, więc wszystko, co potrafi Starlette, działa również w FastAPI. To Starlette zapewnia natywne wsparcie async/await i wysoką wydajność warstwy sieciowej.
Pydantic odpowiada za warstwę danych. Definiujesz modele jako klasy dziedziczące po BaseModel, a Pydantic na ich podstawie waliduje dane wejściowe, konwertuje typy, generuje czytelne błędy i serializuje dane wyjściowe. Pydantic v2 ma rdzeń napisany w Ruście (pydantic-core), dzięki czemu walidacja jest bardzo szybka. To również Pydantic generuje JSON Schema, które FastAPI wykorzystuje do budowy dokumentacji OpenAPI.
Rola samego FastAPI to inteligentne połączenie tych komponentów oraz dodanie funkcji, których żaden z nich nie ma osobno: system wstrzykiwania zależności (Dependency Injection), automatyczne mapowanie parametrów ścieżki/zapytania/ciała żądania na argumenty funkcji, wsparcie bezpieczeństwa (OAuth2, klucze API) oraz generowanie interaktywnej dokumentacji /docs i /redoc.
Warto zapamiętać podział odpowiedzialności: jeśli pytanie dotyczy "jak dane przepływają przez sieć" — to domena Starlette; jeśli "jak dane są walidowane i kształtowane" — to domena Pydantic. Częsta pułapka: mieszanie modeli Pydantic z modelami ORM (np. SQLAlchemy). To dwie różne rzeczy — model Pydantic opisuje kształt danych API, a model ORM opisuje tabelę w bazie. Zwykle definiuje się oba osobno i mapuje jeden na drugi (np. przez model_config = ConfigDict(from_attributes=True)).
Diagram:
flowchart TD
Klient["Klient (HTTP / JSON)"] --> Serwer["Serwer ASGI (Uvicorn)"]
Serwer --> Starlette["Starlette — warstwa webowa: routing, żądanie/odpowiedź, async"]
Starlette --> FastAPI["FastAPI — spaja warstwy, dodaje DI i dokumentację"]
FastAPI --> Pydantic["Pydantic — walidacja i serializacja danych"]
Pydantic --> Funkcja["Twoja funkcja endpointu"]
Funkcja --> Pydantic
Pydantic --> OpenAPI["Dokumentacja OpenAPI (/docs, /redoc)"]
Materiały
↑ Powrót na góręCzym jest ASGI i czym różni się od WSGI? Dlaczego ma to znaczenie dla FastAPI?
Odpowiedź w 30 sekund:
WSGI (Web Server Gateway Interface) to starszy, synchroniczny standard komunikacji między serwerem a aplikacją Pythona — obsługuje jedno żądanie na raz, blokując wątek. ASGI (Asynchronous Server Gateway Interface) to jego następca wspierający async/await, WebSockety i zdarzenia długo żyjące. FastAPI jest frameworkiem ASGI, dzięki czemu może obsługiwać tysiące współbieżnych połączeń bez blokowania.
Odpowiedź w 2 minuty: WSGI to standard, na którym opierają się Flask i Django (w trybie synchronicznym). Definiuje prosty kontrakt: serwer wywołuje funkcję aplikacji raz na żądanie i czeka na zakończenie. Model ten jest synchroniczny i blokujący — w trakcie operacji oczekujących (np. zapytanie do bazy, wywołanie zewnętrznego API) wątek jest zajęty i nie może obsłużyć innego żądania. Skalowanie odbywa się przez uruchamianie wielu procesów/wątków.
ASGI rozwiązuje to ograniczenie. Aplikacja ASGI to funkcja asynchroniczna przyjmująca trzy argumenty: scope (metadane połączenia), receive (do odbierania zdarzeń) i send (do wysyłania zdarzeń). Model oparty na zdarzeniach pozwala obsłużyć nie tylko HTTP, ale też WebSockety i zdarzenia cyklu życia (lifespan). Co najważniejsze — gdy endpoint async czeka na operację I/O, pętla zdarzeń może w tym czasie obsłużyć inne żądania na tym samym wątku. To daje wysoką współbieżność dla obciążeń zdominowanych przez I/O.
Dla FastAPI ma to znaczenie fundamentalne: FastAPI to framework ASGI (przez Starlette), więc natywnie wspiera async def w endpointach oraz WebSockety. Uruchamiasz go serwerem ASGI, takim jak Uvicorn (oparty na uvloop i httptools) czy Hypercorn — nie zadziała pod klasycznym serwerem WSGI typu Gunicorn z domyślnym workerem.
Najważniejsza pułapka: ASGI daje przewagę tylko wtedy, gdy faktycznie nie blokujesz pętli zdarzeń. Jeśli w endpoincie async def wywołasz operację synchroniczną i blokującą (np. time.sleep, synchroniczny sterownik bazy, ciężkie obliczenia CPU), zablokujesz cały worker i unicestwisz korzyść z asynchroniczności. Dla takich operacji albo używaj zwykłego def (FastAPI uruchomi go w puli wątków), albo wynoś ciężkie zadania poza pętlę. Drugie nieporozumienie: ASGI nie czyni kodu CPU-bound szybszym — to rozwiązanie dla obciążeń I/O-bound.
Diagram:
flowchart LR
subgraph WSGI["WSGI — synchroniczny"]
W1["Żądanie A"] --> W2["Wątek zajęty (czeka na I/O)"]
W2 --> W3["Żądanie B czeka w kolejce"]
end
subgraph ASGI["ASGI — asynchroniczny"]
A1["Żądanie A czeka na I/O"] --> A2["Pętla zdarzeń obsługuje żądanie B"]
A2 --> A3["Powrót do A, gdy I/O gotowe"]
end
Materiały
↑ Powrót na góręRouting i parametry
Jak ustawić parametry opcjonalne oraz ich wartości domyślne w endpointach?
Odpowiedź w 30 sekund:
Parametr staje się opcjonalny, gdy nadasz mu wartość domyślną, np. limit: int = 10. Aby zezwolić również na brak wartości (None), użyj typu opcjonalnego: q: str | None = None. Parametry bez wartości domyślnej są wymagane — jeśli ich nie podasz, FastAPI zwróci błąd 422.
Odpowiedź w 2 minuty:
W FastAPI to wartość domyślna decyduje o tym, czy parametr jest wymagany. Jeśli zadeklarujesz limit: int = 10, to przy braku tego parametru w żądaniu funkcja otrzyma 10. Jeśli chcesz, by parametr mógł być pominięty i wówczas przyjmował None, zadeklaruj go jako q: str | None = None (w Pythonie 3.10+) albo Optional[str] = None (z modułu typing, dla 3.9). Sam zapis str | None bez przypisania = None nadal czyni parametr wymaganym — różnica polega na tym, że dopuszcza on dodatkowo wartość pustą, ale klient i tak musi go podać.
Dla parametrów ścieżki sprawa jest prosta: są one zawsze wymagane, bo stanowią część adresu URL, więc nie nadaje się im wartości domyślnych. Opcjonalność dotyczy więc głównie parametrów zapytania (query) i pól ciała żądania. Dobrą praktyką jest podawanie sensownych wartości domyślnych dla paginacji (skip: int = 0, limit: int = 20) — upraszcza to korzystanie z API i czyni je bardziej odpornym na pominięcia.
W nowoczesnym FastAPI zalecaną składnią jest Annotated (opisane szerzej w osobnym pytaniu). Wówczas wartość domyślną nadal podaje się przez znak = po typie, np. q: Annotated[str | None, Query()] = None. To istotne: w stylu Annotated wartość domyślną ustawia się w przypisaniu funkcji, a NIE wewnątrz Query(default=...). Mieszanie obu sposobów (Query(default="x") i = "x" jednocześnie) jest błędem koncepcyjnym i potrafi mylić.
Częsta pułapka: nigdy nie używaj zmiennych typów mutowalnych jako wartości domyślnych w samej sygnaturze (np. tags: list = []) — to klasyczny problem współdzielonego obiektu między wywołaniami. Dla list i innych struktur w ciele żądania użyj modeli Pydantic albo Query()/Field() z default_factory. Druga pułapka: pamiętaj, że 0, "" i False to prawidłowe, „nie-puste" wartości — jeśli chcesz rozróżnić „nie podano" od „podano wartość fałszywą", użyj None jako wartości domyślnej.
Przykład kodu:
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
# skip i limit mają wartości domyślne -> są OPCJONALNE
# q jest opcjonalny i może być None (gdy klient go pominie)
# required_filter NIE ma wartości domyślnej -> jest WYMAGANY
@app.get("/products")
async def list_products(
required_filter: str, # wymagany
skip: int = 0, # opcjonalny, domyślnie 0
limit: int = 20, # opcjonalny, domyślnie 20
q: Annotated[str | None, Query()] = None, # opcjonalny, może być None
):
return {
"filter": required_filter,
"skip": skip,
"limit": limit,
"q": q,
}
# GET /products?required_filter=elektronika -> skip=0, limit=20, q=None
# GET /products -> 422 (brak required_filter)
Materiały
↑ Powrót na góręPydantic i modele danych
Czym różni się BaseModel od dataclass w kontekście FastAPI?
Odpowiedź w 30 sekund:
BaseModel to klasa Pydantic z pełną walidacją, parsowaniem typów, serializacją (model_dump) i generowaniem schematu JSON — to domyślny i najbogatszy wybór w FastAPI. Standardowy @dataclass z biblioteki Python sam z siebie nie waliduje danych; dopiero pydantic.dataclasses.dataclass dodaje walidację Pydantic, zachowując interfejs dataclass.
Odpowiedź w 2 minuty:
Standardowy @dataclass z modułu dataclasses to wygodny sposób na klasę przechowującą dane — generuje __init__, __repr__ i porównania, ale nie sprawdza typów ani nie konwertuje wartości. Adnotacje typu w dataclass są tylko podpowiedzią; przypisanie wiek=int jako tekstu nie spowoduje błędu. BaseModel natomiast traktuje typy jako kontrakt: waliduje, konwertuje (np. "30" → 30), zgłasza czytelne błędy i oferuje bogaty zestaw narzędzi — model_validate, model_dump, model_dump_json, walidatory, Field, aliasy oraz generowanie JSON Schema.
FastAPI wspiera oba podejścia, a nawet pydantic.dataclasses.dataclass, który łączy składnię dataclass z walidacją Pydantic. W praktyce dla ciał żądań i odpowiedzi rekomenduje się BaseModel, bo daje najpełniejszą integrację: walidację wejścia, filtrowanie i serializację wyjścia przez response_model, najbogatszy schemat OpenAPI oraz dostęp do całego ekosystemu (walidatory, konfiguracja przez model_config). Czysty @dataclass bywa kuszący w prostych przypadkach lub gdy obiekt jest już używany w innej warstwie aplikacji, ale tracisz wtedy automatyczną walidację — chyba że sięgniesz po wariant Pydantic.
Pułapki: po pierwsze, założenie, że zwykły @dataclass „też zwaliduje dane" — nie zwaliduje, to najczęstsze nieporozumienie. Jeśli chcesz walidacji, użyj BaseModel lub pydantic.dataclasses.dataclass. Po drugie, mutowalne wartości domyślne: w obu światach pole: list = [] jest błędem — w dataclass wręcz rzuca wyjątek (trzeba field(default_factory=list)), w Pydantic używaj Field(default_factory=list). Po trzecie, metody serializacji różnią się: dla BaseModel to model_dump(), a dla dataclass — dataclasses.asdict() lub TypeAdapter w Pydantic. Po czwarte, część zaawansowanych opcji konfiguracji (model_config, niektóre walidatory) jest pełniej dostępna w BaseModel niż w dataclass.
Przykład kodu:
from dataclasses import dataclass
from pydantic import BaseModel
from pydantic.dataclasses import dataclass as pyd_dataclass
# 1. Zwykły dataclass — BRAK walidacji typów
@dataclass
class PunktZwykly:
x: int
y: int
p = PunktZwykly(x="nie-liczba", y=2) # przejdzie bez błędu (brak walidacji)
# 2. BaseModel — pełna walidacja + serializacja
class PunktModel(BaseModel):
x: int
y: int
p2 = PunktModel.model_validate({"x": "10", "y": 2}) # "10" -> 10
print(p2.model_dump()) # {'x': 10, 'y': 2}
# 3. Dataclass Pydantic — składnia dataclass + walidacja
@pyd_dataclass
class PunktPydantic:
x: int
y: int
# PunktPydantic(x="zły", y=2) zgłosi błąd walidacji
Materiały
↑ Powrót na góręWstrzykiwanie zależności (Depends)
Jak działa cache'owanie zależności w obrębie jednego żądania (use_cache)?
Odpowiedź w 30 sekund:
Domyślnie, jeśli ta sama zależność jest użyta wielokrotnie w obrębie jednego żądania (bezpośrednio i jako sub-dependency innych zależności), FastAPI wywołuje ją tylko raz i reużywa wynik z cache'u. Cache działa w zakresie pojedynczego żądania — przy kolejnym żądaniu zależność wykona się ponownie. Możesz wyłączyć to zachowanie przez Depends(funkcja, use_cache=False).
Odpowiedź w 2 minuty:
Wyobraź sobie zależność get_current_user, z której korzysta zarówno endpoint, jak i dwie inne zależności (np. sprawdzenie uprawnień i pobranie ustawień użytkownika). Bez cache'owania ten sam użytkownik byłby pobierany trzy razy w jednym żądaniu — niepotrzebne zapytania do bazy. FastAPI rozwiązuje to, cache'ując wynik zależności na czas obsługi pojedynczego żądania: pierwszy wywołujący ją „aktor" wykonuje funkcję, a kolejni dostają zapamiętany wynik. Identyfikacja w cache'u opiera się na obiekcie wywoływalnym i jego argumentach.
Cache jest zawsze ograniczony do jednego żądania (per-request) — to nie jest globalny, długotrwały cache między żądaniami. Nowe żądanie = czysty cache = ponowne wywołanie zależności. To rozróżnienie bywa źródłem nieporozumień: jeśli chcesz cache'ować coś na dłużej (np. wynik kosztownego zapytania współdzielony między żądaniami), potrzebujesz osobnego rozwiązania (Redis, functools.lru_cache na ustawieniach, warstwa cache aplikacji), a nie mechanizmu DI.
Parametr use_cache=True jest domyślny. Ustawiasz use_cache=False, gdy świadomie chcesz, by zależność wykonała się przy każdym jej wystąpieniu w żądaniu — np. generator unikalnego identyfikatora na potrzeby logowania, świeży znacznik czasu, albo zależność z efektem ubocznym, który naprawdę ma zajść wielokrotnie. Najczęstsza pułapka jest odwrotna: ktoś umieszcza ciężką inicjalizację (otwarcie połączenia, zapytanie do bazy) w zależności i zakłada, że „cache i tak to załatwi" — ale cache działa tylko w obrębie jednego żądania, więc przy każdym żądaniu ten koszt wraca. Zasoby wieloużyteczne między żądaniami inicjalizuj w lifespan/startup, a przez zależność tylko je udostępniaj. Druga pułapka: oczekiwanie, że zależności użyte w różnych endpointach „dzielą" cache — nie dzielą, bo to różne żądania.
Przykład kodu:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def pobierz_uzytkownika():
print("Wywołano pobierz_uzytkownika()") # zobaczysz to RAZ na żądanie
return {"id": 1, "rola": "admin"}
# Zależność, która sama korzysta z pobierz_uzytkownika (sub-dependency)
def sprawdz_uprawnienia(user=Depends(pobierz_uzytkownika)):
return user["rola"] == "admin"
@app.get("/panel")
def panel(
user=Depends(pobierz_uzytkownika), # 1. użycie
czy_admin=Depends(sprawdz_uprawnienia), # korzysta z 2. użycia -> CACHE: ta sama instancja
):
# pobierz_uzytkownika() wykonało się tylko RAZ dzięki cache per-request
return {"user": user, "admin": czy_admin}
# Wymuszenie ponownego wykonania przy każdym wystąpieniu w żądaniu:
def swiezy_znacznik():
import time
return time.time_ns()
@app.get("/log")
def log(
a=Depends(swiezy_znacznik), # może być z cache
b=Depends(swiezy_znacznik, use_cache=False), # ZAWSZE wywołane na nowo
):
return {"a": a, "b": b}
Materiały
↑ Powrót na góręObsługa błędów i middleware
Jak zgłaszać błędy HTTP za pomocą HTTPException?
Odpowiedź w 30 sekund:
Aby zwrócić klientowi błąd HTTP, podnosisz wyjątek HTTPException z modułu fastapi, podając status_code (np. 404) oraz detail z czytelnym komunikatem. FastAPI przechwytuje go automatycznie i zamienia na odpowiedź JSON {"detail": ...} z właściwym kodem statusu. Używasz raise, a nie return — to wyjątek, który przerywa dalsze wykonanie endpointu.
Odpowiedź w 2 minuty:
HTTPException to wbudowany sposób na sygnalizowanie sytuacji, w której żądanie nie może zostać poprawnie obsłużone i klient powinien dostać konkretny kod błędu. Najczęstsze przypadki to brak zasobu (404), brak uprawnień (403), brak uwierzytelnienia (401) czy konflikt (409). Kluczowe jest słowo raise — podniesienie wyjątku natychmiast przerywa wykonanie funkcji endpointu, więc kod po raise się nie wykona. To wygodne, bo nie musisz pamiętać o wczesnym return.
Pole detail może być stringiem, ale również dowolnym obiektem serializowalnym do JSON (np. słownikiem czy listą), co pozwala zwrócić ustrukturyzowaną informację o błędzie. FastAPI zawsze opakowuje to w klucz detail. Dzięki temu front-end ma przewidywalny kształt odpowiedzi błędu w całym API.
Czasem trzeba dodać nagłówki HTTP do odpowiedzi błędu — np. WWW-Authenticate przy 401 albo Retry-After przy 429 (zbyt wiele żądań). Służy do tego argument headers w HTTPException. Typowa pułapka początkujących: używanie return HTTPException(...) zamiast raise — wtedy FastAPI nie potraktuje tego jako błędu, tylko spróbuje zserializować obiekt wyjątku jako zwykłą odpowiedź, co da niepoprawny wynik.
Przykład kodu:
from fastapi import FastAPI, HTTPException
app = FastAPI()
przedmioty = {"klucz": "wartość"}
@app.get("/przedmioty/{id_przedmiotu}")
async def odczytaj_przedmiot(id_przedmiotu: str):
if id_przedmiotu not in przedmioty:
# raise, nie return — wyjątek przerywa wykonanie endpointu
raise HTTPException(
status_code=404,
detail="Nie znaleziono przedmiotu",
)
return {"przedmiot": przedmioty[id_przedmiotu]}
@app.get("/chroniony")
async def zasob_chroniony():
# Dodanie nagłówków do odpowiedzi błędu (np. dla 401)
raise HTTPException(
status_code=401,
detail="Wymagane uwierzytelnienie",
headers={"WWW-Authenticate": "Bearer"},
)
Materiały
↑ Powrót na góręTestowanie
Jak nadpisywać zależności w testach (dependency_overrides)?
Odpowiedź w 30 sekund:
FastAPI pozwala podmienić dowolną zależność (Depends) w testach poprzez słownik app.dependency_overrides. Mapujesz oryginalną funkcję zależności na jej testową wersję, np. app.dependency_overrides[get_db] = get_test_db. Dzięki temu zamiast prawdziwej bazy czy realnego użytkownika dostajesz wersję testową — bez modyfikowania kodu produkcyjnego. Kluczowe: po teście wyczyść słownik (app.dependency_overrides.clear()), najlepiej w fixture.
Odpowiedź w 2 minuty:
dependency_overrides to najpotężniejszy mechanizm testowy w FastAPI. Aplikacja zwykle korzysta z zależności takich jak get_db (sesja bazy danych) czy get_current_user (zalogowany użytkownik). W teście rzadko chcesz uderzać w prawdziwą bazę produkcyjną albo przechodzić pełny proces logowania. Zamiast tego rejestrujesz w słowniku app.dependency_overrides podmianę: kluczem jest oryginalna funkcja zależności, a wartością — funkcja zastępcza. FastAPI przy obsłudze żądania sprawdza ten słownik i jeśli znajdzie nadpisanie, użyje wersji testowej zamiast oryginalnej.
Najczęstsze zastosowania to dwa: po pierwsze, nadpisanie get_db, aby zwracał sesję do testowej bazy danych (np. SQLite in-memory lub osobny plik), dzięki czemu testy są izolowane od danych produkcyjnych. Po drugie, nadpisanie get_current_user, aby zwracał fałszywego, z góry znanego użytkownika — pozwala to testować chronione endpointy bez generowania prawdziwych tokenów. To eleganckie, bo nie zmieniasz ani jednej linii kodu aplikacji; podmiana dzieje się wyłącznie na poziomie testów.
Najgroźniejsza pułapka to brak czyszczenia nadpisań. Słownik dependency_overrides żyje na obiekcie app, więc jeśli ustawisz nadpisanie w jednym teście i go nie usuniesz, „wycieknie" ono do kolejnych testów i da fałszywe wyniki (np. wszędzie zalogowany użytkownik tam, gdzie testujesz brak autoryzacji). Dlatego nadpisania ustawiaj i czyść w fixture pytest — w sekcji po yield wołaj app.dependency_overrides.clear() (lub usuwaj konkretny klucz). Dzięki temu każdy test startuje z czystym stanem.
Drobny szczegół implementacyjny: kluczem w słowniku musi być dokładnie ta sama funkcja, której używasz w Depends(...). Jeśli zaimportujesz inną instancję funkcji albo użyjesz innego obiektu, nadpisanie nie zadziała.
Przykład kodu:
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
import pytest
app = FastAPI()
# Zależności produkcyjne
def get_db():
db = "prawdziwa-baza"
yield db
def get_current_user():
# W realnym kodzie tu byłaby walidacja tokenu JWT
raise NotImplementedError
@app.get("/profile")
def read_profile(user=Depends(get_current_user), db=Depends(get_db)):
return {"user": user, "db": db}
# --- Testy ---
# Testowe wersje zależności
def get_test_db():
yield "testowa-baza-in-memory"
def fake_user():
return {"id": 1, "email": "test@example.com"}
@pytest.fixture
def client():
# Ustawiamy nadpisania PRZED testem
app.dependency_overrides[get_db] = get_test_db
app.dependency_overrides[get_current_user] = fake_user
yield TestClient(app)
# KLUCZOWE: czyścimy nadpisania PO teście, by nie wyciekły do innych testów
app.dependency_overrides.clear()
def test_read_profile(client):
response = client.get("/profile")
assert response.status_code == 200
data = response.json()
assert data["user"]["email"] == "test@example.com"
assert data["db"] == "testowa-baza-in-memory"
Diagram:
flowchart LR
A[Endpoint: Depends(get_db)] --> B{app.dependency_overrides?}
B -- "Brak nadpisania (produkcja)" --> C[(Prawdziwa baza)]
B -- "Nadpisanie w teście" --> D[(Testowa baza<br/>SQLite in-memory)]
Materiały
↑ Powrót na góręWalidacja, serializacja i odpowiedzi
Do czego służy parametr response_model i jakie daje korzyści?
Odpowiedź w 30 sekund:
response_model to parametr dekoratora ścieżki (np. @app.get(..., response_model=UserOut)), który deklaruje schemat odpowiedzi. FastAPI używa go do walidacji i serializacji danych wychodzących, automatycznej filtracji pól oraz wygenerowania poprawnego schematu w dokumentacji OpenAPI. Dzięki temu zwracasz tylko to, co naprawdę powinno trafić do klienta.
Odpowiedź w 2 minuty:
Gdy ustawisz response_model, FastAPI bierze obiekt zwrócony przez funkcję (dict, model ORM, model Pydantic) i przepuszcza go przez podany model jako filtr wyjściowy. Pola, których nie ma w modelu, są usuwane, typy są konwertowane do JSON-owalnych, a wartości walidowane. To kluczowe dla bezpieczeństwa: jeśli funkcja zwróci pełny obiekt użytkownika z password czy hashed_password, a response_model opisuje tylko publiczne pola, wrażliwe dane nigdy nie wyciekną do odpowiedzi.
Korzyści są trzy. Po pierwsze, dokumentacja: schemat odpowiedzi pojawia się automatycznie w Swagger UI i OpenAPI, więc konsumenci API wiedzą, czego się spodziewać. Po drugie, walidacja wyjścia: jeśli zwrócisz dane niezgodne z modelem, dostaniesz błąd po stronie serwera zamiast wysłać klientowi śmieci. Po trzecie, filtracja i konwersja: Pydantic v2 zamienia np. datetime na ISO string, a Decimal na liczbę, oraz odrzuca nadmiarowe pola.
Typowa pułapka to zwracanie modelu bazodanowego (SQLAlchemy/ORM) bez response_model — wtedy serializacja zależy od tego, co akurat jest załadowane i co umie zserializować FastAPI, co bywa nieprzewidywalne i niebezpieczne. Druga pułapka: jeśli zamiast zwrócić dict/model zwrócisz gotowy obiekt Response (np. JSONResponse), FastAPI pomija response_model — odpowiedź leci „tak jak jest", bez filtracji ani walidacji.
Przykład kodu:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
# Model wejścia zawiera hasło
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
# Model wyjścia NIE zawiera hasła
class UserOut(BaseModel):
username: str
email: EmailStr
@app.post("/users/", response_model=UserOut)
async def create_user(user: UserIn) -> UserIn:
# Zwracamy pełny obiekt z hasłem, ale response_model odfiltruje
# pole password, więc do klienta trafi tylko username i email.
save_user(user)
return user
Materiały
↑ Powrót na góręProgramowanie asynchroniczne
Jaka jest różnica między def a async def w definicji endpointu?
Odpowiedź w 30 sekund:
async def definiuje korutynę, którą FastAPI uruchamia bezpośrednio w głównym event loopie, natomiast zwykłe def to funkcja synchroniczna, którą FastAPI uruchamia w osobnej puli wątków (threadpool), aby nie zablokować pętli zdarzeń. Z punktu widzenia użytkownika oba zwracają tę samą odpowiedź — różnica leży w sposobie wykonania i w tym, że w async def można używać await.
Odpowiedź w 2 minuty:
Endpoint zadeklarowany jako async def jest korutyną. FastAPI wykonuje go bezpośrednio w pojedynczym wątku obsługującym event loop. Dzięki temu można w nim używać słowa kluczowego await, by „oddać" sterowanie pętli zdarzeń w momentach oczekiwania (np. na odpowiedź bazy danych czy zewnętrznego API). W tym czasie event loop może obsłużyć inne żądania — na tym polega współbieżność asynchroniczna.
Endpoint zadeklarowany jako zwykłe def jest funkcją synchroniczną. FastAPI nie uruchamia go w event loopie, lecz deleguje do zewnętrznej puli wątków zarządzanej przez bibliotekę AnyIO. Robi to celowo: gdyby synchroniczny, potencjalnie blokujący kod wykonał się wprost w pętli zdarzeń, zatrzymałby obsługę wszystkich pozostałych klientów. Przeniesienie go do osobnego wątku izoluje to ryzyko.
Najczęstsze nieporozumienie brzmi: „async def jest zawsze szybsze". To nieprawda. async def daje zysk dopiero wtedy, gdy faktycznie używasz await na operacjach I/O. Jeśli w async def wykonasz blokujący kod bez await, zablokujesz cały event loop (patrz pytanie 3). Z kolei zwykłe def w FastAPI jest bezpieczne dla kodu blokującego, bo działa w wątku puli — ale liczba wątków jest ograniczona, więc bardzo duży ruch może te wątki wyczerpać.
Przykład kodu:
from fastapi import FastAPI
import httpx
app = FastAPI()
# async def — uruchamiany w event loopie; używamy await na operacji I/O
@app.get("/async-dane")
async def pobierz_async():
async with httpx.AsyncClient() as client:
# await oddaje sterowanie pętli zdarzeń na czas oczekiwania na sieć
odpowiedz = await client.get("https://api.example.com/dane")
return odpowiedz.json()
# zwykłe def — uruchamiany w puli wątków (threadpool) przez AnyIO
@app.get("/sync-dane")
def pobierz_sync():
# synchroniczny klient — bezpieczny tutaj, bo działa w osobnym wątku
odpowiedz = httpx.get("https://api.example.com/dane")
return odpowiedz.json()
Diagram:
flowchart TD
A["Przychodzi żądanie HTTP"] --> B{"Endpoint to async def?"}
B -->|"Tak"| C["Uruchom w event loopie (główny wątek)"]
B -->|"Nie (zwykłe def)"| D["Uruchom w puli wątków (AnyIO threadpool)"]
C --> E["Zwróć odpowiedź"]
D --> E
Materiały
↑ Powrót na góręBaza danych i integracja z ORM
Jak zintegrować FastAPI z bazą danych przy użyciu SQLAlchemy?
Odpowiedź w 30 sekund:
FastAPI nie ma wbudowanego ORM-a — integrujesz go z SQLAlchemy ręcznie. Tworzysz engine (połączenie z bazą), fabrykę sesji sessionmaker oraz modele deklaratywne dziedziczące po wspólnej klasie bazowej. Sesję wstrzykujesz do endpointów przez system zależności, a tabele tworzysz wywołaniem Base.metadata.create_all() lub migracjami.
Odpowiedź w 2 minuty:
W przeciwieństwie do Django, FastAPI jest mikroframeworkiem i celowo nie narzuca warstwy bazodanowej. Najpopularniejszym wyborem jest SQLAlchemy — dojrzała biblioteka ORM/Core. Integracja sprowadza się do kilku elementów: engine opisuje, gdzie i jak łączyć się z bazą (URL połączenia, pula połączeń), sessionmaker jest fabryką produkującą obiekty Session, a DeclarativeBase (styl SQLAlchemy 2.0) jest klasą bazową dla modeli odwzorowujących tabele na klasy Pythona.
Kluczowa zasada: jedna sesja na jedno żądanie HTTP. Sesja jest jednostką pracy (unit of work) — gromadzi zmiany i zatwierdza je przez commit() albo wycofuje przez rollback(). Nie współdziel jednej sesji między żądaniami ani wątkami, bo sesja SQLAlchemy nie jest bezpieczna wątkowo.
Najczęstsze pułapki początkujących: (1) Tworzenie globalnej, długo żyjącej sesji zamiast sesji per żądanie — prowadzi to do wycieków połączeń i błędów współbieżności. (2) Zapominanie o commit() — zmiany pozostają w transakcji i nie trafiają do bazy. (3) Dla SQLite trzeba dodać connect_args={"check_same_thread": False}, bo domyślnie SQLite blokuje użycie połączenia z wielu wątków. (4) create_all() nadaje się tylko do prototypów — w realnym projekcie schemat zarządzaj migracjami (Alembic).
Przykład kodu:
from sqlalchemy import create_engine, String
from sqlalchemy.orm import sessionmaker, DeclarativeBase, Mapped, mapped_column
# URL połączenia — tu SQLite, dla Postgresa: "postgresql+psycopg://user:haslo@host/baza"
DATABASE_URL = "sqlite:///./app.db"
# engine zarządza pulą połączeń; connect_args wymagane tylko dla SQLite
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
# fabryka sesji — autocommit i autoflush wyłączone (domyślnie w 2.0)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# klasa bazowa modeli w stylu SQLAlchemy 2.0
class Base(DeclarativeBase):
pass
# model deklaratywny z adnotacjami typów (Mapped)
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
name: Mapped[str] = mapped_column(String(100))
# utworzenie tabel — tylko do prototypów; w produkcji użyj Alembic
Base.metadata.create_all(bind=engine)
Materiały
↑ Powrót na góręDokumentacja API (OpenAPI)
Jak FastAPI automatycznie generuje dokumentację OpenAPI?
Odpowiedź w 30 sekund:
FastAPI buduje pełny schemat OpenAPI na podstawie adnotacji typów i modeli Pydantic, których używasz w endpointach — bez żadnej dodatkowej pracy. Z tego schematu (dostępnego pod /openapi.json) framework renderuje interaktywne UI pod /docs (Swagger UI) oraz /redoc (ReDoc). Piszesz normalny kod Pythona, a dokumentacja powstaje sama.
Odpowiedź w 2 minuty: OpenAPI to standard opisu REST API w formacie JSON/YAML. FastAPI jest na nim zbudowany od podstaw — kiedy deklarujesz parametry ścieżki, query, ciało żądania jako modele Pydantic czy typy wejściowe i wyjściowe, framework potrafi z tych adnotacji wywnioskować kompletny kontrakt: jakie pola są wymagane, ich typy, walidacje, kody odpowiedzi i schematy danych. Te informacje trafiają do generowanego dokumentu OpenAPI.
Mechanizm działa leniwie i z cache: schemat budowany jest przy pierwszym żądaniu do /openapi.json, a potem przechowywany w app.openapi_schema. Modele Pydantic są tłumaczone na sekcję components/schemas, parametry funkcji na parameters i requestBody, a typy zwracane oraz response_model na responses. Dzięki temu jedno źródło prawdy — sygnatura funkcji — napędza jednocześnie walidację danych w runtime, serializację odpowiedzi i dokumentację. Nie ma ryzyka, że dokumentacja rozjedzie się z kodem, bo powstaje z tego samego kodu.
Najczęstsze nieporozumienie juniorów: dokumentacja nie jest pisana ręcznie ani generowana przez osobny build krok. Wystarczy uruchomić aplikację i wejść na /docs. Pułapka pojawia się, gdy ktoś pomija adnotacje typów lub używa surowego dict zamiast modelu Pydantic — wtedy schemat jest ubogi (typ object bez pól), a dokumentacja przestaje być użyteczna. Im lepiej otypujesz kod, tym bogatsza dokumentacja.
Przykład kodu:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Model Pydantic = źródło schematu w sekcji components/schemas
class Item(BaseModel):
name: str
price: float
in_stock: bool = True
# Adnotacje typów i response_model trafiają wprost do OpenAPI
@app.post("/items", response_model=Item)
def create_item(item: Item):
# FastAPI samo zwaliduje wejście i opisze je w /openapi.json
return item
# Po uruchomieniu (uvicorn main:app) dostępne są:
# /openapi.json -> surowy schemat OpenAPI
# /docs -> interaktywne Swagger UI
# /redoc -> czytelna dokumentacja ReDoc
Diagram:
flowchart TD
A["Adnotacje typów (parametry endpointów)"] --> C["Generator schematu OpenAPI"]
B["Modele Pydantic (BaseModel)"] --> C
C --> D["Surowy schemat (/openapi.json)"]
D --> E["Swagger UI (/docs)"]
D --> F["ReDoc (/redoc)"]
Materiały
↑ Powrót na góręWydajność, wdrożenie i produkcja
Jak wdrożyć aplikację FastAPI na produkcję (uvicorn, gunicorn, workery)?
Odpowiedź w 30 sekund:
Aplikację uruchamiasz pod serwerem ASGI — najczęściej Uvicornem (uvicorn main:app albo fastapi run), schowanym za reverse proxy typu nginx, które terminuje TLS i serwuje statyki. Żeby wykorzystać wiele rdzeni, uruchamiasz wiele workerów: albo Gunicorn jako menedżer procesów (gunicorn -k uvicorn.workers.UvicornWorker -w 4), albo natywnie uvicorn --workers 4 / fastapi run. Nigdy nie używaj --reload na produkcji.
Odpowiedź w 2 minuty: Typowa architektura produkcyjna ma kilka warstw. Na froncie stoi reverse proxy (nginx, Traefik, Caddy), który terminuje HTTPS, obsługuje statyki, kompresję i limity. Ruch przekazuje do serwera ASGI — Uvicorna — który faktycznie uruchamia twoją aplikację FastAPI. Pojedynczy proces Uvicorna obsługuje wiele żądań współbieżnie dzięki pętli zdarzeń (async), ale działa na jednym rdzeniu CPU. Żeby wykorzystać całą maszynę, potrzebujesz wielu procesów-workerów.
Są dwa główne sposoby na wiele workerów. Pierwszy, klasyczny, to Gunicorn jako menedżer procesów uruchamiający workery klasy uvicorn.workers.UvicornWorker: gunicorn main:app -k uvicorn.workers.UvicornWorker -w 4. Gunicorn jest procesem master — pilnuje workerów, restartuje te, które padły, obsługuje graceful reload. Drugi, nowszy i prostszy, to natywne workery Uvicorna (uvicorn main:app --workers 4) albo komenda fastapi run, która domyślnie startuje produkcyjnie z wieloma workerami. W środowiskach kontenerowych (Kubernetes) często rezygnuje się z wielu workerów w jednym kontenerze na rzecz jednego workera na kontener i skalowania liczbą replik — orkiestrator pełni wtedy rolę menedżera procesów.
Liczbę workerów dobiera się orientacyjnie regułą 2 * liczba_rdzeni + 1, ale to tylko punkt startowy — realna wartość zależy od profilu obciążenia. Aplikacje I/O-bound (dużo oczekiwania na bazę, zewnętrzne API) zniosą więcej workerów, bo i tak głównie czekają; aplikacje CPU-bound nie zyskają na liczbie workerów większej niż liczba rdzeni. Każdy worker to osobny proces zużywający pamięć, więc nie ustawiaj liczby „w ciemno". Najczęstsze pułapki: uruchomienie produkcji z jednym workerem (jeden zablokowany request potrafi opóźnić resztę), pozostawienie --reload (ogromny narzut, przeładowuje kod), oraz wystawienie Uvicorna bezpośrednio do internetu bez reverse proxy i terminacji TLS.
Przykład kodu:
# Wariant 1: Gunicorn jako menedżer procesów + workery Uvicorna (klasyczny)
gunicorn main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000
# Wariant 2: natywne workery Uvicorna (prostsze, bez Gunicorna)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# Wariant 3: komenda fastapi (od FastAPI 0.111+) — produkcyjnie, wiele workerów
fastapi run main.py --workers 4
# NIGDY na produkcji: --reload (tryb deweloperski, duży narzut)
# uvicorn main:app --reload
Diagram:
flowchart LR
K["Klient (przeglądarka)"] --> N["nginx (reverse proxy, TLS)"]
N --> G["Gunicorn (proces master, menedżer)"]
G --> W1["Worker Uvicorn 1 (ASGI)"]
G --> W2["Worker Uvicorn 2 (ASGI)"]
G --> W3["Worker Uvicorn 3 (ASGI)"]
G --> W4["Worker Uvicorn 4 (ASGI)"]
W1 --> A["Aplikacja FastAPI"]
W2 --> A
W3 --> A
W4 --> A
Materiały
↑ Powrót na górę