FastAPI to nowoczesny framework w języku Python, który rewolucjonizuje sposób tworzenia aplikacji webowych i API. Zyskał popularność dzięki swojej szybkości, prostocie i skalowalności, umożliwiając deweloperom budowanie wydajnych aplikacji bez zbędnego skomplikowania. Dzięki swojej architekturze oparty na asynchronicznych wywołaniach, FastAPI może obsługiwać aplikacje o dużym ruchu, jednocześnie utrzymując niskie opóźnienia i wysoką wydajność.

Podstawowe kroki przy pracy z FastAPI obejmują konfigurację środowiska oraz budowanie prostych API. Dzięki integracji z popularnymi narzędziami Pythonowymi, takimi jak SQLAlchemy, Pydantic czy Uvicorn, FastAPI umożliwia łatwą budowę aplikacji, które mogą obsługiwać zarówno małe, jak i duże bazy danych, zarządzając nimi w sposób wydajny i elastyczny. Proces autoryzacji i uwierzytelniania użytkowników również jest prosty do zaimplementowania, co znacząco poprawia bezpieczeństwo aplikacji, szczególnie w kontekście współczesnych wymagań.

Wraz z wprowadzeniem do podstaw, książka stopniowo przechodzi do bardziej zaawansowanych tematów. Wśród nich znajdują się m.in. niestandardowe middleware, komunikacja WebSocket, integracja z zewnętrznymi bibliotekami Pythona, a także techniki optymalizacji wydajności. Oprócz standardowego tworzenia API, użytkownicy uczą się zarządzać zadaniami w tle, co pozwala na budowę aplikacji o wysokiej niezawodności i elastyczności. W tym kontekście jednym z bardziej zaawansowanych zagadnień jest implementacja limitów prędkości (rate limiting), co pozwala na zapobieganie przeciążeniom serwera w przypadku nadmiernego ruchu.

Dodatkowym atutem FastAPI jest jego wsparcie dla testowania i debugowania aplikacji. Narzędzia takie jak pytest umożliwiają tworzenie testów jednostkowych i integracyjnych, które zapewniają, że aplikacja działa zgodnie z oczekiwaniami w różnych scenariuszach. Współczesne aplikacje muszą również spełniać standardy dostępności i skalowalności, dlatego też w książce znajdziesz wskazówki, jak zoptymalizować kod pod kątem wydajności i jak wdrożyć aplikację FastAPI w środowisku produkcyjnym.

Kiedy mówimy o migracji istniejących aplikacji do FastAPI, najważniejszym aspektem jest kompatybilność z różnymi bazami danych, w tym zarówno SQL, jak i NoSQL. Umożliwia to szeroką gamę zastosowań, od prostych aplikacji CRUD, po bardziej zaawansowane systemy analityczne. Migracja do FastAPI nie jest zadaniem trudnym, jednak wymaga zrozumienia różnic między tradycyjnymi frameworkami a tym nowoczesnym narzędziem. Kluczowe jest tutaj wykorzystanie asynchronicznych funkcji oraz zrozumienie, jak FastAPI łączy te funkcje z ekosystemem Pythona.

Warto również podkreślić, że FastAPI znacząco zmienia podejście do budowania aplikacji webowych w Pythonie, szczególnie w kontekście pracy z danymi i zapytań w czasie rzeczywistym. Zwiększa to jego przydatność w aplikacjach, które wymagają przetwarzania danych na żywo, takich jak systemy rekomendacyjne, czaty czy aplikacje monitorujące. WebSockety i inne mechanizmy komunikacji w czasie rzeczywistym, które są integralną częścią FastAPI, dają programistom możliwość łatwego wbudowania interaktywnych funkcji w aplikacje.

FastAPI oferuje również pełne wsparcie dla zależności (dependency injection), co pozwala na budowanie elastycznych i testowalnych komponentów aplikacji. Ten wzorzec projektowy, który umożliwia oddzielenie logiki aplikacji od jej implementacji, ułatwia zarządzanie dużymi projektami oraz ich skalowanie. Zamiast zagnieżdżania logiki w jednym miejscu, FastAPI pozwala na tworzenie czystych, dobrze zorganizowanych aplikacji, w których każda część jest łatwa do przetestowania i rozwijania.

