How to add obj to db with sqlmodel - python

I am new to FastAPI. How can i create record in db using sqlmodel and databases packages?
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
team: Optional[Team] = Relationship(back_populates="heroes")
#app.post("/hero", description="Create new hero")
async def create_hero(data: Hero):
hero = Hero(**data.dict())
q = insert(Hero).values(**hero.dict())
h_id = await db.execute(q)
When i finally try to do this, it shows me:
asyncpg.exceptions.NotNullViolationError: null value in column "id" of relation "hero" violates not-null constraint
DETAIL: Failing row contains (null, spider, black, 18, null).
Referring to the sqlmodel docs, id will be set automatically, but using sqlmodel.Session.
How to do the same thing with
import databases
db = databases.Database("postgresql+asyncpg://postgres:postgres#localhost:5432/testdb")

As some comments suggested, you should probably not use databases together with SQLModel. The beauty of SQLModel is (among many other things) that the database inserts are so simple: You just add your model objects to a database session and commit.
Another suggestion is to make use of the FastAPI dependencies to automatically initialize, start, inject and finally close a session in every route.
Here is a working example:
from typing import Optional
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio.engine import create_async_engine
from sqlalchemy.ext.asyncio.session import AsyncSession
from sqlalchemy.orm.session import sessionmaker
from sqlmodel import Field, SQLModel
api = FastAPI()
db_uri = "postgresql+asyncpg://postgres:postgres#localhost:5432/testdb"
engine = create_async_engine(db_uri, future=True)
session_maker = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
#api.on_event('startup')
async def initialize_db():
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.drop_all)
await conn.run_sync(SQLModel.metadata.create_all)
async def get_session() -> AsyncSession:
session = session_maker()
try:
yield session
finally:
await session.close()
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Optional[int] = Field(default=None, index=True)
...
#api.post("/hero", description="Create new hero")
async def create_hero(hero: Hero, session: AsyncSession = Depends(get_session)):
session.add(hero)
await session.commit()
await session.refresh(hero)
return hero
Note that for testing purposes I simply drop and (re-)create all database tables on startup.
Hope this helps.

Related

How to perform Postgres On_conflict_do_update on SQLAlchemy One to many relationship tables?

I'm looking for a way to implement SQLalchemy's On_conflict_Do_update feature on one to many relationship tables. I have looked at multiple examples but all of them show on single table implementation. But I am looking for an example where we can perform On_conflict_Do_update on multiple tables with relationship and auto generated id.
Here is my code:
from __future__ import annotations
from contextlib import contextmanager
from dataclasses import dataclass, field, asdict, is_dataclass
from functools import lru_cache
from timeit import default_timer
from typing import List
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.orm import registry, relationship, scoped_session, sessionmaker
engine = create_engine(
"postgresql+psycopg2://postgres:postgres#localhost:5432/postgres", echo=True
)
mapper_registry = registry()
#mapper_registry.mapped
#dataclass
class User:
__tablename__ = "user"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
name: str = field(default=None, metadata={"sa": Column(String(50))})
nickname: str = field(default=None, metadata={"sa": Column(String(12))})
addresses: List[Address] = field(
default_factory=list, metadata={"sa": relationship("Address")}
)
#mapper_registry.mapped
#dataclass
class Address:
__tablename__ = "address"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
user_id: int = field(init=False, metadata={"sa": Column(ForeignKey("user.id"))})
email_address: str = field(default=None, metadata={"sa": Column(String(50))})
#contextmanager
def get_session(cleanup=False):
session = scoped_session(
sessionmaker(
autocommit=False, autoflush=False, expire_on_commit=False, bind=engine
)
)
mapper_registry.metadata.create_all(engine)
try:
yield session
except Exception:
session.rollback()
finally:
session.close()
if cleanup:
mapper_registry.metadata.drop_all(engine)
def main():
with get_session(cleanup=False) as session:
data = [
User(
name="Matthew",
nickname="Matt",
addresses=[Address(email_address="matt123#abc.com")],
),
User(
name="Micheal",
nickname="Mike",
addresses=[Address(email_address="mike123#abc.com")],
),
]
session.add_all(data)
session.commit()
if __name__ == "__main__":
START_TIME = default_timer()
main()
elapsed = default_timer() - START_TIME
time_completed_at = "{:5.2f}s".format(elapsed)
print("Total Time to finish the job: {0}".format(time_completed_at))
Any Help is Appreciated.
Thanks

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()

