15

The app has such logic: list of people stored in the database, each man has a rating calculated in realtime and this value is never stored in database. And I want to use one class to work with dababase fields: name, age etc. and non database field: rating.

Is it possible in sqlalchemy? Now I'm using inheritance Man -> ManMapping:

class Man:
  rating = None

  def get_rating(self):
    return self.rating

  ...

class ManMapping(Base, Man):
  __tablename__ = 'man'
  id = Column('man_id', Integer, primary_key=True)
  name = Column(Unicode)

  ...

It works but it looks terrible for me. Is it right approach or I have to do something else?

1

3 Answers 3

8

This is the correct solution: https://docs.sqlalchemy.org/en/13/orm/constructors.html Hybrid properties are somewhat less flexible that this. The accepted answer is not an actual answer to the problem.

The SQLAlchemy ORM does not call init when recreating objects from database rows. The ORM’s process is somewhat akin to the Python standard library’s pickle module, invoking the low level new method and then quietly restoring attributes directly on the instance rather than calling init.

If you need to do some setup on database-loaded instances before they’re ready to use, there is an event hook known as InstanceEvents.load() which can achieve this; it is also available via a class-specific decorator called reconstructor(). When using reconstructor(), the mapper will invoke the decorated method with no arguments every time it loads or reconstructs an instance of the class. This is useful for recreating transient properties that are normally assigned in init:

from sqlalchemy import orm

class MyMappedClass(object):
    def __init__(self, data):
        self.data = data
        # we need stuff on all instances, but not in the database.
        self.stuff = []

    @orm.reconstructor
    def init_on_load(self):
        self.stuff = []
Sign up to request clarification or add additional context in comments.

2 Comments

Do you know if there is an equivalent when using the classical mapping approach: docs.sqlalchemy.org/en/13/orm/…
Incredible! Just a heads up for anyone using 1.4 that for some reason the documentation for this was removed. The documentation for reconstructor still exists but I wasn't able to find the the really nice example.
3

If you are using any data from the DB to calculate rating I would recommend looking at hybrid property. Otherwise I would add self.rating to init and have your function inside the ManMapping class. Something like:

class ManMapping(Base):   
    __tablename__ = 'man'   
    id = Column('man_id', Integer, primary_key=True)   
    name = Column(Unicode)

   def __init__(self)
       self.rating = None

   def get_rating(self):
       return self.rating

2 Comments

For some reason case with init doesnt work. But if I initialize rating after name it works. Maybe this is the way I dont know. Thank you anyway.
Maybe in ManMapping.__init__ method Base.__init__ must be called?
1

In my point of view, you should have two distincts classes. One for the logic in your code and one to communicate with your DB.

class Man(object):
    """This class is for your application"""
   def __init__(self, name, rating):
       # If the identifier is only used by the DB it should not be in this class
       self.name = name
       self.rating = rating

class ManModel(Base):
    """This model is only to communicate with the DB"""
    __tablename__ = 'man'
    id = Column('man_id', Integer, primary_key=True)
    name = Column(Unicode)

You should have a provider that does queries to DB with ManModel objects, then maps results to Man objects and return your mapped data to the caller. Your application will only use Man objects and your provider will do the mapping. Something like below :

class DbProvider(object):
    def get_man(self, id):
       man_model = session.query(ManModel).filter(ManModel.id == id).one_or_none()
       return self.man_mapper(man_model) if man_model else None

    def get_men(self):
        men_model = session.query(ManModel).all()
        return [self.man_mapper(man_model) for man_model in men_model]

    def man_mapper(self, man_model):
         return Man(man_model.name, self.calculate_rating(man_model))

 class Test(object):
      def display_man(self):
          man = db_provider.get_man(15)
          if man:
               print man.name, man.rating  

3 Comments

What I have to do to print the table with two columns: name, rating? It's gonna be too complex got this task.
In my sample, Man object contains both values (name and rating). So you can just do call the display_man method I have just created. It depends also how the rating is calculated.
Maybe I am too capricious, but this is not the code I want to use. But very close )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.