Praca z FastAPI wiąże się również z wykorzystaniem jego wbudowanego wsparcia dla OpenAPI i JSON Schema. Te narzędzia pozwalają na automatyczne generowanie dokumentacji API, co znacząco przyspiesza proces wdrażania i integracji z innymi usługami. Dzięki wbudowanemu wsparciu dla walidacji danych i auto-generowania dokumentacji, FastAPI pomaga utrzymać standardy jakości w projekcie, nawet w złożonych środowiskach produkcyjnych.

Oprócz kwestii technicznych, warto także zwrócić uwagę na aspekty związane z dobrym zarządzaniem projektem i jego organizacją. Chociaż FastAPI jest narzędziem wyjątkowo prostym w użyciu, wymaga od programistów znajomości najlepszych praktyk przy tworzeniu aplikacji webowych. Istotnym elementem jest tu także zrozumienie, jak FastAPI może współpracować z innymi narzędziami ekosystemu Pythona, takimi jak Celery do obsługi zadań asynchronicznych, Redis do przechowywania danych w pamięci podręcznej czy Elasticsearch do wyszukiwania w dużych zbiorach danych.

Ważnym aspektem jest także praktyczne podejście do nauki i eksperymentowania z frameworkiem. Zrozumienie, kiedy i jak wykorzystywać konkretne funkcje, jest kluczowe do stworzenia aplikacji, które będą zarówno wydajne, jak i skalowalne. Programiści, którzy decydują się na użycie FastAPI, powinni mieć świadomość, że chociaż framework ten jest stosunkowo nowy, jego możliwości i wsparcie społeczności są na bardzo wysokim poziomie, co czyni go jednym z najlepszych narzędzi do budowania nowoczesnych aplikacji webowych w Pythonie.

Jak wdrożyć wersjonowanie API w aplikacji i zarządzać dostępem za pomocą OAuth2?

Wersjonowanie API to kluczowy aspekt zarządzania usługami webowymi, który pozwala na ich rozwój bez zakłócania istniejącego środowiska użytkowników. Dzięki wersjonowaniu deweloperzy mogą wprowadzać zmiany, usprawnienia, a nawet niekompatybilne zmiany, zapewniając jednocześnie zgodność wsteczną. W tej części przedstawimy sposób implementacji wersjonowania API w aplikacji do zarządzania zadaniami oraz pokażemy, jak zabezpieczyć API przy pomocy OAuth2.

Wersjonowanie API umożliwia wprowadzenie różnych wersji punktów końcowych (endpointów), co pozwala na ich rozwój, bez obawy o zepsucie działania starszych wersji. Najczęściej stosowaną metodą wersjonowania API jest użycie wersji w ścieżce URL. Przykładem może być dodanie nowego pola „priorytet” do zadań, które będzie dostępne tylko w wersji 2 API.

Aby wdrożyć wersjonowanie, zaczynamy od stworzenia klasy TaskV2, która rozszerza istniejącą strukturę zadań i dodaje nowe pole „priorytet”:

python
from typing import Optional class TaskV2(BaseModel): title: str description: str status: str priority: str | None = "lower"

Następnie w module operacyjnym tworzymy funkcję read_all_tasks_v2, która zwraca wszystkie zadania z nowym polem:

python
from models import TaskV2WithID
def read_all_tasks_v2() -> list[TaskV2WithID]: with open(DATABASE_FILENAME) as csvfile: reader = csv.DictReader(csvfile) return [TaskV2WithID(**row) for row in reader]

Kolejnym krokiem jest zdefiniowanie nowego punktu końcowego w pliku main.py, który będzie obsługiwał żądania do wersji 2 API:

python
from models import TaskV2WithID @app.get("/v2/tasks", response_model=list[TaskV2WithID]) def get_tasks_v2(): tasks = read_all_tasks_v2() return tasks

Po uruchomieniu serwera i zaktualizowaniu pliku CSV, nowy punkt końcowy /v2/tasks będzie dostępny w dokumentacji interaktywnej pod adresem http://localhost:8000/docs. Testowanie tego punktu końcowego pokaże, że nowe pole priorytetu zostało poprawnie dodane, a stare API /tasks działa bez zmian.

