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ł odpowiedzi
  • uvicorn - 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 fakt
  • https://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}, ...]}