Search code examples
pythonflask

what can I do with "can not import partially initialized module" error message?


I'm writing a flask app which has includes another three apps in it! admin, manager and worker. those three has their own folder in project folder. There's also app.py, which gethers ever three apps, and run.py which run the final flask app. but whenever I try to run run.py it goes to an error: can not import name 'db' from partially initialized module 'app'

here's the app.py I've just wrote:

# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# in-project imports
from config import Config, DB_NAME
from admin_app import create_admin_app
from manager_app import create_manager_app
from worker_app import create_worker_app

db = SQLAlchemy()

def create_app():
    # Creates the application
    app = Flask(__name__)
    app.config.from_object(Config)
    db.init_app(app)

    from admin_app.models import Admin
    from manager_app.models import Manager
    from worker_app.models import Worker, Report
    
    
    # Register Blueprints:
    create_admin_app(app)
    create_manager_app(app)
    create_worker_app(app)
    
    return app

this is the full trackback error:

File "C:\Users\user\Desktop\Shortcuts\3-Other Courses\Flask-Projects\Manager\run.py", line 1, in <module>
    from app import create_app
  File "C:\Users\user\Desktop\Shortcuts\3-Other Courses\Flask-Projects\Manager\app.py", line 6, in <module>
    from admin_app import create_admin_app
  File "C:\Users\user\Desktop\Shortcuts\3-Other Courses\Flask-Projects\Manager\admin_app\__init__.py", line 3, in <module>
    from .models import Admin
  File "C:\Users\user\Desktop\Shortcuts\3-Other Courses\Flask-Projects\Manager\admin_app\models.py", line 1, in <module>
    from app import db
ImportError: cannot import name 'db' from partially initialized module 'app' (most likely due to a circular import) (C:\Users\user\Desktop\Shortcuts\3-Other Courses\Flask-Projects\Manager\app.py)

and here's the files mentioned in the trackback:

# __init__.py
from flask import Blueprint
from flask_login import LoginManager
from .models import Admin

admin = Blueprint('admin', __name__, template_folder='templates', static_folder='static')

login_manager = LoginManager()
login_manager.login_view = 'admin.login'

@login_manager.user_loader
def load_user(user_id):
    return Admin.query.get(user_id)

def create_admin_app(app):
    login_manager.init_app(app)
    app.register_blueprint(admin, url_prefix='/admin')


# models.py
from app import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class Admin(db.Model, UserMixin):
    __tablename__ = 'admin'
    id = db.Column(db.Integer, primary_key=True)
    personel_id = db.Column(db.Integer, nullable=False, unique=True)
    password = db.Column(db.String(250), nullable=False)
    
    def set_password(self, password):
        self.password = generate_password_hash(password)
        
    def check_password(self, password):
        return check_password_hash(self.password, password)
    
    def get_by_id(cls, user_id):
        return cls.query.filter_by(personel_id=user_id).first()

Solution

  • What you have is a case of circular dependency not uncommon in your Flask application. Circular imports happen when two or more modules depend on each other, leading to a situation where Python cannot properly initialize the modules.

    This I presume is how it occured

    In your case, the circular import occurs because:

    app.py -> imports create_admin_app from admin_app.

    admin_app/__init__.py -> imports Admin from admin_app/models.py.

    admin_app/models.py -> imports db from app.py.

    You can do this

    1. Move the db Import inside a named function of your choice modify the admin_app/models.py to import db only when it's needed.

    Here is a code snippet to guide you

    
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    
    
    from config import Config, DB_NAME
    from admin_app import create_admin_app
    from manager_app import create_manager_app
    from worker_app import create_worker_app
    
    db = SQLAlchemy()
    
    def create_app():
        # Creates the application
        app = Flask(__name__)
        app.config.from_object(Config)
        db.init_app(app)
    
        # Import models only  after db has been initializied 
        from admin_app.models import Admin
        from manager_app.models import Manager
        from worker_app.models import Worker, Report
        
        # all Blueprints:
        admin_app(app)
        manager_app(app)
        worker_app(app)
        
        return app
    

    OR

    1. Use of Context switching in Flask

    According to Flask documentation found here: docs

    "Flask solves this issue with the application context. Rather than referring to an app directly, you use the current_app proxy, which points to the application handling the current activity. Flask automatically pushes an application context when handling a request."

    Here is a code snippet to guide you

    admin_app/models.py
    
    from flask import current_app
    from flask_login import UserMixin
    from werkzeug.security import generate_password_hash, check_password_hash
    
    class Admin(UserMixin):
        __tablename__ = 'admin'
        id = db.Column(db.Integer, primary_key=True)
        personel_id = db.Column(db.Integer, nullable=False, unique=True)
        password = db.Column(db.String(250), nullable=False)
        
        def set_password(self, password):
            self.password = generate_password_hash(password)
            
        def check_password(self, password):
            return check_password_hash(self.password, password)
        
        def get_by_id(cls, user_id):
            return cls.query.filter_by(personel_id=user_id).first()
    
        @property
        def db(self):
            return current_app.extensions['sqlalchemy'].db
    

    I would also advise you teardown db connection after by following this code sample below:

    from flask import g
    
    def get_db():
        if 'db' not in g:
            g.db = connect_to_database()
    
        return g.db
    
    @app.teardown_appcontext
    def teardown_db(exception):
        db = g.pop('db', None)
    
        if db is not None:
            db.close()