python - 代理模式习语

标签 python sqlalchemy abstraction idioms

我是一名 Web 应用程序开发人员,在使用 SQLAlchemy 时,当我需要来自(例如)users 的特定时,我发现在许多 Controller 中执行此操作很笨拙。表:

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

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

或者说我想将用户添加到表中(假设另一个 Controller ):

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

我对我的所有模型都这样做了(我知道代码的重复令人讨厌)并且效果很好。我可以将关键字参数传递给代理函数,它会为我处理所有 session 查询(甚至为已删除的标志提供默认过滤器关键字)。我可以初始化一个空模型对象,然后通过更新对象属性向其添加数据,并且所有这些更改都会被跟踪(并提交/刷新),因为该对象已添加到 SQLAlchemy session 中。

因此,为了减少重复,我将大部分逻辑放在装饰器函数中,现在正在这样做:

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

现在,在我的 Controller 中我可以这样做:

from model import proxy_user

# Fetch a user
user = proxy_user(email="<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="abd8c4c6cecec6cac2c7ebced385c5cedf" rel="noreferrer noopener nofollow">[email protected]</a>") # 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    = '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5f3e31302b373a2d3a323e36331f3a2771313a2b" rel="noreferrer noopener nofollow">[email protected]</a>'
new_user.password = 'just an example'

如果我需要执行其他更复杂的查询,如果我经常使用它,我通常会编写处理它的函数。如果这是一次性的事情,我将只导入 dbsession 实例,然后执行“标准”SQLAlchemy orm 查询....

这更干净并且工作得很好,但我仍然觉得它没有完全“锁定”。其他人(或者更有经验的Python程序员)能否提供一个更好的习惯用法,在实现更清晰抽象的同时实现与我所寻求的类似的清晰性?

最佳答案

您提到“不喜欢必须执行‘所有这些’”,其中“所有这些”看起来非常像只有 1 - 2 行代码,所以我觉得这并不是真正必要的。基本上,我真的不认为您开头的任何陈述都那么冗长或令人困惑。

但是,如果我必须想出一种方法来表达这一点,我不会在这里使用装饰器,因为您并没有真正装饰任何东西。在我看来,如果没有应用装饰器,函数“proxy_user”实际上不会做任何事情。由于您需要以某种方式提供模型的名称,我认为您最好只使用函数并将模型类传递给它。我还认为将删除功能滚动到代理中是不合适的,并且根据您配置 session 的方式,对 DBSession() 的重复调用可能会创建新的不相关 session ,如果您需要使用,这将导致问题同一事务中的多个对象。

无论如何,这是我如何将装饰器重构为一对函数的快速尝试:

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="<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="20424f42604c4f43414c484f53540e434f4d" rel="noreferrer noopener nofollow">[email protected]</a>")

也许比查找/创建用户并将其添加到 session 所需的直接 SQLAlchemy 代码更好看。

与您当前的装饰器方法相比,我更喜欢上述函数,因为:

  • 这些名称清楚地表明了您的意图,我觉得 proxy_user 并没有真正明确表示您想要一个用户对象(如果存在),否则您想创建它。
  • session 是明确管理的
  • 他们不需要我将每个模型包装在装饰器中
  • find_or_add 函数始终返回模型实例,而不是有时返回 True、查询结果集或模型实例。
  • find_and_delete 函数始终返回一个 bool 值,指示它是否能够成功查找并删除 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="<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a9dac6c4ccc6c7cce9ddc5cd87cac6c4" rel="noreferrer noopener nofollow">[email protected]</a>")

关于python - 代理模式习语,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5783544/

相关文章:

python - xlsxwriter 和 pandas 用于报告

python - 将 django 调试输出转储到文件中?

python - 在 sqlalchemy 中自动更新属性

flask - 如何在 Flask-SQLAlchemy 中声明基本模型类?

java - 什么是有用的抽象/契约来协助 Builder 模式构建 MVC UI?

python - 为了适应大规模数据存储和检索,我应该做什么?

python - Devpi REST API - 如何检索包的版本

python - sqlalchemy 中使用反射和声明性语法的一对多关系定义给出了连接条件错误

language-agnostic - 异常和抽象

Java 抽象和泛型类型