TypeError: 'property' object is not iterable SQLAlchemy

TypeError: 'property' object is not iterable SQLAlchemy

Alembic 的自动生成不断抛出这个错误,这对我来说相当神秘:

INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
Traceback (most recent call last):
  File "/home/mas/projects/icc/icc2-backend/.venv/bin/alembic", line 8, in <module>
    sys.exit(main())
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/config.py", line 581, in main
    CommandLine(prog=prog).main(argv=argv)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/config.py", line 575, in main
    self.run_cmd(cfg, options)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/config.py", line 552, in run_cmd
    fn(
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/command.py", line 214, in revision
    script_directory.run_env()
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/script/base.py", line 489, in run_env
    util.load_python_file(self.dir, "env.py")
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/util/pyfiles.py", line 98, in load_python_file
    module = load_module_py(module_id, path)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/util/compat.py", line 184, in load_module_py
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 855, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/home/mas/projects/icc/icc2-backend/alembic/env.py", line 84, in <module>
    run_migrations_online()
  File "/home/mas/projects/icc/icc2-backend/alembic/env.py", line 78, in run_migrations_online
    context.run_migrations()
  File "<string>", line 8, in run_migrations
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/runtime/environment.py", line 846, in run_migrations
    self.get_context().run_migrations(**kw)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/runtime/migration.py", line 511, in run_migrations
    for step in self._migrations_fn(heads, self):
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/command.py", line 190, in retrieve_migrations
    revision_context.run_autogenerate(rev, context)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/autogenerate/api.py", line 444, in run_autogenerate
    self._run_environment(rev, migration_context, True)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/autogenerate/api.py", line 483, in _run_environment
    compare._populate_migration_script(
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/autogenerate/compare.py", line 25, in _populate_migration_script
    _produce_net_changes(autogen_context, upgrade_ops)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/autogenerate/compare.py", line 50, in _produce_net_changes
    comparators.dispatch("schema", autogen_context.dialect.name)(
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/util/langhelpers.py", line 303, in go
    fn(*arg, **kw)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/autogenerate/compare.py", line 75, in _autogen_for_tables
    [(table.schema, table.name) for table in autogen_context.sorted_tables]
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/util/langhelpers.py", line 230, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
  File "/home/mas/projects/icc/icc2-backend/.venv/lib/python3.9/site-packages/alembic/autogenerate/api.py", line 362, in sorted_tables
    result.extend(m.sorted_tables)
TypeError: 'property' object is not iterable

我的模型是这样的:

# app/models/mixins.py
from app import db

from sqlalchemy.ext.declarative import declared_attr, as_declarative

@as_declarative()
class Base(db.Model):
    """This Base class does nothing. It is here in case I need to expand
    implement something later. I feel like it's a good early practice.

    Attributes
    ----------
    id : int
        The basic primary key id number of any class.

    Notes
    -----
    The __tablename__ is automatically set to the class name lower-cased.
    There's no need to mess around with underscores, that just confuses the
    issue and makes programmatically referencing the table more difficult.
    """
    __abstract__ = True
    id = db.Column(db.Integer, primary_key=True)

    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()
# app/models/annotations.py
from datetime import datetime as dt

from app import db
from app.models.mixins import Base

class Annotation(Base):
    bookid = db.Column(db.Integer)
    author = db.Column(db.String)

    created = db.Column(db.DateTime)

    edits = db.relationship('Edit', back_populates='annotation')

    HEAD = db.relationship('Edit',
                           primaryjoin='Edit.annotation_id==Annotation.id',
                           uselist=False)

    def __init__(self, book, author, start, end, text, *args, **kwargs):
        self.created = dt.now()
        self.bookid = book
        self.author = author
        super().__init__(*args, **kwargs)
        self.edits.append(Edit(author, start, end, text))


class Edit(Base):
    annotation_id = db.Column(db.Integer, db.ForeignKey('annotation.id'),
                              index=True)
    text = db.Column(db.Text)
    editor = db.Column(db.String)

    start = db.Column(db.Integer)
    end = db.Column(db.Integer)

    created = db.Column(db.DateTime)

    annotation = db.relationship('Annotation', back_populates='edits')

    def __init__(self, editor, start, end, text, *args, **kwargs):
        self.editor = editor
        self.start, self.end = start, end
        self.text = text
        self.created = dt.utcnow()
        super().__init__(*args, **kwargs)

而且因为我不确定它是否相关,所以我的 alembic/env.py

from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

import os, inspect, sys
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir)

from config import Config

config = context.config # alembic

config.set_main_option('sqlalchemy.url', Config.SQLALCHEMY_DATABASE_URI)
print(Config.SQLALCHEMY_DATABASE_URI)


# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)


from app import db
target_metadata = db.MetaData

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

代码全部在 https://github.com/anno-wiki/icc2-backend 如果有帮助的话。我希望有人能够对此有所了解。 Alembic 的代码相当抽象,很难理解为什么该特定行在我的配置中出现问题。我以前多次使用 Base mixin,这从来都不是问题。

似乎通过将 target_metadata = db.MetaData 更改为 target_metadata = db.metdata 解决了该问题,但随后未检测到模型。导入 app.models.Annotation 并使用 Annotation.metadata 使整个工作正常。