如何为SQLAlchemy绑定的声明模型扩展额外方法?

9 投票
4 回答
9839 浏览
提问于 2025-04-18 02:08

比如,我在模块 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()

4 个回答

1

这可能对你没用。我之前也遇到过类似的问题。最后我在创建UserWithExtraMethod的时候,把一个User的实例传给了它。

class UserWithExtraMethod(object):
    def __init__(self, user):
        self.user = user

    def name_capitalized(self):
        return self.user.name.capitalize()

希望这能帮到你。

1

这有点不太正规,但为什么不直接修改一下 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)
4

可能这个回复有点晚了。你有没有其他的关系设置指向User呢?

比如说,如果你定义了Address,像这样:

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    address = Column(String(50))
    Users = relationship("User", backref="addresses")

Address试图找到应该指向哪个User时,它会发现有两个User。你可以通过尝试Base._decl_class_registry['User']来验证一下。这和这个话题有点类似,是Michael讨论过的。

./sqlalchemy/ext/declarative/clsregistry.py中,有一个关于如何使用完整路径的例子。在这种情况下,你需要把address中的关系从Users = relationship("User", backref="addresses")改成Users = relationship("a.User", backref="addresses")

希望这能帮助你找到调试的正确方向。

7

除非你有特别的理由需要分开使用,否则你可以直接写:

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

撰写回答