sqlite3.ProgrammingError: Você não deve usar cadeias de bytes de 8 bits, a menos que use uma text_factory que pode interpretar cadeias de bytes de 8 bits

90

Usando SQLite3 em Python, estou tentando armazenar uma versão compactada de um fragmento de código HTML UTF-8.

O código é parecido com este:

...
c = connection.cursor()
c.execute('create table blah (cid integer primary key,html blob)')
...
c.execute('insert or ignore into blah values (?, ?)',(cid, zlib.compress(html)))

Em que ponto em obter o erro:

sqlite3.ProgrammingError: You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.

Se eu usar 'texto' em vez de 'blob' e não compactar o trecho de HTML, tudo funciona bem (db é muito grande). Quando eu uso 'blob' e faço compactação por meio da biblioteca Python zlib, recebo a mensagem de erro acima. Olhei em volta, mas não consegui encontrar uma resposta simples para essa pergunta.

R. Hill
fonte

Respostas:

94

Se você quiser usar strings de 8 bits em vez de strings Unicode no sqlite3, defina o text_factory apropriado para a conexão sqlite:

connection = sqlite3.connect(...)
connection.text_factory = str
zag
fonte
7
Isso pode causar problemas com codificações diferentes, já que você ainda está tentando analisar dados binários como texto. É melhor usar sqlite3.Binary.
MarioVilas
35

Encontrei a solução, deveria ter gasto um pouco mais de tempo pesquisando.

A solução é 'lançar' o valor como um 'buffer' Python, assim:

c.execute('insert or ignore into blah values (?, ?)',(cid, buffer(zlib.compress(html))))

Espero que isso ajude mais alguém.

R. Hill
fonte
1
Quando fiz isso, meu banco de dados estava cheio de texto base36, o que tornaria o banco de dados maior do que armazenar o blob diretamente.
Brian Minton
3
Isso está incorreto, você deve usar sqlite3.Binary ao invés, como diz a documentação.
MarioVilas
Parece que sqlite3.Binary () é simplesmente um alias de buffer (), pelo menos a partir de github.com/ghaering/pysqlite/blob/master/lib/dbapi2.py#L54
stevegt
Hã. E também parece que esta seção da documentação do pysqlite realmente incentiva o uso de buffer (): "Os seguintes tipos de Python podem ser enviados para SQLite sem nenhum problema: ..." [tipo Python] buffer ... [tipo SQLite] BLOB " docs.python.org/2/library/sqlite3.html#introduction
stevegt
35

Para trabalhar com o tipo BLOB, você deve primeiro converter sua string compactada zlib em dados binários - caso contrário, o sqlite tentará processá-la como uma string de texto. Isso é feito com sqlite3.Binary (). Por exemplo:

c.execute('insert or ignore into blah values (?, ?)',(cid, 
sqlite3.Binary(zlib.compress(html))))
MarioVilas
fonte
isso funciona. No entanto, eu queria saber por que isso é necessário. O tipo "BLOB" já indicava que os dados nesta coluna são binários? Observe que no Python 2 a string pode ser texto ou binária. O sqlite3 não deveria apenas tratar o objeto (string compactada zlib) como binário para o tipo BLOB?
user1783732
Não acho que o Python tenha todo o esquema do banco de dados na memória para consultar os tipos de dados corretos - provavelmente ele apenas adivinha os tipos em tempo de execução com base no que você passa, portanto, uma string binária não pode ser diferenciada de uma string de texto.
MarioVilas
Como o SQLite usa o tipo dinâmico: sqlite.org/datatype3.html @ user1783732
Lester Cheung
1

Sintaxe:

5 tipos de armazenamento possíveis: NULL, INTEGER, TEXT, REAL e BLOB

BLOB é geralmente usado para armazenar modelos em conserva ou modelos em conserva de endro

> cur.execute('''INSERT INTO Tablename(Col1, Col2, Col3, Col4) VALUES(?,?,?,?)''', 
                                      [TextValue, Real_Value, Buffer(model), sqlite3.Binary(model2)])
> conn.commit()

> # Read Data:
> df = pd.read_sql('SELECT * FROM Model, con=conn) 
> model1 = str(df['Col3'].values[0]))
> model2 = str(df['Col'].values[0]))
Pranzell
fonte
0

Você pode armazenar o valor usando repr (html) em vez da saída bruta e, em seguida, usar eval (html) ao recuperar o valor para uso.

c.execute('insert or ignore into blah values (?, ?)',(1, repr(zlib.compress(html))))
zwalker
fonte
1
Usar eval e repr assim é muito sujo. Não importa o quanto você confia em uma fonte de dados.
Jason Fried
Eu concordo, qualquer coisa é melhor do que eval () aqui. A solução certa é usar sqlite3.Binary, mas se você não puder por algum motivo, é melhor codificar os dados de uma maneira mais segura - por exemplo, com base64.
MarioVilas