How to use dependency injection with simple functions in Fast API? - python

I have a simple function in Fast API app that gets called by a cron, something like this (note that this is not a get or post Fast API method but a simple function):
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
where get_db is:
from database import SessionLocal
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
And database.py is:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "://url-to-db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Since crud method takes db (object of SessionLocal yielded by get_db) as a param, how do I use it as a dependency injection with my function just like we use in GET or POST as shown in code read_user method above.

Related

sessionmaker object has no attribute add

I am trying to insert data into postgresql server. While doing so, when I try to add the data into the SQLalchemy session, I am getting the error "sessionmaker object has no attribute add":
from sqlalchemy.orm import Session
def create_new_user(user: UserCreate, db: Session):
user=User(username= user.username,
email=user.email,
hashed_password= Hasher.get_password_hash(user.password),
is_active=True,
is_superuser=False
)
db.add(user)
db.commit()
db.refresh(user)
return user
You should create an object from Session as show in this example; which uses a context-manager.
Currently, I use the scoped_session pattern (suitable for most web apps). Here is how my session initialization looks like:
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine("sqlite://")
Session = scoped_session(sessionmaker(bind=engine))
Session() # --> returns the same object in the same thread

Flask and SqlAlchemy session

I've been building a flask app and using flask-sqlalchemy and flask-migrate.
Lately I decided to replace the extension with plain sqlalchemy and alembic and I started to think what's the best place to store the db session object (sqla).
Right now I have the following:
Base = declarative_base()
def init_db_session(app, expire_on_commit=True):
"""
Initialize the database
"""
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'], convert_unicode=True)
db_session = scoped_session(
sessionmaker(autocommit=False, autoflush=False, expire_on_commit=expire_on_commit, bind=engine)
)
Base.query = db_session.query_property()
return db_session
def init_app(app):
"""
Flask app initialization and bootstrap
"""
init_logging(app)
app.celery = init_celery(app)
app.db_session = init_db_session(app)
but given some docs and examples online I'm wondering if using flask global g is any better
They both belong to the same context, I read about that in the docs and in the code but still can't get my head around the practical differences and the potential drawbacks of having it in the current_app compared to g
The flask documentation has a recommendation to declare the session in the module scope. This is also how I use it in my own code.
Base = declarative_base()
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI'], convert_unicode=True)
db_session = scoped_session(
sessionmaker(
autocommit=False,
autoflush=False,
expire_on_commit=expire_on_commit,
bind=engine
)
)
Base.query = db_session.query_property()
def init_db():
""" not much to do here if migrations are handled else where. """
pass
def init_app(app):
"""
Flask app initialization and bootstrap
"""
init_logging(app)
app.celery = init_celery(app)
app.db_session = init_db_session(app)

How to verify session and engine objects are able to connect

The lines below declare the engine and session objects.
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
URL = 'mysql://root:pass#12.34.56.78/mydb'
engine = create_engine(URL, echo=False)
session = scoped_session(sessionmaker())
session.remove()
session.configure(bind=engine, autoflush=False, expire_on_commit=False)
I would like to know how engine and session objects could be used to check if they are able to connect to the database with URL, database name and credentials provided.

SQLAlchemy mock Session connection

Everywhere I need to query the database in my application I import the Session from this file.
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
db_name = 'analytics'
db_url = 'postgresql+psycopg2://**:**#localhost:5432/{0}'.format(db_name)
Engine = create_engine(db_url, echo=False)
SessionMaker = sessionmaker(bind=Engine, autoflush=False)
Session = scoped_session(SessionMaker)
Base = declarative_base()
I'm writing some unit tests and need to mock the Session object so that whenever it is imported in my app, it connects to the unittest database rather than the analytics database.
class FacebookTest(unittest.TestCase):
#classmethod
def setUpClass(self):
"""
create unittest database and insert Premier League 2015-2016 season
"""
con = connect(user='**', host='localhost', password='**', database='postgres')
con.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
cur = con.cursor()
cur.execute('DROP DATABASE IF EXISTS unittest')
cur.execute('CREATE DATABASE unittest')
cur.close()
con.close()
Engine = create_engine('postgresql+psycopg2://**:**#localhost:5432/unittest')
Base.metadata.create_all(Engine)
Session.connection = Engine.connect()
Set your mock database on a different port, for example 5433 and change the port depending on which DB you want to connect to.
You can do like this. Let's say you have a db.py where you initiate your sessions and import them all over. Modify it like this (ignore reference to configurator, that's my internal module):
import psycopg2
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import configurator
Base = declarative_base()
def connect():
cfg = configurator.Configurator("database")
return psycopg2.connect(
user=cfg.user, host=cfg.host, dbname=cfg.db, port=cfg.port, password=cfg.pwd
)
def connect_test():
conn_string = (
"host='localhost' user='postgres' password='yourPassword' port='5433'"
)
return psycopg2.connect(conn_string)
def Session(database='dev'):
if database == 'dev':
engine = create_engine("postgresql://", creator=connect)
elif database == 'test':
engine = create_engine("postgresql://", creator=connect_test)
Session = sessionmaker(bind=engine)
return Session()
UPDATE: Important correction - added brackets in return session(), sorry about that.
UPDATE2: Changed code after explicit testing.

Declarative SQLAlchemy CREATE SQLITE in memory tables

This is how I setup my database for an application (in Flask):
from sqlalchemy.engine import create_engine
from sqlalchemy.orm import scoped_session, create_session
from sqlalchemy.ext.declarative import declarative_base
engine = None
db_session = scoped_session(lambda: create_session(bind=engine,
autoflush=False, autocommit=False, expire_on_commit=True))
Base = declarative_base()
Base.query = db_session.query_property()
def init_engine(uri, **kwargs):
global engine
engine = create_engine(uri, **kwargs)
Base.metadata.create_all(bind=engine)
return engine
If I connect to a file database that has had tables created already, everything works fine, but using sqlite:///:memory: as a target database gives me:
OperationalError: (OperationalError) no such table: users u'DELETE FROM users' ()
when querying like so for ex.:
UsersTable.query.delete()
db_session.commit()
I am accessing this code from a unit test. What is the problem?
Thanks
Edit:
Working setup of the application:
app = Flask(__name__)
app.config.from_object(__name__)
app.secret_key = 'XXX'
# presenters
from presenters.users import users
# register modules (presenters)
app.register_module(users)
# initialize the database
init_engine(db)
The code you posted doesn't contain any table/class declaration. Are you sure that the declaration is done before init_engine() is called?

Categories