I have a project where I want to isolate DB initialization (SQLAlchemy) from the other module so I've create a module
db_initializer.py:
engine = create_engine('sqlite:///db') # TODO from config file
Session = sessionmaker(bind=engine)
Base = declarative_base(bind=engine)
def create_tables():
Base.metadata.create_all(engine)
First of all I need to put create_all in a function because my model is in another package.
model/foo.py:
from core.db_initializer import Base
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
def __init__(self, name: str = None):
self.name = name
def __repr__(self):
return "<User(id=%d, name=%s)>" % (int(self.id), str(self.name))
And my main call create_tables.
Is there any other to do that? And now I need to create the engine with custom config (IP,User, ...) and only the main script know the config it's possible?
Something like
main.py:
import db_initializer as db
import model.foo
db.init(config) # which create the engine and create table
When I do something like that I got problem with the Base object which have not bind to the engine which is not created yet. Any solutions?
You don't need to create engine or session before declaring your models. You can declare models in model/foo.py:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
Let's assume you have some application in myapp.py. Declare it so it can be initialized with engine:
from sqlalchemy.orm import Session
import model.foo
class MyApp:
def __init__(self, engine):
self.engine = engine
def get_users(self):
session = Session(self.engine)
users = session.query(model.foo.User).all()
session.close()
return users
Create engine in main.py and use it to initialize models.foo.Base.metadata and other applications where you need it:
from sqlalchemy import create_engine
import model.foo
import myapp
engine = create_engine('sqlite:///db')
model.foo.Base.metadata.bind = engine
model.foo.Base.metadata.create_all()
app = myapp.MyApp(engine)
UPD: For scoped_session approach myapp.py can be look like this:
import model.foo
class MyApp:
def __init__(self, session):
self.session = session
def get_users(self):
return self.session.query(model.foo.User).all()
And main.py:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
import model.foo
import myapp
engine = create_engine('sqlite:///db')
session = scoped_session(sessionmaker(engine))
model.foo.Base.metadata.bind = engine
model.foo.Base.metadata.create_all()
app = myapp.MyApp(session)
Related
I am Facing Issue With put Method it shows me 200 Successfully response but it does not updated on database.
database.py:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
sql_database_url="mysql://root:zeehan#localhost:3306/mybookstore"
engine=create_engine(sql_database_url)
sessionLocal=sessionmaker(autocommit=False,bind=engine)
base=declarative_base()
This is my database file
main.py:
from typing import Optional
from fastapi import FastAPI,Query,Depends,Path,HTTPException
from sqlalchemy import Column,String,Integer,Float
from pydantic import BaseModel, errors
from sqlalchemy.orm import Session, query, session
from sqlalchemy import create_engine
from sqlalchemy.sql.schema import MetaData
from sqlalchemy import databases
from book_Database import engine,sessionLocal,base,sessionmaker
app=FastAPI()
class BookStore(base):
__tablename__="mybookstore"
id=Column(Integer,primary_key=True,index=True,autoincrement=True)
name=Column(String(255),index=True)
author=Column(String(225))
rating=Column(Float)
description=Column(String(225))
class BookStoreSchema(BaseModel):
name:str
author:str
rating:float
description:str
class BookStoreUpdateSchema(BaseModel):
id=int
name:str
author:str
rating:float
description:str
class Config:
orm_model=True
def get_bookdb():
db=sessionLocal()
try:
yield db
finally:
db.close()
base.metadata.create_all(bind=engine)
#app.get("/books")
def getstudent(db:Session=Depends(get_bookdb)):
return db.query(BookStore).all()
#app.get("/books/{id}")
def getstudent_by_name(id:int,db:Session=Depends(get_bookdb)):
bookstore = db.query(BookStore).get(id)
return bookstore
#app.post("/create-book")
def creatBook(*,BookStoreSchema:BookStoreSchema,rating :float=Query( None,gt=0,lt=6)):
bookstore = BookStore( name=BookStoreSchema.name,author=BookStoreSchema.author,rating=BookStoreSchema.rating ,description=BookStoreSchema.description)
with Session(bind=engine) as session:
session.add(bookstore)
session.commit()
return bookstore
#app.put("/update-book/{id}")
def update_student(id:int,bookstore:BookStoreUpdateSchema,db:Session=Depends(get_bookdb)):
bookstore = db.query(BookStore).get(id)
BookStore.name = bookstore.name
BookStore.author=bookstore.author
BookStore.rating=bookstore.rating
BookStore.description=bookstore.description
db.commit()
return bookstore
When I try to run this API using UVICorn I was running successfully but there is some issue with the put method. It shows successful response but it does not update the content of database.
In your database.py file, declarative_base() returns a class so instead of base make it Base
Base = declarative_base()
class BookStore(Base):
pass
In your main.py file for createBook function try something simple first:
#app.post("/create-book")
def creat_a_book(new_book: BookStoreSchema, db: Session = Depends(get_bookdb)):
book = BookStore(**new_book())
db.add(book)
db.commit()
db.refresh(book)
return book
change the variable name of the updated book in your update function arguments, and in your schema definition change orm_model to orm_mode let's call it updated_book
Try grabbing your book from bookstore like this:
bookstore = db.query(BookStore).filter(BookStore.id == id)
and update the book with the update function
bookstore.update(updated_book.dict(), synchronize_session=False)
db.commit()
I built an API using FastAPI, sqlalchemy, and pydantic. The ORM modeling was working great for some time, but I updated my code and broke the relational mapping somehow. Please help me figure out what I am doing wrong in my ORM mapping.
When I go to http://localhost:8000/test-rel I expect to see this:
[{"name":"P One","pk_id":1,"fk_id":1, "test":{"test_id":1, "test_name":"One"}},{"name":"P Two","pk_id":2,"fk_id":2, "test":{"test_id":2, "test_name":"Two"}},{"name":"P Three","pk_id":3,"fk_id":null, "test": null}]
but instead I see this:
[{"name":"P One","pk_id":1,"fk_id":1},{"name":"P Two","pk_id":2,"fk_id":2},{"name":"P Three","pk_id":3,"fk_id":null}]
Here is the test code portion of my project that I was using to troubleshoot:
schemas.py
from typing import Optional
from pydantic import BaseModel
class Test(BaseModel):
test_id: int
test_name: str
class TestP(BaseModel):
pk_id: int
fk_id: Optional[int]
name: Optional[str]
test: Test
class Config:
orm_mode = True
models.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DATE, DECIMAL
from sqlalchemy.orm import relationship
from sql_app.database import Base
class Test(Base):
__tablename__ = "test"
test_id = Column(Integer, primary_key=True)
test_name = Column(String)
class TestP(Base):
__tablename__ = "test_p"
pk_id = Column(Integer, primary_key=True)
fk_id = Column(Integer, ForeignKey("test.test_id"), nullable=True)
name = Column(String, nullable=True)
test = relationship("Test")
database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import urllib
params = urllib.parse.quote_plus("DRIVER={SQL Server Native Client 11.0};"
"SERVER=SQLEXPRESS;"
"DATABASE=Test;"
"Trusted_Connection=yes")
engine = create_engine(
"mssql+pyodbc:///?odbc_connect={}".format(params)
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
crud.py
from sqlalchemy.orm import Session
from sqlalchemy import and_, update, delete
from fastapi.encoders import jsonable_encoder
from typing import List
from sql_app import models
from sql_app import schemas
def test_rel(db: Session) -> List[schemas.TestP]:
return db.query(models.TestP).all()
main.py
import babel.numbers as bn
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from sql_app import crud, models, schemas
from sql_app.database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
#app.get("/test-rel/")
def test_rel(db: Session = Depends(get_db)):
return crud.test_rel(db=db)
Answered on github discussions by CaseIIT:
A couple of things:
def test_rel(db: Session) -> List[schemas.TestP]:
return db.query(models.TestP).all()
This returns a List[model.TestP]
Also the relationships are lazy loaded by default. Look at https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html for the eager loading options
My Response:
This was exactly what I was looking for. I was unaware that relationships were lazy loaded by default. My code is using pydantic to validate the response and my queries were failing validation because the pydantic validation was not seeing the joined tables. I updated my code to add eager loading and it is now working exactly as I expect it to.
test = relationship("Test", lazy='joined')
I want to write some py.test code to test 2 simple sqlalchemy ORM classes that were created based on this Tutorial. The problem is, how do I set a the database in py.test to a test database and rollback all changes when the tests are done? Is it possible to mock the database and run tests without actually connect to de database?
here is the code for my classes:
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker, relationship
eng = create_engine('mssql+pymssql://user:pass#host/my_database')
Base = declarative_base(eng)
Session = sessionmaker(eng)
intern_session = Session()
class Author(Base):
__tablename__ = "Authors"
AuthorId = Column(Integer, primary_key=True)
Name = Column(String)
Books = relationship("Book")
def add_book(self, title):
b = Book(Title=title, AuthorId=self.AuthorId)
intern_session.add(b)
intern_session.commit()
class Book(Base):
__tablename__ = "Books"
BookId = Column(Integer, primary_key=True)
Title = Column(String)
AuthorId = Column(Integer, ForeignKey("Authors.AuthorId"))
Author = relationship("Author")
I usually do that this way:
I do not instantiate engine and session with the model declarations, instead I only declare a Base with no bind:
Base = declarative_base()
and I only create a session when needed with
engine = create_engine('<the db url>')
db_session = sessionmaker(bind=engine)
You can do the same by not using the intern_session in your add_book method but rather use a session parameter.
def add_book(self, session, title):
b = Book(Title=title, AuthorId=self.AuthorId)
session.add(b)
session.commit()
It makes your code more testable since you can now pass the session of your choice when you call the method.
And you are no more stuck with a session bound to a hardcoded database url.
I add a custom --dburl option to pytest using its pytest_addoption hook.
Simply add this to your top-level conftest.py:
def pytest_addoption(parser):
parser.addoption('--dburl',
action='store',
default='<if needed, whatever your want>',
help='url of the database to use for tests')
Now you can run pytest --dburl <url of the test database>
Then I can retrieve the dburl option from the request fixture
From a custom fixture:
#pytest.fixture()
def db_url(request):
return request.config.getoption("--dburl")
# ...
Inside a test:
def test_something(request):
db_url = request.config.getoption("--dburl")
# ...
At this point you are able to:
get the test db_url in any test or fixture
use it to create an engine
create a session bound to the engine
pass the session to a tested method
It is quite a mess to do this in every test, so you can make a usefull usage of pytest fixtures to ease the process.
Below are some fixtures I use:
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
#pytest.fixture(scope='session')
def db_engine(request):
"""yields a SQLAlchemy engine which is suppressed after the test session"""
db_url = request.config.getoption("--dburl")
engine_ = create_engine(db_url, echo=True)
yield engine_
engine_.dispose()
#pytest.fixture(scope='session')
def db_session_factory(db_engine):
"""returns a SQLAlchemy scoped session factory"""
return scoped_session(sessionmaker(bind=db_engine))
#pytest.fixture(scope='function')
def db_session(db_session_factory):
"""yields a SQLAlchemy connection which is rollbacked after the test"""
session_ = db_session_factory()
yield session_
session_.rollback()
session_.close()
Using the db_session fixture you can get a fresh and clean db_session for each single test.
When the test ends, the db_session is rollbacked, keeping the database clean.
Im trying to import my app config (db_config.py) via:
>>> from app.db_config import db_session
Error received 'ImportError: No module named 'app.db_config'; 'app' is not a package
app.py looks like:
import pymysql.cursors
from flask import Flask, render_template, request
from flask_migrate import Migrate
from app.db_config import db_session
#app setup
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mypass#localhost/mydb'
db = SQLAlchemy(app)
migrate = Migrate(app, db)
#app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()
#app.route('/', methods=('GET', 'POST'))
def index():
return 'meow'
if __name__ == '__main__':
app.run()
db_config.py:
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('mysql+pymysql://root:mypass#localhost/mydb',
convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
def init_db():
import app.db_table
Base.metadata.create_all(bind=engine)
db_table.py:
from sqlalchemy import Column, Integer, String, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
from app.db_config import Base
Base = declarative_base()
class Tld(Base):
__tablename__ = 'Tld'
id = Column(Integer, primary_key=True)
uri = Column(String(80), unique=True)
tmstmp = Column(DateTime())
auth = Column(Boolean())
def __init__(self, uri=None):
self.uri = uri
self.tmstmp = tmstmp
self.auth = auth
What am I doing wrong here? I'm following this tutorial but Im not receiving the output i expected.
Is something wrong with my app.py that makes it not callable like that? or something else wrong with it?
(First time alchemy user)
Thank you so much
I resolved this myself. I needed to do from db_config import db_session, instead of from app.db_config import db_session
Working with SQL Alchemy for the first time and I keep getting an error when trying to commit.
Python3
SQLAlchemy 1.0.12
database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine("sqlite:///db.test")
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
def init_db():
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
import src.models
Base.metadata.create_all(bind=engine)
models.py
from datetime import datetime
from typing import List
from sqlalchemy import Column, Table
from sqlalchemy import Integer, String, DateTime
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from src.database import Base
class Answer(Base):
"""
The answer for a question.
"""
__tablename__ = "answer"
answer_id = Column(Integer(), primary_key=True)
answer = Column(String(), nullable=False, index=True)
question_id = Column(Integer(), ForeignKey("question.question_id"))
question = relationship("Question", back_populates="answer")
def __init__(self, answer: str, question_id: int = None):
self.answer = answer
self.question_id = question_id
class Question(Base):
__tablename__ = "question"
question_id = Column(Integer(), primary_key=True)
question = Column(String(), nullable=False, unique=True, index=True)
answer = relationship("Answer", uselist=False, back_populates="question")
def __init__(self, question: str, answer: Answer, options: List[Option] = None):
self.question = question
self.answer = answer
if options:
self.options = options
Create database
>>> from src.database import init_db
>>> init_db()
This creates the database as expected
Add items to the database
>>> from src.models import Question, Answer
>>> a = Answer("Yes")
>>> q = Question("Doctor Who?", a)
>>> from sqlalchemy.orm import Session
>>> s = Session()
>>> s.add(a)
>>> s.add(q)
Until now I did not get an error
>>> s.commit()
Here I get the error:
sqlalchemy.exc.UnboundExecutionError: Could not locate a bind configured on mapper Mapper|Question|question or this Session
What can I do to get this working?
From "Using the Session":
sessionmaker class is normally used to create a top level Session configuration
You shouldn't try and create a session using the Session class directly. Use the scoped_session wrapped sessionmaker defined in your database.py, as it has the correct binding configured etc.:
>>> from src.models import Question, Answer
>>> from src.database import db_session
>>> a = Answer("Yes")
>>> q = Question("Doctor Who?", a)
>>> s = db_session()
>>> s.add(a)
>>> s.add(q)
>>> s.commit()
You could also use a scoped_session as a proxy.