SQLite é um banco de dados relacional popular que você incorpora em seu aplicativo. Python vem com ligações oficiais para SQLite. Este artigo examina as advertências do uso do SQLite em Python. Ele demonstra problemas que diferentes versões de bibliotecas SQLite vinculadas podem causar, como
datetime
os objetos não são armazenados corretamente e como você precisa ter cuidado extra ao confiar no with connection
do Python gerenciador de contexto para confirmar seus dados. Introdução
SQLite é um sistema de banco de dados relacional (DB) popular . Ao contrário de seus irmãos maiores, baseados em cliente-servidor, como o MySQL, o SQLite pode ser incorporado ao seu aplicativo como uma biblioteca . Python oficialmente suporta SQLite por meio de ligações (documentos oficiais). No entanto, trabalhar com essas ligações nem sempre é simples. Além das advertências genéricas do SQLite que discuti anteriormente, há vários problemas específicos do Python que examinaremos neste artigo .
Incompatibilidades de versão com destino de implantação
É bastante comum que os desenvolvedores criem e testem código em uma máquina (muito) diferente daquela onde o código está implantado, em termos de sistema operacional (SO) e hardware. Isso causa três tipos de problemas:
- O aplicativo se comporta de maneira diferente devido a diferenças de SO ou hardware . Por exemplo, você pode ter problemas de desempenho quando a máquina de destino tiver menos memória do que sua máquina. Ou o SQLite pode executar algumas operações mais lentamente em um SO do que em outros, porque as APIs de SO de baixo nível subjacentes que ele usa são diferentes.
- A versão SQLite no destino de implantação difere da versão da máquina do desenvolvedor . Isso pode causar problemas em ambas as direções, porque novos recursos são adicionados (e mudanças de comportamento) ao longo do tempo, veja o changelog oficial. Por exemplo, uma versão desatualizada do SQLite pode não ter recursos que funcionaram bem no desenvolvimento. Além disso, uma versão mais recente do SQLite na implantação pode se comportar de maneira diferente de uma versão mais antiga que você usa em sua máquina de desenvolvimento, por exemplo. quando a equipe SQLite altera alguns valores padrão.
- As ligações Python do SQLite ou a biblioteca C podem estar totalmente ausentes no destino de implantação . Este é um Linux -problema específico de distribuição . As distribuições oficiais do Windows e do macOS conterão um pacote versão da biblioteca SQLite C. No Linux, a biblioteca SQLite é um pacote separado. Se você mesmo compilar o Python, por exemplo, porque você usa um Debian/Raspbian/etc. distribuição que vem com versões de recursos antigos, o Python
make
O script de construção só construirá as ligações SQLite do Python se uma biblioteca SQLite C instalada foi detectada durante o processo de compilação do Python . Se você mesmo fizer essa recompilação do Python, verifique se a biblioteca SQLite C instalada é recente . Este, novamente, não é o caso do Debian etc. ao instalar o SQLite viaapt
, então você também pode ter que construir e instalar o SQLite, antes para construir Python.
Para descobrir qual versão da biblioteca SQLite C é usada pelo seu interpretador Python, execute este comando:
python3 -c "import sqlite3; print(sqlite3.sqlite_version)"
Code language: Bash (bash)
Substituindo
sqlite3.sqlite_version
com sqlite3.version
fornecerá a versão do SQLite bindings do Python . Atualizando a biblioteca SQLite C subjacente
Se você deseja lucrar com recursos ou correções de bugs da versão mais recente do SQLite, está com sorte. A biblioteca SQLite C normalmente é vinculada em tempo de execução e, portanto, pode ser substituída sem nenhuma alteração no seu interpretador Python instalado. As etapas concretas dependem do seu sistema operacional (testado para Python 3.6+):
1) Windows: Baixe os binários pré-compilados x86 ou x64 da página de download do SQLite e substitua o
sqlite3.dll
arquivo encontrado em DLLs
pasta da sua instalação do Python com a que você acabou de baixar. 2) Linux: na página de download do SQLite, obtenha o autoconf fontes, extraia o arquivo e execute
./configure && make && make install
que instalará a biblioteca em /usr/local/lib
por padrão.Em seguida, adicione a linha
export LD_LIBRARY_PATH=/usr/local/lib
no início do shell script que inicia seu script Python, o que força seu interpretador Python a usar a biblioteca autoconstruída. 3) macOS: da minha análise, parece que a biblioteca SQLite C é compilada nas ligações do Python binário (
_sqlite3.cpython-36m-darwin.so
). Se você quiser substituí-lo, provavelmente precisará obter o código-fonte do Python correspondente à sua instalação do Python instalada (por exemplo, 3.7.6
ou qualquer versão que você usa). Compile o Python a partir da fonte, usando o script de compilação do macOS. Este script inclui baixar e construir a biblioteca C do SQLite, portanto, certifique-se de editar o script para referenciar a versão mais recente do SQLite. Por fim, use o arquivo de vinculações compilado (por exemplo, _sqlite3.cpython-37m-darwin.so
), para substituir o desatualizado. Trabalhando com datetime
com reconhecimento de fuso horário objetos
A maioria dos desenvolvedores Python normalmente usa
datetime
objetos ao trabalhar com carimbos de data/hora. Existem ingênuos datetime
objetos que não conhecem seu fuso horário e não ingênuos aqueles, que estão cientes de fuso horário . É bem conhecido que o datetime
do Python módulo é peculiar, tornando difícil até mesmo criar datetime.datetime
com reconhecimento de fuso horário objetos. Por exemplo, a chamada datetime.datetime.utcnow()
cria um ingênuo objeto, que é contra-intuitivo para desenvolvedores que são novos no datetime
APIs, esperando que o Python use o fuso horário UTC! Bibliotecas de terceiros, como python-dateutil, facilitam essa tarefa. Para criar um objeto com reconhecimento de fuso horário, você pode usar um código como este:from dateutil.tz import tzutc
import datetime
timezone_aware_dt = datetime.datetime.now(tzutc())
Code language: Python (python)
Infelizmente, a documentação oficial do Python do
sqlite3
módulo é enganoso quando se trata de manipulação de carimbos de data/hora. Conforme descrito aqui, datetime
objetos são convertidos automaticamente ao usar PARSE_DECLTYPES
(e declarando um TIMESTAMP
coluna). Embora isso seja tecnicamente correto, a conversão perderá o fuso horário informações ! Conseqüentemente, se você estiver realmente usando o fuso horário-consciente datetime.datetime
objetos, você deve registrar seus próprios conversores , que retêm informações de fuso horário, da seguinte forma: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 você pode ver, o timestamp é armazenado apenas como
TEXT
no fim. Não existe um tipo de dados real “date” ou “datetime” no SQLite. Transações e confirmação automática
sqlite3
do Python módulo não confirma automaticamente dados modificados por suas consultas . Quando você executa consultas que de alguma forma alteram o banco de dados, você precisa emitir um COMMIT
explícito declaração, ou você usa a conexão como gerenciador de contexto objeto, conforme mostrado no exemplo a seguir:with connection: # this uses the connection as context manager
# do something with it, e.g.
connection.execute("SOME QUERY")
Code language: Python (python)
Uma vez que o bloco acima foi encerrado,
sqlite3
chama implicitamente connection.commit()
, mas só o faz se uma transação estiver em andamento . As instruções DML (Data Modification Language) iniciam automaticamente uma transação, mas as consultas envolvendo DROP
ou CREATE
TABLE
/ INDEX
declarações não, porque não contam como DML de acordo com a documentação. Isso é contra-intuitivo, porque essas declarações claramente modificam os dados. Assim, se você executar qualquer
DROP
ou CREATE
TABLE
/ INDEX
instruções dentro do gerenciador de contexto, é uma boa prática executar explicitamente um BEGIN TRANSACTION
declaração primeiro , para que o gerenciador de contexto realmente chame connection.commit()
para você. Tratamento de inteiros de 64 bits
Em um artigo anterior, já discuti que o SQLite tem problemas com grandes inteiros menores que
-2^63
, ou maior ou igual a 2^63
. Se você tentar usá-los em parâmetros de consulta (com o ?
símbolo), o sqlite3
do Python módulo irá gerar um OverflowError: Python int too large to convert to SQLite INTEGER
, protegendo você contra perda acidental de dados. Para lidar adequadamente com números inteiros muito grandes, você deve:
- Use o
TEXT
digite para a coluna da tabela correspondente, e - Converter o número para
str
já em Python , antes de usá-lo como parâmetro. - Converter as strings de volta para
int
em Python, quandoSELECT
dados
Conclusão
O
sqlite3
oficial do Python module é uma excelente ligação ao SQLite. No entanto, os desenvolvedores novos no SQLite precisam entender que há uma diferença entre as ligações do Python e a biblioteca SQLite C subjacente. Há perigo à espreita nas sombras, por causa das diferenças de versão do SQLite. Isso pode acontecer mesmo se você executar o mesmo Versão do Python em duas máquinas diferentes, porque a biblioteca SQLite C ainda pode estar usando uma versão diferente. Também discuti outros problemas, como lidar com objetos de data e hora e alterar dados persistentemente usando transações. Eu mesmo não estava ciente deles, o que causou perda de dados para os usuários dos meus aplicativos, então espero que você possa evitar os mesmos erros que cometi.