Vai al contenuto
Leo

08 Sezione 8 di 15

Internet e database

Con poche righe un programma Leo scarica dati da Internet, manda un'email, accende un piccolo sito sul tuo computer o conserva migliaia di dati in un database. Tutto con comandi in italiano e senza installare niente.

  • 7 minuti di lettura
  • Livello: intermedio
  • 11 esempi verificati

Scaricare pagine e dati#

Leocontrollato con Leo
variabile html = scarica_pagina("https://www.esempio.it")stampa(lunghezza(html)) variabile meteo = scarica_json("https://api.esempio.it/meteo", parametri = {"città": "Roma"})stampa("A Roma ci sono {meteo["temperatura"]} gradi") scarica_file("https://www.esempio.it/mappa.pdf", "documenti/mappa.pdf", avanzamento = vero)
  • scarica_pagina(indirizzo) restituisce il testo della pagina;
  • scarica_json(indirizzo, parametri) restituisce direttamente dizionari e liste di Leo;
  • scarica_file(indirizzo, percorso, avanzamento = vero) salva un file e mostra la percentuale; il file compare solo quando è completo, quindi una connessione che cade non lascia file rovinati.

Richieste web complete#

Quando vuoi decidere tu che cosa fare in caso di errore, o devi inviare dati (per esempio a un servizio con una chiave), usa richiesta_web. La risposta è un dizionario.

Leocontrollato con Leo
variabile r = richiesta_web("https://api.esempio.it/voti",    metodo = "POST",    json = {"studente": "Ada", "voto": 30},    intestazioni = {"Authorization": "Bearer il-mio-token"},    tempo_massimo = 10) se r["riuscita"] allora    stampa("Salvato! Risposta:", r["json"])altrimenti    stampa("Qualcosa non va:", r["spiegazione"])fine
ChiaveContenuto
statoil codice HTTP (200, 404, 500…)
riuscitavero se il codice è 2xx
messaggio, spiegazioneil codice in breve e spiegato in italiano
testo, jsonil corpo della risposta, come testo e come valori Leo
intestazioni, indirizzole intestazioni e l'indirizzo finale

Anche i codici HTTP si fanno spiegare, e gli indirizzi si compongono senza preoccuparsi di spazi e accenti:

Leoeseguito con Leo
stampa(spiega_stato_http(404))stampa(codifica_url("caffè e latte"))stampa(componi_url("https://www.esempio.it/cerca", {"q": "Italo Calvino"}))
Uscita
Pagina non trovata (404): la pagina non esiste: controlla l'indirizzo
caff%C3%A8%20e%20latte
https://www.esempio.it/cerca?q=Italo+Calvino

Errori di rete in parole semplici#

I problemi di rete sono ErroreFile con un messaggio chiaro: «Sito non trovato», «Nessuna connessione» (con il consiglio di controllare Wi-Fi o cavo), «Tempo scaduto», «Il certificato di sicurezza non è valido»… Si gestiscono con prova:

Leocontrollato con Leo
se connessione_attiva() allora    prova        variabile pagina = scarica_pagina("https://www.esempio.it", tempo_massimo = 5)    cattura ErroreFile come e        stampa("Niente da fare: " + e.messaggio)    finealtrimenti    stampa("Sei offline: uso i dati salvati.")fine

Inviare email#

Leocontrollato con Leo
invia_email("ada@esempio.it", "Il programma ha finito", "Tutti i calcoli sono pronti.",    "leo@esempio.it", "smtp.esempio.it",    utente = "leo@esempio.it",    password = leggi_file("password.txt").pulisci())

La connessione è cifrata (porta 587 con STARTTLS, o 465); la password non compare mai nei messaggi d'errore e non viene mai inviata senza cifratura. Con html = "..." aggiungi una versione HTML del messaggio, con copia altri destinatari.

Un piccolo server web#

Leo può accendere un piccolo sito sul tuo computer, visibile solo da lì (127.0.0.1): per imparare, per mostrare dati nel browser o per farli leggere a un'altra applicazione. Questo esempio accende il server, lo interroga e lo spegne:

sito_locale.leoeseguito con Leo
variabile sito = server_web(0, pagina = "<!doctype html><h1>Ciao dal mio programma!</h1>")sito.aggiungi_pagina("/dati", {"voti": [28, 30, 27]})sito.avvia()stampa("Il sito è acceso:", sito.attivo)stampa(scarica_json(sito.indirizzo + "dati"))sito.ferma()
Uscita
Il sito è acceso: vero
{"voti": [28, 30, 27]}

server_web(0) sceglie una porta libera; di solito si scrive una porta precisa, come server_web(8080), e si apre il browser su sito.indirizzo. Per rispondere con il tuo codice si usa un gestore quando:

server.leocontrollato con Leo
quando server.richiesta fai    se evento["percorso"] == "/saluta" allora        variabile nome = evento["parametri"].prendi("nome", "sconosciuto")        rispondi("<!doctype html><h1>Ciao {nome}!</h1>")    altrimenti se evento["percorso"] == "/basta" allora        rispondi("Spengo il server.")        server.ferma()    altrimenti        rispondi("Qui non c'è niente", stato = 404)    finefine variabile server = server_web(8080)stampa("Apri il browser su " + server.indirizzo + "saluta?nome=Ada")server.servi()

In evento trovi percorso, metodo, parametri, corpo, json e intestazioni. rispondi capisce da solo il tipo: un testo che comincia con <!doctype è una pagina, un dizionario o una lista diventano JSON. server.servi() resta in ascolto finché non chiami server.ferma() o premi STOP.

Database: conservare tanti dati#

