如何使用额外的方法扩展 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 : 使用参数绑定执行原始 sql

你可以扩展 SQLAlchemy 查询类并在同一个会话中使用不同的类吗?

在 sqlalchemy 映射类构造函数中忽略额外关键字的选项?

Flask-SQLAlchemy使用方法