Database

Ci sono problemi per coloro che salvano i dati in files di testo, in files XML, o attraverso la serializzazione con pickle o shelve, le cui soluzioni possono essere sconvenienti. A volte siamo costretti a ricorrere alle banche dati (o database), sia per ragioni di scalabilità, d’interoperabilità, di coerenza, di sicurezza, di riservatezza, ecc.
In questo capitolo impareremo a lavorare con i database in Python. Tuttavia, si presuppone una conoscenza di base, come può essere per la sua gestione con SQL. Se questo non è il caso, ci sono migliaia di risorse disponibili online per il lettore che introduce alla gestione dei database.
DB API
Ci sono centinaia di database sul mercato, sia commerciali che gratuiti. Ci sono anche decine di diversi moduli per lavorare con tali banche dati in Python, il che significa decine di diverse API da imparare.
In Python, come in altri linguaggi come Java con JDBC, c’è una proposta di API standard per la gestione dei database, in modo che il codice è praticamente lo stesso indipendentemente dal database che si sta usando sotto. Questa specifica si chiama Python Database API o DB-API e si raccoglie nel PEP 249.
DB-API è adesso nella versione 2.0, e ci sono implementazioni di database relazionali più conosciute e ad alcuni database non-relazionali.
In questo capitolo si utilizzeremo il database SQLite per gli esempi, dal momento che non c’è bisogno di installare ed eseguire un processo server separato che comunica con il programma, ma si tratta di una piccola libreria C che s’integra con applicazione e che viene fornito con Python di default dalla versione 2.5. Dalla stessa versione Python incorpora anche un modulo compatibile con questa banca dati che segue le specifiche di DB API 2.0: sqlite3, quindi, non necessita di alcuna configurazione aggiuntiva.
Nulla impedisce al lettore, però, d’installare e utilizzare qualsiasi altro database, come MySQL, con la quale si può lavorare attraverso il driver compatibile con DB API 2.0 MySQLdb.
Variabili globali
Prima di lavorare con sqlite3, vedremo alcune note interessanti circa il modulo. Tutti i driver compatibili con DB-API 2.0 dovrebbero avere 3 variabili globali che li descrivono. Sono questi:

  1. apilevel: una stringa con la versione DB API in uso. Attualmente può solo prendere un valore di “1.0” o “2.0”. Se la variabile non esiste è considerato uguale a “1.0”.
  2. threadsafety: è un intero che va da 0 a 3 e descrive quanto è sicuro il modulo per l’utilizzo dei threads. Se è “0” non può essere condiviso il modulo tra i threads senza usare un qualche tipo di meccanismo di sincronizzazione; se è “1”, possono condividere il modulo, ma non le connessioni; se è “2”, condividono moduli e connessioni, ma non i cursori; e, infine, se è “3”, è completamente thread-safe.
  3. paramstyle: informa sulla sintassi da utilizzare per inserire dinamicamente i valori nella query SQL.
    1. qmark: interrogativi.
      		sql = "select all from t where valor=?"
      
    2. numeric: un numero indicando la posizione.
      		sql = "select all from t where valor=:1"
      
    3. named: il nome del valore.
      		sql = "select all from t where valor=:valor"
      
    4. format: especificatori di formato simili al printf di C.
      		sql = "select all from t where valor=%s"
      
    5. pyformat: simile al precedente ma con le estensioni Python.
      		sql = "select all from t where valor=%(valor)"
      

Vediamo quali valori corrispondono a sqlite3

>>> import sqlite3 as dbapi
>>> print dbapi.apilevel
2.0
print dbapi.threadsafety
1
>>> print dbapi.paramstyle
qmark
>>> 
Eccezioni
Qui potete trovare la gerarchia delle eccezioni che devono fornire i moduli, insieme a una breve descrizione di ogni eccezione, per riferimento.
	
StandardError<br />
|__Warning<br />
|__Error<br />
|__InterfaceError<br />
|__DatabaseError<br />
|__DataError<br />
|__OperationalError<br />
|__IntegrityError<br />
|__InternalError<br />
|__ProgrammingError<br />
|__NotSupportedError<br />
  1. StandardError: Super clase per tutte le eccezioni da DB API.
  2. Warning: Eccezione sollevata per avvisi importanti.
  3. Error: Super clase degli errori.
  4. InterfaceError: Errori relativiti con l’Interfaccia con il database e non con il database.
  5. DatabaseError: Errori relativi al database.
  6. DataError: Errori relativi ai dati, come una divisione tra lo zero.
  7. OperationalError: Errori relativi al funzionamento del database, come una dsconessione inattesa.
  8. IntegrityError: Errori relativi con la integritò referenziale.
  9. InternalError: Error interno del database.
  10. ProgrammingError: Errori de programmazione, come errori nel codice SQL.
  11. NotSupportedError: Eccezione che si lancia quando si richiede un metodo che non è supportato dal database.
