SQLite est une base de données relationnelle populaire que vous intégrez dans votre application. Python est livré avec des liaisons officielles à SQLite. Cet article examine les mises en garde concernant l'utilisation de SQLite dans Python. Il démontre les problèmes que différentes versions de bibliothèques SQLite liées peuvent causer, comment datetime
les objets ne sont pas correctement stockés et comment vous devez être très prudent lorsque vous vous fiez au with connection
de Python gestionnaire de contexte pour valider vos données.
Présentation
SQLite est un système de base de données relationnelle (DB) populaire . Contrairement à ses grands frères basés sur client-serveur, tels que MySQL, SQLite peut être intégré dans votre application en tant que bibliothèque . Python prend officiellement en charge SQLite via des liaisons (documents officiels). Cependant, travailler avec ces liaisons n'est pas toujours simple. Outre les mises en garde génériques de SQLite dont j'ai parlé précédemment, il existe plusieurs problèmes spécifiques à Python que nous examinerons dans cet article .
Incompatibilités de version avec la cible de déploiement
Il est assez courant que les développeurs créent et testent du code sur une machine (très) différente de celle où le code est déployé, en termes de système d'exploitation (OS) et de matériel. Cela entraîne trois types de problèmes :
- L'application se comporte différemment en raison de différences de système d'exploitation ou de matériel . Par exemple, vous pouvez rencontrer des problèmes de performances lorsque la machine cible dispose de moins de mémoire que votre machine. Ou SQLite peut exécuter certaines opérations plus lentement sur un système d'exploitation que sur d'autres, car les API de système d'exploitation de bas niveau sous-jacentes qu'il utilise sont différentes.
- La version SQLite sur la cible de déploiement diffère de la version de la machine du développeur . Cela peut causer des problèmes dans les deux sens, car de nouvelles fonctionnalités sont ajoutées (et des changements de comportement) au fil du temps, voir le changelog officiel. Par exemple, une version obsolète de SQLite déployée peut manquer de fonctionnalités qui ont bien fonctionné lors du développement. De plus, une version plus récente de SQLite en déploiement peut se comporter différemment d'une version plus ancienne que vous utilisez sur votre machine de développement, par ex. lorsque l'équipe SQLite modifie certaines valeurs par défaut.
- Soit les liaisons Python de SQLite, soit la bibliothèque C, peuvent être totalement absentes sur la cible de déploiement . Ceci est un Linux -problème spécifique à la distribution . Les distributions officielles Windows et macOS contiendront un groupé version de la bibliothèque SQLite C. Sous Linux, la bibliothèque SQLite est un package séparé. Si vous compilez Python vous-même, par ex. parce que vous utilisez un Debian/Raspbian/etc. distribution livrée avec d'anciennes versions de fonctionnalités, le Python
make
le script de construction ne construira que les liaisons SQLite de Python si une bibliothèque SQLite C installée a été détectée lors du processus de compilation de Python . Si vous effectuez vous-même une telle recompilation de Python, vous devez vous assurer que la bibliothèque SQLite C installée est récente . Ceci, encore une fois, n'est pas le cas pour Debian etc. lors de l'installation de SQLite viaapt
, vous devrez donc peut-être également créer et installer SQLite vous-même, avant pour construire Python.
Pour savoir quelle version de la bibliothèque SQLite C est utilisée par votre interpréteur Python, exécutez cette commande :
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Remplacement de sqlite3.sqlite_version
avec sqlite3.version
vous donnera la version des liaisons SQLite de Python .
Mise à jour de la bibliothèque SQLite C sous-jacente
Si vous souhaitez profiter des fonctionnalités ou des corrections de bogues de la version la plus récente de SQLite, vous avez de la chance. La bibliothèque SQLite C est généralement liée au moment de l'exécution et peut donc être remplacée sans aucune modification de votre interpréteur Python installé. Les étapes concrètes dépendent de votre système d'exploitation (testé pour Python 3.6+) :
1) Windows : Téléchargez les binaires précompilés x86 ou x64 depuis la page de téléchargement de SQLite et remplacez le sqlite3.dll
fichier présent dans les DLLs
dossier de votre installation Python avec celui que vous venez de télécharger.
2) Linux : depuis la page de téléchargement de SQLite, obtenez l'autoconf sources, extrayez l'archive et exécutez ./configure && make && make install
qui installera la bibliothèque dans /usr/local/lib
par défaut.
Ajoutez ensuite la ligne export LD_LIBRARY_PATH=/usr/local/lib
au début du script shell qui démarre votre script Python, ce qui oblige votre interpréteur Python à utiliser la bibliothèque auto-construite.
3) macOS : d'après mon analyse, il semble que la bibliothèque SQLite C soit compilée dans les liaisons Python binaire (_sqlite3.cpython-36m-darwin.so
). Si vous souhaitez le remplacer, vous devrez probablement obtenir le code source Python correspondant à votre installation Python installée (par exemple, 3.7.6
ou quelle que soit la version que vous utilisez). Compilez Python à partir de la source, à l'aide du script de construction macOS. Ce script inclut le téléchargement et la construction de la bibliothèque C de SQLite, alors assurez-vous de modifier le script pour référencer la version la plus récente de SQLite. Enfin, utilisez le fichier de liaisons compilé (par exemple _sqlite3.cpython-37m-darwin.so
), pour remplacer l'ancien.
Travailler avec les datetime
sensibles au fuseau horaire objets
La plupart des développeurs Python utilisent généralement datetime
objets lorsque vous travaillez avec des horodatages. Il y a des naïfs datetime
objets qui ne connaissent pas leur fuseau horaire, et non-naïfs ceux qui sont conscients du fuseau horaire . Il est bien connu que le datetime
de Python le module est excentrique, ce qui rend même difficile la création de datetime.datetime
prenant en compte le fuseau horaire objets. Par exemple, l'appel datetime.datetime.utcnow()
crée un naïf objet, ce qui est contre-intuitif pour les développeurs qui découvrent le datetime
API, s'attendant à ce que Python utilise le fuseau horaire UTC ! Les bibliothèques tierces, telles que python-dateutil, facilitent cette tâche. Pour créer un objet sensible au fuseau horaire, vous pouvez utiliser un code tel que :
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Malheureusement, la documentation Python officielle de sqlite3
module est trompeur en ce qui concerne la gestion des horodatages. Comme décrit ici, datetime
les objets sont automatiquement convertis lors de l'utilisation de PARSE_DECLTYPES
(et en déclarant un TIMESTAMP
colonne). Bien que cela soit techniquement correct, la conversion sera perdue le fuseau horaire informations ! Par conséquent, si vous utilisez réellement le fuseau horaire conscient datetime.datetime
objets, vous devez enregistrer vos propres convertisseurs , qui conservent les informations de fuseau horaire, comme suit :
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)
Comme vous pouvez le voir, l'horodatage est simplement stocké en tant que TEXT
à la fin. Il n'y a pas de vrai type de données "date" ou "datetime" dans SQLite.
Transactions et auto-commit
Python sqlite3
le module ne valide pas automatiquement les données modifiées par vos requêtes . Lorsque vous effectuez des requêtes qui modifient d'une manière ou d'une autre la base de données, vous devez soit émettre un COMMIT
explicite déclaration, ou vous utilisez la connexion en tant que gestionnaire de contexte objet, comme illustré dans l'exemple suivant :
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Une fois le bloc ci-dessus quitté, sqlite3
appelle implicitement connection.commit()
, mais ne le fait que si une transaction est en cours . Les instructions DML (Data Modification Language) démarrent automatiquement une transaction, mais les requêtes impliquant DROP
ou CREATE
TABLE
/ INDEX
les instructions ne le font pas, car elles ne comptent pas comme DML selon la documentation. C'est contre-intuitif, car ces déclarations modifient clairement les données.
Ainsi, si vous exécutez un DROP
ou CREATE
TABLE
/ INDEX
instructions à l'intérieur du gestionnaire de contexte, il est recommandé d'exécuter explicitement un BEGIN TRANSACTION
déclaration d'abord , de sorte que le gestionnaire de contexte appellera réellement connection.commit()
pour vous.
Gestion des entiers 64 bits
Dans un article précédent, j'ai déjà expliqué que SQLite avait des problèmes avec les grands entiers inférieurs à -2^63
, ou supérieur ou égal à 2^63
. Si vous essayez de les utiliser dans les paramètres de requête (avec le ?
symbole), sqlite3
de Python module lèvera une OverflowError: Python int too large to convert to SQLite INTEGER
, vous protégeant contre la perte accidentelle de données.
Pour gérer correctement les très grands nombres entiers, vous devez :
- Utilisez le
TEXT
type pour la colonne de tableau correspondante, et - Convertir le nombre en
str
déjà en Python , avant de l'utiliser comme paramètre. - Reconvertir les chaînes en
int
en Python, lorsqueSELECT
données
Conclusion
sqlite3
officiel de Python module est une excellente liaison à SQLite. Cependant, les développeurs qui découvrent SQLite doivent comprendre qu'il existe une différence entre les liaisons Python et la bibliothèque SQLite C sous-jacente. Il y a un danger caché dans l'ombre, à cause des différences de version de SQLite. Cela peut se produire même si vous exécutez le même Version Python sur deux machines différentes, car la bibliothèque SQLite C utilise peut-être encore une version différente. J'ai également discuté d'autres problèmes tels que la gestion des objets datetime et la modification persistante des données à l'aide de transactions. Je n'en avais pas conscience moi-même, ce qui provoquait une perte de données pour les utilisateurs de mes applications, j'espère donc que vous pourrez éviter les mêmes erreurs que j'ai commises.