SQLite
 sql >> Base de Dados >  >> RDS >> SQLite

Advertências sobre Python e SQLite


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 via apt , 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:
  1. Use o TEXT digite para a coluna da tabela correspondente, e
  2. Converter o número para str já em Python , antes de usá-lo como parâmetro.
  3. Converter as strings de volta para int em Python, quando SELECT 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.