Идиома прокси - PullRequest
       11

Идиома прокси

2 голосов
/ 26 апреля 2011

Я разработчик веб-приложений, и при использовании SQLAlchemy я считаю неуклюжим делать это во многих моих контроллерах, когда мне нужна конкретная строка из (скажем) таблицы users:

from model import dbsession # SQLAlchemy SessionMaker instance
from model import User

user = dbsession().query(User).filter_by(some_kw_args).first()

Или сказать, что я хочу добавить пользователя в таблицу (при условии, что другой контроллер):

from model import dbsession # SQLAlchemy SessionMaker instance
from model import User

user = User("someval", "anotherval", "yanv")
dbsession().add(user)

Итак, из-за этой неуклюжести (я не буду вдаваться в некоторые другие мои личные идиомы), мне не нравилось делать все это, просто чтобы добавить запись в таблицу или получить запись из таблицы , Поэтому я решил (после многих неприятных попыток взлома SQLAlchemy и решив, что я делаю слишком много «волшебных» вещей), это подходит для шаблона прокси.

Я (сначала) сделал что-то подобное внутри модуля model:

def proxy_user(delete=False, *args, **kwargs):
    session = DBSession()

    # Keyword args? Let's instantiate it...
    if (len(kwargs) > 0) and delete:
        obj = session.query(User).filter_by(**kwargs).first()
        session.delete(obj)

        return True
    elif len(kwargs) > 0:
        kwargs.update({'removed' : False})
        return session.query(User).filter_by(**kwargs).first()
    else:
        # Otherwise, let's create an empty one and add it to the session...
        obj = User()
        session.add(obj)
        return obj

Я сделал это для всех моих моделей (я знаю, что это неприятное дублирование кода), и это работает довольно хорошо. Я могу передать аргументы ключевого слова в функцию прокси, и она обрабатывает все запросы сеанса для меня (даже предоставляя ключевое слово фильтра по умолчанию для удаленного флага). Я могу инициализировать пустой объект модели, а затем добавить к нему данные, обновив атрибуты объекта, и все эти изменения отслеживаются (и фиксируются / сбрасываются), поскольку объект был добавлен в сеанс SQLAlchemy.

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

def proxy_model(proxy):
    """Decorator for the proxy_model pattern."""

    def wrapper(delete=False, *args, **kwargs):

        model   = proxy()

        session = DBSession()

        # Keyword args? Let's instantiate it...
        if (len(kwargs) > 0) and delete:
            obj = session.query(model).filter_by(**kwargs).first()
            session.delete(obj)

            return True
        elif len(kwargs) > 0:
            kwargs.update({'removed' : False})
            return session.query(model).filter_by(**kwargs).first()
        else:
            # Otherwise, let's create an empty one and add it to the session...
            obj = model()
            session.add(obj)
            return obj

    return wrapper

# The proxy_model decorator is then used like so:
@proxy_model
def proxy_user(): return User

Итак, теперь в моих контроллерах я могу сделать это:

from model import proxy_user

# Fetch a user
user = proxy_user(email="someemail@ex.net") # Returns a user model filtered by that email

# Creating a new user, ZopeTransaction will handle the commit so I don't do it manually
new_user          = proxy_user()
new_user.email    = 'anotheremail@ex.net'
new_user.password = 'just an example'

Если мне нужно выполнить другие более сложные запросы, я обычно напишу функцию, которая обрабатывает это, если я использую это часто. Если это одноразовая вещь, я просто импортирую экземпляр dbsession, а затем выполню «стандартный» запрос SQLAlchemy orm ....

Это намного чище и работает чудесно, но я все еще чувствую, что это не совсем "заперто". Может ли кто-нибудь еще (или более опытные программисты на Python) предложить лучшую идиому, которая позволила бы достичь той же степени ясности, которую я ищу, будучи более ясной абстракцией?

1 Ответ

3 голосов
/ 26 апреля 2011

Вы упомянули, что «вам не нравилось делать все это», где «все это» выглядит ужасно, всего лишь 1-2 строки кода, поэтому я чувствую, что в этом нет необходимости. По сути, я не думаю, что любое утверждение, с которого вы начали, является настолько многословным или запутанным.

Однако, если бы мне пришлось придумать способ выразить это, я бы не использовал здесь декоратор, потому что вы ничего не украшаете. Функция «proxy_user» действительно ничего не делает, если декоратор не применяет imo. Поскольку вам нужно как-то указать название модели, я думаю, что вам лучше просто использовать функцию и передавать ей класс модели. Я также думаю, что внедрение функции удаления в ваш прокси неуместно, и в зависимости от того, как вы настроили свою сессию, повторные вызовы DBSession () могут создавать новые несвязанные сессии, которые могут вызвать проблемы, если вам нужно работать с несколько объектов в одной транзакции.

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

def find_or_add(model, session, **kwargs):
    if len(kwargs) > 0:
         obj = session.query(model).filter_by(**kwargs).first()
         if not obj:
             obj = model(**kwargs)
             session.add(obj)
    else:
         # Otherwise, let's create an empty one and add it to the session...
         obj = model()
         session.add(obj)
    return obj

def find_and_delete(model, session, **kwargs):
    deleted = False
    obj = session.query(model).filter_by(**kwargs).first()
    if obj:
        session.delete(obj)
        deleted = True
    return deleted

Опять же, я не уверен, что это необходимо, но я думаю, что могу согласиться с тем, что:

user = find_or_add(User, mysession, email="bob@localhost.com")

Возможно, выглядит лучше, чем простой код SQLAlchemy, необходимый для поиска / создания пользователя и добавления его в сеанс.

Мне нравятся вышеуказанные функции лучше, чем ваш нынешний подход к декоратору, потому что:

  • Имена четко обозначают ваши намерения, и я считаю, что proxy_user не дает понять, что вам нужен объект пользователя, если он существует, иначе вы хотите его создать.
  • Сеанс управляется явно
  • Они не требуют, чтобы я завернул каждую модель в декоратор
  • Функция find_or_add всегда возвращает экземпляр модели вместо того, чтобы иногда возвращать True, набор результатов запроса или экземпляр модели.
  • функция find_and_delete всегда возвращает логическое значение, указывающее, удалось ли ей успешно найти и удалить запись, указанную в kwargs.

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

# let's add a classmethod to User or its base class:
class User(...):
    ...
    @classmethod
    def find_or_add(cls, session, **kwargs):
        if len(kwargs) > 0:
            obj = session.query(cls).filter_by(**kwargs).first()
            if not obj:
                obj = cls(**kwargs)
                session.add(obj)
        else:
            # Otherwise, let's create an empty one and add it to the session...
            obj = cls()
            session.add(obj)
        return obj
    ...
user = User.find_or_add(session, email="someone@tld.com")
...