Home > Mobile >  Why is SQLAlchemy Postgres ORM requiring __init__(self) for a Declarative Base?
Why is SQLAlchemy Postgres ORM requiring __init__(self) for a Declarative Base?

Time:11-05

Setup: Postgres 13, Python 3.7, SQLAlchemy 1.4

The current structure uses base.py to create an engine, a Scoped Session, and an Augmented Base. All of this gets called in models.py where we define a table Class, and is called again in inserts.py where we test inserting new values to the database using the ORM. All of this is working well.

My question is regarding the def __init__(self) function in the models.py table Classes. Without this function the code will error with TypeError: __init__() takes 1 positional argument but 5 were given

As soon as I include the __init__ function the code works properly.

I am perplexed as to why this error is being produced given that all the models are defined via the Declarative system which means our Class should get an __init__() method constructor which automatically accepts keyword names that match the columns we’ve mapped.

I suspect there is an error in how I am coding the interactions between db_session = scoped_session and Base = declarative_base(cls=Base, metadata=metadata_obj) and the way Base is being passed in class NumLimit(Base).

I can't quite work this out and would appreciate being directed to where I am creating this error. Thank you!

base.py

from sqlalchemy import Column, create_engine, Integer, MetaData
from sqlalchemy.orm import declared_attr, declarative_base, scoped_session, sessionmaker

engine = create_engine('postgresql://user:pass@localhost:5432/dev', echo=True)

db_session = scoped_session(
    sessionmaker(
        bind=engine,
        autocommit=False,
        autoflush=False
    )
)


# Augment the base class by using the cls argument of the declarative_base() function so all classes derived
# from Base will have a table name derived from the class name and an id primary key column.
class Base:
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    id = Column(Integer, primary_key=True)


# Write all tables to schema 'collect'
metadata_obj = MetaData(schema='collect')
# Instantiate a Base class for our classes definitions
Base = declarative_base(cls=Base, metadata=metadata_obj)

models.py

from base import Base
from sqlalchemy import Column, DateTime, Integer, Text
from sqlalchemy.dialects.postgresql import UUID
import uuid


class NumLimit(Base):

    org = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True)
    limits = Column(Integer)
    limits_rate = Column(Integer)
    rate_use = Column(Integer)

    def __init__(self, org, limits, allowance_rate, usage, last_usage):
        super().__init__()
        self.org = org
        self.limits = limits
        self.limits_rate = limits_rate
        self.rate_use = rate_use

    def __repr__(self):
        return f'<NumLimit(org={self.org}, limits={self.limits}, limits_rate={self.limits_rate},' \
               f' rate_use={self.rate_use})>'

insert.py

def insert_num_limit():
    # Generate database schema based on definitions in models.py
    Base.metadata.create_all(bind=engine)

    # Create instances of the NumLimit class
    a_num_limit = NumLimit('123e4567-e89b-12d3-a456-426614174000', 20, 4, 8)
    another_limit = NumLimit('123e4567-e89b-12d3-a456-426614174660', 7, 2, 99)

    # Use the current session to persist data
    db_session.add_all([a_num_limit, another_limit])

    # Commit current session to database and close session
    db_session.commit()
    db_session.close()

    return

CodePudding user response:

All parameters of the __init__() generated by sqlalchemy are keyword-only. It is as if the definition was:

def __init__(self, *, id=None, org=None, limits=None, limits_rate=None, rate_use=None):
    self.id = id
    self.org = org
    # etc...

So when you try to supply the arguments positionally: NumLimit('123e4567-e89b-12d3-a456-426614174000', 20, 4, 8), you will get the error TypeError: __init__() takes 1 positional argument but 5 were given, because __init__() indeed only takes one positional argument. If you however supply them as keyword-arguments: NumLimit(id='123e4567-e89b-12d3-a456-426614174000', org=20, limits=4, limits_rate=8) everything works.

  • Related