17.12.2014       Выпуск 57 (14.12.2014 - 21.12.2014)       Статьи

Грязные технологии: миксины моделей SQLAlchemy

Пример разработки миксинов, в которых доступна метаинформация о модели SQLAlchemy

Читать>>




Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

SQLAlchemy позволяет выносить часть определения модели в отдельный базовый класс, который будет потом «подмешиваться» к другим. Очень удобно, когда есть какой-то повторяющийся кусок в большом количестве классов моделей. Но есть один неприятный момент: все поля должны «знать», к какой модели они принадлежат, а для этого нужно копировать объект поля. С первым уровнем SQLAlchemy хорошо справляется, так что с простым

Column

всё путём, но ведь поля могут содержать ссылки на другие объекты (например,

ForeignKey

), к которым предъявляются такие же требования. И тут авторы SQLAlchemy пошли самым простым путём: если нельзя сделать автоматически на все случаи жизни, то не пусть делает пользователь вручную. В результате код миксина должен выглядеть примерно так:

class WithParent(object):
    @declared_attr
    def parent_id(cls):
        return Column(ForeignKey(Parent.id))
    @declared_attr
    def parent(cls):
        return relationship(Parent)

По сути обработанные

declared_attr

свойства решают ту же проблему, которую

мы уже решали декоратором return_locals

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

def declared_mixin(*args):

    def wrapper(func):
        attrs = weakref.WeakKeyDictionary()
        def create_descriptor(name):
            def get_attr(cls):
                if cls not in attrs:
                    # Call func only once per class
                    attrs[cls] = return_locals(func)()
                return attrs[cls][name]
            get_attr.__name__ = name
            return declared_attr(get_attr)
        dict_ = {name: create_descriptor(name)
                 for name in func.func_code.co_varnames}
        dict_['__doc__'] = func.__doc__
        return type(func.__name__, args, dict_)

    if len(args)==1 and not isinstance(args[0], type):
        # Short form (without args) is used
        func = args[0]
        args = ()
        return wrapper(func)
    else:
        return wrapper

Теперь наш пример миксина выглядит гораздо приятнее:

@declared_mixin
def WithParent():
    parent_id = Column(ForeignKey(Parent.id))
    parent = relationship(Parent)

В комментариях к прошлому посту Андрей Светлов

резонно заметил

, что хак слишком грязен для столь небольшого эффекта. В ситуации же с миксином полученный эффект уже больше: если в исходном варианте на одну смысловую строчку кода приходилось две строчки шума, то здесь мы от шума полностью избавились. И дело даже не в том, что строк стало меньше, зашумлённый код гораздо сложнее читать. Вопрос поиска менее грязных путей получения нужного результата остаётся открытым.





Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus