'How to work with Sqlalchemy session that is not explicitly passed to function(s)

Reading SQLAlchemy about Sessions we can read this:

https://docs.sqlalchemy.org/en/14/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it

https://docs.sqlalchemy.org/en/14/orm/contextual.html#session-lifespan

Both paragraphs are simple to understand but follow a top-down perspective.

My use case is reversed, I am building an internal library that will be pip-installed by REST API as a dependency, and to perform my operation I need access to the database via sqlalchemy. My goal is to preserve the separation of concerns and we lack specific knowledge of which function(s) will be used in which order and if those functions are calling each other or don't.

So let's assume we have the following functions:

def pick_mortgage(user):
    mortgages = pick_chepeast_mortgages(user, 3)

    fees = Session.query(MortgageFee).all ()

    # ... reads from database ...
    # ... saves to database ...

    return mortgages, fees

def pick_chepeast_mortgages(user, number):
    mortgages = find_possible_mortgages(user)

    # ... only calculate some stuff, no db involved ...

    return mortgages[:3]

def find_possible_mortgages(user):
    possible_mortgages = Session.query(models.Mortgage).all()

    # ... reads from database ...
    # ... saves to database ...

    return possible_mortgages

And it might be used in the following, Django Rest Framework-like style:

from mortgage_calulator import pick_mortgage

class ListMortgagesView(mixins.ListModelMixin, viewsets.GenericViewSet):
    def retrieve(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        mortgages, fees = mortgages.pick_mortgage(request.user)

        # ... do more stuff ...

        return Response(
            data,
            status=status.HTTP_200_OK,
        )

My Session is defined following way:

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from config import DATABASE_URL

engine = create_engine(DATABASE_URL, future=True, echo=True)
session_factory = sessionmaker(engine, expire_on_commit=False)

Session = scoped_session(session_factory)

My go-to strategy would be to create a decorator called managed_session and decorate all mortgage functions that need access to database with it:

def managed_session(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        with Session.begin_nested() as session:
            return func(*args, **kwargs)

    return inner

My confusion stems from the fact that documentation talks about session life-cycle to be separate and external and all the examples instantiate it and pass explicitly to the functions that may need it.

In my case, while session is somewhat external and separate from the function itself, it's in a python global scope. Am I using sqlalchemy incorrectly? Or is my approach valid and just one way of doing it?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source