使用类装饰器添加 sqlalchemy 重建器方法
Posted
技术标签:
【中文标题】使用类装饰器添加 sqlalchemy 重建器方法【英文标题】:Adding a sqlalchemy reconstructor method with a class decorator 【发布时间】:2020-01-14 13:54:49 【问题描述】:我有一个带有 @reconstructor 方法的映射类,该方法根据与此类的关系创建查找字典。 这行得通。 它在 init 中(显式)调用,然后在从数据库重建时也由 sqlalchemy 调用,并将每个实例的相关“化合物”映射到 self.compound,例如 self.compound[' Compound_name'] 从相关字段 self.compounds 中检索具有该名称的化合物。它在恒定时间内起作用,与我之前对 self.compounds 的线性搜索相反。太好了……
@reconstructor
def _create_lookup(self):
print('Create lookup called.')
attr = getattr(self, 'compounds')
lookup = dict(zip(
[getattr(c, 'name') for c in attr],
[getattr(c, '', c) for c in attr]
))
setattr(self, 'compound', lookup)
然而,下面更通用的形式在用作类装饰器时不起作用(我想要这个是因为我的几个类都需要这种行为,但不一定能很好地继承):
def give_class_lookup_on_attr(attr_to_lookup, lookup_key_attr, lookup_value_attr, lookup_name):
# None is a key for the lookup_value_attr to return the object itself (set in the default)
if lookup_value_attr is None:
lookup_value_attr = ''
# passing an empty string will never find an attribute, which makes it the key for returning the default
print('give_class_... decorator called.')
def class_wrap(cls):
@reconstructor
def func(self):
print('Create lookup called.')
attr = getattr(self, attr_to_lookup)
lookup = dict(zip(
[getattr(c, lookup_key_attr) for c in attr],
[getattr(c, lookup_value_attr, c) for c in attr]
# return the object as the value in dict if attr is not present
))
setattr(self, lookup_name, lookup)
cls._create_lookup = func
print('reconstructor method added to cls and cls returned')
return cls
return class_wrap
根据打印语句,我已经能够确认,当我使用适当的参数在我的类上调用我的装饰器时,重构器是在类上创建的,但从未被调用。我还检查了类方法的 .__sa_reconstructor__ 标志,该标志设置为 True(重构器装饰器对方法所做的唯一事情)。但是,它仍然没有被调用。
因为类上的装饰器是在创建类之后调用的,然后传入类,所以我的工作假设是 sqlalchemy 的映射器已经完成了它的工作(这就是为什么当内联到类,但不使用类装饰器),因此它永远不会找到添加装饰器的重构器方法。
那么,我的假设是否正确,是否有任何方法可以刷新 sqlalchemy 的映射器以使其在装修后重新审视?或者,我应该开始考虑编写和/或添加我自己的 sqlalchemy 事件侦听器(假设它们不会有同样的问题)。
编辑: 我的假设似乎进一步证实了:
mapper = class_mapper(MyMappedClass)
print(mapper._reconstructor)
如果我在测试中使用内联方法声明调用上述内容,它会返回重构器(函数 MyMappedClass._create_lookup 位于 0x7f452a49e8c8),但在使用我的类装饰器时返回 None。
【问题讨论】:
装饰器只是对事件系统的方便:docs.sqlalchemy.org/en/13/orm/… 感谢@IljaEverilä 我希望避免这种情况,但实际上对于 load 事件实现起来非常简单。我现在有点坚持让我正在添加的方法(cls._create_lookup)被调用在调用_init_之后,'init'事件显式根据文档,不这样做。这有什么共同的模式吗? 【参考方案1】:希望这可以为其他人省去麻烦。感谢@IljaEverilä,我的答案部分是 Python,部分是 Sqlalchemy。
它与我的第一个版本非常相似,但使用来自 sqlalchemy 的“加载”事件来处理从数据库重新加载,否则只会在它被调用且不可用时创建它的查找字典(通过属性)。
该属性可以用 sqlalchemy 中的“init”事件替换,但前提是它引用的任何内容在运行 init 之前可用(或未访问)。在我的例子中,在调用 __init__ 之前访问关系总是会导致空的查找表。
def give_class_lookup_on_attr(attr_to_lookup, lookup_key_attr, lookup_value_attr, lookup_name):
# None is a key for the lookup_value_attr to return the object itself (set in the default)
if lookup_value_attr is None:
lookup_value_attr = ''
# passing an empty string will never find an attribute, which makes it the key for returning the default
def class_wrap(cls):
def func(self):
attr = getattr(self, attr_to_lookup)
lookup = dict(zip(
[getattr(c, lookup_key_attr) for c in attr],
[getattr(c, lookup_value_attr, c) for c in attr]
))
# set a private attr as "self._<lookup_name>"
setattr(self, '_' + lookup_name, lookup)
cls._create_lookup = func
# create a property getter to reveal private attr, bind lookup_name to it
def prop_getter(self, name=lookup_name):
attr = getattr(self, '_' + name, None)
if not attr:
print('Lookup was not found. Created and returned.')
self._create_lookup()
attr = getattr(self, '_' + name)
return attr
setattr(cls, lookup_name, property(prop_getter))
@event.listens_for(cls, 'load')
def recieve_load(target, context):
target._create_lookup()
return cls
return class_wrap
【讨论】:
以上是关于使用类装饰器添加 sqlalchemy 重建器方法的主要内容,如果未能解决你的问题,请参考以下文章