Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid circular imports in a Flask app with Flask SQLAlchemy models?

I'm getting into Flask and building out an app that uses Flask SQLAlchemy. I've created a basic API that works when all the code is in a single file, but would like to better organize it, like so:

app/models/user.py

from datetime import datetime
from app.app import db


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    public_id = db.Column(db.String, unique=True)
    admin = db.Column(db.Boolean)
    username = db.Column(db.String(50), unique=True)
    email = db.Column(db.String(50), unique=True)
    password = db.Column(db.String(100))
    subscription_plan = db.Column(db.Integer)
    created_at = db.Column(db.DateTime, index=True,
                           default=datetime.utcnow())
    updated_at = db.Column(db.DateTime, index=True, default=datetime.utcnow())

app/app.py

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash
import uuid

from app.models.user import User

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = #SQLALCHEMY_DATABASE_URI

db = SQLAlchemy(app)

# ... CRUD routes that will use User

@app.route('/user', methods=['POST'])
def create_user():
data = request.get_json()
hashed_password = generate_password_hash(data['password'])
new_user = User(
    public_id=str(uuid.uuid4()),
    username=data['username'],
    password=hashed_password,
    email=data['email'],
    admin=data['admin'],
    subscription_plan=data['subscription_plan']
)
db.session.add(new_user)
db.session.commit()
return jsonify({'message': 'User successfully created.'})


if __name__ == '__main__':
    app.run(port=5000, debug=True)

I would like to import db from app/app.py into my app/models/user.py file, but when I then try to import the User model into app/app.py, it gives me an error because of the circular import. I have no idea how to get around this because it seems like User needs db after it gets passed an instance of app from app/app.py.

I'd also like to move my routes out of here and into separate files for better organization, so trying to understand the best way to avoid circular imports all around. Any and all help is greatly appreciated!

like image 887
hidace Avatar asked Feb 10 '18 08:02

hidace


People also ask

How do you stop circular imports in Flask?

At the same time, there is a risk that cyclic imports will occur and it will be difficult to maintain a project. Flask documentation and basic tutorials suggest to write a project initialization code in __init__.py to solve the problem. This code creates Flask instance of a class and configures an app.

What is the difference between SQLAlchemy and flask-SQLAlchemy?

One of which is that Flask-SQLAlchemy has its own API. This adds complexity by having its different methods for ORM queries and models separate from the SQLAlchemy API. Another disadvantage is that Flask-SQLAlchemy makes using the database outside of a Flask context difficult.

Can I use flask-SQLAlchemy without Flask?

This does not completely answer your question, because it does not remove Flask dependency, but you can use SqlAlchemy in scripts and tests by just not running the Flask app. One difficulty you may encounter is the requirement of using db.

Can I use SQLAlchemy in Flask?

Flask-SQLAlchemy is a Flask extension that makes using SQLAlchemy with Flask easier, providing you tools and methods to interact with your database in your Flask applications through SQLAlchemy. In this tutorial, you'll build a small student management system that demonstrates how to use the Flask-SQLAlchemy extension.


2 Answers

Instead of having User import app and app import user, bring them together in init.

app/app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
db = SQLAlchemy(app)

app/models/user.py

from app.app import db
class User:
    pass #access db the way you want

app/views.py

from app.app import app,db
@app.route("/")
def home():
    return "Hello World" # use db as you want

app/__init__.py

from app import app
from app.models.user import User
from app import views

This is the leanest fix to the problem. However, I would recommend using Application Factories

like image 59
helloworld Avatar answered Oct 19 '22 10:10

helloworld


I think the best way is reorganize the structure of your project. Try to move the view functions to a separate package or module. You can follow the exploreflask to organize your project. Or you can check my common project structure of Flask project.

like image 20
stamaimer Avatar answered Oct 19 '22 09:10

stamaimer