Django syncdb ведет себя странно со строками Unicode

Заранее извиняюсь за длину поста. Я просто хочу убедиться, что не упустил никакой информации.

У меня есть приложение, которое использует ORM django вне приложения django, которое использует «syncdb», вызывая call_command('syncdb') напрямую (примечание: я использую virtualenv для каждого сценария, указанного ниже).

Мои модульные тесты в приложении пытаются настроить «тестовую» базу данных django, используя SQLite в качестве бэкэнда (тогда как производственная среда использует MySQL).

Каждый раз, когда запускается один из модульных тестов, он вызывает call_command('syncdb'), используя одни и те же настройки тестового django для каждого теста.

Я могу запустить эти модульные тесты в двух разных средах (одна с Windows 7/Python 2.7.3, другая с Mac OS X ML/Python 2.7.2). С тестами проблем нет; но это относительно чистые установки Python на обоих.

Однако, когда я пытаюсь запустить это на сервере RHEL, я получаю следующую ошибку, когда модульные тесты пытаются запустить syncdb:

Ошибка базы данных: таблица «my_app_mytable» уже существует

После долгого гугления и отладки я (думаю) устранил ошибки, о которых сообщалось здесь и здесь.

Я много взламывал и думаю, что сузил проблему до этого утверждения в командный файл django syncdb (как бы безумно это ни звучало) (строка 59):

tables = connection.introspection.table_names()

Я установил pdb.set_trace() внутри источника django syncdb в обеих средах, чтобы посмотреть. Вот что я нашел:

(Env, который работает)

(Pdb) tables
[u'my_app_mytable', u'my_app_myothertable']

Там вроде нормально. Судя по файлу syncdb, django использует переменную tables для сравнения моделей приложения с тем, что уже есть в базе данных.

(Env, который не работает)

(Pdb) tables
[u'm\x00y\x00_\x00a\x00p\x00p\x00_\x00m\x00y\x00t\x00a\x00', u'm\x00y\x00_\x00a\x00p\x00p\x00_\x00m\x00y\x00o\x00t\x00']

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

def model_installed(model):
        opts = model._meta
        converter = connection.introspection.table_name_converter
        return not ((converter(opts.db_table) in tables) or
            (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))

Этот метод вызывается через filter через несколько строк после этого определения и, похоже, проверяет, находится ли converter(opts.db_table) в списке tables. Я также запускал их вручную в обеих средах:

(Env, который работает)

(Pdb) opts = all_models[0][1][0]._meta
(Pdb) converter = connection.introspection.table_name_converter
(Pdb) converter(opts.db_table) in tables
True

Как видите, я (вроде бы) вручную запустил функцию model_installed, чтобы посмотреть, что возвращает converter(opts.db_table), и в обеих средах она выглядит как совершенно нормальная строка. Тем не мение:

(Env, который не работает)

(Pdb) opts = all_models[0][1][0]._meta
(Pdb) converter = connection.introspection.table_name_converter
(Pdb) converter(opts.db_table) in tables
False

Таким образом, похоже, что поскольку переменная tables представляет собой список безумно выглядящего мусора в сломанной среде, этот метод ложно утверждает, что имя таблицы каждой модели отсутствует в базе данных, что дает мне исходную ошибку, о которой я говорил в начале.

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

(Env, который не работает)

(Pdb) converter(opts.db_table) in [u'my_app_mytable', u'my_app_myothertable']
True

Нужно ли мне перекомпилировать Python в этой среде? Я прочитал следующий вопрос в stackoverflow и нашел свой сломанный среда демонстрировала странное поведение:

