Why does SQLAlchemy start a transaction with SELECT - python

The following code raises the error sqlalchemy.exc.InvalidRequestError: A transaction is already begun on this Session when it gets to the line with session.begin():
import datetime
from sqlalchemy import create_engine, select, Integer, Column, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
Base = declarative_base()
class Person(Base):
__tablename__ = "people"
id = Column(Integer, primary_key = True)
name = Column(String(128), nullable = False)
created_at = Column(DateTime, nullable = False,
default=datetime.datetime.now(datetime.timezone.utc))
# # # # # # # # # # # # # # # # # # # #
dsn = 'sqlite:////tmp/people.sqlite'
engine = create_engine(dsn, echo=True)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
session = Session(engine, future=True)
p = Person(name="Able")
session.add(p)
session.commit()
print("About to do a select...")
sel = select(Person).filter_by(name="Able")
q = session.execute(sel).scalars().first()
print(q.created_at)
with session.begin():
print("hello")
Looking at the verbose output I can see that SQLAlchemy starts a transaction when executing the SELECT. I can see why it would start a transaction with an INSERT or UPDATE, but why does it start a transaction with a SELECT?

Related

FastAPI Sqlalchemy get lastest result

I have setup my first Python FastAPI but getting stuck. I have a function that query some results. The following function query the first entry in the database on a specific date. Now I want the last entry on a date or all results sort by highest id but how do i do this?
def get_workday(db: Session, workday_date: date):
return db.query(DBWorkday).where(DBWorkday.date == workday_date).first()
full code:
from datetime import date
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from typing import Optional, List
from sqlalchemy import Date, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker, Session
from sqlalchemy import Column, String, Integer
app = FastAPI()
# SqlAlchemy Setup
SQLALCHEMY_DATABASE_URL = 'sqlite:///../db/database.db'
engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True, future=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# A SQLAlchemny ORM
class DBWorkday(Base):
__tablename__ = 'workdays'
id = Column(Integer, primary_key=True, index=True)
date = Column(Date)
start_time = Column(String(4))
end_time = Column(String(4))
type = Column(String, nullable=True)
location = Column(String, nullable=True)
Base.metadata.create_all(bind=engine)
# Workday Pydantic model
class Workday(BaseModel):
date: date
start_time: str
end_time: str
type: Optional[str] = None
location: Optional[str] = None
class Config:
orm_mode = True
# Methods for interacting with the database
def get_workday(db: Session, workday_date: date):
return db.query(DBWorkday).where(DBWorkday.date == workday_date).first()
#app.get('/workday/{date}')
def get_workday_view(date: date, db: Session = Depends(get_db)):
return get_workday(db, date)
return db.query(DBWorkday).where(DBWorkday.date == workday_date).order_by(DBWorkday.id.desc()).first()

SQLALchemy: Where does the attribute error after data import come from?