Uso basico del DB-API
Vediamo ora come lavorare con il nostro database tramite DB-API.
La prima cosa che dobbiamo fare è creare una connessione al database server. Questo viene fatto attraverso la funzione connect i cui parametri non sono standardizzati e dipendono dal database al quale ci stiamo connettendo.
Per sqlite3 abbiamo solo bisogno di passare come parametro una stringa contenente il percorso del file in cui salvare i dati nel database, o la stringa “:memory:” per usare la RAM invece di un file sul disco.
D’altra parte, nel caso di MySQLdb, connect prende come parametri la macchina che esegue il server (host), la porta (port), il nome utente con cui autenticare (user), la password (password) e il database tra quelli che si trovano nel nostro DBMS (db).
La funzione connect restituisce un oggetto di tipo Connection che rappresenta la connessione al server.
				>>> bbdd = dbapi.connect("bbdd.dat")
				>>> print bbdd
				<sqlite3.Connection object at 0x00A71DA0>
Le varie operazioni che possono essere fatte con il database vengono eseguite tramite un’oggetto Cursor. Per creare questo oggetto si utilizza il metodo cursor() dell’oggetto Connection:
				c = bbdd.cursor()
Le operazioni vengono effettuate tramite il metodo execute di Cursor, passando come parametro una stringa con il codice SQL da eseguire.
Come esempio creiamo una nuova tabella impiegati del database:
				c.execute("""create table impiegati (
		 matricola text,
				             nome text,
				             reparto text)""")
… e di seguito inseriamo una tupla nella nostra nuova tabella:
				c.execute("""insert into impiegati
	                         values (‘12345678-A’, ‘Manuele Rossi’, ‘Magazzino’)""")
Se il nostro database supporta le transazioni, se queste sono abilitate, e se l’auto-commit è disabilitato, sarà necessario chiamare il metodo commit nella connessione per portare atermine le operazioni definite nella transazione.
Se in queste circostanze utilizziamo uno strumento esterno per controllare il contenuto del nostro database senza prima fare il commit troveremo allora con un database vuoto.
Se verifichiamo il contenuto del database da Python, senza chiudere il cursore o la connessione, riceveremo il risultato del contesto della transazione, e sembrerebbe di avere eseguito le modifiche, anche se non è così, perché le modifiche si applicano soltanto, come detto, chiamando commit.
Per i database che non supportano le transazioni, la norma stabilisce che è necessario fornire un metodo commit con l’implementazione vuota, quindi non è una cattiva idea chiamare sempre a commit anche se non è necessario modificare il sistema di database modificando la riga l’import.
Se il nostro database supporta la caratteristica di rollback (ripristino) possiamo annullare la transazione corrente con:
				bbdd.rollback()
Se il database non supporta la chiamata a rollback questo metodo genererà un’eccezione.
Vediamo un esempio completo di utilizzo:
				import sqlite3 as dbapi
				
				bbdd = dbapi.connect("bbdd.dat")
				cursor = bbdd.cursor()
				
				c.execute("""create table impiegati (matricola text,
				             nome text,
				             reparto text)""")
				
				cursor.execute("""insert into impiegati
				                  values (‘12345678-A’, ‘Manuele Rossi’, ‘Magazzino’)""")
				 	
				bbdd.commit()
				
				cursor.execute("""select * from impiegati
				                  where reparto=’Magazzino’""")
				
				for tupla in cursor.fetchall():
				    print tupla
Come si vede, per interrogare il database viene utilizzato anche execute. Per interrogare le tuple risultanti dalla sentenza in SQL è possibile chiamare i metodi di Cursor (fetchone, fetchall o fetchmany) o utilizzare l’oggetto Cursor come iteratore.
				cursor.execute("""select * from impiegati
				                  where reparto=’Magazzino’""")
				                  
				for resultado in cursor:
				    print tupla
