Mahi
Mahi

Reputation: 21923

Property behaviour (getter and setter) for a column, instead of normal attribute behaviour

Does SQLAlchemy support something like this:

class Character(Base):
   __tablename__ = 'character'
   id = Column(Integer, primary_key=True)
   level = Column(Integer)

   @ColumnProperty(Integer)
   def xp(self):
       return self._xp

   @xp.setter
   def xp(self, value):
       self._xp = value
       while self._xp >= self.xp_to_level_up():
           self.level += 1
           self._xp -= 100

   def __init__(self, level=0, xp=0):
       self.level = level
       self._xp = xp

Where id, level, and xp would get stored into the database. So basically a Column but instead of an attribute a property.

Upvotes: 3

Views: 2262

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1122132

Just add a _xp column definition, that maps to the xp column in the table, and use a regular @property object to implement your setter logic:

class Character(Base):
    __tablename__ = 'character'
    id = Column(Integer, primary_key=True)
    level = Column(Integer)
    _xp = Column('xp', Integer)

    def __init__(self, level=0, xp=0):
        self.level = level
        self._xp = xp

    @property
    def xp(self):
        return self._xp

    @xp.setter
    def xp(self, value):
        self._xp = value
        while self._xp >= self.xp_to_level_up():
            self.level += 1
            self._xp -= 100

See Naming Columns Distinctly from Attribute Names.

The above stores the _xp attribute on the instance as xp in the character table, but your code uses the Character.xp property to read and write the value of _xp indirectly.

You can also use a @hybrid_property; this acts just like property as used above, but then also lets you use Character.xp in query filters:

from sqlalchemy.ext.hybrid import hybrid_property

class Character(Base):
    __tablename__ = 'character'
    id = Column(Integer, primary_key=True)
    level = Column(Integer)
    _xp = Column('xp', Integer)

    def __init__(self, level=0, xp=0):
        self.level = level
        self._xp = xp

   @hybrid_property
   def xp(self):
        return self._xp

   @xp.setter
   def xp(self, value):
        self._xp = value
        while self._xp >= self.xp_to_level_up():
            self.level += 1
            self._xp -= 100

Now you can use session.query(Character).filter(Character.xp > 42) and other such filtering queries, rather than have to use Character._xp.

If you want to do bulk updates on your characters, you may have to include a update_expression implementation that would tell SQLAlchemy how to generate a SET expression for the UPDATE; perhaps using a CASE expression to handle updating the level in addition to adjusting XP.

Upvotes: 5

Related Questions