I wrote an application using SQLAlchemy's object relational mapper to store and access data from an SQLite3 database.
I can call add_userto add one or multiple users and call get_users to get them
I can import data from excel and get them with get_users
I can import data from excel and add a user with add_user
BUT I can't get the users with the get_users function afterwards, because I'm getting the following error for the entry created with add_user: AttributeError: 'NoneType' object has no attribute 'id'
What am I doing wrong?
Here's a simple version of the application:
orm_test.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
def orm_setup():
Base = declarative_base()
engine = create_engine('sqlite:///:main:', echo=True)
Base.metadata.create_all(bind=engine)
Session = sessionmaker(bind=engine)
session = Session()
return Base, engine, session
orm_test_class.py
from sqlalchemy import Column, Integer, String
from orm_test import orm_setup
Base = orm_setup()[0]
engine = orm_setup()[1]
class User(Base):
__tablename__ = 'person'
id = Column('id', Integer, primary_key=True)
username = Column('username', String, unique=True)
Base.metadata.create_all(bind=engine)
orm_test_functions.py
from orm_test_class import User
from orm_test import orm_setup
session = orm_setup()[2]
def add_user(name):
u = User()
user_name = str(name)
u.username = user_name
session.add(u)
session.commit()
def get_users():
users = session.query(User).all()
for user in users:
print(user.id, user.username)
session.close()
main.py
import fire
from orm_test_functions import add_user, get_users
if __name__ == '__main__':
fire.Fire()
data_import.py
import fire
import pandas as pd
from orm_test import orm_setup
# import engine from orm
engine = orm_setup()[1]
def data_import():
file = 'Data.xlsx'
df_user = pd.read_excel(file, sheet_name = 'User')
df_user.to_sql('person', engine, if_exists='replace', index=False)
# Command line interface
if __name__ == '__main__':
fire.Fire()
The problem is that df_to_sql drops the original table, which has a primary key defined, and replaces it with a table that does not define a primary key.
From the dataframe_to_sql docs
replace: Drop the table before inserting new values.
You can get around this by setting if_exists='append' instead of if_exists='replace'.
df_user.to_sql('person', engine, if_exists='append', index=False)
If necessary you can emulate the "replace" behaviour by deleting any existing records from the table before importing the data.
This is the code I used to reproduce and resolve:
import io
import sqlalchemy as sa
from sqlalchemy import orm
import pandas as pd
Base = orm.declarative_base()
class User(Base):
__tablename__ = 'person'
id = sa.Column('id', sa.Integer, primary_key=True)
username = sa.Column('username', sa.String, unique=True)
engine = sa.create_engine('sqlite://', echo=True, future=False)
# Drop all is redundant for in-memory db
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
sessionmaker = orm.sessionmaker(engine)
def add_user(name):
session = sessionmaker()
u = User()
user_name = str(name)
u.username = user_name
session.add(u)
session.commit()
def get_users():
session = sessionmaker()
users = session.query(User).all()
for user in users:
print(user.id, user.username)
print()
session.close()
DATA = """\
id,username
1,Alice
2,Bob
"""
buf = io.StringIO(DATA)
df_user = pd.read_csv(buf)
df_user.to_sql('person', engine, if_exists='append', index=False)
users = get_users()
add_user('Carol')
users = get_users()
engine.dispose()
You should set the column id with AUTOINCREMENT keyword, see https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-the-autoincrement-keyword

How to join 2 tables and get the latest records from other table in one-to-many relation using SqlAlchemy ORM

