Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Instantiating object automatically adds to SQLAlchemy Session. Why?

From my understanding of SQLAlchemy, in order to add a model to a session, I need to call session.add(obj). However, for some reason, in my code, SQLAlchemy seems to do this automatically.

Why is it doing this, and how can I stop it? Am I approaching session in the correct way?

example

>>> from database import Session as db
>>> import clients
>>> from instances import Instance
>>> from uuid import uuid4
>>> len(db.query(Instance).all())
>>> 0 # Note, no instances in database/session
>>> i = Instance(str(uuid4()), clients.get_by_code('AAA001'), [str(uuid4())])
>>> len(db.query(Instance).all())
>>> 1 # Why?? I never called db.add(i)!

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base

import config

Base = declarative_base()

class Database():

    def __init__(self):
        db_url = 'postgresql://{:s}:{:s}@{:s}:{}/{:s}'.format(
            config.database['user'],
            config.database['password'],
            config.database['host'],
            config.database['port'],
            config.database['dbname']
        )
        self.engine = create_engine(db_url)
        session_factory = sessionmaker(bind=self.engine)
        self.session = scoped_session(session_factory)

Database = Database()

Session = Database.session

instance.py

from sqlalchemy import Column, Text, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import UUID, ARRAY

import database

Base = database.Base

class Instance(Base):
    __tablename__ = 'instances'

    uuid = Column(UUID, primary_key=True)
    client_code = Column(
        Text, ForeignKey('clients.code', ondelete='CASCADE'), nullable=False)
    mac_addresses = Column(ARRAY(Text, as_tuple=True),
                           primary_key=True)

    client = relationship("Client", back_populates="instances")

    def __init__(self, uuid, client, mac_addresses):
        self.uuid = uuid
        self.client = client
        self.mac_addresses = tuple(mac_addresses)

client.py

from sqlalchemy import Column, Text
from sqlalchemy.orm import relationship

import database
from database import Session as db

Base = database.Base

class Client(Base):
    __tablename__ = 'clients'

    code = Column(Text, primary_key=True)
    name = Column(Text)

    instances = relationship("Instance", back_populates='client')

    def __init__(self, code, name=None):
        self.code = code
        self.name = name

def get_by_code(code):
   client = db.query(Client).filter(Client.code == code).first()
   return client
like image 817
jcalero Avatar asked Jan 26 '16 12:01

jcalero


People also ask

Is it possible to cache a session in SQLAlchemy?

The Session is not designed to be a global object from which everyone consults as a “registry” of objects. That’s more the job of a second level cache. SQLAlchemy provides a pattern for implementing second level caching using dogpile.cache , via the Dogpile Caching example. Use the Session.object_session () classmethod available on Session:

What is the use of sessionmaker in SQLAlchemy?

However, to standardize how sessions are configured and acquired, the sessionmaker class is normally used to create a top-level Session configuration which can then be used throughout an application without the need to repeat the configurational arguments. Syntax: sqlalchemy.orm.session.sessionmaker (bind=None, **kwargs)

How do I Close a session in SQL alchemy?

Session.close () will close the Session and its corresponding connections, which means we are done with the Session and want to release the connection object associated with it. Understanding SQLAlchemy Sessions by Examples Let's use a simple example to illustrate how to use Session to insert objects into the databases. 1

How do I use SQLAlchemy with web applications?

Web applications. In this case, it’s best to make use of the SQLAlchemy integrations provided by the web framework in use. Or otherwise, the basic pattern is create a Session at the start of a web request, call the Session.commit () method at the end of web requests that do POST, PUT, or DELETE, and then close the session at the end of web request.


1 Answers

When you create a SQLAlchemy object and link it directly to another SQLAlchemy object, both objects end up in the session.

The reason is that SQLAlchemy needs to make sure you can query these objects. Take, for example, a user with addresses.

If you create a user in code, with an address, both the user and the address end up in the session, because the address is linked to the user and SQLAlchemy needs to make sure you can query all addresses of a user using user.addresses.all().

In that case all (possibly) existing addresses need to be fetched, as well as the new address you just added. For that purpose the newly added address needs to be saved in the database.
To prevent this from happening (for example if you only need objects to just calculate with), you can link the objects with their IDs/Foreign Keys:

address.user_id = user.user_id

However, if you do this, you won't be able to access the SQLAlchemy properties anymore. So user.addresses or address.user will no longer yield results.

The reverse is also true; I asked a question myself a while back why linking two objects by ID will not result in SQLAlchemy linking these objects in the ORM:

relevant stackoverflow question

another description of this behavior

like image 108
Erik Oosterwaal Avatar answered Sep 28 '22 03:09

Erik Oosterwaal