Image

Django и SQLAlchemy

Originally published at Pythy. You can comment here or there.

В августе мы с вами говорили о создание новой ветки в Django для поддержки SQLAlchemy. Спустя более чем полгода я с сожалением констатирую факт, что в этой ветке SQLAlchemy и не пахнет.


Самый быстрый способ использования SQLAlchemy в Django - описать свои модели в SQLAlchemy-терминах.


Давайте рассмотрим на примере кода из Django Live Tutorial. Для тех, кто не присутствовал на этом мероприятии, поясню, что это модельный веб-сервис для скачивания файлов по cron’у. Оригинальный код можно посмотреть здесь.


Напомню, как выглядит оригинальная модель:


from django.db import models
import datetime

class DownloadItem(models.Model):

    class Admin:
        pass

    class Meta:
        get_latest_by = 'added_at'

    url = models.CharField(maxlength=255, null=False)
    added_at = models.DateTimeField(auto_now_add=True)
    started_at = models.DateTimeField(null=True, blank=True)
    finished_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.url

    def is_started(self):
        return self.started_at is not None and datetime.datetime.now() > self.started_at

    def is_finished(self):
        return self.finished_at is not None and datetime.datetime.now() > self.finished_at

Давайте теперь в нашем приложении (dlt.downloader) создадим описание модели для SQLAlchemy, dlt.downloader.sa_models:


import datetime
import sqlalchemy as sa
from django.conf import settings

meta = sa.BoundMetaData(settings.SQLALCHEMY_DB_URI)

download_item_table = sa.Table('downloader_downloaditem', meta, autoload=True)

class DownloadItem(object):

    def __str__(self):
        return self.url

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self.url)

    def is_started(self):
        return self.started_at is not None and datetime.datetime.now() > self.started_at

    def is_finished(self):
        return self.finished_at is not None and datetime.datetime.now() > self.finished_at

orm.mapper(DownloadItem, download_item_table)


Здесь всё привычно и обычно для тех, кто читал документацию по SQLAlchemy. Единственно, что обращу ваше внимание на параметр SQLALCHEMY_DB_URI, который нужно указать в settings.py.


Теперь напишем простенькую вьюшку с использованием SQLAlchemy ORM:


from sqlalchemy import orm
from django.shortcuts import render_to_response
from dlt.downloader.models_sa import DownloadItem

def list_items(request);
    dbsession = orm.create_session()
    query = dbsession.query(DownloadItem)
    items = query.select()
    context = {'items': items}
    return render_to_response('list_items.html')

Вроде всё правильно. Добавляем в urls.py нашу новую вьюшку и пробуем…


Для разминки на SQLAlchemy ORM сделаем и "качалку":


import os
import time
import datetime

os.environ['DJANGO_SETTINGS_MODULE'] = 'dlt.settings'

from sqlalchemy import orm
from dlt.downloader.models_sa import DownloadItem

dbsession = orm.create_session()
query = dbsession.query(DownloadItem)

for item in query.select_by(started_at=None):
    print u"Начинаем качать с %s" % item.url
    item.started_at = datetime.datetime.now()
    dbsession.flush()
    time.sleep(10)
    print u"Закончили качать с %s" % item.url
    item.finished_at = datetime.datetime.now()
    dbsession.flush()

Всё достаточно просто и понятно.


Давайте оглянемся назад и посмотрим, что же у нас получилось… Настройки дублируются (нужно указывать параметры БД как для Django ORM, так и для SQLAlchemy), код дублируется (модели описываются и для Django ORM, и для SQLAlchemy).


Эти проблемы можно решить так:




  1. Сконвертировать параметры БД из Django в SQLAlchemy


  2. Имя таблицы, которое явно указывается в SQLAlchemy, можно узнать из Django: models.DownloadItem._meta.db_table.


  3. По идее, можно "маппить" SQLAlchemy ORM к Django-моделям (правда, я не гарантирую отсутствия "наводок" между Django ORM и SQLAlchemy, так что действуйте на свой страх и риск).


В итоге, учитывая эти соображения, получаем такой код:


import sqlalchemy as sa
import sqlalchemy.orm as orm
from django.conf import settings

from dlt.downloader.models import DownloadItem

def get_db_uri():
    ...
    # составляем URI из параметров Django
    # не особо интересно
    # так что пропустим реальный код

meta = sa.BoundMetaData(get_db_uri())

download_item_table = sa.Table(DownloadItem._meta.db_table, meta, autoload=True)

orm.mapper(DownloadItem, download_item_table)

Полный код, как всегда, - на code.google.com


Перспективы


Выше упомянутая ветка в репозитории Django имеет шансы на второе дыхание: Брайан Бек горит желанием более плотно интегрировать SQLAlchemy и Django. Пожелаем ему удачи и будем верить, что его энтузиазма хватит на больше чем сделать еще одну ветку в Subversion-репозитории.