Flask-Migrate/Alembic 未检测使用基类和多文件结构的模型

mal*_*lan 6 python flask flask-sqlalchemy alembic flask-migrate

这个问题已被问过一百万次,但似乎没有一个解决方案对我有用。我应该指出,我在其他项目中经常处理无关紧要的问题

\n

目前我正在使用flask-sqlalchemy、flask-migrate 和postgresql。

\n

文件结构:

\n
\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 app\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 main\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 routes.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 users.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 models\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 annotations.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 __init__.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 mixins.py\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 users.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 config.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 docker-compose.yml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Dockerfile\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 icc2.py           <-- the app.py\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 migrations\n
Run Code Online (Sandbox Code Playgroud)\n

app/__init__.py

\n
from flask import Flask\nfrom flask_sqlalchemy import SQLAlchemy\nfrom flask_cors import CORS\nfrom elasticsearch import Elasticsearch\nfrom flask_migrate import Migrate\n\nfrom config import Config\n\ndb = SQLAlchemy()\nmigrate = Migrate()\n\ndef create_app(config_class=Config):\n    app = Flask(__name__)\n    app.config.from_object(config_class)\n\n    db.init_app(app)\n    migrate.init_app(app, db)\n\n    app.es = Elasticsearch([app.config['ELASTICSEARCH_URL']]) \\\n        if app.config['ELASTICSEARCH_URL'] else None\n\n    from app.main import bp as main_bp\n    app.register_blueprint(main_bp, url_prefix='/_api')\n\n    CORS(app, resources={r"/_api/*": {"origins": "*"}})\n    return app\n
Run Code Online (Sandbox Code Playgroud)\n

icc2.py

\n
from app import create_app, db\nfrom app.models import classes\n\napp = create_app()\n\n@app.shell_context_processor\ndef make_shell_context():\n    print(db)\n    return dict(db=db, **classes)\n
Run Code Online (Sandbox Code Playgroud)\n

app/models/mixins.py

\n
from app import db\n\nfrom sqlalchemy.ext.declarative import declared_attr, as_declarative\n\n@as_declarative()\nclass Base(db.Model):\n    """This Base class does nothing. It is here in case I need to expand\n    implement something later. I feel like it's a good early practice.\n\n    Attributes\n    ----------\n    id : int\n        The basic primary key id number of any class.\n\n    Notes\n    -----\n    The __tablename__ is automatically set to the class name lower-cased.\n    There's no need to mess around with underscores, that just confuses the\n    issue and makes programmatically referencing the table more difficult.\n    """\n    __abstract__ = True\n    id = db.Column(db.Integer, primary_key=True)\n\n    @declared_attr\n    def __tablename__(cls):\n        return cls.__name__.lower()\n\n
Run Code Online (Sandbox Code Playgroud)\n

app/models/users.pyannotations.py很相似)

\n
import time\n\nfrom datetime import datetime\n\nfrom app import db\nfrom app.models.mixins import Base\n\nclass User(Base):\n    auth0id = db.Column(db.String(64), index=True)\n    last_seen = db.Column(db.DateTime, default=datetime.utcnow)\n\n    def __repr__(self):\n        return f"<User {self.displayname}>"\n\n    def __str__(self):\n        return self.displayname\n
Run Code Online (Sandbox Code Playgroud)\n

app/models/__init__.py

\n
import pkgutil\nimport os\nimport importlib\nfrom .mixins import Base\n\npkg_dir = os.path.dirname(__file__)\n\nfor (module_loader, name, ispkg) in pkgutil.iter_modules([pkg_dir]):\n    importlib.import_module('.' + name, __package__)\n\nclasses = {cls.__name__: cls for cls in Base.__subclasses__()}\n
Run Code Online (Sandbox Code Playgroud)\n

迭代来自 stackoverflow 代码片段,以便我可以获取类中的模型以公开给 Flask shell 命名空间。

\n

我也许不需要将这两个类放在两个单独的文件中,但我的上一个项目最终有 30 个左右的模型,因此组织需要进行一些拆分,所以这只是我开发的一种实践。

\n

据我了解,alembic 在生成模型之前需要查看对象的元数据,但在实例化app/__init__.pymigrate 时,数据库还没有引擎。事实上,只是为了测试引擎是如何创建的,我添加了 3 个打印语句来icc2.py打印数据库并查看此时是否有引擎,如下所示:

\n
from app import create_app, db\nfrom app.models import classes\n\napp = create_app()\nprint(db)\n@app.shell_context_processor\ndef make_shell_context():\n    print(db)\n    return dict(db=db, **classes)\nprint(db)\n
Run Code Online (Sandbox Code Playgroud)\n

唯一没有显示的打印调用<SQLAlchemy engine=None>是在make_shell_context()函数内。

\n

最后,flask migrate 的输出“初始迁移”:

\n
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.\nINFO  [alembic.runtime.migration] Will assume transactional DDL.\nINFO  [alembic.env] No changes in schema detected.\n
Run Code Online (Sandbox Code Playgroud)\n

那么如何将我的元数据暴露给flask migrate呢?

\n

Ard*_*ner 3

从 alembic 内的模型模块导入 *env.py对我有用。将所有模型模块与基本模块一起组织为 * 在 a 中导入__init__.py,并从该聚合模块导入 inenv.py也可以。