I'm just getting started with ORM SQLAlchemy / SQLModel and I wonder if there is a shortcut function to get an entity from a session given all columns except the primary key.
For example, right now i have this (which works):
from sqlmodel import SQLModel, Field, create_engine, Session, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
secret_name: str
engine = create_engine("sqlite:///hero_db.db")
def get_hero(hero: Hero) -> Hero:
with Session(engine) as session:
return session.exec(
select(Hero).where(
Hero.name == hero.name,
Hero.secret_name == hero.secret_name,
)
).one()
And i wonder if there is a shortcut without explicitly listing every column. For example like this:
...
return session.exec(
select(Hero).where(
Hero == hero
)
).one()
Or even better like this:
...
return session.get_reverse(Hero, hero)
Related
I'm trying to have my Pydantic/ORM models "output" labels,but when using SqlAlchemy ORM I feel a bit locked/ stuck.
I want the fieldname "test1" ( below in the code) to return ( "Test left side") instead of test1 in the JSONResponse
As an explanation, In sql i would for example use “as”
select test1 as ‘Test left side’ from pretest
I know I can do the same in an ORM statement/query, but I want it to be reachable as a field/ attribute from the orm model class, or perhaps as some validation methods from my Pydantic model.
To explain I have added an example of two short models below
my model in Sqlalchemy:
class Pretest(Base):
__tablename__ = "pretest"
user_id = Column(Integer)
pretest_id = Column(Integer, primary_key=True)
timestamp_pretest = Column(DateTime(timezone=True), default=func.now())
test1 = Column(Integer)
my model in Pydantic: ( I use Optional because im testing at the moment)
class Pretest(BaseModel):
user_id: Optional[int] = None
pretest_id: Optional[int] = None
timestamp_pretest: Optional[datetime] = None
test1: Optional[int] = None
class Config:
orm_mode= True
So I’m wondering if Pydantic have a possibility to validate against a labelslist/array/ object that could contain a type of test1= “Test left side”
Or if the ORM models have some additional metadata that could be used like
test1 = Column(Integer, alias=“Test left side”)
I hope I make this question understandable?
My endpoint look a bit simplified something like this:
#router.post("/pretest", tags=["Medicaldata"], status_code=status.HTTP_200_OK)
def pretest(pretest: Pretest, token: str = Depends(oauth2_scheme)):
try:
query = db.query(models.Pretest).first()
except:
query = "query failed"
return JSONResponse(content=query)
Where I use the pretest-object which is type defined by the Pydantic model as query parameters( not shown here)
This response will create a json-object of the fields and values in the database.
The field/variable test1 will return as test1 instead of "Test left side", since I do not have a place to add labels or some sort of aliases.
I can add and map the json object manually in Python before I return it, but it’s a lot of complex queries spanning several tables, so it feels a bit “wrong” to do it that way.
The reason for all this is so that I can have model and label consistency and use the map function with spread operators in components in React as shown below.
get_backend(“/pretest”,data)
.then setPretestlist(response)
{pretestlist.map((item) => {
return <ShowPretest {...item} key={item.name} />;
})}
This will now show as test1 in the webpage instead of a more explanatory text like this "Test 1 left side"
#snakecharmerb, Thx, you put me on the right track.
The solution, if someone else wonder:
the Pydantic model needs to be changed from this:
class Pretest(BaseModel):
user_id: Optional[int] = None
pretest_id: Optional[int] = None
timestamp_pretest: Optional[datetime] = None
test1: Optional[int] = None
class Config:
orm_mode = True
To this:
class Pretest(BaseModel):
user_id: Optional[int] = None
pretest_id: Optional[int] = None
timestamp_pretest: Optional[datetime] = None
test1: Optional[int] = None
class Config:
fields = {
"test1": "Test left side",
"timestamp_pretest": "Time tested",
}
orm_mode = True
The endpoint needed to change in the way it does its response to this:
#router.post("/pretest", tags=["Medicaldata"], status_code=status.HTTP_200_OK)
def pretest(pretest: Pretest, token: str = Depends(oauth2_scheme)):
try:
query = db.query(models.Pretest).first()
query = Pretest.from_orm(query)
except:
query = "query failed"
return query.dict(by_alias=True)
I am new to FastAPI and I'm working on inserting data into MY SQL database using the Fast API POST method. I have created a set of sample codes to create a schema and inserted the single data into the MY SQL table using the below sample code.
Ref link: https://codingnomads.co/blog/python-fastapi-tutorial
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from typing import Optional, List
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker, Session
from sqlalchemy import Boolean, Column, Float, String, Integer
app = FastAPI()
# SqlAlchemy Setup
SQLALCHEMY_DATABASE_URL = 'sqlite+pysqlite:///./db.sqlite3:'
engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True, future=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# A SQLAlchemny ORM Place
class DBPlace(Base):
__tablename__ = 'places'
id = Column(Integer, primary_key=True, index=True)
name = Column(String(50))
description = Column(String, nullable=True)
coffee = Column(Boolean)
wifi = Column(Boolean)
food = Column(Boolean)
lat = Column(Float)
lng = Column(Float)
Base.metadata.create_all(bind=engine)
# A Pydantic Place
class Place(BaseModel):
name: str
description: Optional[str] = None
coffee: bool
wifi: bool
food: bool
lat: float
lng: float
class Config:
orm_mode = True
# Methods for interacting with the database
def get_place(db: Session, place_id: int):
return db.query(DBPlace).where(DBPlace.id == place_id).first()
def get_places(db: Session):
return db.query(DBPlace).all()
def create_place(db: Session, place: Place):
db_place = DBPlace(**place.dict())
db.add(db_place)
db.commit()
db.refresh(db_place)
return db_place
# Routes for interacting with the API
#app.post('/places/', response_model=Place)
def create_places_view(place: Place, db: Session = Depends(get_db)):
db_place = create_place(db, place)
return db_place
#app.get('/places/', response_model=List[Place])
def get_places_view(db: Session = Depends(get_db)):
return get_places(db)
#app.get('/place/{place_id}')
def get_place_view(place_id: int, db: Session = Depends(get_db)):
return get_place(db, place_id)
#app.get('/')
async def root():
return {'message': 'Hello World!'}
But I need to parse a list of arrays in the request to create_places_view and insert multiple values at once. so how to achieve this in Fast API. Any pointers/help? Thanks
Example:
[{name: str
description: Optional[str] = None
coffee: bool
wifi: bool
food: bool
lat: float
lng: float },
{name: str
description: Optional[str] = None
coffee: bool
wifi: bool
food: bool
lat: float
lng: float }]
I have looped the value to create user. While sending the post request in the list of dict.
# Fast API to create/insert a new user
#app.post('/create_place_view/', responses={404: {"model": Message}})
async def create_place(user: List[Place], db: Session = Depends(get_db)):
if not user:
return JSONResponse(status_code=404, content={"message": "Place details not found"})
else:
places_list = []
for places in place:
db_place = create_places(db, places)
place_json = places.dict()
places_list.append(places_json)
return JSONResponse(content=places_list)
Assume the following setup:
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyClass(Base):
id = Column(Integer, primary_key=True)
name = Column(String)
The normal paradigm to query the DB with SQLAlchemy is to do the following:
Session = sessionmaker()
engine = 'some_db_location_string'
session = Session(bind=engine)
session.query(MyClass).filter(MyClass.id == 1).first()
Suppose, I want to simplify the query to the following:
MyClass(s).filter(MyClass.id == 1).first()
OR
MyClass(s).filter(id == 1).first()
How would I do that? My first attempt at that to use a model Mixin class failed. This is what I tried:
class ModelMixins(object)
def __init__(self, session):
self.session = session
def filter(self, *args):
self.session.query(self).filter(*args)
# Redefine MyClass to use the above class
class MyClass(ModelMixins, Base):
id = Column(Integer, primary_key=True)
name = Column(String)
The main failure seems to be that I can't quite transfer the expression 'MyClass.id == 1' to the actual filter function that is part of the session object.
Folks may ask why would I want to do:
MyClass(s).filter(id == 1).first()
I have seen something similar like this used before and thought that the syntax becomes so much cleaner I can achieve this. I wanted to replicate this but have not been able to. Being able to do something like this:
def get_stuff(some_id):
with session_scope() as s:
rec = MyClass(s).filter(MyClass.id== some_id').first()
if rec:
return rec.name
else:
return None
...seems to be the cleanest way of doing things. For one, session management is kept separate. Secondly, the query itself is simplified. Having a Mixin class like this would allow me to add the filter functionality to any number of classes...So can someone help in this regard?
session.query takes a class; you're giving it self, which is an instance. Replace your filter method with:
def filter(self, *args):
return session.query(self.__class__).filter(*args)
and at least this much works:
In [45]: MyClass(session).filter(MyClass.id==1)
Out[45]: <sqlalchemy.orm.query.Query at 0x10e0bbe80>
The generated SQL looks right, too (newlines added for clarity):
In [57]: str(MyClass(session).filter(MyClass.id==1))
Out[57]: 'SELECT "MyClass".id AS "MyClass_id", "MyClass".name AS "MyClass_name"
FROM "MyClass"
WHERE "MyClass".id = ?'
No guarantees there won't be oddities; I've never tried anything like this before.
Ive been using this mixin to good success. Most likely not the most efficient thing in the world and I am no expert. I define a date_created column for every table
class QueryBuilder:
"""
This class describes a query builer.
"""
q_debug = False
def query_from_dict(self, db_session: Session, **q_params: dict):
"""
Creates a query.
:param db_session: The database session
:type db_session: Session
:param q_params: The quarter parameters
:type q_params: dictionary
"""
q_base = db_session.query(type(self))
for param, value in q_params.items():
if param == 'start_date':
q_base = q_base.filter(
type(self).__dict__.get('date_created') >= value
)
elif param == 'end_date':
q_base = q_base.filter(
type(self).__dict__.get('date_created') <= value
)
elif 'like' in param:
param = param.replace('_like', '')
member = type(self).__dict__.get(param)
if member:
q_base = q_base.filter(member.ilike(f'%{value}%'))
else:
q_base = q_base.filter(
type(self).__dict__.get(param) == value
)
if self.q_debug:
print(q_base)
return q_base
I made this statement using flask-sqlalchemy and I've chosen to keep it in its original form. Post.query is equivalent to session.query(Post)
I attempted to make a subquery that would filter out all posts in a database which are in the draft state and not made or modified by the current user. I made this query,
Post.query\
.filter(sqlalchemy.and_(
Post.post_status != Consts.PostStatuses["Draft"],
sqlalchemy.or_(
Post.modified_by_id == current_user.get_id(),
Post.created_by_id == current_user.get_id()))
which created:
Where true AND ("Post".modified_by_id = :modified_by_id_1 OR
"Post".created_by_id = :created_by_id_1)
Expected outcome:
Where "Post".post_status != "Draft" AND (
"Post".modified_by_id = :modified_by_id_1 OR
"Post".created_by_id = :created_by_id_1)
I'm wondering, why this is happening? How can I increase the error level in SQLAlchemy? I think my project is silently failing and I would like to confirm my guess.
Update:
I used the wrong constants dictionary. One dictionary contains ints, the other contains strings (one for data base queries, one for printing).
_post_status = db.Column(
db.SmallInteger,
default=Consts.post_status["Draft"])
post_status contains integers, Consts.PostStatuses contains strings. In hind sight, really bad idea. I'm going to make a single dictionary that returns a tuple instead of two dictionaries.
#property
def post_status(self):
return Consts.post_status.get(getattr(self, "_post_status", None))
the problem is that your post_status property isn't acceptable for usage in an ORM level query, as this is a python descriptor which at the class level by default returns itself:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
_post_status = Column(String)
#property
def post_status(self):
return self._post_status
print (A.post_status)
print (A.post_status != 5678)
output:
$ python test.py
<property object at 0x10165bd08>
True
the type of usage you're looking for seems like that of a hybrid attribute, which is a SQLAlchemy-included extension to a "regular" python descriptor which produces class-level behavior that's compatible with core SQL expressions:
from sqlalchemy.ext.hybrid import hybrid_property
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
_post_status = Column(String)
#hybrid_property
def post_status(self):
return self._post_status
print (A.post_status)
print (A.post_status != 5678)
output:
$ python test.py
A._post_status
a._post_status != :_post_status_1
be sure to read the hybrid doc carefully including how to establish the correct SQL expression behavior, descriptors that work both at the instance and class level is a somewhat advanced Python technique.
Or how do I make this thing work?
I have an Interval object:
class Interval(Base):
__tablename__ = 'intervals'
id = Column(Integer, primary_key=True)
start = Column(DateTime)
end = Column(DateTime, nullable=True)
task_id = Column(Integer, ForeignKey('tasks.id'))
#hybrid_property #used to just be #property
def hours_spent(self):
end = self.end or datetime.datetime.now()
return (end-start).total_seconds()/60/60
And a Task:
class Task(Base):
__tablename__ = 'tasks'
id = Column(Integer, primary_key=True)
title = Column(String)
intervals = relationship("Interval", backref="task")
#hybrid_property # Also used to be just #property
def hours_spent(self):
return sum(i.hours_spent for i in self.intervals)
Add all the typical setup code, of course.
Now when I try to do session.query(Task).filter(Task.hours_spent > 3).all()
I get NotImplementedError: <built-in function getitem> from the sum(i.hours_spent... line.
So I was looking at this part of the documentation and theorized that there might be some way that I can write something that will do what I want. This part also looks like it may be of use, and I'll be looking at it while waiting for an answer here ;)
For a simple example of SQLAlchemy's coalesce function, this may help: Handling null values in a SQLAlchemy query - equivalent of isnull, nullif or coalesce.
Here are a couple of key lines of code from that post:
from sqlalchemy.sql.functions import coalesce
my_config = session.query(Config).order_by(coalesce(Config.last_processed_at, datetime.date.min)).first()
SQLAlchemy is not smart enough to build SQL expression tree from these operands, you have to use explicit propname.expression decorator to provide it. But then comes another problem: there is no portable way to convert interval to hours in-database. You'd use TIMEDIFF in MySQL, EXTRACT(EPOCH FROM ... ) / 3600 in PostgreSQL etc. I suggest changing properties to return timedelta instead, and comparing apples to apples.
from sqlalchemy import select, func
class Interval(Base):
...
#hybrid_property
def time_spent(self):
return (self.end or datetime.now()) - self.start
#time_spent.expression
def time_spent(cls):
return func.coalesce(cls.end, func.current_timestamp()) - cls.start
class Task(Base):
...
#hybrid_property
def time_spent(self):
return sum((i.time_spent for i in self.intervals), timedelta(0))
#time_spent.expression
def hours_spent(cls):
return (select([func.sum(Interval.time_spent)])
.where(cls.id==Interval.task_id)
.label('time_spent'))
The final query is:
session.query(Task).filter(Task.time_spent > timedelta(hours=3)).all()
which translates to (on PostgreSQL backend):
SELECT task.id AS task_id, task.title AS task_title
FROM task
WHERE (SELECT sum(coalesce(interval."end", CURRENT_TIMESTAMP) - interval.start) AS sum_1
FROM interval
WHERE task.id = interval.task_id) > %(param_1)s
I needed to use the text function and could not use 0 as an integer.
import sqlalchemy as sa
session.query(sa.func.coalesce(table1.col1, sa.text("0"))).all()
There is a complete example of making a func action similar to coalesc or nvl.
Note how it takes in arguements, and renders an expression... in this case NVL(a, b) when used with Oracle.
http://docs.sqlalchemy.org/en/latest/core/compiler.html#subclassing-guidelines
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import FunctionElement
class coalesce(FunctionElement):
name = 'coalesce'
#compiles(coalesce)
def compile(element, compiler, **kw):
return "coalesce(%s)" % compiler.process(element.clauses)
#compiles(coalesce, 'oracle')
def compile(element, compiler, **kw):
if len(element.clauses) > 2:
raise TypeError("coalesce only supports two arguments on Oracle")
return "nvl(%s)" % compiler.process(element.clauses)
Then when you want to use it...
from my_oracle_functions_sqla import coalesce
select([coalesce(A.value, '---')]) # etc
Hope that helps.