Way to defer/load_only in SQLAlchemy 2.0

Is there any any way to incorporate defer() or load_only() types of methods in SQL 2.0 types of queries, except adding deffered() to the columns in tables?
What i.m trying to get is smthg like:
class Signals(Base):
__tablename__ = 'signals'
id = sa.Column(sa.BigInteger(), primary_key=True)
value_type_id = sa.Column(sa.Integer(), sa.ForeignKey('value_types.id'))
sources_id = sa.Column(sa.BigInteger(), sa.ForeignKey('sources.id'))
acl = sa.Column(JSONB())
stmt = select(Signals).join(Signals.value_types).defer(acl)
session = Session(engine)
# get all fields except acl
result = await session.execute(stmt)
I've expanded/modified your example a bit to have a full workable example.
In short, you can use .options(defer('field_name')) to prevent loading specific fields. However, implicitly loading those fields later (as the name "defer" implies) will not work.
Documentation link: https://docs.sqlalchemy.org/en/14/orm/loading_columns.html#deferred
import asyncio
import sqlalchemy as sa
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import declarative_base, relationship, defer, sessionmaker
meta = sa.MetaData()
Base = declarative_base(metadata=meta)
class ValueType(Base):
__tablename__ = 'value_types'
id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True)
class Signals(Base):
__tablename__ = 'signals'
id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True)
value_type_id = sa.Column(sa.Integer(), sa.ForeignKey('value_types.id'))
acl = sa.Column(sa.String())
value_type = relationship(ValueType)
async def async_main():
engine = create_async_engine(
"sqlite+aiosqlite:///", echo=True,
)
async_session = sessionmaker(
engine, expire_on_commit=False, class_=AsyncSession
)
async with engine.begin() as conn:
await conn.run_sync(meta.drop_all)
await conn.run_sync(meta.create_all)
async with async_session() as session:
async with session.begin():
v = ValueType()
session.add_all([
Signals(acl='acl1', value_type=v),
Signals(acl='acl2', value_type=v),
])
stmt = sa.select(Signals).join(Signals.value_type).options(defer('acl'))
results = await session.execute(stmt)
for r in results.scalars():
print(r)
# await session.refresh(r, ['acl']) # <-- uncomment to make the following line work
print(r.acl) # <-- error here, deferred loads will not work as the cursor is not awaited
await engine.dispose()
if __name__ == '__main__':
asyncio.run(async_main())

IntegrityError Foreign Key violation not raised [duplicate]

This question already has answers here:
Foreign key contraint is not getting set in SQLlite using flask alchemy [duplicate]
(1 answer)
SQLite3 "forgets" to use foreign keys
(3 answers)
Closed 1 year ago.
With the code below and following sequence I suspect an IntegrityError with a Foreign Key violation. Unfortunately the error isn't raised.
Create Customer(id=1)
Create Booking(cid=1)
Delete Customer(id=1)
#main.py
from typing import List
from fastapi.params import Depends
from schemas import Customer, ShowCustomer, Booking, ShowBooking
from fastapi import FastAPI, HTTPException
from database import get_db, engine, Base
from sqlalchemy.orm import Session
import models
models.Base.metadata.create_all(engine)
app = FastAPI()
#app.post("/customer")
async def customer(req: Customer, db: Session=Depends(get_db)):
new_customer=models.Customer(name=req.name, type=req.type)
db.add(new_customer)
db.commit()
db.refresh(new_customer)
return new_customer
#app.get("/customer", response_model=List[ShowCustomer])
async def create(db: Session=Depends(get_db)):
customers=db.query(models.Customer).all()
return customers
#app.delete('/customer/{id}')
def destory(id, db: Session=Depends(get_db)):
customer=db.query(models.Customer).filter(models.Customer.id == id)
if not customer.first():
raise HTTPException(status_code=404,
detail=f'Blog with the id {id} is not available '
)
customer.delete(synchronize_session=False)
db.commit()
return 'done'
#app.post("/booking")
async def read_root(req: Booking, db: Session=Depends(get_db)):
new_booking=models.Booking(name=req.name, type=req.type, cid=1)
db.add(new_booking)
db.commit()
db.refresh(new_booking)
return new_booking
#app.get("/booking",response_model=List[ShowBooking])
async def read_root(db: Session=Depends(get_db)):
bookings=db.query(models.Booking).all()
return bookings
# schemas.py
from pydantic import BaseModel
class Customer(BaseModel):
name:str
type:str
class ShowCustomer(Customer):
id:int
class Config():
orm_mode = True
class Booking(BaseModel):
name:str
type:str
class ShowBooking(Booking):
id:int
cid:int
class Config():
orm_mode = True
#models.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Customer(Base):
__tablename__ = "customers"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
type = Column(String)
booking = relationship("Booking", back_populates="customer")
class Booking(Base):
__tablename__ = "bookings"
id = Column(Integer, primary_key=True, index=True)
cid = Column(Integer, ForeignKey("customers.id"), nullable=False)
name = Column(String)
type = Column(String)
customer = relationship('Customer', back_populates="booking")
#database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
SQLALCHEMY_DATABASE_URL = 'sqlite:///./cloud2.db'
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
Base = declarative_base()
def get_db():
db=SessionLocal()
try:
yield db
finally:
db.close()