Kiedy rozwijamy nasze API, warto pamiętać o możliwościach zastosowania innych podejść do wersjonowania. Oprócz wersjonowania w ścieżce URL, możemy wykorzystać:

  1. Wersjonowanie za pomocą parametrów zapytania: wersja API jest przekazywana jako parametr zapytania w adresie URL, np. https://api.example.com/resource?version=1.

  2. Wersjonowanie za pomocą nagłówków HTTP: wersja API jest określana w nagłówku HTTP, np. X-API-Version: 1.

  3. Wersjonowanie oparte na konsumencie: w tym przypadku wersja API jest zapisywana w interakcji z klientem i stosowana w przyszłych żądaniach.

Warto również wspomnieć o wersjonowaniu semantycznym, które pozwala na łatwe określenie, czy zmiana w API jest kompatybilna wstecz, czy też wymaga aktualizacji po stronie klienta. Wersja API składa się z trzech części: MAJOR, MINOR i PATCH, gdzie zmiany w wersji MAJOR oznaczają niekompatybilne zmiany, a zmiany w wersjach MINOR i PATCH wskazują na zmiany wstecznie kompatybilne.

Zarządzanie wersjami API pozwala nie tylko na elastyczność w rozwoju, ale również na minimalizowanie zakłóceń w istniejących integracjach klientów.

W przypadku bardziej zaawansowanego użycia, warto rozważyć przejście z użycia plików CSV na bardziej niezawodną bazę danych, np. SQLite, aby uniknąć problemów związanych z utratą danych przy awarii systemu. Przy każdej wersji API warto także zastanowić się nad wprowadzeniem polityki deprecjacji, aby stopniowo przechodzić do nowych wersji i nie zmieniać wszystkiego na raz.

Zabezpieczanie API to kolejny ważny krok w jego rozwoju. OAuth2 jest powszechnie używanym mechanizmem autoryzacji, który umożliwia dostęp do aplikacji przy użyciu tokenów zamiast przechowywania haseł w bazach danych. Mechanizm ten zapewnia większe bezpieczeństwo, ponieważ nie musimy przechowywać danych wrażliwych w formie jawnej. Warto podkreślić, że choć w tym przykładzie używamy uproszczonego mechanizmu haszowania i generowania tokenów, w rzeczywistych warunkach należy stosować bezpieczne metody, takie jak bcrypt do haszowania haseł, oraz właściwe algorytmy do generowania tokenów.

Oto przykład implementacji systemu uwierzytelniania przy pomocy OAuth2 w naszym API:

  1. Tworzymy bazę danych użytkowników, przechowującą ich dane logowania:

python
fake_users_db = { "johndoe": { "username": "johndoe", "hashed_password": "hashedsecret", }, "janedoe": { "username": "janedoe", "hashed_password": "hashedsecret2", }, }
  1. Używamy funkcji do „fałszywego” haszowania haseł:

python
def fakely_hash_password(password: str):
return f"hashed{password}"
  1. Tworzymy klasę użytkownika i funkcję, która zwraca użytkownika na podstawie danych z bazy:

python
class User(BaseModel): username: str class UserInDB(User): hashed_password: str def get_user(db, username: str): if username in db: user_dict = db[username] return UserInDB(**user_dict)
  1. Generowanie i rozwiązywanie tokenów (w tym przykładzie tokeny są tylko „fałszywe”):

python
def fake_token_generator(user: UserInDB) -> str:
return f"tokenized{user.username}" def fake_token_resolver(token: str) -> UserInDB | None: if token.startswith("tokenized"): user_id = token.removeprefix("tokenized") user = get_user(fake_users_db, user_id) return user
  1. Wykorzystujemy OAuth2PasswordBearer do obsługi mechanizmu OAuth2:

python
from fastapi import Depends, HTTPException, status
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

Dzięki tym krokom stworzymy bezpieczne API, które zapewni ochronę zasobów przed nieautoryzowanym dostępem. Warto jednak pamiętać, że w prawdziwym środowisku produkcyjnym należy zastosować prawdziwe mechanizmy autoryzacji i haszowania, a nie uproszczone wersje zaprezentowane w przykładzie.

Jak radzić sobie z relacjami w bazach danych NoSQL, takich jak MongoDB?

