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.