Un database conserva dati in tabelle, come un foglio di calcolo molto più veloce e sicuro. Leo usa SQLite: il database è un singolo file, e non c'è niente da installare.

scuola.leoeseguito con Leo
variabile db = apri_database("scuola.db")      # crea il file se non c'è db.crea_tabella("studenti", {    "nome": "testo obbligatorio",    "età": "intero",    "media": "decimale",    "iscritto": "logico"}) db.inserisci("studenti", {"nome": "Ada", "età": 20, "media": 28.5, "iscritto": vero})db.inserisci("studenti", [    {"nome": "Bruno", "età": 17, "media": 24.0, "iscritto": vero},    {"nome": "Carla", "età": 25, "media": 27.2, "iscritto": falso}]) per ogni s in db.cerca("studenti", {"età": [">=", 18]}, ordina = "nome") fai    stampa(s["nome"], s["età"], s["media"])finestampa("Iscritti:", db.conta("studenti", {"iscritto": vero})) db.aggiorna("studenti", {"nome": "Ada"}, {"età": 21})stampa(db.primo("studenti", {"nome": "Ada"})["età"])db.elimina("studenti", {"nome": "Carla"})stampa("Rimasti:", db.conta("studenti"))db.chiudi()
Uscita
Ada 20 28.5
Carla 25 27.2
Iscritti: 2
21
Rimasti: 2
  • i tipi delle colonne sono intero, decimale, testo, logico, seguiti se vuoi da chiave, obbligatorio o unico; se non c'è una colonna id, Leo ne aggiunge una automatica;
  • crea_tabella non fa niente se la tabella esiste già: puoi chiamarla ogni volta che il programma parte;
  • i risultati sono sempre liste di dizionari, una riga = un dizionario;
  • apri_database() senza nome crea un database solo in memoria, che sparisce alla fine: ottimo per provare.

Le condizioni si scrivono come dizionari; valgono tutte insieme:

ScriviSignifica
{"nome": "Ada"}uguale
{"età": [">", 18]}anche <, >=, <=, !=
{"nome": ["contiene", "an"]}contiene il testo (anche "inizia", "finisce")
{"voto": ["tra", 18, 30]}compreso tra i due valori
{"città": ["in", ["Roma", "Milano"]]}uno dei valori della lista
{"email": nulla}vuoto

Sicurezza: niente SQL injection#

L'attacco più comune ai programmi con un database consiste nello scrivere, al posto di un nome, un pezzo di comando che cambia il significato della ricerca. Con i comandi di Leo non può succedere: i valori restano sempre separati dal comando, e i nomi di tabelle e colonne vengono controllati.

sicuro.leoeseguito con Leo
variabile db = apri_database()db.crea_tabella("utenti", {"nome": "testo"})db.inserisci("utenti", {"nome": "Ada"})variabile furbo = "x' OR '1'='1"stampa(db.cerca("utenti", {"nome": furbo}))     # nessun risultato: è solo un nomestampa(db.esegui("SELECT nome FROM utenti WHERE nome = ?", ["Ada"]))
Uscita
[]
[{"nome": "Ada"}]

Se scrivi tu il comando SQL con db.esegui, la regola è una sola: i valori mai dentro il testo del comando, sempre nei parametri (? con una lista, o :nome con un dizionario).

Transazioni: tutto o niente#

Una transazione raggruppa più modifiche: o riescono tutte, o nessuna. È il caso del bonifico: se il programma si fermasse dopo aver tolto i soldi da un conto ma prima di aggiungerli all'altro, i soldi sparirebbero.

banca.leoeseguito con Leo
variabile db = apri_database()db.crea_tabella("conti", {"nome": "testo unico", "saldo": "intero"})db.inserisci("conti", [{"nome": "Ada", "saldo": 100}, {"nome": "Bruno", "saldo": 20}]) funzione bonifico(da, a, quanto)    db.transazione(funzione ()        variabile conto = db.primo("conti", {"nome": da})        db.aggiorna("conti", {"nome": da}, {"saldo": conto["saldo"] - quanto})        se conto["saldo"] < quanto allora            lancia ErroreValore("Saldo insufficiente")        fine        variabile altro = db.primo("conti", {"nome": a})        db.aggiorna("conti", {"nome": a}, {"saldo": altro["saldo"] + quanto})    fine)fine bonifico("Ada", "Bruno", 30)prova    bonifico("Bruno", "Ada", 500)cattura ErroreValore come e    stampa("Bonifico annullato:", e.messaggio)fineper ogni c in db.cerca("conti") fai    stampa(c["nome"], c["saldo"])fine
Uscita
Bonifico annullato: Saldo insufficiente
Ada 70
Bruno 50

Nel secondo bonifico il saldo di Bruno era già stato cambiato quando è arrivato l'errore: la transazione ha annullato tutto, e i conti sono rimasti giusti.

Esportare e importare CSV#

esporta.leoeseguito con Leo
variabile db = apri_database()db.crea_tabella("studenti", {"nome": "testo", "età": "intero"})db.inserisci("studenti", [{"nome": "Ada", "età": 20}, {"nome": "Bruno", "età": 17}])db.esporta_csv("studenti", "studenti.csv", separatore = ";")stampa(leggi_file("studenti.csv"))variabile altro = apri_database()stampa("Importate", altro.importa_csv("studenti.csv", "copia"), "righe")
Uscita
id;nome;età
1;Ada;20
2;Bruno;17

Importate 2 righe

importa_csv crea la tabella se non c'è, indovinando i tipi delle colonne, ed è «tutto o niente»: se una riga non va bene non viene inserito niente e il messaggio dice il numero della riga.