Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy sessions error

Background: Flask / Flask-SQLAlchemy / Flask-WTF, using declarative and scoped session

Simple POST operation:

@tas.route('/order_add', methods=['GET', 'POST'])  
def tas_order_add():  
    if request.method == 'POST':
        order_form = OrderForm()
        if order_form.validate_on_submit():
            order = Order()
            order_form.populate_obj(order)
            db_session.add(order)
            db_session.commit()

Now trying to run it I get an error:

InvalidRequestError: Object '' is already attached to session '1' (this is '2')

Changing add to merge solves the problem, but:

  • I don't know why do I have to merge an object while I just initiated it
  • If I do change add to merge and try to define one of the properties something in line

    order = Order()
    order_form.populate_obj(order)
    order.order_status = OrderStatus.query.filter(OrderStatus.code=='PLACED').first()
    db_session.merge(order)
    db_session.commit()
    

    I get the same error, just now on OrderStatus object

    InvalidRequestError: Object '' is already attached to session '2' (this is '1')

Can someone point me where I'm doing something wrong because it's driving me nuts. I do have some experience with SQLAlchemy but it's the first time I see such a behaviour and I can't pinpoint the problem.

Searching all I found was a problem with double database session initialization but I don't belive it's this case.

EDIT

db_session is defined in separate file database.py with following content

from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker

engine = create_engine('sqlite:///fundmanager_devel.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
like image 674
Paweł Pogorzelski Avatar asked Nov 04 '13 14:11

Paweł Pogorzelski


People also ask

What is a session in SQLAlchemy?

What are SQLAlchemy Sessions? What does the Session do? One of the core concepts in SQLAlchemy is the Session. A Session establishes and maintains all conversations between your program and the databases. It represents an intermediary zone for all the Python model objects you have loaded in it.

How does SQLAlchemy handle update and delete statements?

SQLAlchemy will always issue UPDATE or DELETE statements for dependent rows which are currently loaded in the Session. For rows which are not loaded, it will by default issue SELECT statements to load those rows and update/delete those as well; in other words it assumes there is no ON DELETE CASCADE configured.

What's new in SQLAlchemy?

New in version 1.4. SQLAlchemy 2.0 will standardize the production of SELECT statements across both Core and ORM by making direct use of the Select object within the ORM, removing the need for there to be a separate Query object. This mode of operation is available in SQLAlchemy 1.4 right now to support applications that will be migrating to 2.0.

Is it possible to check and raise errors in SQLAlchemy?

However sqlalchemy already does that for you - so Is just the same. The check-and-raise part is already inside SQLAlchemy. ArgumentError - Raised when an invalid or conflicting function argument is supplied. This error generally corresponds to construction time state errors. DBAPIError - Raised when the execution of a database operation fails.


2 Answers

seems a problem with session mix up, I used it as follows and it works:

from <project> import db
from <project.models import Category

category = QuerySelectField('category', query_factory=lambda: db.session.query(Category), get_pk=lambda a: a.id, get_label=lambda a: a.name)
like image 124
2ni Avatar answered Oct 10 '22 07:10

2ni


I had a similar problem with QuerySelectField:

forms.py :

class SurgeryForm(Form):
    study = QuerySelectField('Study',
                             query_factory=StudyGroup.query.all,
                             get_label='name')

models.py

class Animal(db.Model):
    study_id = db.Column(db.Integer, db.ForeignKey('study_group.id'))

class StudyGroup(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    animals = db.relationship('Animal', backref='group', lazy='dynamic')

views.py:

def do_surgery():
    form = SurgeryForm(request.form)

    if form.validate_on_submit():
        a = models.Animal()
        form.populate_obj(a)  # Gather the easy stuff automagically
        #a.group = form.data['study']  #FAILS!
        a.study_id = form.data['study'].id  #WORKS!

It seems that SQLAlchemy (or possibly Flask-SQLAlchemy or Flask-WTF) uses one session to gather the values in the QuerySelectField and another session to create the new Animal object.

If you try to attach the StudyGroup object to the Animal using the backref (Animal.group) you'll run into this problem (since the objects associated with different sessions). The workaround that I'm using is to set the foreign key (Animal.study_id) directly.

Clearly I'm a little late to the party with this answer, but I hope it helps someone!

like image 32
mattbo Avatar answered Oct 10 '22 07:10

mattbo