Что я могу сделать с сообщением об ошибке «невозможно импортировать частично инициализированный модуль»?

Я пишу приложение-флягу, в которое входят еще три приложения! администратор, менеджер и работник. у этих троих есть своя папка в папке проекта. Также есть app.py, который объединяет все три приложения, и run.py, который запускает последнее приложение Flask. но всякий раз, когда я пытаюсь запустить run.py, возникает ошибка: невозможно импортировать имя «db» из частично инициализированного модуля «app»

вот app.py, который я только что написал:

# 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

это полная ошибка трекбэка:

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)

и вот файлы, упомянутые в трекбеке:

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

🤔 А знаете ли вы, что...
В Python есть инструменты для тестирования кода, такие как библиотека unittest.


1
50
2

Ответы:

Решено

У вас есть случай circular dependency, не редкий в вашем приложении Flask. Циклический импорт происходит, когда два или более модулей зависят друг от друга, что приводит к ситуации, когда Python не может правильно инициализировать модули.

Я полагаю, именно так это и произошло

В вашем случае циклический импорт происходит потому, что:

app.py -> импортирует create_admin_app из admin_app.

admin_app/__init__.py -> импортирует администратора из admin_app/models.py.

admin_app/models.py -> импортирует базу данных из app.py.

Ты можешь сделать это

  1. Переместите импорт базы данных внутри выбранной вами именованной функции. измените admin_app/models.py, чтобы импортировать db только тогда, когда это необходимо.

Вот фрагмент кода, который поможет вам


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

ИЛИ

  1. Использование переключения контекста в Flask

Согласно документации Flask, которую можно найти здесь: docs

«Flask решает эту проблему с помощью контекста приложения. Вместо того, чтобы напрямую обращаться к приложению, вы используете прокси-сервер current_app, который указывает на приложение, обрабатывающее текущую активность. Flask автоматически передает контекст приложения при обработке запроса».

Вот фрагмент кода, который поможет вам

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

Я бы также посоветовал вам разорвать соединение с базой данных, выполнив следующий пример кода:

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

Эта проблема обычно возникает, когда в вашем коде есть циклическая зависимость.

Например:

Допустим, у вас есть два класса в вашем проекте: класс A и класс B, каждый из которых находится в файлах A.py и B.py соответственно:

# A.py

class A:
    pass

# B.py

class B:
    pass

Теперь вам потребовались некоторые атрибуты и методы класса A в классе B, поэтому вы импортировали класс A в файл B.py. Никаких проблем, до сих пор все работает нормально. Теперь, через некоторое время, вы понимаете, что вам нужны некоторые атрибуты и методы класса B в вашем классе A, поэтому на этот раз вы импортировали класс B в A.py, что привело к циклической зависимости.

Чтобы полностью решить эту проблему, вам нужен третий класс C, который будет использоваться A и B и который должен содержать необходимые атрибуты и методы (РЕКОМЕНДУЕТСЯ).

Но в качестве временного решения вы можете импортировать классы внутри методов, где они фактически используются, а не сверху (НЕ РЕКОМЕНДУЕТСЯ).