Lezione 10: API HTTP — Dati dal web
Obiettivi
Section titled “Obiettivi”- Capire cos’è un’API e come funziona
- Fare richieste HTTP a servizi web
- Parsare risposte JSON
- Gestire loading e errori nella UI
1. Cos’è un’API?
Section titled “1. Cos’è un’API?”API (Application Programming Interface) è un modo per due programmi di comunicare tra loro. Nel nostro caso, l’app Flet parla con un server web per ottenere dati.
App Flet ──── richiesta HTTP (GET) ────→ Server API │ │ ←──── risposta JSON (dati) ────────────┘API pubbliche gratuite (non serve registrazione)
Section titled “API pubbliche gratuite (non serve registrazione)”| API | URL | Cosa restituisce |
|---|---|---|
| JSONPlaceholder | https://jsonplaceholder.typicode.com/posts | Post finti |
| JSONPlaceholder | https://jsonplaceholder.typicode.com/users | Utenti finti |
| Dog API | https://dog.ceo/api/breeds/image/random | Immagine cane casuale |
| Pokemon | https://pokeapi.co/api/v2/pokemon/pikachu | Dati Pokemon |
| Open Trivia DB | https://opentdb.com/api.php?amount=5 | Domande trivia |
2. Fare request in Python
Section titled “2. Fare request in Python”Usando urllib.request (modulo built-in)
Section titled “Usando urllib.request (modulo built-in)”import urllib.requestimport json
def fetch_data(url): """Scarica dati JSON da un URL.""" with urllib.request.urlopen(url) as response: data = json.loads(response.read().decode()) return dataUsando il pacchetto requests (più semplice)
Section titled “Usando il pacchetto requests (più semplice)”Prima installa: pip install requests
import requests
def fetch_data(url): """Scarica dati JSON da un URL usando requests.""" response = requests.get(url) response.raise_for_status() # Solleva errore se status non è 200 return response.json()In questo corso useremo
urllib.request(non serve installare nulla).
3. Esempio base: Fetch dati
Section titled “3. Esempio base: Fetch dati”import flet as ftimport urllib.requestimport json
def main(page: ft.Page): page.title = "Fetch API" page.padding = 30
risultato = ft.Column()
def fetch_posts(e): risultato.controls.clear() risultato.controls.append(ft.Text("Caricamento...", italic=True, color="grey")) page.update()
try: # Chiamata API url = "https://jsonplaceholder.typicode.com/posts" with urllib.request.urlopen(url) as response: posts = json.loads(response.read().decode())
risultato.controls.clear() for post in posts[:5]: # Primi 5 post risultato.controls.append( ft.Container( content=ft.Column([ ft.Text(post["title"], weight="bold", size=16), ft.Text(post["body"], color="grey"), ]), padding=10, bgcolor="white", border_radius=8, margin=5, shadow=ft.BoxShadow(blur_radius=3, color=ft.colors.GREY_300), ) )
except Exception as ex: risultato.controls.clear() risultato.controls.append(ft.Text(f"❌ Errore: {ex}", color="red"))
page.update()
page.add( ft.Text("📡 API Demo", size=28, weight="bold"), ft.ElevatedButton("Carica Post", on_click=fetch_posts, icon=ft.icons.DOWNLOAD), ft.Divider(), risultato, )
ft.app(target=main)4. Esempio guidato: App Pokemon
Section titled “4. Esempio guidato: App Pokemon”import flet as ftimport urllib.requestimport json
def main(page: ft.Page): page.title = "Pokédex" page.padding = 30
nome_pokemon = ft.TextField( label="Nome Pokemon", hint_text="es. pikachu, charizard, mewtwo", prefix_icon=ft.icons.SEARCH, )
card_pokemon = ft.Container(visible=False) # Nascosta inizialmente errore = ft.Text("", color="red") caricamento = ft.ProgressBar(visible=False)
def cerca_pokemon(e): if not nome_pokemon.value.strip(): return
# Mostra caricamento caricamento.visible = True card_pokemon.visible = False errore.value = "" page.update()
try: url = f"https://pokeapi.co/api/v2/pokemon/{nome_pokemon.value.strip().lower()}" with urllib.request.urlopen(url) as response: dati = json.loads(response.read().decode())
# Costruisce card card_pokemon.content = ft.Container( content=ft.Column([ ft.Row([ ft.Container( content=ft.Text(dati["name"].upper(), size=24, weight="bold", color="white"), bgcolor="red", padding=20, border_radius=10, ), ], alignment=ft.MainAxisAlignment.CENTER), ft.Text(f"Altezza: {dati['height'] / 10} m", size=16), ft.Text(f"Peso: {dati['weight'] / 10} kg", size=16), ft.Text("Tipi:", weight="bold"), ft.Row([ ft.Container( content=ft.Text(tipo["type"]["name"]), bgcolor="lightblue", padding=5, border_radius=5, ) for tipo in dati["types"] ]), ft.Text("Abilità:", weight="bold"), ft.Column([ ft.Text(f"• {abilita['ability']['name']}") for abilita in dati["abilities"] ]), ]), padding=20, bgcolor="white", border_radius=15, shadow=ft.BoxShadow(blur_radius=15, color=ft.colors.GREY_400), ) card_pokemon.visible = True
except urllib.error.HTTPError: errore.value = f"❌ Pokemon '{nome_pokemon.value}' non trovato!" except Exception as ex: errore.value = f"❌ Errore: {ex}" finally: caricamento.visible = False page.update()
page.add( ft.Text("🔍 Pokédex", size=28, weight="bold"), ft.Row([nome_pokemon, ft.ElevatedButton("Cerca", on_click=cerca_pokemon)]), errore, caricamento, card_pokemon, )
ft.app(target=main)5. Loading indicator: ProgressBar e CircularProgress
Section titled “5. Loading indicator: ProgressBar e CircularProgress”# Barra orizzontale (indeterminata)ft.ProgressBar( visible=True, # True quando carica color="blue", bgcolor="#e0e0e0",)
# Cerchio di caricamentoft.ProgressRing( visible=True, color="blue", width=50, height=50, stroke_width=4,)Pattern loading standard
Section titled “Pattern loading standard”caricamento = ft.ProgressBar(visible=False)errore = ft.Text("", color="red")contenuto = ft.Column()
def carica_dati(e): # 1. Reset contenuto.controls.clear() errore.value = "" caricamento.visible = True page.update()
try: # 2. Chiamata API dati = fetch_api() # 3. Mostra dati for item in dati: contenuto.controls.append(...) except Exception as ex: # 4. Errore errore.value = f"❌ {ex}" finally: # 5. Fine caricamento caricamento.visible = False page.update()6. API con parametri
Section titled “6. API con parametri”Molte API accettano parametri per filtrare i dati:
# URL con parametribase = "https://jsonplaceholder.typicode.com/posts"params = "?userId=1" # Solo post dell'utente 1url = base + params
# Oppure più parametriurl = "https://opentdb.com/api.php?amount=5&category=18&difficulty=easy"7. Tabella riassuntiva
Section titled “7. Tabella riassuntiva”| Comando | Cosa fa |
|---|---|
urllib.request.urlopen(url) | Apre connessione HTTP |
response.read().decode() | Legge risposta come stringa |
json.loads(testo) | Converte JSON → Python |
requests.get(url) (con pacchetto) | Richiesta HTTP GET |
response.raise_for_status() | Controlla errori HTTP |
ft.ProgressBar(visible=True) | Barra caricamento |
ft.ProgressRing(...) | Cerchio caricamento |
8. Esercizio autonomo
Section titled “8. Esercizio autonomo”🎯 Esercizio: “App Citazioni”
Section titled “🎯 Esercizio: “App Citazioni””Crea un file citazioni.py che usi l’API https://api.quotable.io/random per mostrare citazioni casuali.
- Un titolo ”💬 Generatore di Citazioni”
- Un bottone “Nuova citazione” che chiama l’API
- Una card che mostra:
- Il testo della citazione (in corsivo, grande)
- L’autore (sotto, allineato a destra, grassetto)
- Un tag che mostra la categoria (es. “wisdom”, “success” — guarda la risposta API)
- Loading indicator mentre si carica
- Gestione errore se la chiamata fallisce
Struttura risposta API
Section titled “Struttura risposta API”Chiamando https://api.quotable.io/random ricevi:
{ "content": "La vita è bella", "author": "Qualcuno", "tags": ["filosofia"]}Suggerimenti
Section titled “Suggerimenti”- Gestisci il caricamento con
ft.ProgressBar - Per il corsivo:
ft.Text(italic=True) - Per il tag, usa
ft.Container(bgcolor="lightblue", border_radius=5, padding=5) - Fai fetch ad ogni click del bottone