SQLAlchemy Object already attached to session - python

I'm trying to get a server for an app working, but I'm getting an error upon login:
[!] Object '<User at 0x7f12bc185a90>' is already attached to session '2' (this is '3')
It seems the session I'm adding is already on the database. This is the snippet of code that is causing the problem:
#app.route('/login', methods=['POST'])
def login():
u = User.query.filter(User.username == request.form["username"]).first()
if not u or u.password != request.form["password"]:
return error("E1")
s = Session.get_by_user(u)
if s is not None:
db_session.delete(s)
db_session.commit()
print db_session.execute("SELECT * FROM sessions").fetchall()
s = Session(u)
db_session.add(s)
db_session.commit()
return jsonify(s.values)
As you can see, I'm printing the content from the sessions table before trying to add anything, and it is empty! ([])
What else could be causing this?
Here is the 'Session' implementation:
class Session(Base):
__tablename__ = "sessions"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), unique=True)
user = relationship(User)
key = Column(String(50), unique=True)
created = Column(DateTime)
def __init__(self, user=None):
self.user = user
self.key = base64.encodestring(os.urandom(24)).strip()
self.created = datetime.now()
def __repr__(self):
return '<Session %r>' % (self.key)
#property
def values(self):
return {"username" : self.user.username,
"key" : self.key,
"created" : str(self.created),
}
#classmethod
def get_by_key(cls, key):
s = cls.query.filter(cls.key == key).first()
#print datetime.now() - s.created
if s and datetime.now() - s.created > settings.SESSION_LIFETIME:
s = None
return s
#classmethod
def get_by_user(cls, user):
s = cls.query.filter(cls.user == user).first()
if s and datetime.now() - s.created > settings.SESSION_LIFETIME:
s.query.delete()
db_session.commit()
s = None
return s

As #marcinkuzminski mentioned, you can't add an object that is already attached to another session. Just pulling in the original session from the object with object_session() is risky, though, if you aren't sure that session originated in the same thread context you're currently operating in. A thread-safe method is to use merge():
local_object = db_session.merge(original_object)
db_session.add(local_object)
db_session.commit()

Object you're trying to modify is already attached to another session.
Maybe you have wrong imports, and db_session is a new instance.
A good workaround to this is to extract the current bound session and use it:
Instead of:
db_session.add(s)
Do:
current_db_sessions = db_session.object_session(s)
current_db_sessions.add(s)

This db session issue will arise if you are having server.py and model.py importing each other
server.py
from flask import Flask
import os
import models as appmod #################### importing models here in server.py<----------
app = Flask(__name__) # L1
app.config.from_object(os.environ['APP_SETTINGS']) # L2
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # L3
database = SQLAlchemy(app) # L4
db = database # L5
#app.route('/item_delete/<id>', methods=['DELETE'])
def remove_method(id = None):
data_rec = appmod.Employee.query.get(id)
db.session.delete(data_rec)
db.session.commit()
return "DELETE"
if __name__ == '__main__':
app.run(port=5000, host='0.0.0.0',debug=True,threaded=True)
models.py
from server import db #################### importing server in models.py here <------------
from sqlalchemy.dialects.mysql import JSON
class Employee(db.Model):
__tablename__ = 'employe_flask'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
datetime = db.Column(db.DateTime)
designation = db.Column(db.String(128))
def __init__(self, name, datetime, designation):
self.name = name
self.datetime = datetime
self.designation = designation
#staticmethod
def delete_rec(data_rec):
db.session.delete(data_rec)#.delete
db.session.commit()
def __repr__(self):
record = {"name":self.name,"date":self.datetime.ctime(),"designation":self.designation}.__str__()
return record
Remove the line L1 to L5 from server.py and place it in common file like settings.py
and import 'app' and 'db' to server.py and import db in models.py
like this files below
server.py
from flask import Flask
import os
import models as appmod
from settings import app, db
#app.route('/item_delete/<id>', methods=['DELETE'])
def remove_method(id = None):
data_rec = appmod.Employee.query.get(id)
db.session.delete(data_rec)
db.session.commit()
return "DELETE"
if __name__ == '__main__':
app.run(port=5000, host='0.0.0.0',debug=True,threaded=True)
settings.py
import os
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__) # L1
app.config.from_object(os.environ['APP_SETTINGS']) # L2
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # L3
database = SQLAlchemy(app) # L4
db = database # L5
models.py
from settings import db
from sqlalchemy.dialects.mysql import JSON
class Employee(db.Model):
__tablename__ = 'employe_flask'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
datetime = db.Column(db.DateTime)
designation = db.Column(db.String(128))
def __init__(self, name, datetime, designation):
self.name = name
self.datetime = datetime
self.designation = designation
#staticmethod
def delete_rec(data_rec):
db.session.delete(data_rec)#.delete
db.session.commit()
def __repr__(self):
record = {"name":self.name,"date":self.datetime.ctime(),"designation":self.designation}.__str__()
return record