Is there a way to return two fields from SQLAlchemy / FastAPI relationship?

structure
.
└── sql_app
├── __init__.py
├── crud.py
├── database.py
├── main.py
├── models.py
└── schemas.py
main.py
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
#app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_name(db, name=user.name)
if db_user:
raise HTTPException(status_code=400, detail="Name already registered")
return crud.create_user(db=db, user=user)
#app.get("/users/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
#app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
#app.post("/users/{user_id}/messages/", response_model=schemas.Message)
def create_message_for_user(
user_id: int, message: schemas.MessageCreate, db: Session = Depends(get_db)
):
return crud.create_user_message(db=db, message=message, user_id=user_id)
#app.get("/messages/", response_model=List[schemas.MessageWithUser])
def read_messages(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
messages = crud.get_messages(db, skip=skip, limit=limit)
return messages
crud.py
from sqlalchemy.orm import Session
from . import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_name(db: Session, name: str):
return db.query(models.User).filter(models.User.name == name).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
db_user = models.User(name=user.name)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_messages(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Message).offset(skip).limit(limit).all()
def create_user_message(db: Session, message: schemas.MessageCreate, user_id: int):
db_item = models.Message(**message.dict(), user_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password#postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
models.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True, unique=True, nullable=False)
messages = relationship("Message", back_populates="user")
class Message(Base):
__tablename__ = "messages"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, unique=True, nullable=False)
message = Column(String, index=False)
user_id = Column(Integer, ForeignKey("users.id"))
user = relationship("User", back_populates="messages")
schemas.py
from typing import List
from pydantic import BaseModel
class MessageBase(BaseModel):
title: str
message: str
class MessageCreate(MessageBase):
pass
class Message(MessageBase):
id: int
user_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
name: str
class UserCreate(UserBase):
pass
class User(UserBase):
id: int
messages: List[Message] = []
class Config:
orm_mode = True
class UserIdentifier(UserBase):
id: int
name: str
class MessageWithUser(MessageBase):
id: int
title: str
message: str
user: UserIdentifier
Run cmd:
uvicorn sql_app.main:app --reload
Navigate to: 127.0.0.1:8000/docs
Add users and messages for users
Try to read messages
The example result is the correct format but it does not give a response just 500 error and the following
Error:
pydantic.error_wrappers.ValidationError: 4 validation errors for MessageWithUser
response -> 0
value is not a valid dict (type=type_error.dict)
response -> 1
value is not a valid dict (type=type_error.dict)
response -> 2
value is not a valid dict (type=type_error.dict)
response -> 3
value is not a valid dict (type=type_error.dict)
The expected output is what is shown in the example output of the docs page, with UserIdentifier being added to Message but not any other fields from User if there were any, in this case there is not
I don't think that it is possible to cast a SqlAlchemy object with dependencies to a pydantic model out of the box.
#tiagolo (the creator of FastAPI) did this pydantic-sqlalchemy method that seems to do the trick of what you want:
https://github.com/tiangolo/pydantic-sqlalchemy
Otherwise, I think that you can create a list of MessageWithUser manually and return it.

Categories