2

I am using raw SQLAlchemy in a Flask app and used to face a lot of troubles using SQLAlchemy sessions. I used to get 500 INTERNAL SERVER ERROR frequently mentioning that the last session was not closed properly or a rollback error. After identifying these issues I made modifications to my code and so far it is working great for me. But still, I get the error at times especially when my API breaks before the response. I want to know what will be the best way to use these sessions so that the commit(), rollback(), close(), etc. happen at the right time and it is generalized for all the APIs. I know that Flask-SQLAlchemy is capable of handling this automatically but I want to stick to raw SQLAlchemy.

So far the best working code for me has been -

from flask import Flask
from sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind = mysql_engine())
db_session = Session()

@app.route('/get-score', methods=['POST'])
def handle_route1():

    ...

    row = db_session.query(DB_Model_1) \
        .filter(DB_Model_1.user_id == user_id) \
        .all()

    row = row[0] if row else None
    if not row:
        db_session.close()
        return {}

    db_session.close()

    return {
        'userId': row.user_id,
        'score' : row.score
    }

@app.route('/insert-score', methods=['POST'])
def handle_route2():

    ...

@app.route('/update-score', methods=['POST'])
def handle_route3():

    ...

@app.route('/delete-score', methods=['POST'])
def handle_route3():

    ...

I am taking care of GET, INSERT, UPDATE, DELETE in all the different routes and I am looking for a way to handle these transactions as efficiently as possible to avoid breaking the connection to the database due to any error in API.

One way is to use try-except blocks but I believe there must be a better way than mentioning try-except blocks in every route individually.

2 Answers 2

4

I believe that most elegant way is by using context manager with session / transaction scope:

from contextlib import contextmanager

@contextmanager
def transaction_scope(session, close_at_exit=False):
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        if close_at_exit:
            session.close()

You can use it in two different way:

1.

@app.route('/get-score', methods=['POST'])
def handle_route1():
    with Session() as session:
        with transaction_scope(session):
            ...
            row = session.query(DB_Model_1) \
              .filter(DB_Model_1.user_id == user_id) \
              .all()
            row = row[0] if row else None
        if not row:
           return {}
        return {
           'userId': row.user_id,
           'score' : row.score
        }
@app.route('/get-score', methods=['POST'])
def handle_route1():
    session = Session()
    with transaction_scope(session, close_at_exit=True):
       ...
       row = session.query(DB_Model_1) \
          .filter(DB_Model_1.user_id == user_id) \
          .all()
       row = row[0] if row else None
       if not row:
          return {}
       return {
          'userId': row.user_id,
          'score' : row.score
       }
Sign up to request clarification or add additional context in comments.

10 Comments

I am not sure if I understand this completely since we are still using db_session for the SQLAlchemy query. Any explanation or reference will be appreciated.
When using session.add() in 1st way, it throws a sqlalchemy.orm.exc.DetachedInstanceError and using db_session.add() neither throws an error nor adds a record to the database. 2nd way works while adding a new record for the first time but second API hit gives 500 error MySQL server has gone away
Please note I am not explicitly closing or using rollback sessions within the API function but instead suppose that the context manager should look after it
You should not use db_session that is created outside endpoint anymore. I have corrected my solution, there should be only session
Tried that, gives me sqlalchemy.orm.exc.DetachedInstanceError error for the 1st way. Any idea on this or else I shall debug it?
|
2

I highly recommend to read this part of the SQLAlchemy documentation.

But tl:dr you can use python context manager to control the scope of the session:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('postgresql://scott:tiger@localhost/')
Session = sessionmaker(engine)

with Session.begin() as session:
    session.add(some_object)

1 Comment

During 2nd successive run, it throws sqlalchemy.exc.OperationalError: MySQL server has gone away

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.