Bazy danych NoSQL, w tym MongoDB, różnią się od tradycyjnych baz danych relacyjnych przede wszystkim brakiem wsparcia dla kluczowych cech takich jak operacje łączenia (joins) czy klucze obce (foreign keys). W MongoDB, który jest bazą danych bez schematów, relacje między danymi muszą być tworzone w sposób manualny. Istnieją dwie główne techniki obsługi relacji: osadzanie (embedding) oraz referencjonowanie (referencing). Każda z tych metod ma swoje zalety i ograniczenia, które powinny być dostosowane do specyfiki projektu.

Osadzanie (Embedding) – podejście ściśle związane z dokumentem

Osadzanie polega na przechowywaniu danych powiązanych bezpośrednio w jednym dokumencie. Jest to najczęściej stosowane podejście, gdy dane te są ściśle związane z danym dokumentem, a ich zmiany są rzadkie. W kontekście platformy streamingowej doskonałym przykładem osadzania może być album, który przypisany jest do piosenki. Album jako dokument może być osadzony bezpośrednio w dokumencie piosenki. Tego rodzaju rozwiązanie pozwala na łatwy i szybki dostęp do wszystkich danych, ponieważ wszystkie informacje znajdują się w jednym miejscu.

Przykład takiego osadzania może wyglądać następująco:

json
{
"title": "Title of the Song", "artist": "Singer Name", "genre": "Music genre", "album": { "title": "Album Title", "release_year": 2017 } }

W tym przypadku, przy dodawaniu nowej piosenki do bazy, w jej dokumencie zawarte są również informacje o albumie. Dzięki temu całość jest łatwa do pobrania jednym zapytaniem, a MongoDB zajmuje się przechowywaniem całości w jednym obiekcie. Jest to podejście korzystne w przypadkach, gdy dane takie jak albumy są rzadko zmieniane, co zapewnia efektywność operacji odczytu. Niestety, to podejście wiąże się z ryzykiem powielania danych, a także może prowadzić do problemów z limitami rozmiaru dokumentu w MongoDB, zwłaszcza gdy dane stają się zbyt obszerne.

Referencjonowanie (Referencing) – podejście dla danych powiązanych z wieloma dokumentami

Referencjonowanie to podejście, w którym w jednym dokumencie przechowywana jest tylko referencja do innego dokumentu, najczęściej w postaci jego unikalnego identyfikatora. W MongoDB oznacza to, że zamiast trzymać wszystkie dane wewnątrz jednego dokumentu, przechowywane są odwołania do innych dokumentów, które zawierają potrzebne informacje.

Tego typu rozwiązanie sprawdza się szczególnie w przypadku relacji „wiele do wielu”, na przykład w przypadku tworzenia playlist w platformach streamingowych. Playlisty składają się z wielu piosenek, a każda piosenka może występować w różnych playlistach. W tym przypadku referencjonowanie pozwala na uniknięcie powielania danych piosenek w każdej playliście.

Przykład definicji playlisty z referencjami:

python
class Playlist(BaseModel): name: str songs: list[str] = []

W tym przypadku, przy tworzeniu playlisty, zamiast osadzać pełne dane piosenek, w dokumencie playlisty zapisujemy tylko identyfikatory piosenek. Aby pobrać pełne informacje o playlistach i zawartych w nich utworach, konieczne będzie wykonanie dodatkowego zapytania do kolekcji piosenek.

Referencjonowanie pozwala na większą elastyczność przy aktualizacji danych, ponieważ dane przechowywane w różnych dokumentach mogą być modyfikowane niezależnie od siebie. Z drugiej strony, takie podejście może powodować spowolnienie operacji odczytu, ponieważ każda referencja wymaga dodatkowego zapytania, co może negatywnie wpłynąć na wydajność w przypadku dużej liczby powiązanych danych.

Integracja z FastAPI

Dzięki zastosowaniu odpowiednich technik relacji, MongoDB staje się elastycznym narzędziem do tworzenia aplikacji. Aby zintegrować MongoDB z FastAPI, wystarczy skorzystać z odpowiednich punktów końcowych API, które będą obsługiwać operacje CRUD (Create, Read, Update, Delete) na danych, w tym również na powiązanych dokumentach. W zależności od wybranej techniki relacji (osadzanie lub referencjonowanie), implementacja będzie nieco się różnić.

