当使用Peewee时,我遵循的建议是 在Peewee Models Python中创建 "查询方法" 答:我为所有的查询创建了类方法。
class Person(Model):
name = CharField()
age = IntegerField()
@classmethod
def adults(cls):
return cls.select().where(cls.age > 18)
我为我的所有查询创建类方法,以保持我的模型 "胖",其他的都 "瘦"。现在我引入了一个外键,我很苦恼这个方法,因为Peewee要求我在查询中直接使用模型类。
class Metric(Model):
person = ForeignKeyField(Person, backref='metrics')
name = CharField()
value = IntegerField()
Other file:
class Person(Model):
name = CharField()
age = IntegerField()
@classmethod
def popular(cls, min_likes):
return cls.select(cls, Metric).join(Metric).where(Metric.name == 'likes', Metric.value > min_likes)
这行不通,因为 Metric
定义取决于 Person
反之亦然。循环导入. 文件中有一节 循环的外键依赖性,其中类似情况的解决方案是 DeferredForeignKey
但我不喜欢它,因为它增加了代码中的开销(到处都需要手动创建外键),而且因为我的应用程序使用的是SQLite--文档中明确指出了以下内容。
因为SQLite对改变表的支持有限,所以在表被创建后,不能向表添加外键约束。
如果我的理解正确的话,这实际上意味着我将完全失去FK约束。不过我想要这个约束,应用程序依靠的是缺少对应的记录会抛出异常。
是否有我忽略的不同的变通方法?有这样的胖模型毕竟是Peewee的推荐做法吗?我很喜欢,但这让我的模型设计陷入了死胡同。文档中甚至说。
我个人的观点是,循环外键是一种代码气味,应该重构(例如,通过添加一个中间表)。
更新了一下。 我更新了问题,因为原来我无意中漏掉了主要细节。我在处理循环外键 进口不仅仅是 依赖性 类之间的名称。如果我把这些类拼接到一个文件中,那就可以了,因为Python只在调用classmethods时才会解析它们的名字,但这不是我要解决的问题,我想把这些类放在不同的模块中。
我认为你不了解Python的作用域。在方法体中引用一个相关的模型并没有错,例如。
# Move metric below Person.
class Person(Model):
name = CharField()
age = IntegerField()
@classmethod
def popular(cls, min_likes):
return cls.select(cls, Metric).join(Metric).where(Metric.name == 'likes', Metric.value > min_likes)
class Metric(Model):
person = ForeignKeyField(Person, backref='metrics')
name = CharField()
value = IntegerField()
或者你可以使用 DeferredForeignKey
所建 恰恰 为此。
我想出了一个丑陋的变通方法。为了完整起见,我把它作为自己问题的答案贴出来,但我不喜欢这个解决方案,所以我不接受它作为答案。
鉴于包的结构是这样的。
models/
__init__.py
person.py
metric.py
而鉴于... __init__.py
看起来像这样。
from .person import Person
from .metric import Metric
允许简化导入。from models import Person
而不是 from models.person import Person
那么变通的办法可能是一个丑陋的第三个文件,里面只有其中一个模型的属性。比如说 person_attrs.py
:
models/
__init__.py
person.py
person_attrs.py
metric.py
这些文件的内容如下。个人:
class Person(Model):
name = CharField()
age = IntegerField()
Metric:
from .person import Person
class Metric(Model):
person = ForeignKeyField(Person, backref='metrics')
name = CharField()
value = IntegerField()
人物属性。
from .metric import Metric
__all__ = ['popular']
@classmethod
def popular(cls, min_likes):
return cls.select(cls, Metric).join(Metric).where(Metric.name == 'likes', Metric.value > min_likes)
然后是 __init__.py
作为胶水工作。
from .person import Person
from .metrics import Metric
from . import person_attrs # isort:skip
for attr_name in person_attrs.__all__:
setattr(Person, attr_name, getattr(person_attrs, attr_name))
这个变通方法滥用了 __all__
但我觉得还是显式的好,比用隐式算法查找属性要好),还有就是导入顺序在 __init__.py
变得重要。该 person_attrs.py
允许同时定义 @classmethod
和 @property
方法,现在可以使用其他模型 Metric
随意自由,但代价是作为顶层对象被驱逐到一个单独的档案中,只能与上层对象联合起来 Person
榜样 models
包根。
显然不好看,不直接,挺丑的,但我除了把模型放在一个文件里,还想不出别的办法。