SQLAlchemy: работа с данными CP-1252, когда Python ожидает, что он будет UTF-8

Я работаю с существующей базой данных SQLite и испытываю ошибки из-за данных, которые кодируются в CP-1252, когда Python ожидает, что он будет UTF-8.

>>> import sqlite3
>>> conn = sqlite3.connect('dnd.sqlite')
>>> curs = conn.cursor()
>>> result = curs.execute("SELECT * FROM dnd_characterclass WHERE id=802")
Traceback (most recent call last):
  File "<input>", line 1, in <module>
OperationalError: Could not decode to UTF-8 column 'short_description_html'
with text ' <p>Over a dozen deities have worshipers who are paladins, 
promoting law and good across Faer�n, but it is the Weave itself that 

Оскорбительный символ \0xfb, который декодирует до û. Другие тексты-нарушители включают "?nd and slay illithids.", который использует "умные кавычки" \0x93 и \0x94.

SQLite, python, unicode и не-utf данные подробно описывают, как эта проблема может быть решена при использовании sqlite3 самостоятельно.

Тем не менее, я использую SQLAlchemy. Как я могу обрабатывать закодированные данные CP-1252 в базе данных SQLite, когда я использую SQLAlchemy?


Edit:

Это также применимо к любым другим смешным кодировкам в SQLite TEXT, например, latin-1, cp437 и т.д.

Ответ 1

SQLAlchemy и SQLite ведут себя нормально. Решение состоит в том, чтобы исправить данные не UTF-8 в базе данных.

Я написал ниже, черпая вдохновение из fooobar.com/questions/36047/.... Это:

  • загружает целевую базу данных SQLite
  • перечисляет все столбцы во всех таблицах
  • если столбец имеет тип text, char или clob - включая варианты, такие как varchar и longtext - он перекодирует данные из INPUT_ENCODING в UTF-8.

INPUT_ENCODING = 'cp1252' # The encoding you want to convert from
import sqlite3
db = sqlite3.connect('dnd_fixed.sqlite')
db.create_function('FIXENCODING', 1, lambda s: str(s).decode(INPUT_ENCODING))
cur = db.cursor()
tables = cur.execute('SELECT name FROM sqlite_master WHERE type="table"').fetchall()
tables = [t[0] for t in tables]
for table in tables:
    columns = cur.execute('PRAGMA table_info(%s)' % table ).fetchall() # Note: pragma arguments can't be parameterized.
    for column_id, column_name, column_type, nullable, default_value, primary_key in columns:
        if ('char' in column_type) or ('text' in column_type) or ('clob' in column_type):
            # Table names and column names can't be parameterized either.
            db.execute('UPDATE "{0}" SET "{1}" = FIXENCODING(CAST("{1}" AS BLOB))'.format(table, column_name))

После этого script все поля *text*, *char* и *clob* находятся в UTF-8, и не будет больше ошибок декодирования Unicode. Теперь я могу Faerûn выразить свое сердце.

Ответ 2

Если у вас есть URI соединения, вы можете добавить следующие параметры в URI для подключения к базе данных:

DB_CONNECTION = mysql+pymysql://{username}:{password}@{host}/{db_name}?{options}
DB_OPTIONS = {
    "charset": "cp-1252",
    "use_unicode": 1,
}
connection_uri = DB_CONNECTION.format(
    username=???,
    ...,
    options=urllib.urlencode(DB_OPTIONS)        
)

Предполагая, что ваш драйвер SQLLite может обрабатывать эти параметры (pymysql может, но я не знаю 100% об sqllite), ваши запросы возвратят строки unicode.