Na przykład, operacja dodania nowej piosenki z albumem w jednym dokumencie może wyglądać tak:

python
@app.post("/song")
async def create_song(song: Song, db=Depends(mongo_database)): result = await db.songs.insert_one(song.dict())
return {"message": "Song created successfully", "id": str(result.inserted_id)}

Natomiast operacja tworzenia playlisty z referencjami na piosenki może wyglądać tak:

python
@app.post("/playlist")
async def create_playlist(playlist: Playlist, db=Depends(mongo_database)):
result =
await db.playlists.insert_one(playlist.dict()) return {"message": "Playlist created successfully", "id": str(result.inserted_id)}

Wybór odpowiedniej metody

Wybór między osadzaniem a referencjonowaniem zależy od konkretnego przypadku użycia. Osadzanie jest bardziej odpowiednie, gdy dane są mocno związane z dokumentem i nie zmieniają się często, a ich duplikowanie nie stanowi problemu. Referencjonowanie z kolei lepiej sprawdza się w przypadkach, gdy dane są rozproszone i występują w wielu dokumentach, a także gdy wymagają częstych zmian.

Przy projektowaniu aplikacji warto zwrócić uwagę na kilka kwestii. Po pierwsze, należy ocenić częstotliwość aktualizacji danych oraz ich rozmiar. Jeśli dane często się zmieniają, preferowane będzie referencjonowanie. Po drugie, warto wziąć pod uwagę liczbę odwołań między dokumentami. Im więcej referencji, tym bardziej skomplikowane stanie się pobieranie danych, co może wpłynąć na wydajność. Po trzecie, należy rozważyć skalowalność aplikacji – MongoDB, mimo swojej elastyczności, ma swoje ograniczenia, które mogą ujawnić się przy bardzo dużych zbiorach danych.

Jak zintegrować FastAPI z innymi bibliotekami Pythona?

Współczesne aplikacje webowe często wymagają integracji różnych technologii i bibliotek w celu zapewnienia funkcjonalności i efektywności. FastAPI, jako framework do budowy szybkich i skalowalnych aplikacji API w Pythonie, doskonale współpracuje z innymi bibliotekami, w tym z GraphQL i modelami uczenia maszynowego. Poniżej omówimy sposób integracji FastAPI z GraphQL, tworząc prosty punkt końcowy do zapytań oraz przedstawimy sposób integracji FastAPI z modelami ML za pomocą Joblib.

Rozpoczniemy od integracji FastAPI z GraphQL, co pozwoli na efektywne zapytania i operacje na danych w aplikacjach. W pierwszym kroku tworzymy bazę danych użytkowników, używając prostej listy obiektów klasy User, która będzie symulować bazę danych. Każdy użytkownik posiada identyfikator, nazwę użytkownika, numer telefonu oraz kraj. Klasa User jest definiowana w pliku database.py jako model Pydantic, co zapewnia walidację danych.

python
from pydantic import BaseModel class User(BaseModel): id: int username: str phone_number: str country: str

Następnie tworzymy obiekt users_db, który jest listą użytkowników. Jest to nasza tymczasowa baza danych, z której będziemy czerpać dane. Możemy to zrobić ręcznie, dodając kilku użytkowników, lub skorzystać z pliku udostępnionego na GitHubie. Kolejnym krokiem jest stworzenie zapytania GraphQL, które pozwoli na pobranie użytkowników z określonego kraju. Używamy biblioteki strawberry do definicji typu User oraz zapytania. Model zwrócony przez zapytanie jest również prosty i zawiera te same pola co model użytkownika.

python
import strawberry
@strawberry.type class User: username: str phone_number: str country: str

Definiujemy klasę zapytania Query, która zawiera metodę users zwracającą listę użytkowników z określonego kraju:

python
@strawberry.type class Query: @strawberry.field
def users(self, country: str | None) -> list[User]:
return [ User( username=user.username, phone_number=user.phone_number, country=user.country ) for user in users_db if user.country == country ]