This error means the record you are handling is attached to 2 different session(db)!
One of the reasons is that you may define your model with one db = SQLAlchemy(app) and add/insert/modify the database with another!
My solution is UNIFORMING THE DB!
try this:
u = db.session.query(User).filter(User.username == request.form["username"]).first()
Instead of this:
u = User.query.filter(User.username == request.form["username"]).first()

I had this problem too.
I created a test_file.py and added this code:
from app import app
from models import Tovar
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
tovardel = Tovar.query.filter(Tovar.region == 1 and Tovar.price == 12).first()
db.session.delete(tovardel)
tovar = Tovar.query.filter(Tovar.region == 1 and Tovar.price == 12).first()
print(tovar.description)
and when I ran the code I got this error:
Object '<Tovar at 0x7f09cbf74208>' is already attached to session '1' (this is '2')
PROBLEM SOLVING:
If you have db = SQLAlchemy(app) in, for example, text_file.py, and in app.py, you get this problem all time. You should del db = SQLAlchemy(app), and import db from app from app import db

I faced the same issue. I was defining models in a separate file and I had to call SQLAlchemy twice. That's why there were two different sessions were running.
I solved this by doing following:
In case you are trying to remove an object from db:
Just create the removeObject function inside the model

Related

sqlalchemy db.create_all() creates unexpected tables

I tried adding a second table to my website database. First one works and is where I collect inputted e-mails. I wanted a second table that connects inputted instagram handles. So I made this code for my app.py file:
from flask import Flask, jsonify, request, send_from_directory
from flask_sqlalchemy import SQLAlchemy
import datetime
from flask_marshmallow import Marshmallow
from flask_cors import CORS
app = Flask(__name__, static_url_path='', static_folder='frontend/build')
CORS(app)
app.config['SQLALCHEMY_DATABASE_URI']='mysql://***:***#localhost/pobble_data'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']= False
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Emails(db.Model):
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(120))
def __init__(self, email):
self.email = email
class EmailsSchema(ma.Schema):
class Meta:
fields = ('id','email')
class IGHandles(db.Model):
id = db.Column(db.Integer, primary_key = True)
ighandle = db.Column(db.String(120))
def __init__(self, ighandle):
self.ighandle = ighandle
class IGHandlesSchema(ma.Schema):
class Meta:
fields = ('id','ighandles')
email_schema = EmailsSchema()
emails_schema = EmailsSchema(many=True)
ighandle_schema = IGHandlesSchema()
ighandles_schema = IGHandlesSchema(many=True)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder, 'index.html')
#app.route('/get', methods = ['GET'])
def get_emails():
all_emails = Emails.query.all()
results = emails_schema.dump(all_emails)
return jsonify(results)
def get_ighandles():
all_ighandles = IGHandles.query.all()
results = ighandles_schema.dump(all_ighandles)
#app.route('/get/<id>', methods = ['GET'])
def post_emails(id):
results = Emails.query.get(id)
return email_schema.jsonify(results)
def post_ighandles(id):
results = IGHandles.query.get(id)
return ighandle_schema.jsonify(results)
#app.route('/add', methods = ['POST'])
def add_email2():
email = request.json['email']
entry = Emails(email)
db.session.add(entry)
db.session.commit()
return email_schema.jsonify(entry)
def add_ighandles():
ighandle = request.json['ighandle']
entry = IGHandles(ighandle)
db.session.add(entry)
db.session.commit()
if __name__ == "__main__":
app.run(debug=True)
I saved all of this and went to my python interpreter and ran
from app import db and
db.create_all()
and I get two tables: emails and ig_handles
I expected the following two tables because they are the class names: Emails and IGHandles.
In fact I searched all of my code and don't see ig_handles anywhere so am not sure where it gets that from. I think it's some sort of cached name I might have used when I was experimenting with making the tables earlier.
I've tried running db.session.expire_all() and that doesn't help.

