Python 代理模式习语
我是一名web应用程序开发人员,在使用SQLAlchemy的过程中,我发现在我的许多控制器中这样做很笨拙,因为我希望从(比如)用户表中获得一个特定的行:Python 代理模式习语,python,sqlalchemy,abstraction,idioms,Python,Sqlalchemy,Abstraction,Idioms,我是一名web应用程序开发人员,在使用SQLAlchemy的过程中,我发现在我的许多控制器中这样做很笨拙,因为我希望从(比如)用户表中获得一个特定的行: 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 = dbsession().query(User).filter_by(some_kw_args).first()
或者说我想向表中添加一个用户(假设另一个控制器):
因此,由于这种笨拙(我不想讨论我的其他一些个人习惯用法),我不喜欢仅仅为了向表中添加记录或从表中获取记录而进行所有这些操作。所以我决定(在对SQLAlchemy进行了大量恶意黑客攻击之后,我认为我做了太多“神奇”的事情)这适合代理模式
我(起初)在模型
模块中做了类似的事情:
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会话
因此,为了减少重复,我将大部分逻辑放在一个decorator函数中,现在正在这样做:
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-2行代码,所以我觉得这并不是真的必要。基本上,我并不认为你开头的任何一句话都那么冗长或令人困惑
然而,如果我必须想出一个方法来表达这一点,我不会在这里使用一个装饰师,因为你并没有真正装饰任何东西。函数“proxy_user”在没有应用decorator的情况下真的不能做任何事情。由于您需要以某种方式提供模型的名称,我认为您最好只使用一个函数并将模型类传递给它。我还认为,将删除功能滚动到代理中是不合适的,根据您配置会话的方式,重复调用DBSession()可能会创建新的不相关会话,如果您需要在同一事务中处理多个对象,这将导致问题
无论如何,这里有一个快速尝试,我将如何将您的decorator重构为一对函数:
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代码更好看
与当前的decorator方法相比,我更喜欢上述功能,因为:
- 这些名称清楚地表明了您在这里的意图,我觉得proxy_user并没有真正明确表示您想要一个user对象(如果它存在的话),否则您想要创建它李>
- 会话是显式管理的
- 他们不需要我把每一个模型都包装在一个装饰器里
- find_或_add函数始终返回模型的实例,而不是有时返回True、查询结果集或模型实例
- find_和_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")
你的第一个方法是我一直倾向于我自己的-我认为proxy_model decorator真的没有必要,我可以有一个proxy_model函数,它接受model类和**kwargs作为参数…酷:-)我认为使用decorator的方法是在每个模型上使用一个类decorator,但我还是觉得我更喜欢单独的函数。我采用了单独的函数方法,并将模型类作为参数传入。它工作得非常好,抽象感觉“正确”。感谢您的确认输入!此外,我也尝试过很多方法以类似于上面的方式使用模型对象,但是a)使用一些非常奇怪和低效的解决方法非常困难,B)编程非常糟糕。这种方法非常适合用例。谢谢你!很高兴我能帮忙。快乐的黑客!
# 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")