Następnie tworzymy schemat GraphQL i łączymy go z FastAPI. Schemat GraphQL zostaje przekazany do routera GraphQLRouter z strawberry.fastapi, który obsłuży nasze zapytania. W głównym pliku aplikacji main.py dodajemy router do instancji FastAPI, definiując punkt końcowy /graphql, który będzie obsługiwał zapytania GraphQL.

python
from fastapi import FastAPI
from graphql_utils import graphql_app app = FastAPI() app.include_router(graphql_app, prefix="/graphql")

Teraz, po uruchomieniu serwera za pomocą uvicorn, możemy odwiedzić stronę interaktywną naszego punktu końcowego w przeglądarce pod adresem http://localhost:8000/graphql, gdzie będziemy mogli testować zapytania GraphQL.

graphql
{
users(country: "USA") {
username country phoneNumber
} }

W odpowiedzi otrzymamy dane w formacie JSON, które będą zawierały listę użytkowników z wybranego kraju, w tym ich nazwę użytkownika, kraj oraz numer telefonu.


Kolejnym krokiem w integracji FastAPI z bibliotekami Pythona jest użycie modeli uczenia maszynowego, zwłaszcza w kontekście predykcji i analizy danych. FastAPI sprawdza się doskonale w roli backendu dla modeli ML, dzięki swojej wydajności i prostocie. W tym przypadku zaprezentujemy sposób integracji modelu ML z FastAPI za pomocą biblioteki Joblib, która służy do serializacji modeli.

Załóżmy, że tworzymy aplikację AI, która diagnozuje choroby na podstawie podanych objawów. Pierwszym krokiem jest zainstalowanie wymaganych pakietów, w tym FastAPI, Joblib oraz Scikit-learn, które będą potrzebne do załadowania i uruchomienia modelu ML.

bash
$ pip install fastapi[all] joblib scikit-learn

Model ML, którego używamy, to model predykcji chorób, dostępny na platformie Hugging Face. Aby pobrać model, używamy pakietu huggingface_hub, co pozwala na integrację z Hugging Face i bezpośrednie wykorzystanie pretrenowanych modeli.

bash
$ pip install huggingface_hub

Następnie w projekcie tworzymy folder app, a w nim plik utils.py, który będzie zawierał listę objawów akceptowanych przez model. Z kolei główny plik main.py zawiera kod serwera FastAPI oraz integrację z modelem ML. Korzystamy z funkcji lifespan w FastAPI, która umożliwia ładowanie modelu przed uruchomieniem serwera, a także jego usunięcie po zakończeniu działania aplikacji.

python
from fastapi import FastAPI
from contextlib import asynccontextmanager import joblib from huggingface_hub import hf_hub_download ml_model = {} REPO_ID = "AWeirdDev/human-disease-prediction" FILENAME = "sklearn_model.joblib" @asynccontextmanager async def lifespan(app: FastAPI): ml_model["doctor"] = joblib.load( hf_hub_download(repo_id=REPO_ID, filename=FILENAME) ) yield ml_model.clear()

W ten sposób model zostaje załadowany raz, a następnie dostępny dla wszystkich zapytań API, co znacząco poprawia wydajność. Takie podejście pozwala na efektywne wykorzystanie zasobów serwera, ponieważ model nie jest ładowany przy każdym zapytaniu.


Ważnym aspektem, który należy uwzględnić przy implementacji rozwiązań opartych na FastAPI i integracji z GraphQL, jest zrozumienie, że GraphQL i RESTful API mogą współistnieć w jednej aplikacji, umożliwiając użytkownikom wybór najlepszego sposobu komunikacji z serwerem. REST API jest doskonałe do operacji CRUD (tworzenie, odczyt, aktualizacja, usuwanie), podczas gdy GraphQL pozwala na elastyczne zapytania i optymalizację przesyłanych danych.

W przypadku integracji z modelami ML warto pamiętać o konieczności odpowiedniego przetwarzania danych wejściowych i walidacji, aby zapewnić poprawność wyników predykcji. Ponadto, w produkcyjnych środowiskach należy zadbać o odpowiednią skalowalność aplikacji, w tym o zarządzanie pamięcią i wydajnością serwera.

Zrozumienie tych aspektów pomoże nie tylko w budowie aplikacji, ale także w zapewnieniu ich długoterminowej efektywności i niezawodności.