How to solve SQL alchemy circular imports issue

Below is a sample code base for my flask sqlalchemy project. Below is a snippet about of my project structure.
Below are my problem codes :
main.py:
from application import app
from application.database import db
#app.before_first_request
def create_tables():
db.create_all()
if __name__ == "__main__":
db.init_app(app)
app.run(debug=True)
application/init.py
__package__ = "application"
from flask import Flask,session
from os import urandom
from config import BaseConfig
import logging.config
app = Flask(__name__,template_folder='templates')
from models.user_model import Users
from models.question_model import Questions
from views import *
app.config.from_object(BaseConfig)
log_config = app.config['LOGGING_CONFIG']
logging.config.dictConfig(log_config)
logger = logging.getLogger("question_answer_app")
app.logger.handlers = logger.handlers
app.logger.setLevel(logger.level)
models/user_model.py
__package__ = "application.models"
from ..database import db
from .. import app
from flask import session
class Users(db.Model):
__table_args__ = {'keep_existing': True}
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(20),nullable=False)
password = db.Column(db.String(20),nullable=False)
expert = db.Column(db.Boolean,nullable=False)
admin = db.Column(db.Boolean,nullable=False)
#classmethod
def get_by_name(cls,name):
user = None
user = cls.query.filter_by(name=name).first()
return user
#classmethod
def get_by_id(cls,id):
user = None
user = cls.query.filter_by(id=id).first()
return user
#classmethod
def get_experts(cls):
experts = []
for expert in cls.query.filter(cls.expert == True).all():
temp = {}
temp['id'] = expert.id
temp['name'] = expert.name
app.logger.debug('The expert got from the db is %s',temp['name'])
experts.append(temp)
return experts
#classmethod
def get_all_nonadmin_users(cls):
users = []
users = cls.query.filter_by(admin=False).all()
return users
#classmethod
def get_current_user(cls):
user_result = None
if 'username' in session:
user = session['username']
user_result = cls.get_by_name(name=user)
return user_result
def save_to_db(self):
db.session.add(self)
db.session.commit()
models/question_model.py:
__package__ = "application.models"
from ..database import db
from .. import app
class Questions(db.Model):
__table_args__ = {'keep_existing': True}
id = db.Column(db.Integer, primary_key=True)
question_text = db.Column(db.Text, nullable=False)
answer_text = db.Column(db.Text)
asked_by_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
expert_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
asked_by_user = db.relationship("Users", foreign_keys=[asked_by_id],lazy='dynamic')
expert_user = db.relationship("Users", foreign_keys=[expert_id],lazy='dynamic')
#classmethod
def get_all_answered_questions_details(cls):
questions = []
results = cls.query.filter(cls.answer_text != None).all()
for result in results:
temp = {}
temp['question'] = result.question_text
temp['asked_by'] = result.asked_by_user.name
temp['expert'] = result.expert_user.name
app.logger.debug('Question : %s , asked by : %s and answered by : %s',temp['question'],temp['asked_by'],temp['expert'])
questions.append(temp)
return questions
#classmethod
def get_question_details(cls,question_id):
question = Questions.query.filter_by(id=question_id).first()
return question
#classmethod
def view_question_details(cls,question_id):
question = Questions.query.filter_by(id=question_id).first()
question_details = {}
question_details['question'] = question.question_text
question_details['answer'] = question.answer_text
question_details['asked_by'] = question.asked_by_user.name
question_details['expert'] = question.expert_user.name
return question_details
#classmethod
def get_unanswered_questions(cls,expert_id):
questions = []
questions = Questions.query.filter(Questions.answer_text != None,Questions.expert_id == expert_id).all()
for question in questions:
temp = {}
temp['questions_text'] = question.question_text
temp['name'] = question.asked_by_user.name
questions.append(temp)
return questions
def save_to_db(self):
db.session.add(self)
db.session.commit()
views/index.py
__package__ = "application.views"
from .. import app,Users,Questions
from flask import render_template,redirect,url_for
#app.route('/')
def index():
app.logger.debug("Inside index route")
user_details = Users.get_current_user()
results = Questions.get_all_answered_questions_details()
app.logger.debug("Exiting index route")
return render_template('home.html',user=user_details,results=results)
Now when i hit the first route : http://127.0.0.1:5000
I get the below error:
sqlalchemy.exc.InvalidRequestError
InvalidRequestError: Multiple classes found for path "Users" in the registry of this declarative base. Please use a fully module-qualified path.
Can someone please point out where i am going wrong.
This is driving me crazy. Thanks a lot in advance.

