SQLite è un popolare database relazionale che puoi incorporare nella tua applicazione. Python viene fornito con collegamenti ufficiali a SQLite. Questo articolo esamina le avvertenze sull'utilizzo di SQLite in Python. Dimostra i problemi che possono causare diverse versioni di librerie SQLite collegate, come datetime
gli oggetti non sono archiviati correttamente e come è necessario prestare la massima attenzione quando si fa affidamento su with connection
di Python gestore del contesto per salvare i tuoi dati.
Introduzione
SQLite è un popolare sistema di database relazionali (DB) . A differenza dei suoi fratelli maggiori basati su client-server, come MySQL, SQLite può essere incorporato nella tua applicazione come libreria . Python supporta ufficialmente SQLite tramite collegamenti (documenti ufficiali). Tuttavia, lavorare con queste associazioni non è sempre semplice. A parte gli avvertimenti generici su SQLite di cui ho discusso in precedenza, ci sono diversi problemi specifici di Python che esamineremo in questo articolo .
Incompatibilità della versione con la destinazione di distribuzione
È abbastanza comune che gli sviluppatori creino e testino codice su una macchina (molto) diversa da quella su cui è distribuito il codice, in termini di sistema operativo (SO) e hardware. Ciò causa tre tipi di problemi:
- L'applicazione si comporta in modo diverso a causa delle differenze del sistema operativo o dell'hardware . Ad esempio, potresti riscontrare problemi di prestazioni quando la macchina di destinazione ha meno memoria della tua macchina. Oppure SQLite potrebbe eseguire alcune operazioni più lentamente su un sistema operativo rispetto ad altri, perché le API del sistema operativo di basso livello sottostanti che utilizza sono diverse.
- La versione di SQLite sulla destinazione di distribuzione è diverso dalla versione della macchina dello sviluppatore . Ciò può causare problemi in entrambe le direzioni, perché nel tempo vengono aggiunte nuove funzionalità (e cambia il comportamento), vedere il log delle modifiche ufficiale. Ad esempio, una versione SQLite distribuita obsoleta potrebbe non avere funzionalità che hanno funzionato bene durante lo sviluppo. Inoltre, una versione SQLite più recente in distribuzione potrebbe comportarsi in modo diverso rispetto a una versione precedente utilizzata sulla macchina di sviluppo, ad es. quando il team SQLite cambia alcuni valori predefiniti.
- O i collegamenti Python di SQLite o la libreria C potrebbero mancare del tutto nella destinazione di distribuzione . Questo è un Linux -problema specifico della distribuzione . Le distribuzioni ufficiali di Windows e macOS conterranno un raggruppato versione della libreria SQLite C. Su Linux, la libreria SQLite è un pacchetto separato. Se compili tu stesso Python, ad es. perché usi un Debian/Raspbian/etc. distribuzione fornita con versioni di funzionalità antiche, il Python
make
build script creerà solo i collegamenti SQLite di Python se una libreria SQLite C installata è stata rilevata durante il processo di compilazione di Python . Se esegui tu stesso una tale ricompilazione di Python, dovresti assicurarti che la libreria SQLite C installata sia recente . Questo, ancora una volta, non è il caso di Debian ecc. quando si installa SQLite tramiteapt
, quindi potresti anche dover compilare e installare SQLite da solo, prima alla creazione di Python.
Per scoprire quale versione della libreria SQLite C è utilizzata dal tuo interprete Python, esegui questo comando:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Sostituzione di sqlite3.sqlite_version
con sqlite3.version
ti fornirà la versione di SQLite binding di Python .
Aggiornamento della libreria C SQLite sottostante
Se vuoi trarre profitto dalle funzionalità o dalle correzioni di bug della versione SQLite più recente, sei fortunato. La libreria SQLite C è in genere collegata in fase di esecuzione e quindi può essere sostituita senza alcuna modifica all'interprete Python installato. I passaggi concreti dipendono dal tuo sistema operativo (testato per Python 3.6+):
1) Finestre: Scarica i binari precompilati x86 o x64 dalla pagina di download di SQLite e sostituisci sqlite3.dll
file trovato nelle DLLs
cartella della tua installazione di Python con quella appena scaricata.
2) Linux: dalla pagina di download di SQLite ottieni autoconf sorgenti, estrai l'archivio ed esegui ./configure && make && make install
che installerà la libreria in /usr/local/lib
per impostazione predefinita.
Quindi aggiungi la riga export LD_LIBRARY_PATH=/usr/local/lib
all'inizio dello script di shell che avvia il tuo script Python, che costringe il tuo interprete Python a utilizzare la libreria autocostruita.
3) macOS: dalla mia analisi, sembra che la libreria SQLite C sia compilata nei bindings di Python binario (_sqlite3.cpython-36m-darwin.so
). Se vuoi sostituirlo, probabilmente dovrai ottenere il codice sorgente Python che corrisponda all'installazione Python installata (ad es. 3.7.6
o qualunque versione tu usi). Compila Python dal sorgente, usando lo script di build di macOS. Questo script include il download e la creazione della libreria C di SQLite, quindi assicurati di modificare lo script per fare riferimento alla versione SQLite più recente. Infine, usa il file di binding compilato (ad es. _sqlite3.cpython-37m-darwin.so
), per sostituire quello obsoleto.
Utilizzo di datetime
in grado di riconoscere il fuso orario oggetti
La maggior parte degli sviluppatori Python in genere utilizza datetime
oggetti quando si lavora con timestamp. Ci sono ingenui datetime
oggetti che non conoscono il loro fuso orario e non ingenui quelli, che sono a conoscenza del fuso orario . È noto che datetime
di Python modulo è bizzarro, rendendo difficile persino creare datetime.datetime
in grado di riconoscere il fuso orario oggetti. Ad esempio, la chiamata datetime.datetime.utcnow()
crea un ingenuo oggetto, che è controintuitivo per gli sviluppatori che non conoscono datetime
API, in attesa che Python utilizzi il fuso orario UTC! Le librerie di terze parti, come python-dateutil, facilitano questa attività. Per creare un oggetto sensibile al fuso orario, puoi utilizzare un codice come questo:
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Sfortunatamente, la documentazione Python ufficiale di sqlite3
modulo è fuorviante quando si tratta di gestire i timestamp. Come descritto qui, datetime
gli oggetti vengono convertiti automaticamente quando si utilizza PARSE_DECLTYPES
(e dichiarando un TIMESTAMP
colonna). Sebbene ciò sia tecnicamente corretto, la conversione perderà il fuso orario informazioni ! Di conseguenza, se stai effettivamente utilizzando il fuso orario, consapevole datetime.datetime
oggetti, devi registrare i tuoi convertitori , che conservano le informazioni sul fuso orario, come segue:
def convert_timestamp_to_tzaware(timestamp: bytes) -> datetime.datetime:
# sqlite3 provides the timestamp as byte-string
return dateutil.parser.parse(timestamp.decode("utf-8"))
def convert_timestamp_to_sqlite(dt: datetime.datetime) -> str:
return dt.isoformat() # includes the timezone information at the end of the string
sqlite3.register_converter("timestamp", convert_timestamp_to_tzaware)
sqlite3.register_adapter(datetime.datetime, convert_timestamp_to_sqlite)
Code language: Python (python)
Come puoi vedere, il timestamp è semplicemente memorizzato come TEXT
alla fine. Non esiste un vero tipo di dati "data" o "data ora" in SQLite.
Transazioni e commit automatico
sqlite3
di Python module non esegue automaticamente il commit dei dati che vengono modificati dalle tue query . Quando esegui query che in qualche modo modificano il database, devi emettere un esplicito COMMIT
istruzione oppure utilizzi la connessione come gestore di contesto oggetto, come mostrato nell'esempio seguente:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Una volta terminato il blocco precedente, sqlite3
chiama implicitamente connection.commit()
, ma lo fa solo se è in corso una transazione . Le istruzioni DML (Data Modification Language) avviano automaticamente una transazione, ma le query che coinvolgono DROP
o CREATE
TABLE
/ INDEX
le istruzioni non lo fanno, perché non contano come DML secondo la documentazione. Questo è contro-intuitivo, perché queste affermazioni modificano chiaramente i dati.
Pertanto, se esegui qualsiasi DROP
o CREATE
TABLE
/ INDEX
istruzioni all'interno del gestore del contesto, è buona norma eseguire esplicitamente una BEGIN TRANSACTION
prima l'affermazione , in modo che il gestore del contesto chiami effettivamente connection.commit()
per te.
Gestione di interi a 64 bit
In un articolo precedente ho già discusso del fatto che SQLite ha problemi con numeri interi grandi inferiori a -2^63
, o maggiore o uguale a 2^63
. Se provi a usarli nei parametri di query (con il ?
simbolo), sqlite3
di Python il modulo solleverà un OverflowError: Python int too large to convert to SQLite INTEGER
, proteggendoti dalla perdita accidentale dei dati.
Per gestire correttamente numeri interi molto grandi, devi:
- Usa il
TEXT
digitare per la colonna della tabella corrispondente, e - Converti il numero in
str
già in Python , prima di utilizzarlo come parametro. - Riconvertire le stringhe in
int
in Python, quandoSELECT
zione dei dati
Conclusione
sqlite3
ufficiale di Python module è un eccellente legame con SQLite. Tuttavia, gli sviluppatori che non conoscono SQLite devono comprendere che esiste una differenza tra i collegamenti Python e la libreria C SQLite sottostante. C'è un pericolo in agguato nell'ombra, a causa delle differenze di versione di SQLite. Questi possono accadere anche se esegui lo uguale Versione Python su due macchine diverse, perché la libreria SQLite C potrebbe ancora utilizzare una versione diversa. Ho anche discusso di altri problemi come la gestione di oggetti datetime e la modifica persistente dei dati utilizzando le transazioni. Non ne ero a conoscenza, il che ha causato la perdita di dati agli utenti delle mie applicazioni, quindi spero che tu possa evitare gli stessi errori che ho commesso.