Visitar URL original
`_extract_mapped_subtype` seems to be broken in Python 3.14 without `from __future__ import annotations` · Issue #13602 · sqlalchemy/sqlalchemy · GitHub
Skip to content

_extract_mapped_subtype seems to be broken in Python 3.14 without from __future__ import annotations #13602

Closed

Description

@zzzeek

Discussed in #13601

Originally posted by Daverball September 22, 2026
Currently _extract_mapped_subtype seems to make the mistaken assumption that the only way a NameError occurs in de_stringify_annotation is if the outside of the annotation is a string. But this is no longer the case with Python 3.14+ without from __future__ import annotations, it could very easily be a ForwardRef too.

This means this line, which is supposed to ignore errors on annotations that are not wrapped in Mapped, will now error on any annotation that results in a NameError when trying to de-stringify it.

This line needs to take into account whether raw_annotation is a string, if it's a ForwardRef it should check __forward_arg__ instead and if it's not its typing.get_origin should match Mapped.

Minimal reproducer:

from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase): ...
class Foo(Base):
    __tablename__ = 'foo'
    x: Bogus 

Here's a quick draft for a possible helper function to check if raw_annotation is a Mapped subscript, I'm not sure if the last case can ever actually occur, but it still seems worth to guard against it just in case.

def _is_mapped_annotation(raw_annotation: _AnnotationScanType) -> bool:
    if isinstance(raw_annotation, str):
        return "Mapped[" in raw_annotation
    if isinstance(raw_annotation, typing.ForwardRef):
        return "Mapped[" in raw_annotation.__forward_arg__
    return issubclass(typing.get_origin(raw_annotation), Mapped)

The given example will fail either way, but rather than getting a descriptive MappedAnnotationError you will get a non-descriptive TypeError in _is_mapped_annotation.

It might also be worth considering skipping de_stringify_annotation if raw_annotation is not a string, since this particular function seems to be more of a quick pre-check and only cares about the outermost annotation, with errors for the inner annotations being emitted later, but I might be interpreting it wrong.

Activity

  1. added
    bugSomething isn't working
    orm - annotated declarativeissues with the new annotations-based declarative ORM approach
    on Sep 22, 2026
  2. added this to the 2.0.x milestone on Sep 22, 2026
  3. sqla-tester commented on Sep 22, 2026

    @sqla-tester
    Collaborator

    Mike Bayer has proposed a fix for this issue in the main branch:

    handle ForwardRef in un-resolvable annotation Mapped[] check https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/6987

  4. sqla-tester commented on Sep 22, 2026

    @sqla-tester
    Collaborator

    Mike Bayer has proposed a fix for this issue in the rel_2_0 branch:

    handle ForwardRef in un-resolvable annotation Mapped[] check https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/6988

  5. CaselIT commented on Sep 22, 2026

    @CaselIT
    Member

    @Abdellox don't allow your bot to comment automatically. ban will happen

  6. added a commit that references this issue on Sep 24, 2026
    c0d1e21
  7. added a commit that references this issue on Sep 24, 2026
    5503ac2
  8. added 2 commits that reference this issue on Oct 29, 2026
    47e37e7
    9642112
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingorm - annotated declarativeissues with the new annotations-based declarative ORM approach

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions