Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using __slots__ with SQLAlchemy model

I am using SQLAlchemy on my new project and would like to use __slots__ with models (in beta version without alchemy, __slots__ were necessary because a large number of objects was created). But I am unable to combine them with SQLAlchemy declarations and get the following error for code:

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

class NotWorking(Base):
     __tablename__ = 'table1'
     pk = Column(Integer, primary_key=True)
     name = Text(length=64, convert_unicode=True)

     __slots__ = ['name', 'pk']

Error

ValueError: 'name' in __slots__ conflicts with class variable

Which is expected as __slots__ modify class to create descriptors for fields defined in them, so one workaround is to make hidden fields (_name) and make model fields act as properties as shown here:

class Working(Base):
    __tablename__ = 'table2'
    pk = Column(Integer, primary_key=True)
    name = Text(length=64, convert_unicode=True)

    __slots__ = ['_name', '_pk']

     # Workaround
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

But this code can be a bit tedious to write and you need to add corresponding property for each new field. I am wondering, is there a better way to do without properties, while still using declarative base (without using Classical Mappings).

like image 433
jOOsko Avatar asked Jun 27 '26 06:06

jOOsko


1 Answers

You can use simple Python statements on the class body to automate the creation of simple properties, based on the __slots__ attribute itself:

class Working(Base):
    ...

    __slots__ = ['_name', '_pk']

    for _tmp_name in __slots__:
        locals()[_tmp_name[1:]] = property(lambda self, name=_tmp_name: getattr(self, name))
    del _tmp_name

Note that while the dictionary returned y locals inside a function or method is somewhat special, in which modifications to it do not affect the actual variables, it works as a plain dictionary in the class body itself.

If you have several models,or find this too hacky, it can be put in a metaclass or __init_subclass__ method as well (Python 3.6+):

class MyBase(Base):
    def __init_subclass__(cls, *args, **kwargs):
        for name in cls.__slots__:
            setattr(cls, name[1:], property(lambda self, name=name: getattr(self, name)))

class Working(MyBase):
    ...

For the metaclass case (python < 3.6), just put the same three lines in the metaclass __init__:

class MetaBase(type):
    def __init__(cls, name, bases, namespace):
         super().__init__(name, bases, namespace)
         for name in cls.__slots__:
             setattr(cls, name[1:], property(lambda self, name=name: getattr(self, name)))


class Working(Base, metaclass=MetaBase):
    ...
like image 154
jsbueno Avatar answered Jun 28 '26 20:06

jsbueno



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!