Search code examples
pythonflasksqlalchemy

sqlalchemy new style with Mapped anotations causes sqlalchemy.exc.ArgumentError: Could not interpret annotation Mapped chicken-egg-issue


I am following the example from sqlalchemy docs https://docs.sqlalchemy.org/en/20/orm/inheritance.html#relationships-with-joined-inheritance

and I am facing this kind of chicken-egg-issue with Mapped[List[Manager]] before actuall Manager class definition what leads to:

sqlalchemy.exc.ArgumentError: Could not interpret annotation Mapped[List[Manager]]. Check that it uses names that are correctly imported at the module level. See chained stack trace for more hints.

what is the proper way to define and then import and create the tables with this new style with Mapped annotate?

all the details:

I store them in models.py

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")


class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }


class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }


class Engineer(Employee):
    ...

inside database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker, declarative_base



engine = create_engine('postgresql+psycopg2://datalake:password@localhost/flask_db')
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()

Base.__table_args__ = (
        {'schema': 'metadata'}
    )


def init_db():
    # import all modules here that might define models so that
    # they will be registered properly on the metadata.  Otherwise
    # you will have to import them first before calling init_db()
    import models
    Base.metadata.drop_all(bind=engine)
    Base.metadata.create_all(bind=engine)

and inside app.py

from flask import Flask


def create_app():
    app = Flask(__name__)
    from database import init_db
    init_db()
    return app


app = create_app()



@app.route('/')
def hello():
    return '<h1>Hello, World!</h1>'

when running flask run I get:

sqlalchemy.exc.ArgumentError: Could not interpret annotation Mapped[List[Manager]]. Check that it uses names that are correctly imported at the module level. See chained stack trace for more hints.

the full stack trace

Traceback (most recent call last):
  File "/Users/andi/.pyenv/versions/spike/bin/flask", line 8, in <module>
    sys.exit(main())
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/flask/cli.py", line 1064, in main
    cli.main()
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/click/decorators.py", line 92, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/flask/cli.py", line 912, in run_command
    raise e from None
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/flask/cli.py", line 898, in run_command
    app = info.load_app()
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/flask/cli.py", line 309, in load_app
    app = locate_app(import_name, name)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/flask/cli.py", line 219, in locate_app
    __import__(module_name)
  File "/Users/andi/Projects/sqlalchemy_spike/app.py", line 11, in <module>
    app = create_app()
  File "/Users/andi/Projects/sqlalchemy_spike/app.py", line 7, in create_app
    init_db()
  File "/Users/andi/Projects/sqlalchemy_spike/database.py", line 22, in init_db
    import models
  File "/Users/andi/Projects/sqlalchemy_spike/models.py", line 14, in <module>
    class Company(Base):
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/decl_api.py", line 195, in __init__
    _as_declarative(reg, cls, dict_)
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/decl_base.py", line 247, in _as_declarative
    return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/decl_base.py", line 328, in setup_mapping
    return _ClassScanMapperConfig(
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/decl_base.py", line 563, in __init__
    self._scan_attributes()
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/decl_base.py", line 1006, in _scan_attributes
    collected_annotation = self._collect_annotation(
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/decl_base.py", line 1277, in _collect_annotation
    extracted = _extract_mapped_subtype(
  File "/Users/andi/.pyenv/versions/3.9.6/envs/spike/lib/python3.9/site-packages/sqlalchemy/orm/util.py", line 2353, in _extract_mapped_subtype
    raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: Could not interpret annotation Mapped[List[Manager]].  Check that it uses names that are correctly imported at the module level. See chained stack trace for more hints.

using:

Flask==3.0.0
SQLAlchemy==2.0.23

Solution

  • In this situation - where you need to reference a type before it is declared - you can make a forward declaration by passing the type as a string (note that you also need to import List from the typing module to complete the type hint.

    from typing import List
    ...
    class Company(Base):
        ...
        managers: Mapped[List["Manager"]] = relationship(back_populates="company")