SQLite es una base de datos relacional popular que usted integra en su aplicación. Python viene con enlaces oficiales a SQLite. Este artículo examina las advertencias sobre el uso de SQLite en Python. Demuestra los problemas que pueden causar las diferentes versiones de las bibliotecas SQLite vinculadas, cómo datetime
los objetos no se almacenan correctamente, y cómo debe tener mucho cuidado al confiar en with connection
de Python administrador de contexto para confirmar sus datos.
Introducción
SQLite es un popular sistema de base de datos relacional (DB) . A diferencia de sus hermanos mayores basados en cliente-servidor, como MySQL, SQLite se puede integrar en su aplicación como una biblioteca . Python admite oficialmente SQLite a través de enlaces (documentos oficiales). Sin embargo, trabajar con esos enlaces no siempre es sencillo. Además de las advertencias genéricas de SQLite que mencioné anteriormente, existen varios problemas específicos de Python que examinaremos en este artículo .
Incompatibilidades de versión con el destino de implementación
Es bastante común que los desarrolladores creen y prueben el código en una máquina que es (muy) diferente a aquella en la que se implementa el código, en términos de sistema operativo (SO) y hardware. Esto causa tres tipos de problemas:
- La aplicación se comporta de manera diferente debido a diferencias de SO o hardware . Por ejemplo, puede tener problemas de rendimiento cuando la máquina de destino tiene menos memoria que su máquina. O SQLite podría ejecutar algunas operaciones más lentamente en un sistema operativo que en otros, porque las API subyacentes del sistema operativo de bajo nivel que utiliza son diferentes.
- La versión de SQLite en el destino de implementación difiere de la versión de la máquina del desarrollador . Esto puede causar problemas en ambas direcciones, porque se agregan nuevas características (y cambios de comportamiento) con el tiempo, consulte el registro de cambios oficial. Por ejemplo, una versión implementada de SQLite desactualizada puede carecer de funciones que funcionaron bien en el desarrollo. Además, una versión más nueva de SQLite en implementación puede comportarse de manera diferente a una versión anterior que usa en su máquina de desarrollo, p. cuando el equipo de SQLite cambia algunos valores predeterminados.
- Es posible que los enlaces Python de SQLite o la biblioteca C falten por completo en el destino de implementación . Este es un Linux -problema específico de distribución . Las distribuciones oficiales de Windows y macOS contendrán un empaquetado versión de la biblioteca SQLite C. En Linux, la biblioteca SQLite es un paquete separado. Si compila Python usted mismo, p. porque usas un Debian/Raspbian/etc. distribución que se envía con versiones de características antiguas, la
make
de Python el script de compilación solo compilará los enlaces SQLite de Python if se detectó una biblioteca SQLite C instalada durante el proceso de compilación de Python . Si realiza una recompilación de Python usted mismo, debe asegurarse de que la biblioteca SQLite C instalada sea reciente . Este, nuevamente, no es el caso para Debian, etc. al instalar SQLite a través deapt
, por lo que es posible que también deba compilar e instalar SQLite usted mismo, previamente para construir Python.
Para averiguar qué versión de la biblioteca SQLite C utiliza su intérprete de Python, ejecute este comando:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Reemplazando sqlite3.sqlite_version
con sqlite3.version
le dará la versión de los enlaces de SQLite de Python .
Actualización de la biblioteca SQLite C subyacente
Si desea beneficiarse de las funciones o las correcciones de errores de la versión más reciente de SQLite, tiene suerte. La biblioteca SQLite C generalmente está vinculada en tiempo de ejecución y, por lo tanto, se puede reemplazar sin ningún cambio en su intérprete de Python instalado. Los pasos concretos dependen de su sistema operativo (probado para Python 3.6+):
1) Ventanas: Descargue los binarios precompilados x86 o x64 desde la página de descarga de SQLite y reemplace el sqlite3.dll
archivo encontrado en las DLLs
carpeta de su instalación de Python con la que acaba de descargar.
./configure && make && make install
que instalará la biblioteca en /usr/local/lib
por defecto.
Luego agregue la línea export LD_LIBRARY_PATH=/usr/local/lib
al comienzo de la secuencia de comandos de shell que inicia su secuencia de comandos de Python, lo que obliga a su intérprete de Python a usar la biblioteca autoconstruida.
3) mac OS: de mi análisis, parece que la biblioteca SQLite C está compilada en los enlaces de Python binario (_sqlite3.cpython-36m-darwin.so
). Si desea reemplazarlo, es probable que necesite obtener el código fuente de Python que coincida con su instalación de Python instalada (por ejemplo, 3.7.6
o la versión que uses). Compile Python desde la fuente, utilizando el script de compilación de macOS. Este script incluye la descarga y la creación de la biblioteca C de SQLite, así que asegúrese de editar el script para hacer referencia a la versión más reciente de SQLite. Finalmente, use el archivo de enlaces compilado (por ejemplo, _sqlite3.cpython-37m-darwin.so
), para reemplazar el obsoleto.
Trabajar con timezoneaware datetime
objetos
La mayoría de los desarrolladores de Python suelen utilizar datetime
objetos cuando se trabaja con marcas de tiempo. Hay ingenuos datetime
objetos que no conocen su zona horaria y no ingenuos que tienen en cuenta la zona horaria consciente . Es bien sabido que el datetime
de Python El módulo es peculiar, lo que dificulta incluso la creación de datetime.datetime
consciente de la zona horaria objetos. Por ejemplo, la llamada datetime.datetime.utcnow()
crea un ingenuo objeto, que es contrario a la intuición para los desarrolladores que son nuevos en datetime
API, ¡esperando que Python use la zona horaria UTC! Las bibliotecas de terceros, como python-dateutil, facilitan esta tarea. Para crear un objeto que reconozca la zona horaria, puede usar un código como este:
from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Desafortunadamente, la documentación oficial de Python del sqlite3
El módulo es engañoso cuando se trata de manejar las marcas de tiempo. Como se describe aquí, datetime
los objetos se convierten automáticamente cuando se usa PARSE_DECLTYPES
(y declarando un TIMESTAMP
columna). Si bien esto es técnicamente correcto, la conversión perderá la zona horaria información ! En consecuencia, si en realidad está utilizando timezone-aware datetime.datetime
objetos, debe registrar sus propios convertidores , que conservan la información de la zona horaria, de la siguiente manera:
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)
Como puede ver, la marca de tiempo se almacena como TEXT
en el final. No existe un tipo de datos real de "fecha" o "fecha y hora" en SQLite.
Transacciones y compromiso automático
sqlite3
de Python el módulo no confirma automáticamente los datos modificados por sus consultas . Cuando realiza consultas que de alguna manera cambian la base de datos, debe emitir un COMMIT
explícito declaración, o usa la conexión como administrador de contexto objeto, como se muestra en el siguiente ejemplo:
with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Una vez que se salió del bloque anterior, sqlite3
implícitamente llama a connection.commit()
, pero solo lo hace si hay una transacción en curso . Las declaraciones DML (lenguaje de modificación de datos) inician automáticamente una transacción, pero las consultas que involucran DROP
o CREATE
TABLE
/ INDEX
las declaraciones no lo hacen, porque no cuentan como DML según la documentación. Esto es contrario a la intuición, porque estas declaraciones claramente modifican los datos.
Por lo tanto, si ejecuta cualquier DROP
o CREATE
TABLE
/ INDEX
declaraciones dentro del administrador de contexto, es una buena práctica ejecutar explícitamente un BEGIN TRANSACTION
declaración primero , para que el administrador de contexto llame a connection.commit()
para ti.
Manejo de enteros de 64 bits
En un artículo anterior, ya discutí que SQLite tiene problemas con números enteros grandes que son más pequeños que -2^63
, o mayor o igual que 2^63
. Si intenta usarlos en los parámetros de consulta (con el ?
símbolo), sqlite3
de Python generará un OverflowError: Python int too large to convert to SQLite INTEGER
, protegiéndolo de la pérdida accidental de datos.
Para manejar correctamente números enteros muy grandes, debe:
- Utilice el
TEXT
escriba para la columna de la tabla correspondiente, y - Convertir el número a
str
ya en Python , antes de usarlo como parámetro. - Convertir las cadenas de nuevo a
int
en Python, cuandoSELECT
ing datos
Conclusión
sqlite3
oficial de Python El módulo es un enlace excelente para SQLite. Sin embargo, los desarrolladores nuevos en SQLite deben comprender que existe una diferencia entre los enlaces de Python y la biblioteca SQLite C subyacente. Existe un peligro al acecho en las sombras, debido a las diferencias de versión de SQLite. Esto puede suceder incluso si ejecuta el mismo Versión de Python en dos máquinas diferentes, porque la biblioteca SQLite C aún podría estar usando una versión diferente. También discutí otros problemas, como el manejo de objetos de fecha y hora y el cambio persistente de datos mediante transacciones. Yo mismo no estaba al tanto de ellos, lo que causó la pérdida de datos para los usuarios de mis aplicaciones, así que espero que pueda evitar los mismos errores que cometí.