There is a code:

app = Flask(__name__) db = SQLAlchemy(app) def make_db_session(engine): return scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine)) def make_db_engine(): return create_engine(app.config['SQLALCHEMY_DATABASE_URI'], convert_unicode=True) engine = make_db_engine() db_session = make_db_session(engine) 

Next, I see several examples of referring to the session.

  1. First option

     db.session.some_action() 
  2. Second option

     db_session.some_action() 
  3. Third option

     session = db_session() session.some_action() 

What are their differences? Which way is more true?

P.S. Another interesting thing is that if you do the sample as follows

 model = SomeModel.query.filter(some_filter).first() model.field = new_value 

It is necessary to save as

 db.session.commit() 

That is, it seems that the default session is db.session .

    1 answer 1

    You can start with the differences:

    The function make_db_session returns an object of type ScopedSession ( scoped_session is a class that was named for some reason as a function). scoped_session is the usual such session, but in addition to the blah blah blah about threads, its main difference seen by users is as follows:

     # ΠžΠ±Ρ‹Ρ‡Π½Ρ‹Π΅ сСссии Session = sessionmaker(__config_here__) session_1 = Session() session_2 = Session() my_db_entry = Entry(slug='Yolo') session_1.add(my_db_entry) # На этом ΠΌΠΎΠΌΠ΅Π½Ρ‚Π΅ my_db_entry привязан Π² ΠΏΠ΅Ρ€Π²ΠΎΠΉ сСссии session_2.add(my_db_entry) 

    # На этом ΠΌΠΎΠΌΠ΅Π½Ρ‚Π΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ½Π΅Ρ‚ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ # На этом ΠΌΠΎΠΌΠ΅Π½Ρ‚Π΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ½Π΅Ρ‚

    That is, adding the same object from different sessions will not work.

    ScopedSession is actually the same session always (in this thread, the explanation below). Those.

     session_factory = sessionmaker(bind=some_engine) print(type(session_factory)) >>> <class 'sqlalchemy.orm.session.sessionmaker'> session_registry = ScopedSession(session_factory) print(type(session_registry)) >>> <class 'sqlalchemy.orm.scoping.scoped_session'> 

    When you call session_registry() ( __call__ ) without parameters, it returns what is in the register. Register is an instance of the class ThreadLocalRegistry . What was once put there cannot be changed (Actually, I am not sure - perhaps the bug method or the extra code is strangely written - see 64 lines in sqlalchemy/orm/scoping.py ). Also, if you call session_registry() from another thread, then it will already have another session created specifically for it. That is, the "thread-local" in the documentation means that for each thread its own session, and not that the session is somehow cleverly synchronized, put locks, etc. This is achieved by using threading.local () in the register.

     session_1 = session_registry() session_2 = session_registry() print(type(session1), type(session_2)) >>> <class 'sqlalchemy.orm.session.Session'> session_1 is session_2 >>> True 

    this is the same session. This means, for example, that the register with the session can be made global. It is also not necessary to create objects of the Session class ( session_1 and session-2 ) - since the session alone will go completely to session_registry.query(MyClass).all()

    Also in the same section there is a visual diagram showing when to create a register, when to create and close a session.

    It is difficult to say what to use "more correctly", but I would say that scoped_session , and not create your session session Session(...) , because sessions are not thread-safe and you will have to create a new session for each stream, or to synchronize somehow.

    To use your session, rather than the provided flask-sqlalchemy , there are several ways:

    call query on the desired session:

     session.query(MyObject.prop, AnotherOne.prop2).filter(blah-blah) 

    You can also set a default session on your models. Each model can have a query field, where you can add a "query" (at this moment everything starts to be so confusing that it cannot be explained). Something like that . There is also a small discussion of the difference between these approaches at ENSO.

    Update:

    Returning to what option is "correct", I will say that the second and third are equivalent, but it is worth using the third to know for sure that the Session object is used, and not some other. But then why use flask-SQLAlchemy ? It turns out that much of the fact that this library hides from the user (session settings, callbacks like before_rollback , after_commit , after_flush , etc. are hung up for the library session. Also uses its own special "request" - orm.Query class with all sorts of buns , like 404 automatic sending, if there is no object in the database. Logs are all, built-in pagination (page by page queries in the database).

    Also, the default session - db.session - during the creation of models, it is she who is recorded in the property query . You can verify this by finding the SQLAlchemy class in the package, in it the __init__ method, and in it the line in which the Model field is initialized (which is then used as the base class). This field is initialized by the result of the function make_declarative_base and this is how this function looks (quite simple):

     def make_declarative_base(self, metadata=None): """Creates the declarative base.""" base = declarative_base(cls=Model, name='Model', metadata=metadata, metaclass=_BoundDeclarativeMeta) base.query = _QueryProperty(self) return base 

    As you can see, the coveted query initialized with its class, which is created with the flask-SQLAlchemy session, and not with any other. So The default session was explicitly declared in the db.Model base class. You can change it by overwriting the query with the one you need.

    • As I understand it, it would still be correct to use exactly the one created through scoped_session (that is, the third option)? Now in the model classes (inheriting from db.Model ) I don’t declare the query field at all, it turns out, is some other session being used by default? - Nicolas Chabanovsky ♦
    • @NicolasChabanovsky, updated the answer. - m9_psy