19 июн. 2008 г.

Борьба с обNULLением в SQLAlchemy

SQLAlchemy, пожалуй, самый продвинутый ORM для питона. Но, к сожалению, он постоянно подбрасывает неприятные сюрпризы. В очередной раз натолкнувшись на одну из проблем и потратив время на повторный поиск её решения, я решил его задокументировать. Речь об установки в NULL поля с идентификатором при удалении объекта, на который он ссылается, если в маппере для связи используется relation. Для тех, кто привык работать с SQL, такое поведение по умолчанию в лучшем случае вызывает недоумение. Фактически оно означает использование на уровне кода по умолчанию правила ON DELETE SET NULL, вместо привычного (и логичного!) ON DELETE RESTRICT. Если бы не моя чрезмерная педантичность в проставлении nullable=False для полей с FOREIGN KEY, этот сюрприз мог бы привести к весьма печальным последствиям - потери данных. Упоминание об описанном поведении в документации к SQLAlchemy встречается только один раз - при описании ключа passive_deletes функции relation(). Собственно его установка в 'all' и решает проблему. Так как имя ключа ничего не говорит о его истинном назначении, то соответствующий комментарий явно не помешает.

5 июн. 2008 г.

Использование copy в mercurial для разбиения модуля

Продолжаю смотреть на поведение mercurial при слиянии изменений. Тест второй, copy + merge. Простое дублирование одинаковой информации вряд ли когда-либо понадобится, поэтому я решил рассмотреть ситуацию, когда часть содержимого черезчур разросшегося модуля выносится в отдельный модуль.
Для начала создаём репозиторий trunk с одним модулем module.py:
def func1():
    return 'initial'

def func2():
    return 'initial'
и клонируем trunk в branch. Теперь изменяем функции в trunk:
def func1():
    return 'changed'

def func2():
    return 'changed'
В branch мы выносим функцию func2 в модуль submodule.py. Но для того, чтобы mercurial "знал", откуда появилась новая функция в submodule.py, мы выполняем команду hg copy module.py submodule.py и удаляем из первого func1, а из второго func2.
Остаётся только перелить изменения из trunk и сделать merge. Однако теперь процесс проходит не так гладко: изменения обоих функций успешно попадают куда нужно, но изменения второй отсутствующей функции mercurial для обоих подмодулей считает конфликтом, который и предлагает разрешить вручную. Впрочем, в данном случае всё разрешение конфликта сводится к удалению ненужной функции. К сожалению, такую операцию, как перенос части кода из одного файла в другой существующий, mercurial никак не позволяет отработать.

Обработка rename и merge в mercurial

Иван Сагалаев написал про проблемы слияния веток репозитория при использовании subversion. Это навело меня на мысль проверить, как с такими вещами в используемом мной mercurial? По ходу обнаружилось, что на моей локальной машине до сих пор установленная версия 0.8, в том время как все вкусности появились только начиная с 1.0. Чтож, хороший повод обновиться.
Итак, тест первый.
mkdir trunk
cd trunk
hg init
Создаю в нём модуль old_name.py с одной функцией:
def old_func():
  return 'initial'
Клонирую репозиторий
cd ..
hg clone trunk branch
и переименовываю модуль в новой ветке
cd branch
hg rename old_name.py new_name.py
hg ci -m 'old_name.py -> new_name.py' old_name.py new_name.py
Меняю функцию в old_name.py в trunk и добавляю новую, в результате получаю такой код:
def old_func():
   return 'changed'

def new_func():
   pass
Комичу изменения и пробую слить trunk с branch:
hg ci -m 'old_func is changed; new_func is added' old_name.py
cd ../branch
hg pull ../trunk
hg merge
Всё проходит замечательно и все изменения old_name.py в trunk успешно попадают в new_name.py в branch.
Ради интереса, я повторил тест со старой версией. При слиянии mercurial сообщает о том, что был изменён удалённый файл и предлагает либо добавить его, либо удалить. То есть ни о каком слиении изменений речи не идёт.