declared_attr.cascading fails to typecheck under mypy / pyright on documented usage

Ouverte Adaptée aux débutants
#13,731 3 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

  • #13736 par @xixi-box — fermée sans fusion

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
76/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
python, sqlalchemy

Piste de recherche

Commencez dans lib/sqlalchemy/orm/decl_api.py et comparez _stateful_declared_attr.__call__ à _declared_directive.__call__, comme l’issue le signale pour la différence présumée concernant la variable de type. Vérifiez l’utilisation documentée de declared_attr.cascading avec mypy et pyright, puis exécutez les tests de typage pertinents. Le travail est terminé lorsque l’exemple documenté passe la vérification de types et que les types révélés de ses attributs sont précis.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

bug orm typing
Ensure stubs packages are not installed
  • No sqlalchemy stub packages is installed (both sqlalchemy-stubs and sqlalchemy2-stubs are not compatible with v2)
Verify if the api is typed
  • The api is not in a module listed in #6810 so it should pass type checking
Describe the typing issue

@declared_attr.cascading produces a mypy/pyright error when used as documented.
The error seems to be in the library's annotations, not in user-side code.
Adding @classmethod as the 2.1 docs recommend for declared_attr, gives the same error with Callable[[type[HasUser]], ...]. Unfortunately, I didn't find a workaround.
The probable cause is:
_stateful_declared_attr.__call__ reuses the class level _T_co type variable, which is already bound by the time declared_attr.cascading is accessed, so nothing is left to solve and no argument matches. The sibling method _declared_directive.__call__ introduces a fresh _TT type variable and works correctly as expected

To Reproduce
from typing import reveal_type

from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class HasIdMixin:
    @declared_attr.cascading
    @classmethod
    def id(cls) -> Mapped[int]:
        if has_inherited_table(cls):
            return mapped_column(ForeignKey("person.id"), primary_key=True)
        else:
            return mapped_column(Integer, primary_key=True)


class Person(HasIdMixin, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}


class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}


reveal_type(Engineer.id) # Expected: InstrumentedAttribute[int], actual: InstrumentedAttribute[_T`1] (mypy)
reveal_type(Engineer().id) # Expected: int, actual: _T`1 (mypy)
Error

mypy 1.15.0

sqlalc_issue.py:17: error: Argument 1 to "__call__" of "_stateful_declared_attr" has incompatible type "Callable[[type[HasIdMixin]], Mapped[int]]"; expected "Callable[..., Mapped[_T] | ORMDescriptor[_T] | SQLCoreOperations[_T]]"  [arg-type]
sqlalc_issue.py:40: note: Revealed type is "sqlalchemy.orm.attributes.InstrumentedAttribute[_T`1]"
sqlalc_issue.py:41: note: Revealed type is "_T`1"
Found 1 error in 1 file (checked 1 source file)

pyright 1.1.414

/Users/daniil.kalinin/PyCharmMiscProject/sqlalc_issue.py
  /Users/daniil.kalinin/PyCharmMiscProject/sqlalc_issue.py:40:13 - information: Type of "Engineer.id" is "InstrumentedAttribute[Unknown]"
  /Users/daniil.kalinin/PyCharmMiscProject/sqlalc_issue.py:41:13 - information: Type of "Engineer().id" is "Unknown"
0 errors, 0 warnings, 2 informations
Versions
  • OS: macOS 26.7.1
  • Python: 3.15.1
  • SQLAlchemy: 2.1.6
  • Type checker (eg: mypy 0.991, pyright 1.1.290, etc): mypy 1.15.0, pyright 1.1.414
Additional context

The fix should be quite easy:
file: lib/sqlalchemy/orm/decl_api.py

     def __call__(
-        self, fn: _DeclaredAttrDecorated[_T_co]
-    ) -> declared_attr[_T_co]:
+        self, fn: _DeclaredAttrDecorated[_TT]
+    ) -> declared_attr[_TT]:
         return declared_attr(fn, **self.kw)

I would be happy to create a PR

Langage dominant
Python
Étoiles
12.3k
Forks
1.8k
Merge moyen
2 j 10 h
PR mergées (30 j)
9

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de sqlalchemy/sqlalchemy

Toutes les issues de sqlalchemy/sqlalchemy

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.