How to get the LAST INSERT ID when using python-asyncio databases - sqlalchemy

I'm trying to persist an object in the database using the following stack: Starlette, SQLArchemy and Databases.
How can I get the LAST INSERT ID?
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
async def create_user(request: Request) -> JSONResponse:
data = await request.json()
query = User.__table__.insert().values(name=data["username"])
await database.execute(query)
# I need the LAST INSERT ID here

I was not expecting the solution to be that simple. I couldn't find it in the documentation.
It seems that the result of the query will return the LAST INSERT ID if there is one:
last_insert_id = await database.execute(query)

Related

SQLAlchemy: Set audit columns prior to insert or update

Each of my mapped class contains created_by and updated_by audit properties that I would like to set automatically upon INSERT and UPDATE of respective objects.
class User(Base):
__tablename__ = 'user'
id = Column(BigInteger, primary_key=True)
name = Column(Text, nullable=False)
...
class Address(Base):
__tablename__ = 'address'
id = Column(BigInteger, primary_key=True)
street = Column(Text, nullable=False)
...
created_by = Column(BigInteger) # references user.id
updated_by = Column(BigInteger) # references user.id
...
Is there a way to handle this centrally in SQLAlchemy? I looked at the events but it appears it needs to be setup for every single mapped class individually (note the SomeClass in the decorator).
#event.listens_for(SomeClass, 'before_insert')
def on_insert(mapper, connection, target):
target.created_by = context["current_user"] # I want to be able to do this not just for 'SomeClass' but for all mapped classes
#event.listens_for(SomeClass, 'before_update')
def on_update(mapper, connection, target):
target.updated_by = context["current_user"] # I want to be able to do this not just for 'SomeClass' but for all mapped classes
One solution here is to use the default parameters in the Column class provided by sqlalchemy. You can actually pass a callable to both default (to execute when first created) and onupdate to execute whenever updated.
def get_current_user():
return context["user"].id
class Address(Base):
__tablename__ = 'address'
...
created_by = Column(default = get_current_user)
updated_by = Column(default = get_current_user, onupdate=get_current_user)
Managed to figure it out, though somewhat concerned about using a dunder method __subclasses__() on declarative_base. If there is a better alternative do suggest.
def on_insert(mapper, connection, target):
target.created_by = context["user"].id
target.updated_at = datetime.utcnow()
def on_update(mapper, connection, target):
target.updated_by = context["user"].id
target.updated_at = datetime.utcnow()
Base.metadata.create_all()
mapped_classes = Base.__subclasses__()
for mapped_class in mapped_classes:
event.listen(mapped_class, 'before_insert', on_insert)
event.listen(mapped_class, 'before_update', on_update)
The context being referred to here is actually starlette-context

SqlAlchemy - make a query where Relationship Attribute is a list

I have two models:
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='profiles_list',
secondary=stageP_profile
)
class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='projects_list',
secondary=stageP_project
)
I need to select Profiles for which at least one value of the Profile.stagesP_list is contained in the project.stagesP_list.
Please help to compose the query or indicate the direction in which to search.
If you have project instance loaded, you can compose the following query:
project = ...
stageP_ids = [obj.id for obj in project.stagesP_list]
query = session.query(Profile).filter(
Profile.stagesP_list.any(StageP.id.in_(stageP_ids))
)
You can also perform joins on the database directly from having only project_id:
query = (
session.query(Profile)
.join(StageP, Profile.stagesP_list)
.join(Project, StageP.projects_list)
.where(Project.id == project_id)
.distinct()
)

SQLAlchemy: Get primary key of child table using unique key of it to use in main table

