Python Lab 8
Własne API
Wysyłamy dane na zewnątrz!
Na poprzednim spotkaniu korzystaliśmy z zewnętrznego API, a w naszej aplikacji wyciągaliśmy dane za pomocą requests. Teraz postawimy się po drugiej stronie i pracować będziemy nad aplikacją serwerową - czyli teraz to nasza aplikacja będzie wysyłała dane.
FastAPI to nowoczesny framework webowy dla języka Python, który pozwala szybko budować interfejsy API. Jest lekki, szybki i bardzo intuicyjny, idealny do nauki i prototypowania.
Architektura naszej aplikacji będzie dziś relatywnie prosta, ale nadal będziemy mieli kilka różnic względem tego, jak dotychczas pracowaliśmy. Najpierw musimy zainstalować 2 packages:
fastapi- cały framework, który będzie odbierał zapytania HTTP i zwracał odpowiedziuvicorn- najprostszy serwer
Nasze aplikacje serwerowe zawsze zaczynać będziemy od importu fastapi:
from fastapi import FastAPI
Następnie musimy utworzyć instancję aplikacji - to obiekt app będzie zarządzał naszym API
app = FastAPI()
@app.get("/")
def root():
return {"message": "Witaj w FastAPI!"}
To definicja trasy (ang. route) w aplikacji:
@app.get("/")– dekorator, który mówi:“kiedy użytkownik wyśle żądanie GET na adres główny (
/), uruchom funkcjęroot“.def root():– to funkcja, która obsługuje to żądanie.
Jej wynik zostanie wysłany jako odpowiedź HTTP.return {"message": "Witaj w FastAPI!"}– zwraca słownik (dict), który FastAPI automatycznie konwertuje na JSON, czyli:
Uruchomienie
Tu mamy małą rewolucję, względem tego, co robiliśmy do tej pory - aplikację w uvicorn uruchomić będziemy musieli przez terminal. W terminalu (np. w PyCharmie) wpisać należy polecenie uvicorn main:app --reload i wcisnąć Enter. Od teraz za każdym razem jak zapiszemy plik pythona, to nasze API będzie się ładowało samoczynnie, nic nie musimy więcej robić. W odpowiedzi na terminalu pojawi się nam adres, na którym działa aplikacja.
Jak to działa w praktyce?
Jeśli uruchomisz aplikację, a następnie otworzysz przeglądarkę i wejdziesz na http://127.0.0.1:8000/, zobaczysz:
{"message": "Witaj w FastAPI!"}
A to już po wczorajszych zajęciach powinien być znajomy widok.
Bonus: automatyczna dokumentacja
FastAPI ma wbudowane narzędzie Swagger UI – jeśli wejdziesz na http://127.0.0.1:8000/docs, zobaczysz interaktywne API, które tworzy się automatycznie.
Obsługa zapytań
Po wczorajszych zajęciach poznaliśmy dwa sposoby na wskazanie API, jakie dane nas interesują:
https://catfact.ninja/fact- ‘goły’ adres - zwracał losowy fakthttps://restcountries.com/v3.1/all?fields=name,capital,population,region- w tym wypadku po znaku?podawaliśmy listę pól, które nas interesowały - był to parametr - w ten sposób będziemy pracować dziś:
Na razie nasze API powinno wyglądać tak:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello, FastAPI!"}
Czyli zapytanie na adres API zawsze zwraca nam wiadomość “Hello, FastAPI”. Czyli mamy w aplikacji jednego, prostego endpointa. Dopiszemy teraz nowego, który będzie przyjmował dane w parametrze.
@app.get("/hello")
def hello(name: str = "Anon"):
return {"greeting": f"Hello {name}!"}
Teraz zwróćcie uwagę: nasz dekorator wskazuje, że do funkcji hello będziemy wchodzić, gdy do API przyjdzie request na adres URL http://127.0.0.1:8000/hello. Funkcja w argumencie przyjmuje
(name: str = "Anon")
Wskazaliśmy w ten sposób, że w adresie URL może znaleźć się parametr name i będzie on stringiem. Gdy do aplikacji trafi zapytanie z tym parametrem, to wewnątrz funkcji będziemy mieli do niego dostęp poprzez zmienną name. Jeśli zapytanie na adres /hello będzie puste (czyli bez parametru), to aplikacja dopisze sobie do niego stringa “Anon”.
Zadanie na zajęcia 1:
Przygotuj prostą aplikację, która będzie obsługiwała jeden endpoint - /square. Powinien on przyjmować w parametrze liczbę typu int i zwracać jej drugą potęgę - czyli zapytanie na http://127.0.0.1:8000/square?x=10 powinno zwrócić nam JSON-a
{ "square": 100 }
Możemy w ramach jednego zapytania podać dwa lub więcej parametrów - np.
@app.get("/sum")
def add(x: int, y: int):
return {"result": x + y}
W takim wypadku request, który będziemy musieli wysłać do API będzie potrzebował dwóch parametrów - x i y - http://127.0.0.1:8000/sum?x=10&y=5 - pomiędzy argumentami znajduje się znak &.
Mini-projekt: Kalkulator w FastAPI (/calc)
Celem tego mini-projektu jest stworzenie endpointa /calc, który przyjmuje trzy parametry: x, y oraz op, a następnie wykonuje odpowiednią operację matematyczną.
Krok 1: Przygotuj bazowy endpoint /calc
Opis:
Utwórz w pliku aplikacji nowy endpoint /calc, który przyjmuje dwa parametry typu int: x oraz y. Na razie nie musi niczego jeszcze liczyć — niech tylko zwraca odebrane dane w formie słownika.
Wymagania:
| ID | Wymaganie |
|---|---|
| K1 | Endpoint działa pod adresem /calc |
| K2 | Funkcja przyjmuje dwa parametry x: int i y: int |
| K3 | Wynikiem działania funkcji jest słownik z tymi danymi, np. {"x": 2, "y": 3} |
Krok 2: Dodaj trzeci parametr op i podstawową obsługę
Opis:
Dodaj trzeci parametr op typu str, który będzie mówił, jaką operację należy wykonać. Jeśli użytkownik wpisze op=add, funkcja ma zwrócić sumę x + y.
Wymagania:
| ID | Wymaganie |
|---|---|
| K1 | Endpoint nadal działa pod /calc |
| K2 | Funkcja przyjmuje trzy parametry: x: int, y: int, op: str |
| K3 | Gdy op to "add", funkcja zwraca {"result": x + y} |
| K4 | Dla innych wartości op funkcja zwraca na razie {"error": "Nieznana operacja"} |
Krok 3: Rozszerz obsługę o wszystkie operacje
Opis:
Rozszerz funkcję tak, aby obsługiwała dodatkowe wartości parametru op: sub, mul, div.
Wymagania:
| ID | Wymaganie |
|---|---|
| K1 | Gdy op == "sub", zwracana jest różnica x - y |
| K2 | Gdy op == "mul", zwracany jest iloczyn x * y |
| K3 | Gdy op == "div" i y != 0, zwracany jest iloraz x / y |
| K4 | Dla nieznanych wartości op, zwracany jest błąd {"error": "Nieznana operacja"} |
Krok 4: Zabezpiecz dzielenie przez zero
Opis:
Dodaj obsługę przypadku, gdy użytkownik wybierze op=div i poda y = 0. W takim przypadku funkcja powinna nie wykonywać dzielenia, lecz zwrócić informację o błędzie.
Wymagania:
| ID | Wymaganie |
|---|---|
| K1 | Jeśli op == "div" i y == 0, funkcja zwraca {"error": "Dzielenie przez zero!"} |
| K2 | Dla poprawnych danych zwracany jest wynik dzielenia |
Zadanie dodatkowe (opcjonalnie)
Dopisz mechanizm przechowywania historii - każdy wykonany request poinien zostać zapisany. Utwórz endpoint /history, który zwróci nam wszystkie zgłoszone zapytania.
Zadania do laboratorium
Celem zadania jest stworzenie własnego API (/weather), które po otrzymaniu nazwy miasta wysyła zapytanie do zewnętrznego API (np. Open-Meteo), pobiera dane pogodowe, przetwarza je i zwraca jako własną odpowiedź JSON.
Nie korzystamy z baz danych — dane są pobierane na żywo i odpowiednio filtrowane.
Krok 1: Endpoint /weather z parametrem city
Opis:
Utwórz endpoint /weather, który przyjmuje parametr city (string) i na razie tylko zwraca go w odpowiedzi.
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Endpoint /weather |
| W2 | Przyjmuje parametr city: str |
| W3 | Zwraca {"city": city} |
Krok 2: Znajdź współrzędne miasta
Opis:
Użyj zewnętrznego API geolokalizacji (np. Open-Meteo lub api.api-ninjas.com), aby znaleźć współrzędne lat, lon dla podanej nazwy miasta.
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Wysłano zapytanie do API zewnętrznego |
| W2 | Odczytano latitude i longitude z odpowiedzi |
Krok 3: Pobierz dane pogodowe z Open-Meteo
Opis:
Zbuduj zapytanie do API Open-Meteo z wykorzystaniem uzyskanych współrzędnych. Pobierz aktualną pogodę.
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Użyto poprawnego URL do API Open-Meteo |
| W2 | Odczytano odpowiedź z temperaturą, zachmurzeniem, itp. |
Krok 4: Przetwórz dane pogodowe
Opis:
Wyodrębnij najważniejsze informacje z danych pogodowych: np. temperaturę, prędkość wiatru, zachmurzenie i godzinę pomiaru.
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Wyciągnięto pola: temperature, windspeed, cloudcover, time |
| W2 | Przygotowano słownik z tymi wartościami |
Krok 5: Zwróć odpowiedź w ładnej formie
Opis:
Zbuduj odpowiedź JSON, która zawiera tylko interesujące dane, np.:
{
"temperature": 21.3,
"windspeed": 14.2,
"cloudcover": 85,
"time": "2025-08-05T14:00"
}
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Odpowiedź API zawiera tylko przetworzone dane |
| W2 | Format danych jest zgodny z JSON |
Krok 6: Obsłuż błędy — brak miasta
Opis:
Jeśli API nie zwróci współrzędnych lub danych pogodowych (np. zła nazwa miasta), funkcja powinna zwrócić czytelny komunikat o błędzie.
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Dla błędnego miasta zwróć {"error": "Nie znaleziono lokalizacji"} |
| W2 | Brak błędów aplikacji (żadne 500, KeyError, itp.) |
Krok 7: (Opcjonalnie) Historia zapytań
Opis:
Dodaj prostą historię zapytań w pamięci (np. lista w Pythonie). Dodaj nowy endpoint /history, który zwraca historię wcześniejszych zapytań.
Wymagania:
| ID | Wymaganie |
|---|---|
| W1 | Endpoint /history |
| W2 | Zwraca listę ostatnich zapytań, np. {"history": [{"city": "Gdansk", "temp": 20.1}, ...]} |