Il metodo fetchone restituisce la successiva tupla dell’insieme dei risultati, o None se non ci sono più tuple, fetchmany restituisce il numero di righe indicato dal numero intero passato come parametro o il numero indicato dall’attributo Cursor.arraysize se non si passa nessun parametro (Cursor.arraysize è valorizzato a 1 per default) e fetchall restituisce un’oggetto iterable con tutte le tuple.
Quando si lavora con i selects o altri tipi di istruzioni SQL è importante tenere conto che non si devrebbero usare i metodi per le stringhe standard per costruire le frasi, in quanto ciò rende vulnerabile agli attacchi SQL injection, ma invece si devrebbe utilizzare la caratteristica di sostituzione di parametri di DB API.
Supponiamo che stiamo sviluppando un’applicazione web con Python per una banca in cui si può consultare un elenco di sportelli bancari di una determinata città con un URL nella seguente forma http://www.miabanca.com/sportelli?citta=Roma
Potremmo avere una query come questa:
				cursor.execute("""select * from sportelli
				                  where citta=’" + citta + "’""")
A prima vista potrebbe sembrare che non vi è nessun problema: non facciamo altro che ottenere gli sportelli che si trovano nella città indicata dalla variabile citta. Ma cosa succederebbe se un utente malintenzionato accede con un URL del tipo “http://www.miabanca.com/sportelli?citta=Roma ‘; SELECT * FROM utenti“?
Poiché non vi è alcuna convalida sui valori che possono contenere la variabile citta, sarebbe facile per chiunque ottenere il controllo completo dell’applicazione.
La cosa giusta sarebbe, come detto prima, è di utilizzare la caratteristica di sostituzione di parametri del DB API. Il valore paramstyle per il modulo sqlite3 era qmark. Questo significa che dovremo scrivere un punto interrogativo (?)nel luogo in cui vogliamo inserire il valore, e basta passare un secondo parametro a execute come una sequenza o una mappatura (mapping) con i valori da utilizzare per il modulo che creerà la frase per noi.
				cursor.execute("""select * from sportelli
				                  where citta=?""", (citta,))
Infine, al termine del programma si deve chiudere il cursore e la connessione:
				cursor.close()
				bbdd.close()
TipI SQL
A volte abbiamo bisogno di lavorare con i tipi di SQL, e memorizzare, ad esempio, date od ore usando Date e Time e non con le stringhe. Le API per il database in Python include un certo numero di costruttori da utilizzare per creare questi tipi di dati. Sono i seguenti:

  1. Date(year, month, day): Per memorizzare le date.
  2. Time(hour, minute, second): Per memorizzare le ore.
  3. Timestamp(year, month, day, hour, minute, second): Per memorizzare timestamps (una data conla sua ora).
  4. DateFromTicks(ticks): Per creare una data da un certo numero di secondi trascorsi da epoch (il 1 ° gennaio 1970 00:00:00 GMT).
  5. TimeFromTicks(ticks): Simile alla precedente, ma per ore anziché le date.
  6. TimestampFromTicks(ticks): Simile alla precedente, ma timestamps.
  7. Binary(string): Valore binario.
Altre Opzioni
Naturalmente non siamo tenuti ad utilizzare il DB-API, o i database relazionali. In Python ci sono moduli per lavorare con i database object-oriented, come ZODB (Zope Object Database) e motori per il mapping object-relational (ORM) come SQLAlchemy, SQLObject o Storm.
Inoltre, se usiamo IronPython al posto di CPython saremo in grado di utilizzare le connessioni ai database nelle piattaforme .NET, e se usiamo Jython, in quella di Java.


Similari
Overloading di metodi in Java
12% Java
Un metodo overload viene utilizzato per riutilizzare il nome di un metodo ma con argomenti diversi, opzionalmente con un differente tipo di ritorno. [expand title=”Regole per overload” startwrap=”” endwrap=”” excerpt=”⤽” s…
Modi di fare e di non fare in Python
11% Python
Questo documento può essere considerato un compagno del tutorial di Python. Viene illustrato come utilizzare Python, e quasi ancora più importante, come non usare Python. [expand title=”Costrutti del linguaggio che non dov…
redirect 301 usando mod_alias
9% Server
mod_alias è fondamentalmente la versione più semplice di mod_rewrite. Non può fare le cose che fa mod_rewrite, ad esempio modificare la stringa di query. Per eseguire reindirizzamenti nel server web Apache è possibile di u…
Installare Python e Django su Windows
8% Django
Quando ci riferiamo allo sviluppo web con Python, la prima cosa che viene in mente è usare un qualche framework. Il più famoso e utilizzato da tutti è il Django, ma non è l’unico. Ci sono Pylons, Grok, TurboGears e Zope: t…
Metodi magici e costanti predefinite in PHP
8% Php
PHP fornisce un insieme di costanti predefinite e metodi magici per i nostri programmi. A differenza delle normali costanti i quali si impostano con define(), il valore delle costanti predefinite o speciali dipendono da do…