Fugs
Fugs

Reputation: 39

How do I correct a circular import with Flask and SQLAlchemy?

I'm working on a flask application that uses SQLAlchemy, and I have the models for the database declared in a different file 'db_schema.py' from the main application file 'app.py'. I am getting a circular import error when trying to import the models from db_schema.py back in to app.py.

Error:

ImportError: cannot import name 'User' from partially initialized module 'db_schema' (most likely due to a circular import) (/home/deric/projects/python/flask/chatroom/chatroom/project/db_schema.py)

db_schema.py

from app import db
from flask_login import UserMixin
from datetime import datetime

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key = True)
    username = db.Column(db.String(25), unique = True, index = True, nullable = False)
    password = db.Column(db.String(125), unique = False, index = False, nullable = False)
    email = db.Column(db.String(30), unique = True, index = True, nullable = False)
    chatroom_id = db.Column(db.Integer, db.ForeignKey('chatroom.id'))
    messages_sent = db.relationship('Message', backref='sender', lazy='dynamic', cascade='all, delete')


class Chatroom(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    

class Message(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    chatroom_id = db.Column(db.Integer, db.ForeignKey('chatroom.id'))
    sender_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    content = db.Column(db.String(150), unique = False, index = True)
    time_sent = db.Column(db.DateTime, unique = False, index = True, default=datetime.utcnow)


app.py

from flask import Flask, render_template, url_for, redirect, request
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from forms import Registration_Form


app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite3.db'
login_manager = LoginManager()
db = SQLAlchemy(app)

from db_schema import User, Message, Chatroom

@login_manager.user_loader
def load_user(user_id):
    return User.query.filter(id=user_id).first()

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run()

Upvotes: 3

Views: 1652

Answers (1)

ggorlen
ggorlen

Reputation: 56945

One solution is to move the conflicted depedency variable app out to a third file. Import app from both db_schema.py and app.py.

Here's the dependency graph:

config.py (app) <---- db_schema.py (User, ...)
      ^                      ^
      |                      |
      +------- app.py -------+

In code:

config.py:

from flask import Flask

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite3.db'

db_schema.py:

from config import app

db = SQLAlchemy(app)

# model classes: User, ...

app.py:

from config import app
from db_schema import Chatroom, Message, User

login_manager = LoginManager()

@login_manager.user_loader
def load_user(user_id):
    return User.query.filter(id=user_id).first()

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run()

If db seems misplaced in the db_schema.py file, you could move it out to config.py or its own file. You can rename config.py if you don't feel it represents what it does well.

Another way to resolve the dependency without more files is to provide a class or function that can be invoked to initialize a variable. This allows all files to be loaded before fulfilling the topological ordering requirements through function calls.

The canonical thread appears to be Flask circular dependency which has an answer showing the function-based approach.

Also: if you use plain SQLAlchemy instead of Flask-SQLAlchemy, then there's no SQLAlchemy(app) call. This has other benefits of loose coupling such as better model portability, with the drawback that initial configuration of session scoping is a bit more tedious and error-prone.

Upvotes: 4

Related Questions