sqlite3.ProgrammingError: Nie możesz używać 8-bitowych bajtów, chyba że używasz text_factory, który może interpretować 8-bitowe bajty

90

Używając SQLite3 w Pythonie, próbuję przechowywać skompresowaną wersję fragmentu kodu HTML UTF-8.

Kod wygląda następująco:

...
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)))

W którym momencie pojawi się błąd:

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.

Jeśli użyję „tekstu” zamiast „bloba” i nie kompresuję fragmentu kodu HTML, wszystko działa dobrze (choć db jest za duży). Kiedy używam „blob” i kompresuję przez bibliotekę Python zlib, pojawia się powyższy komunikat o błędzie. Rozejrzałem się, ale nie mogłem znaleźć prostej odpowiedzi na to pytanie.

R. Hill
źródło

Odpowiedzi:

94

Jeśli chcesz używać 8-bitowych ciągów zamiast znaków Unicode w sqlite3, ustaw odpowiednią wartość text_factory dla połączenia sqlite:

connection = sqlite3.connect(...)
connection.text_factory = str
zag
źródło
7
Może to powodować problemy z różnymi kodowaniami, ponieważ nadal próbujesz analizować dane binarne jako tekst. Najlepiej zamiast tego użyć sqlite3.Binary.
MarioVilas
35

Znalazłem rozwiązanie, powinienem był poświęcić trochę więcej czasu na szukanie.

Rozwiązaniem jest „rzutowanie” wartości jako „bufor” Pythona, na przykład:

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

Mam nadzieję, że pomoże to komuś innemu.

R. Hill
źródło
1
Kiedy to zrobiłem, moja baza danych była pełna tekstu base36, co spowodowałoby, że baza danych byłaby większa niż bezpośrednie przechowywanie obiektu blob.
Brian Minton,
3
To jest niepoprawne, powinieneś zamiast tego użyć sqlite3.Binary, jak mówi dokumentacja.
MarioVilas
Wygląda na to, że sqlite3.Binary () to po prostu alias funkcji buffer (), przynajmniej od github.com/ghaering/pysqlite/blob/master/lib/dbapi2.py#L54
stevegt
Huh. Wygląda też na to, że ta sekcja dokumentacji pysqlite faktycznie zachęca do użycia funkcji buffer (): „Następujące typy Pythona można więc bez problemu wysłać do SQLite: ...” [typ Pythona] bufor ... [typ SQLite] BLOB " docs.python.org/2/library/sqlite3.html#introduction
stevegt
35

Aby pracować z typem BLOB, musisz najpierw przekonwertować skompresowany ciąg zlib na dane binarne - w przeciwnym razie sqlite spróbuje przetworzyć go jako ciąg tekstowy. Odbywa się to za pomocą sqlite3.Binary (). Na przykład:

c.execute('insert or ignore into blah values (?, ?)',(cid, 
sqlite3.Binary(zlib.compress(html))))
MarioVilas
źródło
to działa. Jednak zastanawiałem się, dlaczego jest to potrzebne. Czy typ „BLOB” już wskazuje, że dane w tej kolumnie są binarne? Uwaga w Pythonie 2 ciąg może być tekstowy lub binarny. Czy sqlite3 nie powinien po prostu traktować obiektu (skompresowany ciąg zlib) jako binarny dla typu BLOB?
user1783732
Nie sądzę, że Python ma w pamięci cały schemat bazy danych, aby sprawdzić poprawne typy danych - najprawdopodobniej po prostu zgaduje typy w czasie wykonywania na podstawie tego, co go przekazujesz, więc ciąg binarny nie może być odróżniony od ciągu tekstowego.
MarioVilas,
Ponieważ SQLite używa typu dynamicznego: sqlite.org/datatype3.html @ user1783732
Lester Cheung
1

Składnia:

5 możliwych typów przechowywania: NULL, INTEGER, TEXT, REAL i BLOB

BLOB jest zwykle używany do przechowywania modeli marynowanych lub modeli marynowanych z koperkiem

> 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
źródło
0

Możesz zapisać wartość za pomocą repr (html) zamiast surowych danych wyjściowych, a następnie użyć eval (html) podczas pobierania wartości do użycia.

c.execute('insert or ignore into blah values (?, ?)',(1, repr(zlib.compress(html))))
zwalker
źródło
1
Używanie eval i repr w ten sposób jest bardzo brudne. Bez względu na to, jak bardzo ufasz źródłu danych.
Jason Fried
Zgadzam się, wszystko jest lepsze niż eval () tutaj. Właściwym rozwiązaniem jest użycie sqlite3.Binary, ale jeśli z jakiegoś powodu nie możesz, lepiej zakodować dane w bezpieczniejszy sposób - na przykład za pomocą base64.
MarioVilas