Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I extend a SQLAlchemy bound declarative model with extra methods?

For example, I have a declarative class on module a:

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    addresses = relationship("Address", backref="user")

Now, in module b I want to use the mapped entity, but add a method:

from a import User

class UserWithExtraMethod(User):
    def name_capitalized(self):
        return self.name.capitalize()

user = UserWithExtraMethod()
print(user.name_capitalized)

However, when I run the script, I will get the following error:

InvalidRequestError: Multiple classes found for path "User" in the registry of this declarative base. Please use a fully module-qualified path.

What have I missed when declaring the user entity? I would like to reuse the previous declared entity.

I am expecting something would be like:

class UserWithExtraMethod(User):
    ___magic_reuse_previous_mapper__ = True

    def name_capitalized(self):
        return self.name.capitalize()
like image 263
hllau Avatar asked Apr 10 '14 02:04

hllau


People also ask

What is SQLAlchemy ext Declarative?

function sqlalchemy.ext.declarative. has_inherited_table(cls) Given a class, return True if any of the classes it inherits from has a mapped table, otherwise return False. This is used in declarative mixins to build attributes that behave differently for the base class vs. a subclass in an inheritance hierarchy.

What is lazy dynamic SQLAlchemy?

lazy = 'dynamic': When querying with lazy = 'dynamic', however, a separate query gets generated for the related object. If you use the same query as 'select', it will return: You can see that it returns a sqlalchemy object instead of the city objects.

What is Declared_attr?

For more complex attributes, a function decorated with @declared_attr ensures that a new instance is created for each subclass. During instrumentation, SQLAlchemy calls each declared attr for each class, assigning the result to the target name.


1 Answers

Unless you've got a particular reason to have separate classes, you should just write:

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()

Since the name_capitalized is not special as far as SQLAlchemy is concerned (it's not a ColumnExpression or some such), it is completely ignored by the mapper.

Actually, there's an even better way to do this; your version works fine for instances of User, but is of no use in sql expressions.

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)

which will allow you to do things like:

>>> 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
like image 137
SingleNegationElimination Avatar answered Sep 27 '22 20:09

SingleNegationElimination