lets say I've 2 tables users and devices. They have relation one-to-many.
In Sql, I can solve the mentioned problem by following query.
SELECT
users.*, devices.*
FROM
users
LEFT JOIN ( SELECT d1.*
FROM devices as d1
LEFT JOIN devices AS d2
ON d1.user_id = d2.user_id AND d1.date < d2.date
WHERE d2.user_id IS NULL ) as device_temp
ON (users.id = device_temp.user_id)
Here is my python code
#user_model.py
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(String(500), nullable=False)
last_name = Column(String(250), nullable=False)
device_model.py
#device_model.py
from sqlalchemy import Column, ForeignKey, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, relation
from sqlalchemy import create_engine
from user_model import User
Base = declarative_base()
class DeviceModel(Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
created_at = Column(DateTime(), nullable=False)
device_id = Column(String(250), nullable=False)
user_uid = Column(String, ForeignKey((User.id)))
owner = relation(User, backref='user_device')
run.py
#run.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from user_model import User, Base
from sleep_session import SleepSession, Base
from device_model import DeviceModel, Base
engine = create_engine(connection_string)
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
query = session.query(User,DeviceModel).join(DeviceModel)
results = query.all()
for row in results:
print(row.User.first_name +" "+ row.DeviceModel.device_id + " "+ str(row.DeviceModel.created_at))
I know this type of question is asked multiple times, but I could not find one with SqlAlchemy ORM.
I want the same result as described here
Thanks.
I used this question to practive sqlalchemy as I'm new to it.
Closest answer I can get is the following:
If you want to see 1 file full workable code go into the edits - I'll remove boilerplate code
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column, ForeignKey, Integer, String, DateTime
from sqlalchemy.orm import relationship, relation
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
connection_string = 'postgres://postgres:password#localhost/test'
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(String(500), nullable=False)
last_name = Column(String(250), nullable=False)
class DeviceModel(Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
created_at = Column(DateTime(), nullable=False)
device_id = Column(String(250), nullable=False)
user_uid = Column(Integer, ForeignKey((User.id))) # error Key columns "user_uid" and "id" are of incompatible types: character varying and integer.
owner = relation(User, backref='user_device')
engine = create_engine(connection_string)
Base.metadata.bind = engine
#User.__table__.create(engine)
#DeviceModel.__table__.create(engine)
DBSession = sessionmaker(bind=engine)
session = DBSession()
My Answer:
from sqlalchemy import and_, or_
from sqlalchemy.orm import aliased
DeviceModel2 = aliased(DeviceModel)
subquery = (session
.query(DeviceModel.created_at)
.outerjoin(DeviceModel2,
and_(DeviceModel.user_uid == DeviceModel2.user_uid,
DeviceModel.created_at < DeviceModel2.created_at))
.filter(DeviceModel2.user_uid == None)
.subquery('subq'))
query = (session
.query(User, DeviceModel)
.outerjoin(DeviceModel)
.filter(or_(
DeviceModel.created_at == subquery.c.created_at,
DeviceModel.id == None)))
print(query)
results = query.all()
for row in results:
if row[1]:
print({**row.User.__dict__, **row.DeviceModel.__dict__})
else:
print(row.User.__dict__)
from db_config import connection_string
from sqlalchemy import create_engine , and_ , inspect
from sqlalchemy.orm import sessionmaker, aliased
from user_model import User, Base
from device_model import DeviceModel, Base
engine = create_engine(connection_string)
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
DeviceModel_aliased = aliased(DeviceModel)
#make sub-query
query_for_latest_device = session.query(DeviceModel).\
outerjoin(DeviceModel_aliased,
and_(DeviceModel_aliased.user_uid == DeviceModel.user_uid,
DeviceModel_aliased.created_at > DeviceModel.created_at)).\
filter(DeviceModel_aliased.id == None).\
subquery()
use_subquery_and_join = session.query(User.first_name,latest_device).\
join(query_for_latest_device,
query_for_latest_device.c.user_uid == User.user_id).\
all()
for row in join_user_and_device:
print(row._asdict())

Extract table names from SQLAlchemy exist query

How do i extract the table name from SQLAlchemy exist statement
assume we have the following code
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
engine = create_engine('mysql://...')
Session = sessionmaker(bind=engine)
conn = engine.connect()
session = Session(bind=conn)
query_exists = session.query(Person).exists()
how can i extract the table name from the query_exists?
from sqlalchemy.sql.visitors import ClauseVisitor
from sqlalchemy import Table
def extract_tables(sql_stmt):
tables = []
visitor = ClauseVisitor()
cluase_iter = visitor.iterate(elem)
for e in cluase_iter:
if isinstance(e, Table):
tables.append(e)
if isinstance(e, (ValuesBase, UpdateBase)):
tables.append(e.table)
return set(tables)

Use JOIN in proxy association

I have proxy association between Content and ContentRevision. Usage of sqlalchemy.ext.associationproxy.association_proxy produce an EXISTS condition:
from sqlalchemy import create_engine, Column, Integer, Text, ForeignKey, inspect, String, and_
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, foreign, remote
DeclarativeBase = declarative_base()
class ContentRevision(DeclarativeBase):
__tablename__ = 'content_revision'
revision_id = Column(Integer, primary_key=True)
content_id = Column(Integer, ForeignKey('content.id'))
description = Column(Text())
title = Column(String(32))
class Content(DeclarativeBase):
__tablename__ = 'content'
id = Column(Integer, primary_key=True)
revisions = relationship("ContentRevision",
foreign_keys=[ContentRevision.content_id])
revision = relationship(
"ContentRevision",
uselist=False,
primaryjoin=lambda: and_(
remote(Content.id) == foreign(ContentRevision.content_id),
ContentRevision.revision_id == ContentRevision.revision_id == session.query(ContentRevision.revision_id)
.filter(ContentRevision.content_id == Content.id)
.order_by(ContentRevision.revision_id.desc())
.limit(1)
.correlate(Content)
),
)
title = association_proxy('revision', 'title')
description = association_proxy('revision', 'description')
# Prepare database and session
engine = create_engine('sqlite://', echo=False)
DeclarativeBase.metadata.create_all(engine)
session_maker = sessionmaker(engine)
session = session_maker()
#
c1 = Content()
c1.revisions.append(ContentRevision(title='rev', description='rev1'))
session.add(c1)
session.flush()
c1.revisions.append(ContentRevision(title='rev', description='rev2'))
assert [('rev', 'rev1'), ('rev', 'rev2')] == session.query(ContentRevision.title, ContentRevision.description).all()
print(str(session.query(Content).filter(Content.title == 'foo')))
"""
SELECT content.id AS content_id
FROM content
WHERE EXISTS (SELECT 1
FROM content_revision
WHERE content.id = content_revision.content_id AND content_revision.revision_id = (SELECT content_revision.revision_id AS content_revision_revision_id
FROM content_revision
WHERE content_revision.content_id = content.id ORDER BY content_revision.revision_id DESC
LIMIT :param_1) AND content_revision.title = :title_1)
"""
How to make a query on associated column Content.title using the join declared in primaryjoin relationship of Content.revision ?

Categories