代理模式习语

时间:2011-04-25 21:34:01

标签: python sqlalchemy abstraction idioms

我是一名Web应用程序开发人员,在使用SQLAlchemy时,我发现在我想要一个特定的(比如说users时,在我的许多控制器中执行此操作很笨拙} table:

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 个答案:

答案 0 :(得分:3)

你提到“不喜欢不得不做'所有这些'”,其中“所有这些”看起来非常像只有1-2行代码,所以我觉得这不是真的有必要。基本上我并不认为你开始的任何一个陈述都是冗长或令人困惑的。

但是,如果我不得不提出一种表达方式,我不会在这里使用装饰器,因为你并没有真正装饰任何东西。如果没有应用装饰器imo,函数“proxy_user”实际上没有做任何事情。因为你需要以某种方式提供模型的名称,我认为你最好只使用一个函数并将模型类传递给它。我还认为将删除功能滚动到您的代理中是不合适的,并且取决于您如何配置Session,重复调用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")