I have employee table and location table. All locations data is loaded into the table first. Then employee table will be loaded later using data from source system.
Tables:
Locations
location_id(pk)| code(unique)| city | country
Employees
emp_id(pk)| name| email | phone | location_id
Models(SQLAlchemy):
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
location_id = Column(Integer, ForeignKey('location.id'))
location = relationship("Location")
class Location(Base):
__tablename__ = 'location'
id = Column(Integer, primary_key=True)
Employees data from source will have location code but while inserting into employee table I need location_id. I will be getting many records from source as part of API call. Is there a way I can use the location code and insert the data into employee table without DB call.
I have these two approaches - need a more optimized one.
Make call to DB using code and get location id for each employee.
Load all locations in memory in a map structure and use it to get location id for code.
Expecting something like
emp = Employee(name="a",email="a__#__.com",phone="123")
emp.location = Location(code="L1")
db.session.add(emp)
db.session.commit()
This should create employee record with location id corresponding to L1 code.
Maintaining a mapping of location codes to ids or location objects is an entirely reasonable strategy if locations are not being added or removed from the database too frequently.
If you need to control the size of the cache you could use a function decorated with functools.lru_cache:
import functools
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm
Base = declarative_base()
class Employee(Base):
__tablename__ = 'employee'
id = sa.Column(sa.Integer, primary_key=True)
location_id = sa.Column(sa.Integer, sa.ForeignKey('location.id'))
location = orm.relationship("Location")
class Location(Base):
__tablename__ = 'location'
id = sa.Column(sa.Integer, primary_key=True)
code = sa.Column(sa.String)
# Set echo=True on the engine so we can se the queries.
engine = sa.create_engine('sqlite:///', echo=True)
Base.metadata.drop_all(bind=engine, checkfirst=True)
Base.metadata.create_all(bind=engine)
session_factory = orm.sessionmaker(bind=engine)
Session = orm.scoped_session(session_factory)
#functools.lru_cache
def get_location_by_code(code):
# We can proxy the session factory to avoid having to pass
# the session to this function (otherwise the session is part
# of the cache key, which we don't want).
# We could add logic to handle a missing code.
return Session.query(Location).filter_by(code=code).one()
# Add a location to the db
session = Session()
session.add(Location(code='L1'))
session.commit()
Session.remove()
# Add some employees to the database.
# Observe that we only query for location once.
session = Session()
for _ in range(3):
employee = Employee()
employee.location = get_location_by_code('L1')
session.add(employee)
session.commit()
Session.remove()

SqlAlchemy Relationship and Marshmallow

I am trying to return JSON or even a complete string of a returned one to many sqlalchemy query. I am using Marshmallow at this point to try do it but it keeps returning incomplete data
I have two models defined as :
class UserModel(db.Model):
__tablename__ = 'usermodel'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
password = db.Column(db.String(120))
weekday = db.relationship('weekDay', cascade='all,delete-orphan', single_parent=True, backref=db.backref('usermodel', lazy='joined'))
class weekDay(db.Model):
__tablename__ = 'weekday'
id = db.Column(db.Integer, primary_key=True)
#Defining the Foreign Key on the Child Table
dayname = db.Column(db.String(15))
usermodel_id = db.Column(db.Integer, db.ForeignKey('usermodel.id'))
I have defined two schemas
class WeekdaySchema(Schema):
id = fields.Int(dump_only=True)
dayname = fields.Str()
class UserSchema(Schema):
id = fields.Int(dump_only=True)
username = fields.Str()
password = fields.Str()
weekday = fields.Nested(WeekdaySchema)
and finally I run the command (I pass the name in userName variable)
userlist = UserModel.query.filter_by(parentuser=userName).all()
full_schema = UserSchema(many=True)
result, errors = full_schema.dump(userlist)
print (result)
I print the result to see before I attempt to Jsonify it:
My weekday object is completely empty
'weekday': {}
Doesn anyone know how I can do this correctly
It's a one-to-many relationship and you must indicate it on UserSchema, like that
class UserSchema(Schema):
id = fields.Int(dump_only=True)
username = fields.Str()
password = fields.Str()
weekday = fields.Nested(WeekdaySchema, many=True)
read more on documentation

SQLAlchemy Add Many to One row logic

I have this table structure in SQLAlchemy:
User(Base)
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
addresses = relationship("Address", backref="user")
Address(Base)
__talbename__ = 'addresses'
id = Column(Integer, primary_key = True)
user_id = Column(Integer, ForeignKey(users.id))
So a Many (addresses) to One (user) relationship.
My question is now how can I easily add a an address to a user without deleting the addresses already stored.
In pure SQL I would just insert a row in the address table with a foreign key to the right user.
This is how I'm doing it right now in SQLAlchemy (method in User Class):
def add_address(self, address):
adds = self.addresses
adds.append(address)
self.addresses = adds
So What I'm basically doing is first finding all the addresses, then appending the list to overwrite the list again with the extra address.
I was wondering if there is a more efficient way to do this?
You can define another relationship between User and Address, which will not be loaded using lazy='noload' (see Setting Noload):
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key = True)
addresses = relationship("Address", backref="user")
addresses_noload = relationship("Address", lazy='noload')
def add_address(self, address):
adds = self.addresses_noload # #note: use the relationship which does not load items from DB
adds.append(address)
Also see sqlalchemy add child in one-to-many relationship for similar question.