Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use an equivalent to __post_init__ method with normal class?

I would like to store entity used in my code and avoid multiple occurrences. Thus, my idea was to use an __init__ method for collecting the main data for my class, and then use a kind of __post_init__ method for computing an id from my class object. Here is the code:

class Worker(Base):
    __tablename__='worker'
    id = Column(Integer,primary_key=True)
    profile=Column(String(100),nullable=False)
    useragent=Column(String(100),nullable=False)
    def __init__(self,useragent,profile):
        """ specify the main information"""
        print('init')
        self.profile= profile
        self.useragent=useragent
    def __post_init__(self):
        """ compute an id based on self, the worker"""
        self.id=id(self)
        print('dans post init')

With this example, the __init__ method can be used, but it doesn't run the __post_init__ method like we could expect with dataclass, for example.

How could I run this method just after the execution of the __init__ method ?

like image 509
HappyCloudNinja Avatar asked Mar 15 '19 13:03

HappyCloudNinja


1 Answers

The __post_init__ method is specific to the dataclasses library, because the __init__ method on dataclass classes is generated and overriding it would entirely defeat the purpose of generating it in the first place.

SQLAlchemy, on the other hand, provides an __init__ implementation on the base model class (generated for you with declarative_base()). You can safely re-use that method yourself after setting up default values, via super().__init__(). Take into account that the SQLAlchemy-provided __init__ method only takes keyword arguments:

def __init__(self, useragent, profile):
    """specify the main information"""
    id = generate_new_id(self)
    super().__init__(id=id, useragent=useragent, profile=profile)

If you need to wait for the other columns to be given updated values first (because perhaps they define Python functions as a default), then you can also run functions after calling super().__init__(), and just assign to self:

def __init__(self, useragent, profile):
    """specify the main information"""
    super().__init__(useragent=useragent, profile=profile)
    self.id = generate_new_id(self)

Note: you do not want to use the built-in id() function to generate ids for SQL-inserted data, the values that the function returns are not guaranteed to be unique. They are only unique for the set of all active Python objects only, and only in the current process. The next time you run Python, or when objects are deleted from memory, values can and will be reused, and you can't control what values it'll generate next time, or in a different process altogether.

If you were looking to only ever create rows with unique combinations of the useragent and profile columns, then you need to define a UniqueConstraint in the table arguments. Don't try to detect uniqueness at the Python level, as you can't guarantee that another process will not make the same check at the same time. The database is in a much better position to determine if you have duplicate values, without risking race conditions:

class Worker(Base):
    __tablename__='worker'
    id = Column(Integer, primary_key=True, autoincrement=True)
    profile = Column(String(100), nullable=False)
    useragent = Column(String(100), nullable=False)

    __table_args__ = (
        UniqueConstraint("profile", "useragent"),
    )

or you could use a composite primary key based on the two columns; primary keys (composite or otherwise) must always be unique:

class Worker(Base):
    __tablename__='worker'
    profile = Column(String(100), primary_key=True, nullable=False)
    useragent = Column(String(100), primary_key=True, nullable=False)
like image 136
Martijn Pieters Avatar answered Oct 19 '22 03:10

Martijn Pieters