|
Specifiche dell'API Python per i Database v.2.0
Questa API è stata creata allo scopo di favorire una certa uniformità dei moduli Python usati per accedere ai vari database. Speriamo di ottenere così del codice più semplice da capire e facilmente portabile su database diversi, e di ampliare in qualità e quantità il supporto Python ai database.
Le specifiche consistono di diverse sezioni:
Commenti e domande su tali specifiche possono essere inviate al SIG per le Interfacce Python ai Database.
Per maggiori informazioni sull'interfacciamento ai database con Python si veda la Database
Topics Guide su http://www.python.org/.
Su questo sito è anche disponibile la documentazione in italiano di mysqldb, un modulo per MySQL conforme all'API DB 2.0.
L'originale in inglese del documento è disponibile presso www.python.org.
Questo documento descrive le Specifiche dell'API Python per i Database v.2.0. La precedente versione 1.0 è ancora disponibile come riferimento. Gli scrittori di package sono fortemente incoraggiati a usare la versione 2.0 qui presentata come base per le loro nuove interfacce.
Interfaccia del Modulo
L'accesso al database è reso disponibile per mezzo di oggetti Connessione.
Il modulo deve fornire il seguente costruttore per essi:
- connect(parametri...)
-
Il costruttore per la creazione di una connessione al database. Restituisce
un oggetto Connessione. Accetta un certo
numero di parametri in dipendenza dal singolo database.
[1]
Devono essere definiti i seguenti nomi globali di modulo:
- apilevel
-
Costante stringa che specifica il livello della API DB supportata.
Attualmente sono permesse solo le stringhe
'1.0' e
'2.0' .
Ove assente si deve assumere che l'interfaccia non sia conforme alla API 2.0
e si basi sulla Database
API 1.0.
- threadsafety
-
Costante intera che specifica il livello di supporto al multithreading offerto. I
valori possibili sono:
0 |
= i thread non possono condividere il modulo. |
1 |
= i thread possono condividere il modulo ma non le connessioni. |
2 |
= i thread possono condividere il modulo e le connessioni. |
3 |
= i thread possono condividere il modulo, le connessioni e i cursori. |
"Condivisione" nel contesto soprastante significa che due thread possono usare una
risorsa senza aver bisogno di implementare un meccanismo di lock tramite mutex. Si
noti che non sempre si può rendere "a prova di [uso coi] thread" [`thread
safe'] una risorsa esterna con un controllo degli accessi tramite mutex: la risorsa
potrebbe fare assegnamento su variabili globali o altre risorse esterne che sono al
di fuori del nostro controllo.
- paramstyle
-
Costante stringa che specifica il tipo di formattazione del marcatore di parametro
atteso dall'interfaccia. I valori possibili sono [2]:
'qmark' |
= con punto interrogativo, p.e. '...WHERE name=?' |
'numeric' |
= in stile numerico, posizionale, p.e. '...WHERE name=:1' |
'named' |
= per nome, p.e. '...WHERE name=:name' |
'format' |
= nel codice di formato della printf ANSI C, p.e. '...WHERE name=%s' |
'pyformat' |
= nei codici di formato estesi Python, p.e. '...WHERE name=%(name)s' |
Il modulo dovrebbe rendere disponibile ogni informazione di errore attraverso le
eccezioni seguenti, o loro sottoclassi:
- Warning
-
Eccezione sollevata per avvisi importanti, quali troncamento di dati nel corso
del loro inserimento, ecc. Dev'essere una sottoclasse dell'eccezione Python
StandardError (definita nel modulo exceptions ).
- Error
-
Eccezione che costituisce la classe base di tutte le altre eccezioni di errore. La
si può utilizzare per catturare tutti gli errori con un singolo
except .
Gli avvisi [`warning'] non sono considerati errori, perciò non dovrebbero
usare questa classe come base. Dev'essere una sottoclasse dell'eccezione Python
StandardError (definita nel modulo exceptions ).
- InterfaceError
-
Eccezione sollevata per errori legati all'interfaccia più che al database
stesso. Dev'essere una sottoclasse di
Error .
- DatabaseError
-
Eccezione sollevata per errori sul database. Dev'essere una sottoclasse di
Error .
- DataError
-
Eccezione sollevata per errori dovuti a problemi con dati calcolati, come divisioni
per zero, valori numerici fuori dall'intervallo consentito, ecc. Dev'essere una
sottoclasse di
DatabaseError .
- OperationalError
-
Eccezione sollevata per errori legati a operazioni del database non necessariamente
sotto il controllo del programmatore, per esempio quando avviene una disconnessione
inaspettata, non viene trovato il nome della sorgente dei dati, non è possibile
effettuare una transazione, si ha un errore di memoria durante l'elaborazione, ecc.
Dev'essere una sottoclasse di
DatabaseError .
- IntegrityError
-
Eccezione sollevata quando è interessata l'integrità relazionale del
database, p.e. fallisce l'ispezione di una chiave esterna. Dev'essere una sottoclasse
di
DatabaseError .
- InternalError
-
Eccezione sollevata per un errore interno del database, p.e. il cursore non è
più valido, la transazione non è sincronizzata ecc. Dev'essere una
sottoclasse di
DatabaseError .
- ProgrammingError
-
Eccezione sollevata in caso si tratti di errori diretti di programmazione, p.e. una
tabella non viene trovata o è già esistente, c'è un errore di
sintassi nel comando SQL, è stato specificato un numero errato di parametri,
ecc. Dev'essere una sottoclasse di
DatabaseError .
- NotSupportedError
-
Eccezione sollevata quando viene usato un metodo o un'API non supportato dal database,
p.e. viene richiesto un
.rollback() su una connessione che non supporta le
transazioni o le ha disabilitate. Dev'essere una sottoclasse di DatabaseError .
Questo è lo schema di ereditarietà delle eccezioni:
StandardError
|__Warning
|__Error
|__InterfaceError
|__DatabaseError
|__DataError
|__OperationalError
|__IntegrityError
|__InternalError
|__ProgrammingError
|__NotSupportedError
Nota: I valori di tali eccezioni non sono definiti. Dovrebbero comunque fornire
all'utente un'idea abbastanza chiara sull'origine del problema.
Oggetti Connessione
Oggetti Cursore
Questi oggetti rappresentano un cursore del database, usato nella gestione del contesto
di un'operazione di fetch [prelievo di dati NdT].
Gli oggetti Cursore dovrebbero rispondere ai seguenti metodi e attributi:
- description
-
È un attributo a sola lettura che consiste di una sequenza di sequenze a
sette elementi. Ognuna di queste sequenze contiene informazioni che descrivono
una colonna di risultati:
(name, type_code, display_size, internal_size,
precision, scale, null_ok) . L'attributo sarà None
per operazioni che non restituiscono righe, o nel caso non sia stata ancora
effettuata un'operazione tramite il metodo executeXXX .
type_code può essere interpretato comparandolo agli
oggetti Tipo specificati più sotto.
- rowcount
-
È un attributo a sola lettura che specifica il numero di righe che l'ultimo
executeXXX() ha prodotto (per comandi DQL come select) o
comunque interessato (per comandi DML come update o insert).
Il valore dell'attributo è pari a -1 nel caso non sia stato eseguito alcun
executeXXX() sul cursore o il conteggio delle righe dell'ultima
operazione non sia calcolabile dall'interfaccia.[7]
- callproc(nomeprocedura[,parametri])
-
Questo metodo è opzionale, dato che non tutti i database supportano le
"stored procedure".[3]
Invoca la stored procedure del database passata con nomeprocedura. La
sequenza dei parametri deve contenere una voce per ogni argomento richiesto dalla
procedura. Il risultato della chiamata è restituito come copia modificata
della sequenza in ingresso. I parametri in ingresso non vengono toccati, i parametri
in uscita e ingresso/uscita vengono eventualmente rimpiazzati con nuovi valori.
La procedura può anche fornire come risultato un insieme di righe, che
dev'essere quindi reso disponibile attraverso i metodi standard fetchXXX() .
- close()
-
Chiude il cursore al momento (piuttosto che alla chiamata di __del__). Il cursore
non sarà più utilizzabile da qui in avanti: in caso venga tentata una
qualsiasi operazione sul cursore verrà sollevata un'eccezione
Error
(o una sua sottoclasse).
- execute(operazione[,parametri])
-
Prepara ed esegue un'operazione sul database (interrogazione o comando). I parametri
possono essere passati come sequenza o dizionario e verranno associati a variabili
nell'operazione. Le variabili sono specificate in una notazione specifica al database
(si veda l'attributo di modulo
paramstyle per i dettagli).
[5]
Il cursore conserverà un riferimento all'operazione. Se lo stesso oggetto
operazione gli verrà passato nuovamente, il cursore saraà così
in grado di ottimizzare il suo comportamento. Questo è molto efficace in caso
di algoritmi in cui la stessa operazione viene svolta molte volte con parametri
diversi.
Per garantire la massima efficienza nel riuso di un'operazione è bene usare il
metodo setinputsizes() per specificare anzitempo i tipi e le dimensioni dei parametri.
Un parametro può anche non corrispondere all'informazione definita in tal modo;
l'implementazione dovrebbe compensare automaticamente, a prezzo di un'eventuale perdita
in efficienza.
I parametri possono anche essere specificati come lista di tuple, p.e. per inserire
più righe in una singola operazione, ma quest'uso è sconsigliato, si
dovrebbe piuttosto usare executemany() .
I valori restituiti non sono definiti.
- executemany(operazione,sequenza_di_parametri)
- Prepara un'operazione sul database (interrogazione o comando), quindi la esegue
usando tutte le sequenze o dizionari di parametri trovati in
sequenza_di_parametri .
I moduli sono liberi di implementare questo metodo usando invocazioni multiple del
metodo execute() o usando operazioni con array per far sì che
il database processi la sequenza come un tutt'uno in una singola chiamata.
A questo metodo si applicano le stesse considerazioni espresse per execute() .
I valori restituiti non sono definiti.
- fetchone()
- Preleva [`fetch'] la riga seguente dal risultato di un'interrogazione, restituendo
una singola sequenza oppure
None quando non ci sono più dati
disponibili. [6]
In caso l'ultima invocazione di executeXXX() non abbia prodotto alcun
risultato o non sia ancora stata inoltrata alcuna invocazione, verrà sollevata
un'eccezione Error (o una sua sottoclasse).
- fetchmany([size=cursor.arraysize])
- Preleva l'insieme successivo di righe facenti parti del risultato di
un'interrogazione, restituendo una sequenza di sequenze (p.e. una lista di tuple).
In caso non siano più disponibili altre righe, viene restituita una sequenza
vuota.
Il numero di righe da prelevare a ogni chiamata è specificato dal parametro.
Se non viene passato esplicitamente, sarà l'attributo arraysize del
cursore a determinare il numero di righe che verranno prelevate. Il metodo dovrebbe
tentare di prelevare tante righe quante indicate dal parametro size . Ove
questo non fosse possibile perché non ci sono abbastanza righe disponibili, ne
verranno restituite in numero minore.
In caso l'ultima invocazione di executeXXX() non abbia prodotto alcun
risultato o non sia ancora stata inoltrata alcuna invocazione, verrà sollevata
un'eccezione Error (o una sua sottoclasse).
Si noti che ci sono alcune considerazioni da fare circa l'influenza del parametro
size sulle prestazioni. Per ottenere prestazioni ottimali di solito
la cosa migliore è usare l'attributo arraysize . Se viene passato
esplicitamente un parametro size , risulta ottimale mantenere lo stesso
valore nelle invocazioni successive di fetchmany() .
- fetchall()
- Preleva tutte le righe (restanti) dal risultato di una interrogazione,
restituendole come sequenza di sequenze (p.e. una lista di tuple). Si noti che
l'attributo
arraysize del cursore può influenzare la velocità
dell'operazione.
In caso l'ultima invocazione di executeXXX() non abbia prodotto alcun
risultato o non sia ancora stata inoltrata alcuna invocazione, verrà sollevata
un'eccezione Error (o una sua sottoclasse).
- nextset()
- Questo metodo è opzionale, dato che non tutti i database supportano
risultati multipli. [3]
Questo metodo farà saltare il cursore al prossimo risultato disponibile,
scartando tutte le righe rimanenti del risultato corrente.
Se non ci sono più altri risultati, il metodo restituisce None .
In caso contrario restituisce un valore vero, e le chiamate ai metodi fetch che
seguono restituiranno righe appartenenti al nuovo risultato.
In caso l'ultima invocazione di executeXXX() non abbia prodotto alcun
risultato o non sia ancora stata inoltrata alcuna invocazione verrà sollevata
un'eccezione Error (o una sua sottoclasse).
- arraysize
- Questo attributo scrivibile specifica il numero di righe da prelevare in una
singola invocazione di
fetchmany() . Il valore predefinito è 1,
il che significa che verrà prelevata una sola riga alla volta.
Le diverse implementazioni devono rispettare questo valore per quanto concerne
il metodo fetchmany() , ma sono libere di interagire con il database
una singola riga alla volta. Tale valore può essere usato anche
nell'implementazione di executemany() .
- setinputsizes(sizes)
- Questo attributo può essere usato prima di un
executeXXX()
per definire in anticipo aree di memoria per i parametri dell'operazione.
sizes è specificato come sequenza, in cui a ogni elemento
corrisponde un parametro in ingresso. L'elemento dovrebbe essere un oggetto Tipo
che corrisponda a quanto verrà usato in ingresso, oppure un intero che
specifichi la lunghezza massima di una stringa parametro.
Se l'elemento è None , allora non verrà riservata un'area
di memoria predefinita per la colonna corrispondente (utile per nel caso di input
molto grossi).
Questo metodo dovrebbe essere usato prima dell'invocazione di executeXXX() .
Le varie implementazioni sono libere di non fargli fare in realtà nulla e
gli utenti possono tranquillamente fare a meno di usarlo.
- setoutputsize(size[,column])
- Imposta la dimensione del buffer di una colonna, utile nel caso di prelievi di
dati voluminosi (p.e. LONG, BLOB, ecc.). La colonna è specificata secondo
l'indice nella sequenza risultato. Se non viene specificata, allora tali dimensioni
di default verranno applicate a tutte le colonne molto ampie del cursore.
Questo metodo dovrebbe essere usato prima dell'invocazione di executeXXX() .
Le varie implementazioni sono libere di non fargli fare in realtà nulla e
gli utenti possono tranquillamente fare a meno di usarlo.
Oggetti Tipo e Costruttori
Molti database hanno bisogno di avere l'input in un particolare formato per
associarlo ai parametri di ingresso di un'operazione. Per esempio, se un input
è destinato a finire in una colonna DATE [data anno-mese-giorno] allora
dev'essere connessi al database come stringa di un formato particolare. Problemi
simili esistono per colonne Identificativo o dati binari molto voluminosi (p.e.
BLOB). Tutto ciò è fonte di potenziali problemi per Python, dato che
i parametri per il metodo executeXXX() non sono tipizzati. Quando il
modulo database vede un oggetto stringa Python, non sa se debba essere connesso
come una semplice colonna CHAR, come un elemento BINARY o come una DATE.
Per risolvere questo problema, un modulo deve fornire i costruttori definiti più
avanti per creare oggetti che possono conservare valori speciali. Quando verranno passati
ai metodi del cursore il modulo potrà discernere il tipo appropriato del parametro
d'ingresso e connetterlo opportunamente.
L'attributo description di un oggetto Cursore restituisce informazioni su
ciascuna delle colonne del risultato di un'interrogazione. type_code dev'essere
uguale a uno degli oggetti Tipo definiti più. Gli oggetti Tipo possono essere
uguali a più di un codice tipo (p.e. DATETIME potrebbe essere uguale ai codici tipo
per le colonne DATE, TIME e TIMESTAMP [informazione oraria pi&ugfrave; completa del tipo
TIME, prevede tutte le informazioni dell'anno a, eventualmente, frazioni di secondo NdT];
si veda Suggerimenti per l'implementazione per i dettagli).
Il modulo esporta i seguenti costruttori e insiemi a singolo elemento [`singleton']:
- Date(anno,mese,giorno)
- Questa funzione crea un oggetto che contiene un valore DATE.
- Time(ore,minuti,secondi)
- Questa funzione crea un oggetto che contiene un valore TIME.
- Timestamp(anno,mese,giorno,ore,minuti,secondi)
- Questa funzione crea un oggetto che contiene un valore TIMESTAMP.
- DateFromTicks(istante)
- Questa funzione crea un oggetto che contiene un valore DATE che parte
da un dato valore di tick (in secondi dall'ora zero del sistema, si veda la
documentazione del modulo Python standard time per dettagli).
- TimeFromTicks(ticks)
- Questa funzione crea un oggetto che contiene un valore TIME che parte
dal numero di tick dato (in secondi dall'ora zero del sistema, si veda la
documentazione del modulo Python standard time per dettagli).
- TimestampFromTicks(ticks)
- Questa funzione crea un oggetto che contiene un valore TIMESTAMP che parte
da un dato valore di tick (in secondi dall'ora zero del sistema, si veda la
documentazione del modulo Python standard time per dettagli).
- Binary(string)
- Questa funzione crea un oggetto capace di contenere un valore stringa
binario (lungo).
- STRING
- Questo oggetto tipo è usato per descrivere colonne basate su stringhe
(p.e. CHAR).
- BINARY
- Questo oggetto tipo è usato per descrivere colonne di dati binari
(lunghi) (p.e. LONG, RAW, BLOB).
- NUMBER
- Questo oggetto tipo è usato per descrivere colonne numeriche.
- DATETIME
- Questo oggetto tipo è usato per descrivere colonne DATE/TIME.
- ROWID
- Questo oggetto tipo è usato per descrivere colonne Identificativo.
I valori NULL di SQL sono rappresentati in Python da None sia in ingresso
che in uscita.
Nota: l'uso dei tick Unix per interfacciarsi a database può causare problemi
a causa dell'intervallo limitato di date che sono in grado di coprire.
Suggerimenti per l'implementazione
- I tipi di oggetto consigliati per date e orari sono quelli definiti nel
package mxDateTime
. Esso fornisce tutti i costruttori e i metodi necessari sia a livello Python
che C.
- Il tipo consigliato per gli oggetti Binary sono i buffer, disponibili come
standard in Python a partire dalla versione 1.5.2. Si faccia riferimento alla
documentazione Python per i dettagli. Per informazioni sull'interfaccia C si
veda Include/bufferobject.h e Objects/bufferobject.c nella
distribuzione dei sorgenti Python.
- Ecco una semplice implementazione dei costruttori per date e orari basati
su tick Unix che delegano il lavoro ai costruttori generici:
import time
def DateFromTicks(ticks):
return apply(Date,time.localtime(ticks)[:3])
def TimeFromTicks(ticks):
return apply(Time,time.localtime(ticks)[3:6])
def TimestampFromTicks(ticks):
return apply(Timestamp,time.localtime(ticks)[:6])
- Questa classe Python permette di implementare i tipi di oggetti summenzionati anche
nel caso in cui il campo
type_code dell'attributo description
fornisce valori multipli per un oggetto Tipo:
class DBAPITypeObject:
def __init__(self,*values):
self.values = values
def __cmp__(self,other):
if other in self.values:
return 0
if other < self.values:
return 1
else:
return -1
L'oggetto Tipo risultante eguaglia tutti i valori passati al costruttore.
- Ecco un pezzetto di codice Python che implementa la gerarchia delle
eccezioni definita in precedenza:
import exceptions
class Error(exceptions.StandardError):
pass
class Warning(exceptions.StandardError):
pass
class InterfaceError(Error):
pass
class DatabaseError(Error):
pass
class InternalError(DatabaseError):
pass
class OperationalError(DatabaseError):
pass
class ProgrammingError(DatabaseError):
pass
class IntegrityError(DatabaseError):
pass
class DataError(DatabaseError):
pass
class NotSupportedError(DatabaseError):
pass
In C si può usare l'API PyErr_NewException(fullname, base, NULL) per
creare gli oggetti Eccezione.
Principali modifiche dall'API v.1.0
L'API Python per i Database v.2.0 introduce un piccolo numero di cambiamenti importanti
rispetto alla versione 1.0. Dato che alcuni di questi saranno causa di errori fatali negli
script basati sulla vecchia DB
API 1.0, si è deciso di variare il numero di versione principale.
Ecco i cambiamenti più significativi dalla versione 1.0:
- Non è più necessario un modulo dbi separato, le sue funzionalità
sono state fuse nell'interfaccia di modulo.
- Sono stati aggiunti nuovi costruttori e oggetti Tipo per i valori di date e orari.
Il tipo di oggetto RAW è stato rinominato come BINARY. L'insieme di tipi
risultante dovrebbe coprire tutti i tipi di dato fondamentali che si trovano
comunemente nei moderni database SQL.
- Sono stati aggiunti nuove costanti (apilevel, threadlevel, paramstyle) e metodi
(executemany, nextset) per garantire connessioni migliori ai database.
- Le semantiche di .callproc() necessarie a chiamare stored procedure sono ora
definite chiaramente.
- La definizione del valore restituito da .execute() è stata cambiata. In
precedenza il valore restituito era basato sul tipo di operazione SQL (difficile
da implementare in modo soddisfacente). Ora il tipo non è definito, si usi
piuttosto il più flessibile attributo .rowcount. I moduli sono liberi di
restituire valori nel vecchio stile, ma le specifiche non se ne occupano più
e dovrebbero essere considerati dipendenti dalle singole interfacce [e da non
utilizzarsi nell'ottica di portabilità del codice NdT].
- È stata incorporata nelle specifiche una gerarchia di eccezioni basata
sulle classi. Gli implementatori del modulo sono liberi di estendere lo schema
presentato in questo documento tramite sottoclassi.
Questioni ancora in sospeso
Sebbene la versione 2.0 delle specifiche chiarisca molti punti ch'erano lasciati in
sospeso nella versione 1.0, rimangono ancora delle questioni da affrontare:
- Definire un valore di ritorno utile per .nextset() nel caso in cui un nuovo
risultato sia effettivamente disponibile.
- Creare un tipo numerico a virgola fissa da usare come formato di scambio senza
sprechi per valori monetari e decimali.
Note a piè di pagina
- Come linea guida, i parametri del costruttore di oggetti
Connessione dovrebbero essere implementati come argomenti a parola chiave per un
uso più intuitivo, e seguire quest'ordine:
dsn |
= Nome della sorgente dei dati come stringa |
|
user |
= Nome dell'utente come stringa |
(opzionale) |
password |
= Password come stringa |
(opzionale |
host |
= Nome dell'host |
(opzionale) |
database |
= Nome del database |
(opzionale) |
ecco un esempio di un tale uso di connect :
connect(dsn='myhost:MYDB',user='guido',password='234$¶')
- Gli autori del modulo dovrebbero preferire i formati
'numeric', 'named' o 'pyformat' sugli altri, dato che offrono maggior chiarezza e
flessibilità.
- Se il database non supporta la funzionalità
richiesta dal metodo, l'interfaccia dovrebbe sollevare un'eccezione in caso venga
invocato.
L'approccio preferito è non implementare il metodo, in modo che Python generi
un'eccezione AttributeError in caso di richiesta del metodo. Ciò
permette al programmatore di verificare quanto può offrire il database usando
la funzione standard hasattr() .
Per alcune interfacce configurate dinamicamente potrebbe non essere appropriato
richiedere di rendere disponibile il metodo dinamicamente.
Queste interfacce dovrebbero quindi sollevare un'eccezione NotSupportedError
per indicare l'incapacità di eseguire il roll-back allorché il metodo
venisse invocato.
- Un'interfaccia può scegliere di supportare i
cursori con nome permettendo l'aggiunta di un argomento stringa al metodo. Questa
caratteristica non viene contemplata nelle specifiche, dato che complicherebbe le
semantiche dei metodi
.fetchXXX() .
- Il modulo userà il metodo __getitem__ degli oggetti
dei parametri per mappare o le posizioni (interi) o i nomi (stringhe) dei valori dei
parametri. Questo permette di accettare in ingresso sia sequenze che dizionari.
Il termine "connesso" si riferisce al processo di connettere un valore in ingresso a
un buffer di esecuzione del database. In termini pratici questo significa che il valore
in ingresso è usato direttamente come valore in un'operazione. Al client non
dovrebbe essere richiesto di dover proteggere il valore in modo che possa essere usato,
esso dovrebbe infatti essere uguale al valore "reale" del database.
- Si noti che l'interfaccia potrebbe implementare il prelievo
delle righe usando array e altre ottimizzazioni. Nulla garantisce che una chiamata a tale
metodo muova il cursore associato in avanti di una sola riga.
- L'attributo
rowcount può essere codificato
in modo che aggiorni dinamicamente il proprio valore. Ciò può essere utile
per database che restituiscono valori di rowcount utilizzabili solo dopo una prima chiamata
al metodo .fetchXXX() .
| |