(myvirtualenv)[username@myserver]$ python
Python 2.7.3 (default, Apr 12 2012, 10:40:11)
[GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import StringIO, cStringIO, sys
>>> StringIO.StringIO(u"fubar").getvalue()
u'fubar'
>>> cStringIO.StringIO(u"fubar").getvalue()
'fubar'
>>> cStringIO.StringIO(u"\u0405\u0406").getvalue()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>> sys.maxunicode
65535
>>> sys.byteorder
'little'

РЕДАКТИРОВАТЬ: ОК, поэтому я еще немного просмотрел исходный код django, и похоже, что они получают список таблиц таким образом:

def get_table_list(self, cursor):
    "Returns a list of table names in the current database."
    # Skip the sqlite_sequence system table used for autoincrement key
    # generation.
    cursor.execute("""
        SELECT name FROM sqlite_master
        WHERE type='table' AND NOT name='sqlite_sequence'
        ORDER BY name""")
    return [row[0] for row in cursor.fetchall()]

Поэтому я подключился к файлу sqlite вручную в интерпретаторе Python и выполнил этот запрос:

(Env, который не работает)

>>> import sqlite3
>>> conn = sqlite3.connect('/path/to/sqlite/file')
>>> curs = conn.cursor()
>>> curs.execute("""
... SELECT name FROM sqlite_master
... WHERE type='table' AND NOT name='sqlite_sequence'
... ORDER BY name""")
<sqlite3.Cursor object at 0xb7557500>
>>> curs.fetchall()
[(u'c\x00c\x00_\x00s\x00t\x00a\x00t\x00s\x00_\x00c\x00c\x00',), (u'c\x00c\x00_\x00s\x00t\x00a\x00t\x00s\x00_\x00c\x00c\x00s',)]

Итак, похоже, что SQLite возвращает строку UTF16-LE для этого запроса. В рабочей среде он вернул следующее:

(Env, который работает)

>>> curs.fetchall()
[(u'my_app_mytable',), (u'my_app_myothertable',)]

Даже без кодировки, определенной вверху, у «рабочей» среды, похоже, нет проблем с интерпретацией моего файла models и соответствующим созданием таблиц. Есть ли какая-то настройка SQLite по умолчанию, которая вызывает это? Или git конвертирует файл в UTF-16LE в сломанной среде и придерживается UTF-8/ASCII в рабочей среде?


person Nitzle    schedule 10.10.2012    source источник
comment
Откуда вы берете этот сломанный unicode (который на самом деле является UTF-16LE, за исключением того, что он находится в unicode) и при чем здесь cStringIO?   -  person Ignacio Vazquez-Abrams    schedule 10.10.2012
comment
В исходном вопросе похоже, что вызов cStringIO.StringIO(u"fubar").getvalue() возвращает строку типа 'f\x00u\x00b\x00a\x00r\x00'. Я думаю, что, возможно, неправильно прочитал вопрос, является ли поведение, которое я показал в своем вопросе, ожидаемым поведением?   -  person Nitzle    schedule 10.10.2012


Ответы (3)


Я столкнулся с аналогичной проблемой: одна и та же команда syncdb успешно выполнялась в одной среде, а не в другой с одинаковым импортом базы данных и моделью. Изменение call_command(syncdb) на os.system('python ' + |path|, 'manage.py syncdb')) решило это для меня.

Надеюсь, это поможет другим.

person lmc    schedule 20.12.2013

Ваш текстовый редактор решил сохранить файл как UTF-16LE. Поместите следующее в начало исходных файлов, под шебангом:

# -*- coding: utf-16le -*-
person Ignacio Vazquez-Abrams    schedule 10.10.2012
comment
У меня странное поведение с этой кодировкой. Я добавил его в начало своих исходных файлов, но получаю SyntaxError: invalid syntax, который, кажется, указывает на новую строку EOF. - person Nitzle; 10.10.2012

Что ж, похоже, мой модуль sqlite3 начал барахлить. В итоге я переустановил пакет Red Hat sqlite-devel, перекомпилировал Python 2.7.3, а затем обновил исполняемый файл в virtualenv.

Кажется, сейчас работает хорошо. Теперь, когда я запускаю запрос get_table_list() django, я получаю следующее:

>>> import sqlite3
>>> conn = sqlite3.connect('/path/to/sqlite/file')
>>> curs = conn.cursor()
>>> curs.execute("""
... SELECT name FROM sqlite_master
... WHERE type='table' AND NOT name='sqlite_sequence'
... ORDER BY name""")
<sqlite3.Cursor object at 0xb7774ca0>
>>> curs.fetchall()
[(u'my_app_mytable',), (u'my_app_myothertable',)]

Я не вносил никаких изменений в сам файл sqlite, поэтому, похоже, это была проблема либо с модулем sqlite3, либо с моей установкой Python 2.7.3.

person Nitzle    schedule 11.10.2012