How to solve SQL alchemy circular imports issue - python

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.

Related

data is not added in Route.py but added in terminal in python flask sql_alchemy

Making an ECommerce with flask using SQlite database and Flask SQLAlchemy
when I am trying to add data from terminal, it is adding but when i add data from route.py it gives me an error
TypeError: Cart() got an unexpected keyword argument 'Cart_User_id'
command in terminal
cart3 = Cart(Cart_User_id = Users.query.filter_by(User_name = "Hadi").first().User_id)
this command succesfully adds the data
my route.py
#app.route("/Product",methods = ["POST" , "GET"])
def Product():
form = addToCartForm()
if request.method == "POST":
if current_user.is_authenticated:
User_id = current_user.User_id
Prod_id = request.form.get('Prod_id')
Prod_qty = request.form.get('Prod_qty')
**user_cart = Cart(Cart_User_id = Users.query.filter_by(User_id = User_id).first().User_id)**
productDetails_in_cartDetails = CartItem(CartItem_Cart_id = user_cart.Cart_id,CartItem_Prod_id =Prod_id, CartItem_quanitity=Prod_qty)
db.session.add_all([user_cart,productDetails_in_cartDetails])
db.session.commit()
flash('Item Has been added to your Cart', category='success')
else:
flash('Please Login to add item in your cart', category='info')
productData = Products.query.all()
return render_template('product.html',productData = productData, form =form)
my model for cart:
class Cart(db.Model):
Cart_id = db.Column(db.Integer, primary_key=True)
Cart_User_id = db.Column(db.Integer, db.ForeignKey('users.User_id'))
Cart_CartItem = db.relationship("CartItem" ,backref='cart' ,lazy = True)
def __repr__(self):
return f'{self.Cart_id}'
class CartItem(db.Model):
CartItem_id = db.Column(db.Integer, primary_key=True)
CartItem_Cart_id = db.Column(db.Integer ,db.ForeignKey('cart.Cart_id') )
CartItem_Prod_id = db.Column(db.Integer, db.ForeignKey('products.Prod_id'))
CartItem_quanitity = db.Column(db.Integer, nullable=False)
def __repr__(self):
return f'{self.CartItem_id}'
can any body tell whats the issue

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.

Instance <User at xxxx> is not bound to a Session; attribute refresh operation cannot proceed

I first request is right, But when I second request is wrong. I know the reason get this result is session object expired. But I don't know why? The flowing is my test script.
Detail:
In my actual project, I set a global variable current_user to save the current user, and then add some cache-related attributes to this global variable. The cache-related attributes are actually an object, which is called in the method of the object Pass in the ginseng.
import datetime
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, Column, DateTime, Integer, String, func, VARCHAR
app = Flask(__name__)
app.config[
"SQLALCHEMY_DATABASE_URI"
] = "mysql+mysqlconnector://root:123456#127.0.0.1:3306/pool"
# app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
# In my project I set global var to solve memory leak
current_user = {}
def get_cache(user):
def inner():
class hello:
def get_user_name(self):
# if user not in db.session:
# print("user 已经过期了")
# session_user = db.session.merge(user)
# else:
# session_user = user
# return "%s" % (session_user.username)
return "%s" % (user.username)
return hello()
return inner()
def get_current_user(ticker):
user = current_user.get(ticker)
if user:
return user
user = User.query.first()
current_user[ticker] = user
user.cache = get_cache(user)
return user
class User(db.Model):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
username = Column(String(50), nullable=False, unique=True)
mobile = Column(String(20), nullable=False, unique=True)
password = Column(String(100), nullable=False)
is_admin = Column(Integer, default=0)
created_at = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
updated_at = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
level = Column(Integer, default=1)
remark = Column(String(100), nullable=False)
#app.after_request
def after_request(response):
db.session.rollback()
db.session.remove()
return response
#app.route("/test", methods=["GET"])
def test():
user = get_current_user("ltc")
print(user.username, "这个")
print(user.cache.get_user_name())
return "hello world"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000)
My environment:
Flask==1.0.2
Flask-SQLAlchemy==2.3.2

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()

SQLAlchemy Object already attached to session

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

Categories