如何使用额外的方法扩展 SQLAlchemy 绑定声明性模型?
Posted
技术标签:
【中文标题】如何使用额外的方法扩展 SQLAlchemy 绑定声明性模型?【英文标题】:How do I extend a SQLAlchemy bound declarative model with extra methods? 【发布时间】:2014-04-10 02:08:06 【问题描述】:例如,我在模块 a
上有一个声明类:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
addresses = relationship("Address", backref="user")
现在,在模块 b
中,我想使用映射实体,但添加一个方法:
from a import User
class UserWithExtraMethod(User):
def name_capitalized(self):
return self.name.capitalize()
user = UserWithExtraMethod()
print(user.name_capitalized)
但是,当我运行脚本时,我会收到以下错误:
InvalidRequestError: Multiple classes found for path "User" in the registry of this declarative base. Please use a fully module-qualified path.
声明用户实体时我错过了什么?我想重用之前声明的实体。
我期待会是这样的:
class UserWithExtraMethod(User):
___magic_reuse_previous_mapper__ = True
def name_capitalized(self):
return self.name.capitalize()
【问题讨论】:
你到底想做什么?您确实意识到这是以 sqlalchemy 期望为UserWithExtraMethod
创建一个新表的方式定义的,导致该错误消息,因为未为此定义__tablename__
。切向相关信息:***.com/a/1337871/2904896
我不想创建额外的表或做任何继承。我只想在创建实体后添加一个额外的方法。它希望扩展类而不会对底层 SQL 映射器产生任何副作用。
我希望不同的模块对相同的方法有不同的实现。
您使用的是哪个 sqlalchemy 版本?我无法使用最新版本的 0.9 或 0.8 实际重现您的问题。或者,您的代码示例可能无法触发此操作。
“不同的实现”?你确定你想要那个吗?你是否给你的持久类太多的责任?您需要哪些不同的实现?
【参考方案1】:
除非你有特殊的理由需要单独的类,你应该写:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
addresses = relationship("Address", backref="user")
def name_capitalized(self):
return self.name.capitalize()
由于就 SQLAlchemy 而言,name_capitalized
并不特殊(它不是 ColumnExpression
或类似的),所以映射器完全忽略了它。
实际上,有一个更好的方法可以做到这一点;您的版本适用于 User
的实例,但在 sql 表达式中没有用。
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
class User(Base):
# ... body as before
@hybrid_method
def name_capitalized(self):
return self.name.capitalize()
@name_capitalized.expression
def name_capitalized(cls):
# works for postgresql, other databases spell this differently.
return sqlalchemy.func.initcap(cls.name)
这将允许您执行以下操作:
>>> print Query(User).filter(User.name_capitalized() == "Alice")
SELECT users.id AS users_id, users.name AS users_name
FROM users
WHERE initcap(users.name) = :initcap_1
【讨论】:
【参考方案2】:这个回复可能有点晚了。您是否有任何指向User
的其他 关系设置?
例如,如果您将Address
定义为:
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
address = Column(String(50))
Users = relationship("User", backref="addresses")
当Address
试图解析声明基中的哪个用户指向时,它会找到其中两个。要验证尝试Base._decl_class_registry['User']
。这类似于 Michael 所涵盖的 this 主题。
在./sqlalchemy/ext/declarative/clsregistry.py
中有一个关于如何使用完全限定路径的示例。在这种情况下,它会将地址内的关系从Users = relationship("User", backref="addresses")
更改为
Users = relationship("a.User", backref="addresses")
希望这有助于为您指明正确的调试方向。
【讨论】:
我很好奇的一件事是检查何时定义了第二个用户类。这也可能有助于清除一些烟雾。【参考方案3】:Hacky,但为什么不只是为您的目的对 User
类进行猴子补丁,而不是从它继承呢?
# modude b
from a import User
def name_capitalized(self):
return self.name.capitalize()
User.name_capitalized = name_capitalized
user = User() # and it has extra-method as well
print(user.name_capitalized)
【讨论】:
我目前正在这样做,只是猴子补丁不合适。问题是 SQLAlchemy 声明性类有一个创建实体的元类。似乎没有办法禁止调用元类再次创建它。【参考方案4】:这可能不适合您。我有一个类似的问题。我最终在实例化期间将 User 的实例传递给 UserWithExtraMethod
class UserWithExtraMethod(object):
def __init__(self, user):
self.user = user
def name_capitalized(self):
return self.user.name.capitalize()
希望对你有帮助
【讨论】:
以上是关于如何使用额外的方法扩展 SQLAlchemy 绑定声明性模型?的主要内容,如果未能解决你的问题,请参考以下文章
在 SQLAlchemy 中使用 declarative_base 时如何绑定引擎?
使用 sqlalchemy 的声明性 ORM 扩展时的多列索引
你可以扩展 SQLAlchemy 查询类并在同一个会话中使用不同的类吗?