Error deseralizing JSON string with nested objects with SqlAlchemy and Marshmallow

I'm trying to build a REST API application in Python 3.4, using Flask, SQLAlchemy and Marshmallow.
In my model I have a User class with a one to many relationship with a MailAddress class.
If I run a GET request, I manage to read data from the DB, and the data is correctly returned as a JSON string.
Instead, if I run a POST request with the JSON serialization of my User object with some MailAddresses, I get this error:
File "X:\test\...\site-packages\sqlalchemy\orm\collections.py", line 785, in bulk_replace
constants = existing_idset.intersection(values or ())
File "X:\test\...\site-packages\sqlalchemy\util\_collections.py", line 612, in intersection
result._members.update(self._working_set(members).intersection(other))
TypeError: unhashable type: 'dict'
I've tried adding a __hash__ function to my model classes (as suggested at sqlalchemy: TypeError: unhashable type creating instance, sqlalchemy) but that didn't help.
Here is a complete code example that shows this problem:
from flask import Flask, request
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from marshmallow import fields
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
class Config(object):
SQLALCHEMY_DATABASE_URI = '<CONNECTION STRING HERE>'
SQLALCHEMY_TRACK_MODIFICATIONS = False
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
ma = Marshmallow(app)
# Model
class MailAddress(db.Model):
__tablename__ = 'mail_addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
mail_type = Column(String(200), nullable=False)
mail = Column(String(200), nullable=False)
def __init__(self, mail, mail_type):
self.mail = mail
self.mail_type = mail_type
class MailAddressSchema(ma.ModelSchema):
class Meta:
model = MailAddress
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
mail_addresses = relationship('MailAddress', backref='user')
def __init__(self, name, mail_addresses):
self.name = name
self.mail_addresses = mail_addresses
def __hash__(self):
return hash(self.name)
class UserSchema(ma.ModelSchema):
mail_addresses = fields.Nested(MailAddressSchema, many = True, only=('mail', 'mail_type'))
class Meta:
model = User
# Routes
user_schema = UserSchema()
#app.route('/api/v0/user', methods=['GET'])
def user_get():
users = db.session.query(User).all()
return user_schema.jsonify(users, many = True), 200
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance = user_schema.make_instance(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201
# Main
if __name__ == '__main__':
app.run('localhost', 5555)
Is there something I'm missing?
Use load instead make_instance
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance, errors = user_schema.load(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201

Update not commited on mysql server flask-sqlalchemy

I am able to perform 'select' queries to my Mysql database.
However, the "insert" ones don't change the database, only the python objects. So when I restart the flask app, all the commited(?) editions are gone.
Views:
from flask import Flask, render_template, request, redirect, url_for, flash, Response
from sqlalchemy import exc
from models import *
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'kjhS7usfHGJHDez78'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://admin:admin#127.0.0.1:3306/grenier'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
db.create_all()
#app.route('/ajax/submit_edition', methods=['POST'])
def submit_edition():
if request.method == 'POST':
given_id=1
show = Shows.query.filter_by(id=given_id).first()
show.short_description = "Hello"
try:
db.session.commit()
db.session.flush()
return "ok"
except exc.SQLAlchemyError:
return "Error in commiting the edition"
No particular exception is found. The route always returns "ok".
Models:
from sqlalchemy import Column, ForeignKey
from sqlalchemy.dialects.mysql import LONGTEXT, YEAR
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Shows(db.Model):
__tablename__ = "shows"
id = Column(db.Integer, ForeignKey("programmation.id"), primary_key=True)
date = Column(db.DATETIME)
title = Column(db.VARCHAR(50))
short_description = Column(db.VARCHAR(200))
type = Column(db.VARCHAR(20))
background_image = Column(db.VARCHAR(150))
content = Column(LONGTEXT)
def serialize(self, whatTo):
result = {}
if 'id' in whatTo:
result['id'] = self.id
if 'date' in whatTo:
result['date'] = str(self.date)
if 'title' in whatTo:
result['title'] = self.title
if 'short_description' in whatTo:
result['short_description'] = self.short_description
if 'type' in whatTo:
result['type'] = self.type
if 'background_image' in whatTo:
result['background_image'] = self.background_image
if 'content' in whatTo:
result['content'] = self.content
return result
class Programmation(db.Model):
__tablename__ = "programmation"
id = Column(db.Integer, primary_key=True)
semester = Column(db.Integer)
year = Column(YEAR)
When I look at the logs, the sql request is created for the select. But for the insert commit(), there is nothing.
Thank you !
The problem is usage of two different SQLAlchemy instance. When you call db.create_all() method it creates all tables which which inherited from db.Model but in your views you don't have any model inherited from db = SQLAlchemy(app). All your models inherited from other SQLAlchemy instance. To fix this import the db object from views to models module and use it as parent class for inheritance:
#models.py
from views import db
#db = SQLAlchemy() #remove this line
class Show(db.Model):
...

Initialize dB(sqlite3) with Flask-SQLAlchemy

I need to create a dB for testing purposes and I would like initialize it with "some" users.
The "intended" code is:
import os
from flask import Flask
from flask.ext.script import Manager, Shell
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
manager = Manager(app)
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(64), unique = True)
email = db.Column(db.String(64), unique = True)
def __init__(self, user = None, email = None):
name = self.__tablename__
db.drop_all()
db.create_all()
for i in range(100):
user = User('user%d' % i, 'someone#example.com')
db.session.add(user)
db.session.commit()
def __repr__(self):
return '<User %r>' % self.name
The call usr_db = User() generates RuntimeError: maximum recursion depth exceeded.
The question is , when during construction the class is "ready" to be called recursively to avoid the error above.
I think creating test users inside the model class is the wrong way to do it. This is going to recursively call __init__() if you instantiate a User() instance inside __init__(). A better place would be inside a unittest setUp() method.
However, if you really want to do this, you could guard against instantiating the users more than once like this:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(64), unique = True)
email = db.Column(db.String(64), unique = True)
_test_users_initialised = False
def _initialise_test_users(self):
if self._test_users_initialised:
return
self.__class__._test_users_initialised = True
for i in range(100):
user = User('user%d' % i, 'someone#example.com')
db.session.add(user)
def __init__(self, user = None, email = None):
name = self.__tablename__
db.drop_all()
db.create_all()
self._initialise_test_users()
db.session.commit()

Categories