--- /dev/null
+.. change::
+ :tags: typing
+ :tickets: 12293
+
+ Removed the deprecated mypy plugin.
+ The plugin was non-functional with newer version of mypy and it's no
+ longer needed with modern SQLAlchemy declarative style.
:func:`_orm.mapped_column`.
.. versionchanged:: 2.0 For users coming from the 1.4 series of SQLAlchemy
- who may have been using the :ref:`mypy plugin <mypy_toplevel>`, the
+ who may have been using the ``mypy plugin``, the
:func:`_orm.declarative_mixin` class decorator is no longer needed
to mark declarative mixins, assuming the mypy plugin is no longer in use.
automap
baked
declarative/index
- mypy
mutable
orderinglist
horizontal_shard
+++ /dev/null
-.. _mypy_toplevel:
-
-Mypy / Pep-484 Support for ORM Mappings
-========================================
-
-Support for :pep:`484` typing annotations as well as the
-MyPy_ type checking tool when using SQLAlchemy
-:ref:`declarative <orm_declarative_mapper_config_toplevel>` mappings
-that refer to the :class:`_schema.Column` object directly, rather than
-the :func:`_orm.mapped_column` construct introduced in SQLAlchemy 2.0.
-
-.. deprecated:: 2.0
-
- **The SQLAlchemy Mypy Plugin is DEPRECATED, and will be removed possibly
- as early as the SQLAlchemy 2.1 release. We would urge users to please
- migrate away from it ASAP. The mypy plugin also works only up until
- mypy version 1.10.1. version 1.11.0 and greater may not work properly.**
-
- This plugin cannot be maintained across constantly changing releases
- of mypy and its stability going forward CANNOT be guaranteed.
-
- Modern SQLAlchemy now offers
- :ref:`fully pep-484 compliant mapping syntaxes <whatsnew_20_orm_declarative_typing>`;
- see the linked section for migration details.
-
-.. topic:: SQLAlchemy Mypy Plugin Status Update
-
- **Updated July 2024**
-
- The mypy plugin is supported **only up until mypy 1.10.1, and it will have
- issues running with 1.11.0 or greater**. Use with mypy 1.11.0 or greater
- may have error conditions which currently cannot be resolved.
-
- For SQLAlchemy 2.0, the Mypy plugin continues to work at the level at which
- it reached in the SQLAlchemy 1.4 release. SQLAlchemy 2.0 however features
- an
- :ref:`all new typing system <whatsnew_20_orm_declarative_typing>`
- for ORM Declarative models that removes the need for the Mypy plugin and
- delivers much more consistent behavior with generally superior capabilities.
- Note that this new capability is **not
- part of SQLAlchemy 1.4, it is only in SQLAlchemy 2.0**.
-
- The SQLAlchemy Mypy plugin, while it has technically never left the "alpha"
- stage, should **now be considered as deprecated in SQLAlchemy 2.0, even
- though it is still necessary for full Mypy support when using
- SQLAlchemy 1.4**.
-
- The Mypy plugin itself does not solve the issue of supplying correct typing
- with other typing tools such as Pylance/Pyright, Pytype, Pycharm, etc, which
- cannot make use of Mypy plugins. Additionally, Mypy plugins are extremely
- difficult to develop, maintain and test, as a Mypy plugin must be deeply
- integrated with Mypy's internal datastructures and processes, which itself
- are not stable within the Mypy project itself. The SQLAlchemy Mypy plugin
- has lots of limitations when used with code that deviates from very basic
- patterns which are reported regularly.
-
- For these reasons, new non-regression issues reported against the Mypy
- plugin are unlikely to be fixed. **Existing code that passes Mypy checks
- using the plugin with SQLAlchemy 1.4 installed will continue to pass all
- checks in SQLAlchemy 2.0 without any changes required, provided the plugin
- is still used. SQLAlchemy 2.0's API is fully
- backwards compatible with the SQLAlchemy 1.4 API and Mypy plugin behavior.**
-
- End-user code that passes all checks under SQLAlchemy 1.4 with the Mypy
- plugin may incrementally migrate to the new structures, once
- that code is running exclusively on SQLAlchemy 2.0. See the section
- :ref:`whatsnew_20_orm_declarative_typing` for background on how this
- migration may proceed.
-
- Code that is running exclusively on SQLAlchemy version
- 2.0 and has fully migrated to the new declarative constructs will enjoy full
- compliance with pep-484 as well as working correctly within IDEs and other
- typing tools, without the need for plugins.
-
-
-Installation
-------------
-
-For **SQLAlchemy 2.0 only**: No stubs should be installed and packages
-like sqlalchemy-stubs_ and sqlalchemy2-stubs_ should be fully uninstalled.
-
-The Mypy_ package itself is a dependency.
-
-Mypy may be installed using the "mypy" extras hook using pip:
-
-.. sourcecode:: text
-
- pip install sqlalchemy[mypy]
-
-The plugin itself is configured as described in
-`Configuring mypy to use Plugins <https://mypy.readthedocs.io/en/latest/extending_mypy.html#configuring-mypy-to-use-plugins>`_,
-using the ``sqlalchemy.ext.mypy.plugin`` module name, such as within
-``setup.cfg``::
-
- [mypy]
- plugins = sqlalchemy.ext.mypy.plugin
-
-.. _sqlalchemy-stubs: https://github.com/dropbox/sqlalchemy-stubs
-
-.. _sqlalchemy2-stubs: https://github.com/sqlalchemy/sqlalchemy2-stubs
-
-What the Plugin Does
---------------------
-
-The primary purpose of the Mypy plugin is to intercept and alter the static
-definition of SQLAlchemy
-:ref:`declarative mappings <orm_declarative_mapper_config_toplevel>` so that
-they match up to how they are structured after they have been
-:term:`instrumented` by their :class:`_orm.Mapper` objects. This allows both
-the class structure itself as well as code that uses the class to make sense to
-the Mypy tool, which otherwise would not be the case based on how declarative
-mappings currently function. The plugin is not unlike similar plugins
-that are required for libraries like
-`dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ which
-alter classes dynamically at runtime.
-
-To cover the major areas where this occurs, consider the following ORM
-mapping, using the typical example of the ``User`` class::
-
- from sqlalchemy import Column, Integer, String, select
- from sqlalchemy.orm import declarative_base
-
- # "Base" is a class that is created dynamically from the
- # declarative_base() function
- Base = declarative_base()
-
-
- class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
-
- # "some_user" is an instance of the User class, which
- # accepts "id" and "name" kwargs based on the mapping
- some_user = User(id=5, name="user")
-
- # it has an attribute called .name that's a string
- print(f"Username: {some_user.name}")
-
- # a select() construct makes use of SQL expressions derived from the
- # User class itself
- select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
-
-Above, the steps that the Mypy extension can take include:
-
-* Interpretation of the ``Base`` dynamic class generated by
- :func:`_orm.declarative_base`, so that classes which inherit from it
- are known to be mapped. It also can accommodate the class decorator
- approach described at :ref:`orm_declarative_decorator`.
-
-* Type inference for ORM mapped attributes that are defined in declarative
- "inline" style, in the above example the ``id`` and ``name`` attributes of
- the ``User`` class. This includes that an instance of ``User`` will use
- ``int`` for ``id`` and ``str`` for ``name``. It also includes that when the
- ``User.id`` and ``User.name`` class-level attributes are accessed, as they
- are above in the ``select()`` statement, they are compatible with SQL
- expression behavior, which is derived from the
- :class:`_orm.InstrumentedAttribute` attribute descriptor class.
-
-* Application of an ``__init__()`` method to mapped classes that do not
- already include an explicit constructor, which accepts keyword arguments
- of specific types for all mapped attributes detected.
-
-When the Mypy plugin processes the above file, the resulting static class
-definition and Python code passed to the Mypy tool is equivalent to the
-following::
-
- from sqlalchemy import Column, Integer, String, select
- from sqlalchemy.orm import Mapped
- from sqlalchemy.orm.decl_api import DeclarativeMeta
-
-
- class Base(metaclass=DeclarativeMeta):
- __abstract__ = True
-
-
- class User(Base):
- __tablename__ = "user"
-
- id: Mapped[Optional[int]] = Mapped._special_method(
- Column(Integer, primary_key=True)
- )
- name: Mapped[Optional[str]] = Mapped._special_method(Column(String))
-
- def __init__(self, id: Optional[int] = ..., name: Optional[str] = ...) -> None: ...
-
-
- some_user = User(id=5, name="user")
-
- print(f"Username: {some_user.name}")
-
- select_stmt = select(User).where(User.id.in_([3, 4, 5])).where(User.name.contains("s"))
-
-The key steps which have been taken above include:
-
-* The ``Base`` class is now defined in terms of the :class:`_orm.DeclarativeMeta`
- class explicitly, rather than being a dynamic class.
-
-* The ``id`` and ``name`` attributes are defined in terms of the
- :class:`_orm.Mapped` class, which represents a Python descriptor that
- exhibits different behaviors at the class vs. instance levels. The
- :class:`_orm.Mapped` class is now the base class for the :class:`_orm.InstrumentedAttribute`
- class that is used for all ORM mapped attributes.
-
- :class:`_orm.Mapped` is defined as a generic class against arbitrary Python
- types, meaning specific occurrences of :class:`_orm.Mapped` are associated
- with a specific Python type, such as ``Mapped[Optional[int]]`` and
- ``Mapped[Optional[str]]`` above.
-
-* The right-hand side of the declarative mapped attribute assignments are
- **removed**, as this resembles the operation that the :class:`_orm.Mapper`
- class would normally be doing, which is that it would be replacing these
- attributes with specific instances of :class:`_orm.InstrumentedAttribute`.
- The original expression is moved into a function call that will allow it to
- still be type-checked without conflicting with the left-hand side of the
- expression. For Mypy purposes, the left-hand typing annotation is sufficient
- for the attribute's behavior to be understood.
-
-* A type stub for the ``User.__init__()`` method is added which includes the
- correct keywords and datatypes.
-
-Usage
-------
-
-The following subsections will address individual uses cases that have
-so far been considered for pep-484 compliance.
-
-
-Introspection of Columns based on TypeEngine
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For mapped columns that include an explicit datatype, when they are mapped
-as inline attributes, the mapped type will be introspected automatically::
-
- class MyClass(Base):
- # ...
-
- id = Column(Integer, primary_key=True)
- name = Column("employee_name", String(50), nullable=False)
- other_name = Column(String(50))
-
-Above, the ultimate class-level datatypes of ``id``, ``name`` and
-``other_name`` will be introspected as ``Mapped[Optional[int]]``,
-``Mapped[Optional[str]]`` and ``Mapped[Optional[str]]``. The types are by
-default **always** considered to be ``Optional``, even for the primary key and
-non-nullable column. The reason is because while the database columns "id" and
-"name" can't be NULL, the Python attributes ``id`` and ``name`` most certainly
-can be ``None`` without an explicit constructor::
-
- >>> m1 = MyClass()
- >>> m1.id
- None
-
-The types of the above columns can be stated **explicitly**, providing the
-two advantages of clearer self-documentation as well as being able to
-control which types are optional::
-
- class MyClass(Base):
- # ...
-
- id: int = Column(Integer, primary_key=True)
- name: str = Column("employee_name", String(50), nullable=False)
- other_name: Optional[str] = Column(String(50))
-
-The Mypy plugin will accept the above ``int``, ``str`` and ``Optional[str]``
-and convert them to include the ``Mapped[]`` type surrounding them. The
-``Mapped[]`` construct may also be used explicitly::
-
- from sqlalchemy.orm import Mapped
-
-
- class MyClass(Base):
- # ...
-
- id: Mapped[int] = Column(Integer, primary_key=True)
- name: Mapped[str] = Column("employee_name", String(50), nullable=False)
- other_name: Mapped[Optional[str]] = Column(String(50))
-
-When the type is non-optional, it simply means that the attribute as accessed
-from an instance of ``MyClass`` will be considered to be non-None::
-
- mc = MyClass(...)
-
- # will pass mypy --strict
- name: str = mc.name
-
-For optional attributes, Mypy considers that the type must include None
-or otherwise be ``Optional``::
-
- mc = MyClass(...)
-
- # will pass mypy --strict
- other_name: Optional[str] = mc.name
-
-Whether or not the mapped attribute is typed as ``Optional``, the
-generation of the ``__init__()`` method will **still consider all keywords
-to be optional**. This is again matching what the SQLAlchemy ORM actually
-does when it creates the constructor, and should not be confused with the
-behavior of a validating system such as Python ``dataclasses`` which will
-generate a constructor that matches the annotations in terms of optional
-vs. required attributes.
-
-
-Columns that Don't have an Explicit Type
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Columns that include a :class:`_schema.ForeignKey` modifier do not need
-to specify a datatype in a SQLAlchemy declarative mapping. For
-this type of attribute, the Mypy plugin will inform the user that it
-needs an explicit type to be sent::
-
- # .. other imports
- from sqlalchemy.sql.schema import ForeignKey
-
- Base = declarative_base()
-
-
- class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
-
- class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id = Column(ForeignKey("user.id"))
-
-The plugin will deliver the message as follows:
-
-.. sourcecode:: text
-
- $ mypy test3.py --strict
- test3.py:20: error: [SQLAlchemy Mypy plugin] Can't infer type from
- ORM mapped expression assigned to attribute 'user_id'; please specify a
- Python type or Mapped[<python type>] on the left hand side.
- Found 1 error in 1 file (checked 1 source file)
-
-To resolve, apply an explicit type annotation to the ``Address.user_id``
-column::
-
- class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
-
-Mapping Columns with Imperative Table
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In :ref:`imperative table style <orm_imperative_table_configuration>`, the
-:class:`_schema.Column` definitions are given inside of a :class:`_schema.Table`
-construct which is separate from the mapped attributes themselves. The Mypy
-plugin does not consider this :class:`_schema.Table`, but instead supports that
-the attributes can be explicitly stated with a complete annotation that
-**must** use the :class:`_orm.Mapped` class to identify them as mapped attributes::
-
- class MyClass(Base):
- __table__ = Table(
- "mytable",
- Base.metadata,
- Column(Integer, primary_key=True),
- Column("employee_name", String(50), nullable=False),
- Column(String(50)),
- )
-
- id: Mapped[int]
- name: Mapped[str]
- other_name: Mapped[Optional[str]]
-
-The above :class:`_orm.Mapped` annotations are considered as mapped columns and
-will be included in the default constructor, as well as provide the correct
-typing profile for ``MyClass`` both at the class level and the instance level.
-
-Mapping Relationships
-^^^^^^^^^^^^^^^^^^^^^^
-
-The plugin has limited support for using type inference to detect the types
-for relationships. For all those cases where it can't detect the type,
-it will emit an informative error message, and in all cases the appropriate
-type may be provided explicitly, either with the :class:`_orm.Mapped`
-class or optionally omitting it for an inline declaration. The plugin
-also needs to determine whether or not the relationship refers to a collection
-or a scalar, and for that it relies upon the explicit value of
-the :paramref:`_orm.relationship.uselist` and/or :paramref:`_orm.relationship.collection_class`
-parameters. An explicit type is needed if neither of these parameters are
-present, as well as if the target type of the :func:`_orm.relationship`
-is a string or callable, and not a class::
-
- class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
-
- class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
-
- user = relationship(User)
-
-The above mapping will produce the following error:
-
-.. sourcecode:: text
-
- test3.py:22: error: [SQLAlchemy Mypy plugin] Can't infer scalar or
- collection for ORM mapped expression assigned to attribute 'user'
- if both 'uselist' and 'collection_class' arguments are absent from the
- relationship(); please specify a type annotation on the left hand side.
- Found 1 error in 1 file (checked 1 source file)
-
-The error can be resolved either by using ``relationship(User, uselist=False)``
-or by providing the type, in this case the scalar ``User`` object::
-
- class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
-
- user: User = relationship(User)
-
-For collections, a similar pattern applies, where in the absence of
-``uselist=True`` or a :paramref:`_orm.relationship.collection_class`,
-a collection annotation such as ``List`` may be used. It is also fully
-appropriate to use the string name of the class in the annotation as supported
-by pep-484, ensuring the class is imported with in
-the `TYPE_CHECKING block <https://www.python.org/dev/peps/pep-0484/#runtime-or-type-checking>`_
-as appropriate::
-
- from typing import TYPE_CHECKING, List
-
- from .mymodel import Base
-
- if TYPE_CHECKING:
- # if the target of the relationship is in another module
- # that cannot normally be imported at runtime
- from .myaddressmodel import Address
-
-
- class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
- addresses: List["Address"] = relationship("Address")
-
-As is the case with columns, the :class:`_orm.Mapped` class may also be
-applied explicitly::
-
- class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
- addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
-
-
- class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
-
- user: Mapped[User] = relationship(User, back_populates="addresses")
-
-.. _mypy_declarative_mixins:
-
-Using @declared_attr and Declarative Mixins
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The :class:`_orm.declared_attr` class allows Declarative mapped attributes to
-be declared in class level functions, and is particularly useful when using
-:ref:`declarative mixins <orm_mixins_toplevel>`. For these functions, the return
-type of the function should be annotated using either the ``Mapped[]``
-construct or by indicating the exact kind of object returned by the function.
-Additionally, "mixin" classes that are not otherwise mapped (i.e. don't extend
-from a :func:`_orm.declarative_base` class nor are they mapped with a method
-such as :meth:`_orm.registry.mapped`) should be decorated with the
-:func:`_orm.declarative_mixin` decorator, which provides a hint to the Mypy
-plugin that a particular class intends to serve as a declarative mixin::
-
- from sqlalchemy.orm import declarative_mixin, declared_attr
-
-
- @declarative_mixin
- class HasUpdatedAt:
- @declared_attr
- def updated_at(cls) -> Column[DateTime]: # uses Column
- return Column(DateTime)
-
-
- @declarative_mixin
- class HasCompany:
- @declared_attr
- def company_id(cls) -> Mapped[int]: # uses Mapped
- return mapped_column(ForeignKey("company.id"))
-
- @declared_attr
- def company(cls) -> Mapped["Company"]:
- return relationship("Company")
-
-
- class Employee(HasUpdatedAt, HasCompany, Base):
- __tablename__ = "employee"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
-Note the mismatch between the actual return type of a method like
-``HasCompany.company`` vs. what is annotated. The Mypy plugin converts
-all ``@declared_attr`` functions into simple annotated attributes to avoid
-this complexity::
-
- # what Mypy sees
- class HasCompany:
- company_id: Mapped[int]
- company: Mapped["Company"]
-
-Combining with Dataclasses or Other Type-Sensitive Attribute Systems
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The examples of Python dataclasses integration at :ref:`orm_declarative_dataclasses`
-presents a problem; Python dataclasses expect an explicit type that it will
-use to build the class, and the value given in each assignment statement
-is significant. That is, a class as follows has to be stated exactly
-as it is in order to be accepted by dataclasses::
-
- mapper_registry: registry = registry()
-
-
- @mapper_registry.mapped
- @dataclass
- class User:
- __table__ = Table(
- "user",
- mapper_registry.metadata,
- Column("id", Integer, primary_key=True),
- Column("name", String(50)),
- Column("fullname", String(50)),
- Column("nickname", String(12)),
- )
- id: int = field(init=False)
- name: Optional[str] = None
- fullname: Optional[str] = None
- nickname: Optional[str] = None
- addresses: List[Address] = field(default_factory=list)
-
- __mapper_args__ = { # type: ignore
- "properties": {"addresses": relationship("Address")}
- }
-
-We can't apply our ``Mapped[]`` types to the attributes ``id``, ``name``,
-etc. because they will be rejected by the ``@dataclass`` decorator. Additionally,
-Mypy has another plugin for dataclasses explicitly which can also get in the
-way of what we're doing.
-
-The above class will actually pass Mypy's type checking without issue; the
-only thing we are missing is the ability for attributes on ``User`` to be
-used in SQL expressions, such as::
-
- stmt = select(User.name).where(User.id.in_([1, 2, 3]))
-
-To provide a workaround for this, the Mypy plugin has an additional feature
-whereby we can specify an extra attribute ``_mypy_mapped_attrs``, that is
-a list that encloses the class-level objects or their string names.
-This attribute can be conditional within the ``TYPE_CHECKING`` variable::
-
- @mapper_registry.mapped
- @dataclass
- class User:
- __table__ = Table(
- "user",
- mapper_registry.metadata,
- Column("id", Integer, primary_key=True),
- Column("name", String(50)),
- Column("fullname", String(50)),
- Column("nickname", String(12)),
- )
- id: int = field(init=False)
- name: Optional[str] = None
- fullname: Optional[str]
- nickname: Optional[str]
- addresses: List[Address] = field(default_factory=list)
-
- if TYPE_CHECKING:
- _mypy_mapped_attrs = [id, name, "fullname", "nickname", addresses]
-
- __mapper_args__ = { # type: ignore
- "properties": {"addresses": relationship("Address")}
- }
-
-With the above recipe, the attributes listed in ``_mypy_mapped_attrs``
-will be applied with the :class:`_orm.Mapped` typing information so that the
-``User`` class will behave as a SQLAlchemy mapped class when used in a
-class-bound context.
-
-.. _Mypy: https://mypy.readthedocs.io/
+++ /dev/null
-# ext/mypy/__init__.py
-# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
+++ /dev/null
-# ext/mypy/apply.py
-# Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-from __future__ import annotations
-
-from typing import List
-from typing import Optional
-from typing import Union
-
-from mypy.nodes import ARG_NAMED_OPT
-from mypy.nodes import Argument
-from mypy.nodes import AssignmentStmt
-from mypy.nodes import CallExpr
-from mypy.nodes import ClassDef
-from mypy.nodes import MDEF
-from mypy.nodes import MemberExpr
-from mypy.nodes import NameExpr
-from mypy.nodes import RefExpr
-from mypy.nodes import StrExpr
-from mypy.nodes import SymbolTableNode
-from mypy.nodes import TempNode
-from mypy.nodes import TypeInfo
-from mypy.nodes import Var
-from mypy.plugin import SemanticAnalyzerPluginInterface
-from mypy.plugins.common import add_method_to_class
-from mypy.types import AnyType
-from mypy.types import get_proper_type
-from mypy.types import Instance
-from mypy.types import NoneTyp
-from mypy.types import ProperType
-from mypy.types import TypeOfAny
-from mypy.types import UnboundType
-from mypy.types import UnionType
-
-from . import infer
-from . import util
-from .names import expr_to_mapped_constructor
-from .names import NAMED_TYPE_SQLA_MAPPED
-
-
-def apply_mypy_mapped_attr(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- item: Union[NameExpr, StrExpr],
- attributes: List[util.SQLAlchemyAttribute],
-) -> None:
- if isinstance(item, NameExpr):
- name = item.name
- elif isinstance(item, StrExpr):
- name = item.value
- else:
- return None
-
- for stmt in cls.defs.body:
- if (
- isinstance(stmt, AssignmentStmt)
- and isinstance(stmt.lvalues[0], NameExpr)
- and stmt.lvalues[0].name == name
- ):
- break
- else:
- util.fail(api, f"Can't find mapped attribute {name}", cls)
- return None
-
- if stmt.type is None:
- util.fail(
- api,
- "Statement linked from _mypy_mapped_attrs has no "
- "typing information",
- stmt,
- )
- return None
-
- left_hand_explicit_type = get_proper_type(stmt.type)
- assert isinstance(
- left_hand_explicit_type, (Instance, UnionType, UnboundType)
- )
-
- attributes.append(
- util.SQLAlchemyAttribute(
- name=name,
- line=item.line,
- column=item.column,
- typ=left_hand_explicit_type,
- info=cls.info,
- )
- )
-
- apply_type_to_mapped_statement(
- api, stmt, stmt.lvalues[0], left_hand_explicit_type, None
- )
-
-
-def re_apply_declarative_assignments(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- attributes: List[util.SQLAlchemyAttribute],
-) -> None:
- """For multiple class passes, re-apply our left-hand side types as mypy
- seems to reset them in place.
-
- """
- mapped_attr_lookup = {attr.name: attr for attr in attributes}
- update_cls_metadata = False
-
- for stmt in cls.defs.body:
- # for a re-apply, all of our statements are AssignmentStmt;
- # @declared_attr calls will have been converted and this
- # currently seems to be preserved by mypy (but who knows if this
- # will change).
- if (
- isinstance(stmt, AssignmentStmt)
- and isinstance(stmt.lvalues[0], NameExpr)
- and stmt.lvalues[0].name in mapped_attr_lookup
- and isinstance(stmt.lvalues[0].node, Var)
- ):
- left_node = stmt.lvalues[0].node
-
- python_type_for_type = mapped_attr_lookup[
- stmt.lvalues[0].name
- ].type
-
- left_node_proper_type = get_proper_type(left_node.type)
-
- # if we have scanned an UnboundType and now there's a more
- # specific type than UnboundType, call the re-scan so we
- # can get that set up correctly
- if (
- isinstance(python_type_for_type, UnboundType)
- and not isinstance(left_node_proper_type, UnboundType)
- and (
- isinstance(stmt.rvalue, CallExpr)
- and isinstance(stmt.rvalue.callee, MemberExpr)
- and isinstance(stmt.rvalue.callee.expr, NameExpr)
- and stmt.rvalue.callee.expr.node is not None
- and stmt.rvalue.callee.expr.node.fullname
- == NAMED_TYPE_SQLA_MAPPED
- and stmt.rvalue.callee.name == "_empty_constructor"
- and isinstance(stmt.rvalue.args[0], CallExpr)
- and isinstance(stmt.rvalue.args[0].callee, RefExpr)
- )
- ):
- new_python_type_for_type = (
- infer.infer_type_from_right_hand_nameexpr(
- api,
- stmt,
- left_node,
- left_node_proper_type,
- stmt.rvalue.args[0].callee,
- )
- )
-
- if new_python_type_for_type is not None and not isinstance(
- new_python_type_for_type, UnboundType
- ):
- python_type_for_type = new_python_type_for_type
-
- # update the SQLAlchemyAttribute with the better
- # information
- mapped_attr_lookup[stmt.lvalues[0].name].type = (
- python_type_for_type
- )
-
- update_cls_metadata = True
-
- if (
- not isinstance(left_node.type, Instance)
- or left_node.type.type.fullname != NAMED_TYPE_SQLA_MAPPED
- ):
- assert python_type_for_type is not None
- left_node.type = api.named_type(
- NAMED_TYPE_SQLA_MAPPED, [python_type_for_type]
- )
-
- if update_cls_metadata:
- util.set_mapped_attributes(cls.info, attributes)
-
-
-def apply_type_to_mapped_statement(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- lvalue: NameExpr,
- left_hand_explicit_type: Optional[ProperType],
- python_type_for_type: Optional[ProperType],
-) -> None:
- """Apply the Mapped[<type>] annotation and right hand object to a
- declarative assignment statement.
-
- This converts a Python declarative class statement such as::
-
- class User(Base):
- # ...
-
- attrname = Column(Integer)
-
- To one that describes the final Python behavior to Mypy::
-
- ... format: off
-
- class User(Base):
- # ...
-
- attrname : Mapped[Optional[int]] = <meaningless temp node>
-
- ... format: on
-
- """
- left_node = lvalue.node
- assert isinstance(left_node, Var)
-
- # to be completely honest I have no idea what the difference between
- # left_node.type and stmt.type is, what it means if these are different
- # vs. the same, why in order to get tests to pass I have to assign
- # to stmt.type for the second case and not the first. this is complete
- # trying every combination until it works stuff.
-
- if left_hand_explicit_type is not None:
- lvalue.is_inferred_def = False
- left_node.type = api.named_type(
- NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type]
- )
- else:
- lvalue.is_inferred_def = False
- left_node.type = api.named_type(
- NAMED_TYPE_SQLA_MAPPED,
- (
- [AnyType(TypeOfAny.special_form)]
- if python_type_for_type is None
- else [python_type_for_type]
- ),
- )
-
- # so to have it skip the right side totally, we can do this:
- # stmt.rvalue = TempNode(AnyType(TypeOfAny.special_form))
-
- # however, if we instead manufacture a new node that uses the old
- # one, then we can still get type checking for the call itself,
- # e.g. the Column, relationship() call, etc.
-
- # rewrite the node as:
- # <attr> : Mapped[<typ>] =
- # _sa_Mapped._empty_constructor(<original CallExpr from rvalue>)
- # the original right-hand side is maintained so it gets type checked
- # internally
- stmt.rvalue = expr_to_mapped_constructor(stmt.rvalue)
-
- if stmt.type is not None and python_type_for_type is not None:
- stmt.type = python_type_for_type
-
-
-def add_additional_orm_attributes(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- attributes: List[util.SQLAlchemyAttribute],
-) -> None:
- """Apply __init__, __table__ and other attributes to the mapped class."""
-
- info = util.info_for_cls(cls, api)
-
- if info is None:
- return
-
- is_base = util.get_is_base(info)
-
- if "__init__" not in info.names and not is_base:
- mapped_attr_names = {attr.name: attr.type for attr in attributes}
-
- for base in info.mro[1:-1]:
- if "sqlalchemy" not in info.metadata:
- continue
-
- base_cls_attributes = util.get_mapped_attributes(base, api)
- if base_cls_attributes is None:
- continue
-
- for attr in base_cls_attributes:
- mapped_attr_names.setdefault(attr.name, attr.type)
-
- arguments = []
- for name, typ in mapped_attr_names.items():
- if typ is None:
- typ = AnyType(TypeOfAny.special_form)
- arguments.append(
- Argument(
- variable=Var(name, typ),
- type_annotation=typ,
- initializer=TempNode(typ),
- kind=ARG_NAMED_OPT,
- )
- )
-
- add_method_to_class(api, cls, "__init__", arguments, NoneTyp())
-
- if "__table__" not in info.names and util.get_has_table(info):
- _apply_placeholder_attr_to_class(
- api, cls, "sqlalchemy.sql.schema.Table", "__table__"
- )
- if not is_base:
- _apply_placeholder_attr_to_class(
- api, cls, "sqlalchemy.orm.mapper.Mapper", "__mapper__"
- )
-
-
-def _apply_placeholder_attr_to_class(
- api: SemanticAnalyzerPluginInterface,
- cls: ClassDef,
- qualified_name: str,
- attrname: str,
-) -> None:
- sym = api.lookup_fully_qualified_or_none(qualified_name)
- if sym:
- assert isinstance(sym.node, TypeInfo)
- type_: ProperType = Instance(sym.node, [])
- else:
- type_ = AnyType(TypeOfAny.special_form)
- var = Var(attrname)
- var._fullname = cls.fullname + "." + attrname
- var.info = cls.info
- var.type = type_
- cls.info.names[attrname] = SymbolTableNode(MDEF, var)
+++ /dev/null
-# ext/mypy/decl_class.py
-# Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-from __future__ import annotations
-
-from typing import List
-from typing import Optional
-from typing import Union
-
-from mypy.nodes import AssignmentStmt
-from mypy.nodes import CallExpr
-from mypy.nodes import ClassDef
-from mypy.nodes import Decorator
-from mypy.nodes import LambdaExpr
-from mypy.nodes import ListExpr
-from mypy.nodes import MemberExpr
-from mypy.nodes import NameExpr
-from mypy.nodes import PlaceholderNode
-from mypy.nodes import RefExpr
-from mypy.nodes import StrExpr
-from mypy.nodes import SymbolNode
-from mypy.nodes import SymbolTableNode
-from mypy.nodes import TempNode
-from mypy.nodes import TypeInfo
-from mypy.nodes import Var
-from mypy.plugin import SemanticAnalyzerPluginInterface
-from mypy.types import AnyType
-from mypy.types import CallableType
-from mypy.types import get_proper_type
-from mypy.types import Instance
-from mypy.types import NoneType
-from mypy.types import ProperType
-from mypy.types import Type
-from mypy.types import TypeOfAny
-from mypy.types import UnboundType
-from mypy.types import UnionType
-
-from . import apply
-from . import infer
-from . import names
-from . import util
-
-
-def scan_declarative_assignments_and_apply_types(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- is_mixin_scan: bool = False,
-) -> Optional[List[util.SQLAlchemyAttribute]]:
- info = util.info_for_cls(cls, api)
-
- if info is None:
- # this can occur during cached passes
- return None
- elif cls.fullname.startswith("builtins"):
- return None
-
- mapped_attributes: Optional[List[util.SQLAlchemyAttribute]] = (
- util.get_mapped_attributes(info, api)
- )
-
- # used by assign.add_additional_orm_attributes among others
- util.establish_as_sqlalchemy(info)
-
- if mapped_attributes is not None:
- # ensure that a class that's mapped is always picked up by
- # its mapped() decorator or declarative metaclass before
- # it would be detected as an unmapped mixin class
-
- if not is_mixin_scan:
- # mypy can call us more than once. it then *may* have reset the
- # left hand side of everything, but not the right that we removed,
- # removing our ability to re-scan. but we have the types
- # here, so lets re-apply them, or if we have an UnboundType,
- # we can re-scan
-
- apply.re_apply_declarative_assignments(cls, api, mapped_attributes)
-
- return mapped_attributes
-
- mapped_attributes = []
-
- if not cls.defs.body:
- # when we get a mixin class from another file, the body is
- # empty (!) but the names are in the symbol table. so use that.
-
- for sym_name, sym in info.names.items():
- _scan_symbol_table_entry(
- cls, api, sym_name, sym, mapped_attributes
- )
- else:
- for stmt in util.flatten_typechecking(cls.defs.body):
- if isinstance(stmt, AssignmentStmt):
- _scan_declarative_assignment_stmt(
- cls, api, stmt, mapped_attributes
- )
- elif isinstance(stmt, Decorator):
- _scan_declarative_decorator_stmt(
- cls, api, stmt, mapped_attributes
- )
- _scan_for_mapped_bases(cls, api)
-
- if not is_mixin_scan:
- apply.add_additional_orm_attributes(cls, api, mapped_attributes)
-
- util.set_mapped_attributes(info, mapped_attributes)
-
- return mapped_attributes
-
-
-def _scan_symbol_table_entry(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- name: str,
- value: SymbolTableNode,
- attributes: List[util.SQLAlchemyAttribute],
-) -> None:
- """Extract mapping information from a SymbolTableNode that's in the
- type.names dictionary.
-
- """
- value_type = get_proper_type(value.type)
- if not isinstance(value_type, Instance):
- return
-
- left_hand_explicit_type = None
- type_id = names.type_id_for_named_node(value_type.type)
- # type_id = names._type_id_for_unbound_type(value.type.type, cls, api)
-
- err = False
-
- # TODO: this is nearly the same logic as that of
- # _scan_declarative_decorator_stmt, likely can be merged
- if type_id in {
- names.MAPPED,
- names.RELATIONSHIP,
- names.COMPOSITE_PROPERTY,
- names.MAPPER_PROPERTY,
- names.SYNONYM_PROPERTY,
- names.COLUMN_PROPERTY,
- }:
- if value_type.args:
- left_hand_explicit_type = get_proper_type(value_type.args[0])
- else:
- err = True
- elif type_id is names.COLUMN:
- if not value_type.args:
- err = True
- else:
- typeengine_arg: Union[ProperType, TypeInfo] = get_proper_type(
- value_type.args[0]
- )
- if isinstance(typeengine_arg, Instance):
- typeengine_arg = typeengine_arg.type
-
- if isinstance(typeengine_arg, (UnboundType, TypeInfo)):
- sym = api.lookup_qualified(typeengine_arg.name, typeengine_arg)
- if sym is not None and isinstance(sym.node, TypeInfo):
- if names.has_base_type_id(sym.node, names.TYPEENGINE):
- left_hand_explicit_type = UnionType(
- [
- infer.extract_python_type_from_typeengine(
- api, sym.node, []
- ),
- NoneType(),
- ]
- )
- else:
- util.fail(
- api,
- "Column type should be a TypeEngine "
- "subclass not '{}'".format(sym.node.fullname),
- value_type,
- )
-
- if err:
- msg = (
- "Can't infer type from attribute {} on class {}. "
- "please specify a return type from this function that is "
- "one of: Mapped[<python type>], relationship[<target class>], "
- "Column[<TypeEngine>], MapperProperty[<python type>]"
- )
- util.fail(api, msg.format(name, cls.name), cls)
-
- left_hand_explicit_type = AnyType(TypeOfAny.special_form)
-
- if left_hand_explicit_type is not None:
- assert value.node is not None
- attributes.append(
- util.SQLAlchemyAttribute(
- name=name,
- line=value.node.line,
- column=value.node.column,
- typ=left_hand_explicit_type,
- info=cls.info,
- )
- )
-
-
-def _scan_declarative_decorator_stmt(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- stmt: Decorator,
- attributes: List[util.SQLAlchemyAttribute],
-) -> None:
- """Extract mapping information from a @declared_attr in a declarative
- class.
-
- E.g.::
-
- @reg.mapped
- class MyClass:
- # ...
-
- @declared_attr
- def updated_at(cls) -> Column[DateTime]:
- return Column(DateTime)
-
- Will resolve in mypy as::
-
- @reg.mapped
- class MyClass:
- # ...
-
- updated_at: Mapped[Optional[datetime.datetime]]
-
- """
- for dec in stmt.decorators:
- if (
- isinstance(dec, (NameExpr, MemberExpr, SymbolNode))
- and names.type_id_for_named_node(dec) is names.DECLARED_ATTR
- ):
- break
- else:
- return
-
- dec_index = cls.defs.body.index(stmt)
-
- left_hand_explicit_type: Optional[ProperType] = None
-
- if util.name_is_dunder(stmt.name):
- # for dunder names like __table_args__, __tablename__,
- # __mapper_args__ etc., rewrite these as simple assignment
- # statements; otherwise mypy doesn't like if the decorated
- # function has an annotation like ``cls: Type[Foo]`` because
- # it isn't @classmethod
- any_ = AnyType(TypeOfAny.special_form)
- left_node = NameExpr(stmt.var.name)
- left_node.node = stmt.var
- new_stmt = AssignmentStmt([left_node], TempNode(any_))
- new_stmt.type = left_node.node.type
- cls.defs.body[dec_index] = new_stmt
- return
- elif isinstance(stmt.func.type, CallableType):
- func_type = stmt.func.type.ret_type
- if isinstance(func_type, UnboundType):
- type_id = names.type_id_for_unbound_type(func_type, cls, api)
- else:
- # this does not seem to occur unless the type argument is
- # incorrect
- return
-
- if (
- type_id
- in {
- names.MAPPED,
- names.RELATIONSHIP,
- names.COMPOSITE_PROPERTY,
- names.MAPPER_PROPERTY,
- names.SYNONYM_PROPERTY,
- names.COLUMN_PROPERTY,
- }
- and func_type.args
- ):
- left_hand_explicit_type = get_proper_type(func_type.args[0])
- elif type_id is names.COLUMN and func_type.args:
- typeengine_arg = func_type.args[0]
- if isinstance(typeengine_arg, UnboundType):
- sym = api.lookup_qualified(typeengine_arg.name, typeengine_arg)
- if sym is not None and isinstance(sym.node, TypeInfo):
- if names.has_base_type_id(sym.node, names.TYPEENGINE):
- left_hand_explicit_type = UnionType(
- [
- infer.extract_python_type_from_typeengine(
- api, sym.node, []
- ),
- NoneType(),
- ]
- )
- else:
- util.fail(
- api,
- "Column type should be a TypeEngine "
- "subclass not '{}'".format(sym.node.fullname),
- func_type,
- )
-
- if left_hand_explicit_type is None:
- # no type on the decorated function. our option here is to
- # dig into the function body and get the return type, but they
- # should just have an annotation.
- msg = (
- "Can't infer type from @declared_attr on function '{}'; "
- "please specify a return type from this function that is "
- "one of: Mapped[<python type>], relationship[<target class>], "
- "Column[<TypeEngine>], MapperProperty[<python type>]"
- )
- util.fail(api, msg.format(stmt.var.name), stmt)
-
- left_hand_explicit_type = AnyType(TypeOfAny.special_form)
-
- left_node = NameExpr(stmt.var.name)
- left_node.node = stmt.var
-
- # totally feeling around in the dark here as I don't totally understand
- # the significance of UnboundType. It seems to be something that is
- # not going to do what's expected when it is applied as the type of
- # an AssignmentStatement. So do a feeling-around-in-the-dark version
- # of converting it to the regular Instance/TypeInfo/UnionType structures
- # we see everywhere else.
- if isinstance(left_hand_explicit_type, UnboundType):
- left_hand_explicit_type = get_proper_type(
- util.unbound_to_instance(api, left_hand_explicit_type)
- )
-
- left_node.node.type = api.named_type(
- names.NAMED_TYPE_SQLA_MAPPED, [left_hand_explicit_type]
- )
-
- # this will ignore the rvalue entirely
- # rvalue = TempNode(AnyType(TypeOfAny.special_form))
-
- # rewrite the node as:
- # <attr> : Mapped[<typ>] =
- # _sa_Mapped._empty_constructor(lambda: <function body>)
- # the function body is maintained so it gets type checked internally
- rvalue = names.expr_to_mapped_constructor(
- LambdaExpr(stmt.func.arguments, stmt.func.body)
- )
-
- new_stmt = AssignmentStmt([left_node], rvalue)
- new_stmt.type = left_node.node.type
-
- attributes.append(
- util.SQLAlchemyAttribute(
- name=left_node.name,
- line=stmt.line,
- column=stmt.column,
- typ=left_hand_explicit_type,
- info=cls.info,
- )
- )
- cls.defs.body[dec_index] = new_stmt
-
-
-def _scan_declarative_assignment_stmt(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- attributes: List[util.SQLAlchemyAttribute],
-) -> None:
- """Extract mapping information from an assignment statement in a
- declarative class.
-
- """
- lvalue = stmt.lvalues[0]
- if not isinstance(lvalue, NameExpr):
- return
-
- sym = cls.info.names.get(lvalue.name)
-
- # this establishes that semantic analysis has taken place, which
- # means the nodes are populated and we are called from an appropriate
- # hook.
- assert sym is not None
- node = sym.node
-
- if isinstance(node, PlaceholderNode):
- return
-
- assert node is lvalue.node
- assert isinstance(node, Var)
-
- if node.name == "__abstract__":
- if api.parse_bool(stmt.rvalue) is True:
- util.set_is_base(cls.info)
- return
- elif node.name == "__tablename__":
- util.set_has_table(cls.info)
- elif node.name.startswith("__"):
- return
- elif node.name == "_mypy_mapped_attrs":
- if not isinstance(stmt.rvalue, ListExpr):
- util.fail(api, "_mypy_mapped_attrs is expected to be a list", stmt)
- else:
- for item in stmt.rvalue.items:
- if isinstance(item, (NameExpr, StrExpr)):
- apply.apply_mypy_mapped_attr(cls, api, item, attributes)
-
- left_hand_mapped_type: Optional[Type] = None
- left_hand_explicit_type: Optional[ProperType] = None
-
- if node.is_inferred or node.type is None:
- if isinstance(stmt.type, UnboundType):
- # look for an explicit Mapped[] type annotation on the left
- # side with nothing on the right
-
- # print(stmt.type)
- # Mapped?[Optional?[A?]]
-
- left_hand_explicit_type = stmt.type
-
- if stmt.type.name == "Mapped":
- mapped_sym = api.lookup_qualified("Mapped", cls)
- if (
- mapped_sym is not None
- and mapped_sym.node is not None
- and names.type_id_for_named_node(mapped_sym.node)
- is names.MAPPED
- ):
- left_hand_explicit_type = get_proper_type(
- stmt.type.args[0]
- )
- left_hand_mapped_type = stmt.type
-
- # TODO: do we need to convert from unbound for this case?
- # left_hand_explicit_type = util._unbound_to_instance(
- # api, left_hand_explicit_type
- # )
- else:
- node_type = get_proper_type(node.type)
- if (
- isinstance(node_type, Instance)
- and names.type_id_for_named_node(node_type.type) is names.MAPPED
- ):
- # print(node.type)
- # sqlalchemy.orm.attributes.Mapped[<python type>]
- left_hand_explicit_type = get_proper_type(node_type.args[0])
- left_hand_mapped_type = node_type
- else:
- # print(node.type)
- # <python type>
- left_hand_explicit_type = node_type
- left_hand_mapped_type = None
-
- if isinstance(stmt.rvalue, TempNode) and left_hand_mapped_type is not None:
- # annotation without assignment and Mapped is present
- # as type annotation
- # equivalent to using _infer_type_from_left_hand_type_only.
-
- python_type_for_type = left_hand_explicit_type
- elif isinstance(stmt.rvalue, CallExpr) and isinstance(
- stmt.rvalue.callee, RefExpr
- ):
- python_type_for_type = infer.infer_type_from_right_hand_nameexpr(
- api, stmt, node, left_hand_explicit_type, stmt.rvalue.callee
- )
-
- if python_type_for_type is None:
- return
-
- else:
- return
-
- assert python_type_for_type is not None
-
- attributes.append(
- util.SQLAlchemyAttribute(
- name=node.name,
- line=stmt.line,
- column=stmt.column,
- typ=python_type_for_type,
- info=cls.info,
- )
- )
-
- apply.apply_type_to_mapped_statement(
- api,
- stmt,
- lvalue,
- left_hand_explicit_type,
- python_type_for_type,
- )
-
-
-def _scan_for_mapped_bases(
- cls: ClassDef,
- api: SemanticAnalyzerPluginInterface,
-) -> None:
- """Given a class, iterate through its superclass hierarchy to find
- all other classes that are considered as ORM-significant.
-
- Locates non-mapped mixins and scans them for mapped attributes to be
- applied to subclasses.
-
- """
-
- info = util.info_for_cls(cls, api)
-
- if info is None:
- return
-
- for base_info in info.mro[1:-1]:
- if base_info.fullname.startswith("builtins"):
- continue
-
- # scan each base for mapped attributes. if they are not already
- # scanned (but have all their type info), that means they are unmapped
- # mixins
- scan_declarative_assignments_and_apply_types(
- base_info.defn, api, is_mixin_scan=True
- )
+++ /dev/null
-# ext/mypy/infer.py
-# Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-from __future__ import annotations
-
-from typing import Optional
-from typing import Sequence
-
-from mypy.maptype import map_instance_to_supertype
-from mypy.nodes import AssignmentStmt
-from mypy.nodes import CallExpr
-from mypy.nodes import Expression
-from mypy.nodes import FuncDef
-from mypy.nodes import LambdaExpr
-from mypy.nodes import MemberExpr
-from mypy.nodes import NameExpr
-from mypy.nodes import RefExpr
-from mypy.nodes import StrExpr
-from mypy.nodes import TypeInfo
-from mypy.nodes import Var
-from mypy.plugin import SemanticAnalyzerPluginInterface
-from mypy.subtypes import is_subtype
-from mypy.types import AnyType
-from mypy.types import CallableType
-from mypy.types import get_proper_type
-from mypy.types import Instance
-from mypy.types import NoneType
-from mypy.types import ProperType
-from mypy.types import TypeOfAny
-from mypy.types import UnionType
-
-from . import names
-from . import util
-
-
-def infer_type_from_right_hand_nameexpr(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
- infer_from_right_side: RefExpr,
-) -> Optional[ProperType]:
- type_id = names.type_id_for_callee(infer_from_right_side)
- if type_id is None:
- return None
- elif type_id is names.MAPPED:
- python_type_for_type = _infer_type_from_mapped(
- api, stmt, node, left_hand_explicit_type, infer_from_right_side
- )
- elif type_id is names.COLUMN:
- python_type_for_type = _infer_type_from_decl_column(
- api, stmt, node, left_hand_explicit_type
- )
- elif type_id is names.RELATIONSHIP:
- python_type_for_type = _infer_type_from_relationship(
- api, stmt, node, left_hand_explicit_type
- )
- elif type_id is names.COLUMN_PROPERTY:
- python_type_for_type = _infer_type_from_decl_column_property(
- api, stmt, node, left_hand_explicit_type
- )
- elif type_id is names.SYNONYM_PROPERTY:
- python_type_for_type = infer_type_from_left_hand_type_only(
- api, node, left_hand_explicit_type
- )
- elif type_id is names.COMPOSITE_PROPERTY:
- python_type_for_type = _infer_type_from_decl_composite_property(
- api, stmt, node, left_hand_explicit_type
- )
- else:
- return None
-
- return python_type_for_type
-
-
-def _infer_type_from_relationship(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
-) -> Optional[ProperType]:
- """Infer the type of mapping from a relationship.
-
- E.g.::
-
- @reg.mapped
- class MyClass:
- # ...
-
- addresses = relationship(Address, uselist=True)
-
- order: Mapped["Order"] = relationship("Order")
-
- Will resolve in mypy as::
-
- @reg.mapped
- class MyClass:
- # ...
-
- addresses: Mapped[List[Address]]
-
- order: Mapped["Order"]
-
- """
-
- assert isinstance(stmt.rvalue, CallExpr)
- target_cls_arg = stmt.rvalue.args[0]
- python_type_for_type: Optional[ProperType] = None
-
- if isinstance(target_cls_arg, NameExpr) and isinstance(
- target_cls_arg.node, TypeInfo
- ):
- # type
- related_object_type = target_cls_arg.node
- python_type_for_type = Instance(related_object_type, [])
-
- # other cases not covered - an error message directs the user
- # to set an explicit type annotation
- #
- # node.type == str, it's a string
- # if isinstance(target_cls_arg, NameExpr) and isinstance(
- # target_cls_arg.node, Var
- # )
- # points to a type
- # isinstance(target_cls_arg, NameExpr) and isinstance(
- # target_cls_arg.node, TypeAlias
- # )
- # string expression
- # isinstance(target_cls_arg, StrExpr)
-
- uselist_arg = util.get_callexpr_kwarg(stmt.rvalue, "uselist")
- collection_cls_arg: Optional[Expression] = util.get_callexpr_kwarg(
- stmt.rvalue, "collection_class"
- )
- type_is_a_collection = False
-
- # this can be used to determine Optional for a many-to-one
- # in the same way nullable=False could be used, if we start supporting
- # that.
- # innerjoin_arg = util.get_callexpr_kwarg(stmt.rvalue, "innerjoin")
-
- if (
- uselist_arg is not None
- and api.parse_bool(uselist_arg) is True
- and collection_cls_arg is None
- ):
- type_is_a_collection = True
- if python_type_for_type is not None:
- python_type_for_type = api.named_type(
- names.NAMED_TYPE_BUILTINS_LIST, [python_type_for_type]
- )
- elif (
- uselist_arg is None or api.parse_bool(uselist_arg) is True
- ) and collection_cls_arg is not None:
- type_is_a_collection = True
- if isinstance(collection_cls_arg, CallExpr):
- collection_cls_arg = collection_cls_arg.callee
-
- if isinstance(collection_cls_arg, NameExpr) and isinstance(
- collection_cls_arg.node, TypeInfo
- ):
- if python_type_for_type is not None:
- # this can still be overridden by the left hand side
- # within _infer_Type_from_left_and_inferred_right
- python_type_for_type = Instance(
- collection_cls_arg.node, [python_type_for_type]
- )
- elif (
- isinstance(collection_cls_arg, NameExpr)
- and isinstance(collection_cls_arg.node, FuncDef)
- and collection_cls_arg.node.type is not None
- ):
- if python_type_for_type is not None:
- # this can still be overridden by the left hand side
- # within _infer_Type_from_left_and_inferred_right
-
- # TODO: handle mypy.types.Overloaded
- if isinstance(collection_cls_arg.node.type, CallableType):
- rt = get_proper_type(collection_cls_arg.node.type.ret_type)
-
- if isinstance(rt, CallableType):
- callable_ret_type = get_proper_type(rt.ret_type)
- if isinstance(callable_ret_type, Instance):
- python_type_for_type = Instance(
- callable_ret_type.type,
- [python_type_for_type],
- )
- else:
- util.fail(
- api,
- "Expected Python collection type for "
- "collection_class parameter",
- stmt.rvalue,
- )
- python_type_for_type = None
- elif uselist_arg is not None and api.parse_bool(uselist_arg) is False:
- if collection_cls_arg is not None:
- util.fail(
- api,
- "Sending uselist=False and collection_class at the same time "
- "does not make sense",
- stmt.rvalue,
- )
- if python_type_for_type is not None:
- python_type_for_type = UnionType(
- [python_type_for_type, NoneType()]
- )
-
- else:
- if left_hand_explicit_type is None:
- msg = (
- "Can't infer scalar or collection for ORM mapped expression "
- "assigned to attribute '{}' if both 'uselist' and "
- "'collection_class' arguments are absent from the "
- "relationship(); please specify a "
- "type annotation on the left hand side."
- )
- util.fail(api, msg.format(node.name), node)
-
- if python_type_for_type is None:
- return infer_type_from_left_hand_type_only(
- api, node, left_hand_explicit_type
- )
- elif left_hand_explicit_type is not None:
- if type_is_a_collection:
- assert isinstance(left_hand_explicit_type, Instance)
- assert isinstance(python_type_for_type, Instance)
- return _infer_collection_type_from_left_and_inferred_right(
- api, node, left_hand_explicit_type, python_type_for_type
- )
- else:
- return _infer_type_from_left_and_inferred_right(
- api,
- node,
- left_hand_explicit_type,
- python_type_for_type,
- )
- else:
- return python_type_for_type
-
-
-def _infer_type_from_decl_composite_property(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
-) -> Optional[ProperType]:
- """Infer the type of mapping from a Composite."""
-
- assert isinstance(stmt.rvalue, CallExpr)
- target_cls_arg = stmt.rvalue.args[0]
- python_type_for_type = None
-
- if isinstance(target_cls_arg, NameExpr) and isinstance(
- target_cls_arg.node, TypeInfo
- ):
- related_object_type = target_cls_arg.node
- python_type_for_type = Instance(related_object_type, [])
- else:
- python_type_for_type = None
-
- if python_type_for_type is None:
- return infer_type_from_left_hand_type_only(
- api, node, left_hand_explicit_type
- )
- elif left_hand_explicit_type is not None:
- return _infer_type_from_left_and_inferred_right(
- api, node, left_hand_explicit_type, python_type_for_type
- )
- else:
- return python_type_for_type
-
-
-def _infer_type_from_mapped(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
- infer_from_right_side: RefExpr,
-) -> Optional[ProperType]:
- """Infer the type of mapping from a right side expression
- that returns Mapped.
-
-
- """
- assert isinstance(stmt.rvalue, CallExpr)
-
- # (Pdb) print(stmt.rvalue.callee)
- # NameExpr(query_expression [sqlalchemy.orm._orm_constructors.query_expression]) # noqa: E501
- # (Pdb) stmt.rvalue.callee.node
- # <mypy.nodes.FuncDef object at 0x7f8d92fb5940>
- # (Pdb) stmt.rvalue.callee.node.type
- # def [_T] (default_expr: sqlalchemy.sql.elements.ColumnElement[_T`-1] =) -> sqlalchemy.orm.base.Mapped[_T`-1] # noqa: E501
- # sqlalchemy.orm.base.Mapped[_T`-1]
- # the_mapped_type = stmt.rvalue.callee.node.type.ret_type
-
- # TODO: look at generic ref and either use that,
- # or reconcile w/ what's present, etc.
- the_mapped_type = util.type_for_callee(infer_from_right_side) # noqa
-
- return infer_type_from_left_hand_type_only(
- api, node, left_hand_explicit_type
- )
-
-
-def _infer_type_from_decl_column_property(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
-) -> Optional[ProperType]:
- """Infer the type of mapping from a ColumnProperty.
-
- This includes mappings against ``column_property()`` as well as the
- ``deferred()`` function.
-
- """
- assert isinstance(stmt.rvalue, CallExpr)
-
- if stmt.rvalue.args:
- first_prop_arg = stmt.rvalue.args[0]
-
- if isinstance(first_prop_arg, CallExpr):
- type_id = names.type_id_for_callee(first_prop_arg.callee)
-
- # look for column_property() / deferred() etc with Column as first
- # argument
- if type_id is names.COLUMN:
- return _infer_type_from_decl_column(
- api,
- stmt,
- node,
- left_hand_explicit_type,
- right_hand_expression=first_prop_arg,
- )
-
- if isinstance(stmt.rvalue, CallExpr):
- type_id = names.type_id_for_callee(stmt.rvalue.callee)
- # this is probably not strictly necessary as we have to use the left
- # hand type for query expression in any case. any other no-arg
- # column prop objects would go here also
- if type_id is names.QUERY_EXPRESSION:
- return _infer_type_from_decl_column(
- api,
- stmt,
- node,
- left_hand_explicit_type,
- )
-
- return infer_type_from_left_hand_type_only(
- api, node, left_hand_explicit_type
- )
-
-
-def _infer_type_from_decl_column(
- api: SemanticAnalyzerPluginInterface,
- stmt: AssignmentStmt,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
- right_hand_expression: Optional[CallExpr] = None,
-) -> Optional[ProperType]:
- """Infer the type of mapping from a Column.
-
- E.g.::
-
- @reg.mapped
- class MyClass:
- # ...
-
- a = Column(Integer)
-
- b = Column("b", String)
-
- c: Mapped[int] = Column(Integer)
-
- d: bool = Column(Boolean)
-
- Will resolve in MyPy as::
-
- @reg.mapped
- class MyClass:
- # ...
-
- a: Mapped[int]
-
- b: Mapped[str]
-
- c: Mapped[int]
-
- d: Mapped[bool]
-
- """
- assert isinstance(node, Var)
-
- callee = None
-
- if right_hand_expression is None:
- if not isinstance(stmt.rvalue, CallExpr):
- return None
-
- right_hand_expression = stmt.rvalue
-
- for column_arg in right_hand_expression.args[0:2]:
- if isinstance(column_arg, CallExpr):
- if isinstance(column_arg.callee, RefExpr):
- # x = Column(String(50))
- callee = column_arg.callee
- type_args: Sequence[Expression] = column_arg.args
- break
- elif isinstance(column_arg, (NameExpr, MemberExpr)):
- if isinstance(column_arg.node, TypeInfo):
- # x = Column(String)
- callee = column_arg
- type_args = ()
- break
- else:
- # x = Column(some_name, String), go to next argument
- continue
- elif isinstance(column_arg, (StrExpr,)):
- # x = Column("name", String), go to next argument
- continue
- elif isinstance(column_arg, (LambdaExpr,)):
- # x = Column("name", String, default=lambda: uuid.uuid4())
- # go to next argument
- continue
- else:
- assert False
-
- if callee is None:
- return None
-
- if isinstance(callee.node, TypeInfo) and names.mro_has_id(
- callee.node.mro, names.TYPEENGINE
- ):
- python_type_for_type = extract_python_type_from_typeengine(
- api, callee.node, type_args
- )
-
- if left_hand_explicit_type is not None:
- return _infer_type_from_left_and_inferred_right(
- api, node, left_hand_explicit_type, python_type_for_type
- )
-
- else:
- return UnionType([python_type_for_type, NoneType()])
- else:
- # it's not TypeEngine, it's typically implicitly typed
- # like ForeignKey. we can't infer from the right side.
- return infer_type_from_left_hand_type_only(
- api, node, left_hand_explicit_type
- )
-
-
-def _infer_type_from_left_and_inferred_right(
- api: SemanticAnalyzerPluginInterface,
- node: Var,
- left_hand_explicit_type: ProperType,
- python_type_for_type: ProperType,
- orig_left_hand_type: Optional[ProperType] = None,
- orig_python_type_for_type: Optional[ProperType] = None,
-) -> Optional[ProperType]:
- """Validate type when a left hand annotation is present and we also
- could infer the right hand side::
-
- attrname: SomeType = Column(SomeDBType)
-
- """
-
- if orig_left_hand_type is None:
- orig_left_hand_type = left_hand_explicit_type
- if orig_python_type_for_type is None:
- orig_python_type_for_type = python_type_for_type
-
- if not is_subtype(left_hand_explicit_type, python_type_for_type):
- effective_type = api.named_type(
- names.NAMED_TYPE_SQLA_MAPPED, [orig_python_type_for_type]
- )
-
- msg = (
- "Left hand assignment '{}: {}' not compatible "
- "with ORM mapped expression of type {}"
- )
- util.fail(
- api,
- msg.format(
- node.name,
- util.format_type(orig_left_hand_type, api.options),
- util.format_type(effective_type, api.options),
- ),
- node,
- )
-
- return orig_left_hand_type
-
-
-def _infer_collection_type_from_left_and_inferred_right(
- api: SemanticAnalyzerPluginInterface,
- node: Var,
- left_hand_explicit_type: Instance,
- python_type_for_type: Instance,
-) -> Optional[ProperType]:
- orig_left_hand_type = left_hand_explicit_type
- orig_python_type_for_type = python_type_for_type
-
- if left_hand_explicit_type.args:
- left_hand_arg = get_proper_type(left_hand_explicit_type.args[0])
- python_type_arg = get_proper_type(python_type_for_type.args[0])
- else:
- left_hand_arg = left_hand_explicit_type
- python_type_arg = python_type_for_type
-
- assert isinstance(left_hand_arg, (Instance, UnionType))
- assert isinstance(python_type_arg, (Instance, UnionType))
-
- return _infer_type_from_left_and_inferred_right(
- api,
- node,
- left_hand_arg,
- python_type_arg,
- orig_left_hand_type=orig_left_hand_type,
- orig_python_type_for_type=orig_python_type_for_type,
- )
-
-
-def infer_type_from_left_hand_type_only(
- api: SemanticAnalyzerPluginInterface,
- node: Var,
- left_hand_explicit_type: Optional[ProperType],
-) -> Optional[ProperType]:
- """Determine the type based on explicit annotation only.
-
- if no annotation were present, note that we need one there to know
- the type.
-
- """
- if left_hand_explicit_type is None:
- msg = (
- "Can't infer type from ORM mapped expression "
- "assigned to attribute '{}'; please specify a "
- "Python type or "
- "Mapped[<python type>] on the left hand side."
- )
- util.fail(api, msg.format(node.name), node)
-
- return api.named_type(
- names.NAMED_TYPE_SQLA_MAPPED, [AnyType(TypeOfAny.special_form)]
- )
-
- else:
- # use type from the left hand side
- return left_hand_explicit_type
-
-
-def extract_python_type_from_typeengine(
- api: SemanticAnalyzerPluginInterface,
- node: TypeInfo,
- type_args: Sequence[Expression],
-) -> ProperType:
- if node.fullname == "sqlalchemy.sql.sqltypes.Enum" and type_args:
- first_arg = type_args[0]
- if isinstance(first_arg, RefExpr) and isinstance(
- first_arg.node, TypeInfo
- ):
- for base_ in first_arg.node.mro:
- if base_.fullname == "enum.Enum":
- return Instance(first_arg.node, [])
- # TODO: support other pep-435 types here
- else:
- return api.named_type(names.NAMED_TYPE_BUILTINS_STR, [])
-
- assert node.has_base("sqlalchemy.sql.type_api.TypeEngine"), (
- "could not extract Python type from node: %s" % node
- )
-
- type_engine_sym = api.lookup_fully_qualified_or_none(
- "sqlalchemy.sql.type_api.TypeEngine"
- )
-
- assert type_engine_sym is not None and isinstance(
- type_engine_sym.node, TypeInfo
- )
- type_engine = map_instance_to_supertype(
- Instance(node, []),
- type_engine_sym.node,
- )
- return get_proper_type(type_engine.args[-1])
+++ /dev/null
-# ext/mypy/names.py
-# Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-from __future__ import annotations
-
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import Union
-
-from mypy.nodes import ARG_POS
-from mypy.nodes import CallExpr
-from mypy.nodes import ClassDef
-from mypy.nodes import Decorator
-from mypy.nodes import Expression
-from mypy.nodes import FuncDef
-from mypy.nodes import MemberExpr
-from mypy.nodes import NameExpr
-from mypy.nodes import OverloadedFuncDef
-from mypy.nodes import SymbolNode
-from mypy.nodes import TypeAlias
-from mypy.nodes import TypeInfo
-from mypy.plugin import SemanticAnalyzerPluginInterface
-from mypy.types import CallableType
-from mypy.types import get_proper_type
-from mypy.types import Instance
-from mypy.types import UnboundType
-
-from ... import util
-
-COLUMN: int = util.symbol("COLUMN")
-RELATIONSHIP: int = util.symbol("RELATIONSHIP")
-REGISTRY: int = util.symbol("REGISTRY")
-COLUMN_PROPERTY: int = util.symbol("COLUMN_PROPERTY")
-TYPEENGINE: int = util.symbol("TYPEENGNE")
-MAPPED: int = util.symbol("MAPPED")
-DECLARATIVE_BASE: int = util.symbol("DECLARATIVE_BASE")
-DECLARATIVE_META: int = util.symbol("DECLARATIVE_META")
-MAPPED_DECORATOR: int = util.symbol("MAPPED_DECORATOR")
-SYNONYM_PROPERTY: int = util.symbol("SYNONYM_PROPERTY")
-COMPOSITE_PROPERTY: int = util.symbol("COMPOSITE_PROPERTY")
-DECLARED_ATTR: int = util.symbol("DECLARED_ATTR")
-MAPPER_PROPERTY: int = util.symbol("MAPPER_PROPERTY")
-AS_DECLARATIVE: int = util.symbol("AS_DECLARATIVE")
-AS_DECLARATIVE_BASE: int = util.symbol("AS_DECLARATIVE_BASE")
-DECLARATIVE_MIXIN: int = util.symbol("DECLARATIVE_MIXIN")
-QUERY_EXPRESSION: int = util.symbol("QUERY_EXPRESSION")
-
-# names that must succeed with mypy.api.named_type
-NAMED_TYPE_BUILTINS_OBJECT = "builtins.object"
-NAMED_TYPE_BUILTINS_STR = "builtins.str"
-NAMED_TYPE_BUILTINS_LIST = "builtins.list"
-NAMED_TYPE_SQLA_MAPPED = "sqlalchemy.orm.base.Mapped"
-
-_RelFullNames = {
- "sqlalchemy.orm.relationships.Relationship",
- "sqlalchemy.orm.relationships.RelationshipProperty",
- "sqlalchemy.orm.relationships._RelationshipDeclared",
- "sqlalchemy.orm.Relationship",
- "sqlalchemy.orm.RelationshipProperty",
-}
-
-_lookup: Dict[str, Tuple[int, Set[str]]] = {
- "Column": (
- COLUMN,
- {
- "sqlalchemy.sql.schema.Column",
- "sqlalchemy.sql.Column",
- },
- ),
- "Relationship": (RELATIONSHIP, _RelFullNames),
- "RelationshipProperty": (RELATIONSHIP, _RelFullNames),
- "_RelationshipDeclared": (RELATIONSHIP, _RelFullNames),
- "registry": (
- REGISTRY,
- {
- "sqlalchemy.orm.decl_api.registry",
- "sqlalchemy.orm.registry",
- },
- ),
- "ColumnProperty": (
- COLUMN_PROPERTY,
- {
- "sqlalchemy.orm.properties.MappedSQLExpression",
- "sqlalchemy.orm.MappedSQLExpression",
- "sqlalchemy.orm.properties.ColumnProperty",
- "sqlalchemy.orm.ColumnProperty",
- },
- ),
- "MappedSQLExpression": (
- COLUMN_PROPERTY,
- {
- "sqlalchemy.orm.properties.MappedSQLExpression",
- "sqlalchemy.orm.MappedSQLExpression",
- "sqlalchemy.orm.properties.ColumnProperty",
- "sqlalchemy.orm.ColumnProperty",
- },
- ),
- "Synonym": (
- SYNONYM_PROPERTY,
- {
- "sqlalchemy.orm.descriptor_props.Synonym",
- "sqlalchemy.orm.Synonym",
- "sqlalchemy.orm.descriptor_props.SynonymProperty",
- "sqlalchemy.orm.SynonymProperty",
- },
- ),
- "SynonymProperty": (
- SYNONYM_PROPERTY,
- {
- "sqlalchemy.orm.descriptor_props.Synonym",
- "sqlalchemy.orm.Synonym",
- "sqlalchemy.orm.descriptor_props.SynonymProperty",
- "sqlalchemy.orm.SynonymProperty",
- },
- ),
- "Composite": (
- COMPOSITE_PROPERTY,
- {
- "sqlalchemy.orm.descriptor_props.Composite",
- "sqlalchemy.orm.Composite",
- "sqlalchemy.orm.descriptor_props.CompositeProperty",
- "sqlalchemy.orm.CompositeProperty",
- },
- ),
- "CompositeProperty": (
- COMPOSITE_PROPERTY,
- {
- "sqlalchemy.orm.descriptor_props.Composite",
- "sqlalchemy.orm.Composite",
- "sqlalchemy.orm.descriptor_props.CompositeProperty",
- "sqlalchemy.orm.CompositeProperty",
- },
- ),
- "MapperProperty": (
- MAPPER_PROPERTY,
- {
- "sqlalchemy.orm.interfaces.MapperProperty",
- "sqlalchemy.orm.MapperProperty",
- },
- ),
- "TypeEngine": (TYPEENGINE, {"sqlalchemy.sql.type_api.TypeEngine"}),
- "Mapped": (MAPPED, {NAMED_TYPE_SQLA_MAPPED}),
- "declarative_base": (
- DECLARATIVE_BASE,
- {
- "sqlalchemy.ext.declarative.declarative_base",
- "sqlalchemy.orm.declarative_base",
- "sqlalchemy.orm.decl_api.declarative_base",
- },
- ),
- "DeclarativeMeta": (
- DECLARATIVE_META,
- {
- "sqlalchemy.ext.declarative.DeclarativeMeta",
- "sqlalchemy.orm.DeclarativeMeta",
- "sqlalchemy.orm.decl_api.DeclarativeMeta",
- },
- ),
- "mapped": (
- MAPPED_DECORATOR,
- {
- "sqlalchemy.orm.decl_api.registry.mapped",
- "sqlalchemy.orm.registry.mapped",
- },
- ),
- "as_declarative": (
- AS_DECLARATIVE,
- {
- "sqlalchemy.ext.declarative.as_declarative",
- "sqlalchemy.orm.decl_api.as_declarative",
- "sqlalchemy.orm.as_declarative",
- },
- ),
- "as_declarative_base": (
- AS_DECLARATIVE_BASE,
- {
- "sqlalchemy.orm.decl_api.registry.as_declarative_base",
- "sqlalchemy.orm.registry.as_declarative_base",
- },
- ),
- "declared_attr": (
- DECLARED_ATTR,
- {
- "sqlalchemy.orm.decl_api.declared_attr",
- "sqlalchemy.orm.declared_attr",
- },
- ),
- "declarative_mixin": (
- DECLARATIVE_MIXIN,
- {
- "sqlalchemy.orm.decl_api.declarative_mixin",
- "sqlalchemy.orm.declarative_mixin",
- },
- ),
- "query_expression": (
- QUERY_EXPRESSION,
- {
- "sqlalchemy.orm.query_expression",
- "sqlalchemy.orm._orm_constructors.query_expression",
- },
- ),
-}
-
-
-def has_base_type_id(info: TypeInfo, type_id: int) -> bool:
- for mr in info.mro:
- check_type_id, fullnames = _lookup.get(mr.name, (None, None))
- if check_type_id == type_id:
- break
- else:
- return False
-
- if fullnames is None:
- return False
-
- return mr.fullname in fullnames
-
-
-def mro_has_id(mro: List[TypeInfo], type_id: int) -> bool:
- for mr in mro:
- check_type_id, fullnames = _lookup.get(mr.name, (None, None))
- if check_type_id == type_id:
- break
- else:
- return False
-
- if fullnames is None:
- return False
-
- return mr.fullname in fullnames
-
-
-def type_id_for_unbound_type(
- type_: UnboundType, cls: ClassDef, api: SemanticAnalyzerPluginInterface
-) -> Optional[int]:
- sym = api.lookup_qualified(type_.name, type_)
- if sym is not None:
- if isinstance(sym.node, TypeAlias):
- target_type = get_proper_type(sym.node.target)
- if isinstance(target_type, Instance):
- return type_id_for_named_node(target_type.type)
- elif isinstance(sym.node, TypeInfo):
- return type_id_for_named_node(sym.node)
-
- return None
-
-
-def type_id_for_callee(callee: Expression) -> Optional[int]:
- if isinstance(callee, (MemberExpr, NameExpr)):
- if isinstance(callee.node, Decorator) and isinstance(
- callee.node.func, FuncDef
- ):
- if callee.node.func.type and isinstance(
- callee.node.func.type, CallableType
- ):
- ret_type = get_proper_type(callee.node.func.type.ret_type)
-
- if isinstance(ret_type, Instance):
- return type_id_for_fullname(ret_type.type.fullname)
-
- return None
-
- elif isinstance(callee.node, OverloadedFuncDef):
- if (
- callee.node.impl
- and callee.node.impl.type
- and isinstance(callee.node.impl.type, CallableType)
- ):
- ret_type = get_proper_type(callee.node.impl.type.ret_type)
-
- if isinstance(ret_type, Instance):
- return type_id_for_fullname(ret_type.type.fullname)
-
- return None
- elif isinstance(callee.node, FuncDef):
- if callee.node.type and isinstance(callee.node.type, CallableType):
- ret_type = get_proper_type(callee.node.type.ret_type)
-
- if isinstance(ret_type, Instance):
- return type_id_for_fullname(ret_type.type.fullname)
-
- return None
- elif isinstance(callee.node, TypeAlias):
- target_type = get_proper_type(callee.node.target)
- if isinstance(target_type, Instance):
- return type_id_for_fullname(target_type.type.fullname)
- elif isinstance(callee.node, TypeInfo):
- return type_id_for_named_node(callee)
- return None
-
-
-def type_id_for_named_node(
- node: Union[NameExpr, MemberExpr, SymbolNode]
-) -> Optional[int]:
- type_id, fullnames = _lookup.get(node.name, (None, None))
-
- if type_id is None or fullnames is None:
- return None
- elif node.fullname in fullnames:
- return type_id
- else:
- return None
-
-
-def type_id_for_fullname(fullname: str) -> Optional[int]:
- tokens = fullname.split(".")
- immediate = tokens[-1]
-
- type_id, fullnames = _lookup.get(immediate, (None, None))
-
- if type_id is None or fullnames is None:
- return None
- elif fullname in fullnames:
- return type_id
- else:
- return None
-
-
-def expr_to_mapped_constructor(expr: Expression) -> CallExpr:
- column_descriptor = NameExpr("__sa_Mapped")
- column_descriptor.fullname = NAMED_TYPE_SQLA_MAPPED
- member_expr = MemberExpr(column_descriptor, "_empty_constructor")
- return CallExpr(
- member_expr,
- [expr],
- [ARG_POS],
- ["arg1"],
- )
+++ /dev/null
-# ext/mypy/plugin.py
-# Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-"""
-Mypy plugin for SQLAlchemy ORM.
-
-"""
-from __future__ import annotations
-
-from typing import Callable
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Type as TypingType
-from typing import Union
-
-from mypy import nodes
-from mypy.mro import calculate_mro
-from mypy.mro import MroError
-from mypy.nodes import Block
-from mypy.nodes import ClassDef
-from mypy.nodes import GDEF
-from mypy.nodes import MypyFile
-from mypy.nodes import NameExpr
-from mypy.nodes import SymbolTable
-from mypy.nodes import SymbolTableNode
-from mypy.nodes import TypeInfo
-from mypy.plugin import AttributeContext
-from mypy.plugin import ClassDefContext
-from mypy.plugin import DynamicClassDefContext
-from mypy.plugin import Plugin
-from mypy.plugin import SemanticAnalyzerPluginInterface
-from mypy.types import get_proper_type
-from mypy.types import Instance
-from mypy.types import Type
-
-from . import decl_class
-from . import names
-from . import util
-
-try:
- __import__("sqlalchemy-stubs")
-except ImportError:
- pass
-else:
- raise ImportError(
- "The SQLAlchemy mypy plugin in SQLAlchemy "
- "2.0 does not work with sqlalchemy-stubs or "
- "sqlalchemy2-stubs installed, as well as with any other third party "
- "SQLAlchemy stubs. Please uninstall all SQLAlchemy stubs "
- "packages."
- )
-
-
-class SQLAlchemyPlugin(Plugin):
- def get_dynamic_class_hook(
- self, fullname: str
- ) -> Optional[Callable[[DynamicClassDefContext], None]]:
- if names.type_id_for_fullname(fullname) is names.DECLARATIVE_BASE:
- return _dynamic_class_hook
- return None
-
- def get_customize_class_mro_hook(
- self, fullname: str
- ) -> Optional[Callable[[ClassDefContext], None]]:
- return _fill_in_decorators
-
- def get_class_decorator_hook(
- self, fullname: str
- ) -> Optional[Callable[[ClassDefContext], None]]:
- sym = self.lookup_fully_qualified(fullname)
-
- if sym is not None and sym.node is not None:
- type_id = names.type_id_for_named_node(sym.node)
- if type_id is names.MAPPED_DECORATOR:
- return _cls_decorator_hook
- elif type_id in (
- names.AS_DECLARATIVE,
- names.AS_DECLARATIVE_BASE,
- ):
- return _base_cls_decorator_hook
- elif type_id is names.DECLARATIVE_MIXIN:
- return _declarative_mixin_hook
-
- return None
-
- def get_metaclass_hook(
- self, fullname: str
- ) -> Optional[Callable[[ClassDefContext], None]]:
- if names.type_id_for_fullname(fullname) is names.DECLARATIVE_META:
- # Set any classes that explicitly have metaclass=DeclarativeMeta
- # as declarative so the check in `get_base_class_hook()` works
- return _metaclass_cls_hook
-
- return None
-
- def get_base_class_hook(
- self, fullname: str
- ) -> Optional[Callable[[ClassDefContext], None]]:
- sym = self.lookup_fully_qualified(fullname)
-
- if (
- sym
- and isinstance(sym.node, TypeInfo)
- and util.has_declarative_base(sym.node)
- ):
- return _base_cls_hook
-
- return None
-
- def get_attribute_hook(
- self, fullname: str
- ) -> Optional[Callable[[AttributeContext], Type]]:
- if fullname.startswith(
- "sqlalchemy.orm.attributes.QueryableAttribute."
- ):
- return _queryable_getattr_hook
-
- return None
-
- def get_additional_deps(
- self, file: MypyFile
- ) -> List[Tuple[int, str, int]]:
- return [
- #
- (10, "sqlalchemy.orm", -1),
- (10, "sqlalchemy.orm.attributes", -1),
- (10, "sqlalchemy.orm.decl_api", -1),
- ]
-
-
-def plugin(version: str) -> TypingType[SQLAlchemyPlugin]:
- return SQLAlchemyPlugin
-
-
-def _dynamic_class_hook(ctx: DynamicClassDefContext) -> None:
- """Generate a declarative Base class when the declarative_base() function
- is encountered."""
-
- _add_globals(ctx)
-
- cls = ClassDef(ctx.name, Block([]))
- cls.fullname = ctx.api.qualified_name(ctx.name)
-
- info = TypeInfo(SymbolTable(), cls, ctx.api.cur_mod_id)
- cls.info = info
- _set_declarative_metaclass(ctx.api, cls)
-
- cls_arg = util.get_callexpr_kwarg(ctx.call, "cls", expr_types=(NameExpr,))
- if cls_arg is not None and isinstance(cls_arg.node, TypeInfo):
- util.set_is_base(cls_arg.node)
- decl_class.scan_declarative_assignments_and_apply_types(
- cls_arg.node.defn, ctx.api, is_mixin_scan=True
- )
- info.bases = [Instance(cls_arg.node, [])]
- else:
- obj = ctx.api.named_type(names.NAMED_TYPE_BUILTINS_OBJECT)
-
- info.bases = [obj]
-
- try:
- calculate_mro(info)
- except MroError:
- util.fail(
- ctx.api, "Not able to calculate MRO for declarative base", ctx.call
- )
- obj = ctx.api.named_type(names.NAMED_TYPE_BUILTINS_OBJECT)
- info.bases = [obj]
- info.fallback_to_any = True
-
- ctx.api.add_symbol_table_node(ctx.name, SymbolTableNode(GDEF, info))
- util.set_is_base(info)
-
-
-def _fill_in_decorators(ctx: ClassDefContext) -> None:
- for decorator in ctx.cls.decorators:
- # set the ".fullname" attribute of a class decorator
- # that is a MemberExpr. This causes the logic in
- # semanal.py->apply_class_plugin_hooks to invoke the
- # get_class_decorator_hook for our "registry.map_class()"
- # and "registry.as_declarative_base()" methods.
- # this seems like a bug in mypy that these decorators are otherwise
- # skipped.
-
- if (
- isinstance(decorator, nodes.CallExpr)
- and isinstance(decorator.callee, nodes.MemberExpr)
- and decorator.callee.name == "as_declarative_base"
- ):
- target = decorator.callee
- elif (
- isinstance(decorator, nodes.MemberExpr)
- and decorator.name == "mapped"
- ):
- target = decorator
- else:
- continue
-
- if isinstance(target.expr, NameExpr):
- sym = ctx.api.lookup_qualified(
- target.expr.name, target, suppress_errors=True
- )
- else:
- continue
-
- if sym and sym.node:
- sym_type = get_proper_type(sym.type)
- if isinstance(sym_type, Instance):
- target.fullname = f"{sym_type.type.fullname}.{target.name}"
- else:
- # if the registry is in the same file as where the
- # decorator is used, it might not have semantic
- # symbols applied and we can't get a fully qualified
- # name or an inferred type, so we are actually going to
- # flag an error in this case that they need to annotate
- # it. The "registry" is declared just
- # once (or few times), so they have to just not use
- # type inference for its assignment in this one case.
- util.fail(
- ctx.api,
- "Class decorator called %s(), but we can't "
- "tell if it's from an ORM registry. Please "
- "annotate the registry assignment, e.g. "
- "my_registry: registry = registry()" % target.name,
- sym.node,
- )
-
-
-def _cls_decorator_hook(ctx: ClassDefContext) -> None:
- _add_globals(ctx)
- assert isinstance(ctx.reason, nodes.MemberExpr)
- expr = ctx.reason.expr
-
- assert isinstance(expr, nodes.RefExpr) and isinstance(expr.node, nodes.Var)
-
- node_type = get_proper_type(expr.node.type)
-
- assert (
- isinstance(node_type, Instance)
- and names.type_id_for_named_node(node_type.type) is names.REGISTRY
- )
-
- decl_class.scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)
-
-
-def _base_cls_decorator_hook(ctx: ClassDefContext) -> None:
- _add_globals(ctx)
-
- cls = ctx.cls
-
- _set_declarative_metaclass(ctx.api, cls)
-
- util.set_is_base(ctx.cls.info)
- decl_class.scan_declarative_assignments_and_apply_types(
- cls, ctx.api, is_mixin_scan=True
- )
-
-
-def _declarative_mixin_hook(ctx: ClassDefContext) -> None:
- _add_globals(ctx)
- util.set_is_base(ctx.cls.info)
- decl_class.scan_declarative_assignments_and_apply_types(
- ctx.cls, ctx.api, is_mixin_scan=True
- )
-
-
-def _metaclass_cls_hook(ctx: ClassDefContext) -> None:
- util.set_is_base(ctx.cls.info)
-
-
-def _base_cls_hook(ctx: ClassDefContext) -> None:
- _add_globals(ctx)
- decl_class.scan_declarative_assignments_and_apply_types(ctx.cls, ctx.api)
-
-
-def _queryable_getattr_hook(ctx: AttributeContext) -> Type:
- # how do I....tell it it has no attribute of a certain name?
- # can't find any Type that seems to match that
- return ctx.default_attr_type
-
-
-def _add_globals(ctx: Union[ClassDefContext, DynamicClassDefContext]) -> None:
- """Add __sa_DeclarativeMeta and __sa_Mapped symbol to the global space
- for all class defs
-
- """
-
- util.add_global(ctx, "sqlalchemy.orm", "Mapped", "__sa_Mapped")
-
-
-def _set_declarative_metaclass(
- api: SemanticAnalyzerPluginInterface, target_cls: ClassDef
-) -> None:
- info = target_cls.info
- sym = api.lookup_fully_qualified_or_none(
- "sqlalchemy.orm.decl_api.DeclarativeMeta"
- )
- assert sym is not None and isinstance(sym.node, TypeInfo)
- info.declared_metaclass = info.metaclass_type = Instance(sym.node, [])
+++ /dev/null
-# ext/mypy/util.py
-# Copyright (C) 2021-2025 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: https://www.opensource.org/licenses/mit-license.php
-
-from __future__ import annotations
-
-import re
-from typing import Any
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import overload
-from typing import Tuple
-from typing import Type as TypingType
-from typing import TypeVar
-from typing import Union
-
-from mypy import version
-from mypy.messages import format_type as _mypy_format_type
-from mypy.nodes import CallExpr
-from mypy.nodes import ClassDef
-from mypy.nodes import CLASSDEF_NO_INFO
-from mypy.nodes import Context
-from mypy.nodes import Expression
-from mypy.nodes import FuncDef
-from mypy.nodes import IfStmt
-from mypy.nodes import JsonDict
-from mypy.nodes import MemberExpr
-from mypy.nodes import NameExpr
-from mypy.nodes import Statement
-from mypy.nodes import SymbolTableNode
-from mypy.nodes import TypeAlias
-from mypy.nodes import TypeInfo
-from mypy.options import Options
-from mypy.plugin import ClassDefContext
-from mypy.plugin import DynamicClassDefContext
-from mypy.plugin import SemanticAnalyzerPluginInterface
-from mypy.plugins.common import deserialize_and_fixup_type
-from mypy.typeops import map_type_from_supertype
-from mypy.types import CallableType
-from mypy.types import get_proper_type
-from mypy.types import Instance
-from mypy.types import NoneType
-from mypy.types import Type
-from mypy.types import TypeVarType
-from mypy.types import UnboundType
-from mypy.types import UnionType
-
-_vers = tuple(
- [int(x) for x in version.__version__.split(".") if re.match(r"^\d+$", x)]
-)
-mypy_14 = _vers >= (1, 4)
-
-
-_TArgType = TypeVar("_TArgType", bound=Union[CallExpr, NameExpr])
-
-
-class SQLAlchemyAttribute:
- def __init__(
- self,
- name: str,
- line: int,
- column: int,
- typ: Optional[Type],
- info: TypeInfo,
- ) -> None:
- self.name = name
- self.line = line
- self.column = column
- self.type = typ
- self.info = info
-
- def serialize(self) -> JsonDict:
- assert self.type
- return {
- "name": self.name,
- "line": self.line,
- "column": self.column,
- "type": serialize_type(self.type),
- }
-
- def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None:
- """Expands type vars in the context of a subtype when an attribute is
- inherited from a generic super type.
- """
- if not isinstance(self.type, TypeVarType):
- return
-
- self.type = map_type_from_supertype(self.type, sub_type, self.info)
-
- @classmethod
- def deserialize(
- cls,
- info: TypeInfo,
- data: JsonDict,
- api: SemanticAnalyzerPluginInterface,
- ) -> SQLAlchemyAttribute:
- data = data.copy()
- typ = deserialize_and_fixup_type(data.pop("type"), api)
- return cls(typ=typ, info=info, **data)
-
-
-def name_is_dunder(name: str) -> bool:
- return bool(re.match(r"^__.+?__$", name))
-
-
-def _set_info_metadata(info: TypeInfo, key: str, data: Any) -> None:
- info.metadata.setdefault("sqlalchemy", {})[key] = data
-
-
-def _get_info_metadata(info: TypeInfo, key: str) -> Optional[Any]:
- return info.metadata.get("sqlalchemy", {}).get(key, None)
-
-
-def _get_info_mro_metadata(info: TypeInfo, key: str) -> Optional[Any]:
- if info.mro:
- for base in info.mro:
- metadata = _get_info_metadata(base, key)
- if metadata is not None:
- return metadata
- return None
-
-
-def establish_as_sqlalchemy(info: TypeInfo) -> None:
- info.metadata.setdefault("sqlalchemy", {})
-
-
-def set_is_base(info: TypeInfo) -> None:
- _set_info_metadata(info, "is_base", True)
-
-
-def get_is_base(info: TypeInfo) -> bool:
- is_base = _get_info_metadata(info, "is_base")
- return is_base is True
-
-
-def has_declarative_base(info: TypeInfo) -> bool:
- is_base = _get_info_mro_metadata(info, "is_base")
- return is_base is True
-
-
-def set_has_table(info: TypeInfo) -> None:
- _set_info_metadata(info, "has_table", True)
-
-
-def get_has_table(info: TypeInfo) -> bool:
- is_base = _get_info_metadata(info, "has_table")
- return is_base is True
-
-
-def get_mapped_attributes(
- info: TypeInfo, api: SemanticAnalyzerPluginInterface
-) -> Optional[List[SQLAlchemyAttribute]]:
- mapped_attributes: Optional[List[JsonDict]] = _get_info_metadata(
- info, "mapped_attributes"
- )
- if mapped_attributes is None:
- return None
-
- attributes: List[SQLAlchemyAttribute] = []
-
- for data in mapped_attributes:
- attr = SQLAlchemyAttribute.deserialize(info, data, api)
- attr.expand_typevar_from_subtype(info)
- attributes.append(attr)
-
- return attributes
-
-
-def format_type(typ_: Type, options: Options) -> str:
- if mypy_14:
- return _mypy_format_type(typ_, options)
- else:
- return _mypy_format_type(typ_) # type: ignore
-
-
-def set_mapped_attributes(
- info: TypeInfo, attributes: List[SQLAlchemyAttribute]
-) -> None:
- _set_info_metadata(
- info,
- "mapped_attributes",
- [attribute.serialize() for attribute in attributes],
- )
-
-
-def fail(api: SemanticAnalyzerPluginInterface, msg: str, ctx: Context) -> None:
- msg = "[SQLAlchemy Mypy plugin] %s" % msg
- return api.fail(msg, ctx)
-
-
-def add_global(
- ctx: Union[ClassDefContext, DynamicClassDefContext],
- module: str,
- symbol_name: str,
- asname: str,
-) -> None:
- module_globals = ctx.api.modules[ctx.api.cur_mod_id].names
-
- if asname not in module_globals:
- lookup_sym: SymbolTableNode = ctx.api.modules[module].names[
- symbol_name
- ]
-
- module_globals[asname] = lookup_sym
-
-
-@overload
-def get_callexpr_kwarg(
- callexpr: CallExpr, name: str, *, expr_types: None = ...
-) -> Optional[Union[CallExpr, NameExpr]]: ...
-
-
-@overload
-def get_callexpr_kwarg(
- callexpr: CallExpr,
- name: str,
- *,
- expr_types: Tuple[TypingType[_TArgType], ...],
-) -> Optional[_TArgType]: ...
-
-
-def get_callexpr_kwarg(
- callexpr: CallExpr,
- name: str,
- *,
- expr_types: Optional[Tuple[TypingType[Any], ...]] = None,
-) -> Optional[Any]:
- try:
- arg_idx = callexpr.arg_names.index(name)
- except ValueError:
- return None
-
- kwarg = callexpr.args[arg_idx]
- if isinstance(
- kwarg, expr_types if expr_types is not None else (NameExpr, CallExpr)
- ):
- return kwarg
-
- return None
-
-
-def flatten_typechecking(stmts: Iterable[Statement]) -> Iterator[Statement]:
- for stmt in stmts:
- if (
- isinstance(stmt, IfStmt)
- and isinstance(stmt.expr[0], NameExpr)
- and stmt.expr[0].fullname == "typing.TYPE_CHECKING"
- ):
- yield from stmt.body[0].body
- else:
- yield stmt
-
-
-def type_for_callee(callee: Expression) -> Optional[Union[Instance, TypeInfo]]:
- if isinstance(callee, (MemberExpr, NameExpr)):
- if isinstance(callee.node, FuncDef):
- if callee.node.type and isinstance(callee.node.type, CallableType):
- ret_type = get_proper_type(callee.node.type.ret_type)
-
- if isinstance(ret_type, Instance):
- return ret_type
-
- return None
- elif isinstance(callee.node, TypeAlias):
- target_type = get_proper_type(callee.node.target)
- if isinstance(target_type, Instance):
- return target_type
- elif isinstance(callee.node, TypeInfo):
- return callee.node
- return None
-
-
-def unbound_to_instance(
- api: SemanticAnalyzerPluginInterface, typ: Type
-) -> Type:
- """Take the UnboundType that we seem to get as the ret_type from a FuncDef
- and convert it into an Instance/TypeInfo kind of structure that seems
- to work as the left-hand type of an AssignmentStatement.
-
- """
-
- if not isinstance(typ, UnboundType):
- return typ
-
- # TODO: figure out a more robust way to check this. The node is some
- # kind of _SpecialForm, there's a typing.Optional that's _SpecialForm,
- # but I can't figure out how to get them to match up
- if typ.name == "Optional":
- # convert from "Optional?" to the more familiar
- # UnionType[..., NoneType()]
- return unbound_to_instance(
- api,
- UnionType(
- [unbound_to_instance(api, typ_arg) for typ_arg in typ.args]
- + [NoneType()]
- ),
- )
-
- node = api.lookup_qualified(typ.name, typ)
-
- if (
- node is not None
- and isinstance(node, SymbolTableNode)
- and isinstance(node.node, TypeInfo)
- ):
- bound_type = node.node
-
- return Instance(
- bound_type,
- [
- (
- unbound_to_instance(api, arg)
- if isinstance(arg, UnboundType)
- else arg
- )
- for arg in typ.args
- ],
- )
- else:
- return typ
-
-
-def info_for_cls(
- cls: ClassDef, api: SemanticAnalyzerPluginInterface
-) -> Optional[TypeInfo]:
- if cls.info is CLASSDEF_NO_INFO:
- sym = api.lookup_qualified(cls.name, cls)
- if sym is None:
- return None
- assert sym and isinstance(sym.node, TypeInfo)
- return sym.node
-
- return cls.info
-
-
-def serialize_type(typ: Type) -> Union[str, JsonDict]:
- try:
- return typ.serialize()
- except Exception:
- pass
- if hasattr(typ, "args"):
- typ.args = tuple(
- (
- a.resolve_string_annotation()
- if hasattr(a, "resolve_string_annotation")
- else a
- )
- for a in typ.args
- )
- elif hasattr(typ, "resolve_string_annotation"):
- typ = typ.resolve_string_annotation()
- return typ.serialize()
from ..assertions import eq_
from ... import util
+try:
+ from mypy import version
+
+ _mypy_vers_tuple = tuple(
+ int(x) for x in version.__version__.split(".") if x.isdecimal()
+ )
+except ImportError:
+ _mypy_vers_tuple = (0, 0, 0)
+
+mypy_14 = _mypy_vers_tuple >= (1, 4)
+
@config.add_to_marker.mypy
class MypyTest(TestBase):
mypy_path = ""
with tempfile.TemporaryDirectory() as cachedir:
- with open(
- Path(cachedir) / "sqla_mypy_config.cfg", "w"
- ) as config_file:
- config_file.write(
- f"""
- [mypy]\n
- plugins = sqlalchemy.ext.mypy.plugin\n
- show_error_codes = True\n
- {mypy_path}
- disable_error_code = no-untyped-call
-
- [mypy-sqlalchemy.*]
- ignore_errors = True
-
- """
- )
with open(
Path(cachedir) / "plain_mypy_config.cfg", "w"
) as config_file:
def mypy_runner(self, cachedir):
from mypy import api
- def run(path, use_plugin=False, use_cachedir=None):
+ def run(path, use_cachedir=None):
if use_cachedir is None:
use_cachedir = cachedir
args = [
"--cache-dir",
use_cachedir,
"--config-file",
- os.path.join(
- use_cachedir,
- (
- "sqla_mypy_config.cfg"
- if use_plugin
- else "plain_mypy_config.cfg"
- ),
- ),
+ os.path.join(use_cachedir, "plain_mypy_config.cfg"),
]
# mypy as of 0.990 is more aggressively blocking messaging
@config.fixture
def mypy_typecheck_file(self, mypy_runner):
- def run(path, use_plugin=False):
+ def run(path):
expected_messages = self._collect_messages(path)
- stdout, stderr, exitcode = mypy_runner(path, use_plugin=use_plugin)
+ stdout, stderr, exitcode = mypy_runner(path)
self._check_output(
path, expected_messages, stdout, stderr, exitcode
)
return files
def _collect_messages(self, path):
- from sqlalchemy.ext.mypy.util import mypy_14
-
expected_messages = []
expected_re = re.compile(r"\s*# EXPECTED(_MYPY)?(_RE)?(_TYPE)?: (.+)")
py_ver_re = re.compile(r"^#\s*PYTHON_VERSION\s?>=\s?(\d+\.\d+)")
[tool.slotscheck]
exclude-modules = '''
-^sqlalchemy\.(
- testing
- |ext\.mypy # see slotscheck/issues/178
-)
+^sqlalchemy\.testing
'''
**/__init__.py:F401
test/*:FA100
test/typing/plain_files/*:F821,E501,FA100
- test/ext/mypy/plugin_files/*:F821,E501,FA100
lib/sqlalchemy/events.py:F401
lib/sqlalchemy/schema.py:F401
lib/sqlalchemy/types.py:F401
+++ /dev/null
-from typing import TYPE_CHECKING
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy.orm import as_declarative
-from sqlalchemy.orm import declared_attr
-from sqlalchemy.orm import Mapped
-from .address import Address
-from .user import User
-
-if TYPE_CHECKING:
- from sqlalchemy.orm.decl_api import DeclarativeMeta
-
-
-@as_declarative()
-class Base:
- @declared_attr
- def __tablename__(self) -> Mapped[str]:
- return self.__name__.lower()
-
- id = Column(Integer, primary_key=True)
-
-
-__all__ = ["User", "Address"]
+++ /dev/null
-from typing import TYPE_CHECKING
-
-from . import Base
-from .user import HasUser
-
-if TYPE_CHECKING:
- from sqlalchemy import Column # noqa
- from sqlalchemy import Integer # noqa
- from sqlalchemy.orm import RelationshipProperty # noqa
- from .user import User # noqa
-
-
-class Address(Base, HasUser):
- pass
+++ /dev/null
-diff --git a/test/ext/mypy/incremental/stubs_14/user.py b/test/ext/mypy/incremental/stubs_14/user.py
-index 2c60403e4..c7e8f8874 100644
---- a/user.py
-+++ b/user.py
-@@ -18,6 +18,8 @@ if TYPE_CHECKING:
- class User(Base):
- name = Column(String)
-
-+ othername = Column(String)
-+
- addresses: Mapped[List["Address"]] = relationship(
- "Address", back_populates="user"
- )
+++ /dev/null
-from typing import List
-from typing import TYPE_CHECKING
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm.decl_api import declared_attr
-from sqlalchemy.orm.relationships import RelationshipProperty
-from . import Base
-
-if TYPE_CHECKING:
- from .address import Address
-
-
-class User(Base):
- name = Column(String)
-
- othername = Column(String)
-
- addresses: Mapped[List["Address"]] = relationship(
- "Address", back_populates="user"
- )
-
-
-class HasUser:
- @declared_attr
- def user_id(self) -> "Column[Integer]":
- return Column(
- Integer,
- ForeignKey(User.id, ondelete="CASCADE", onupdate="CASCADE"),
- nullable=False,
- )
-
- @declared_attr
- def user(self) -> RelationshipProperty[User]:
- return relationship(User)
+++ /dev/null
-from sqlalchemy.orm import declarative_base
-
-Base = declarative_base()
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from .base import Base
-
-
-class One(Base):
- __tablename__ = "one"
- id = Column(Integer, primary_key=True)
-
-
-o1 = One(id=5)
-
-One.id.in_([1, 2])
+++ /dev/null
---- a/one.py 2021-04-03 15:32:22.214287290 -0400
-+++ b/one.py 2021-04-03 15:34:56.397398510 -0400
-@@ -1,15 +1,13 @@
- from sqlalchemy import Column
- from sqlalchemy import Integer
--from sqlalchemy import String
- from .base import Base
-
-
- class One(Base):
- __tablename__ = "one"
- id = Column(Integer, primary_key=True)
-- name = Column(String(50))
-
-
--o1 = One(id=5, name="name")
-+o1 = One(id=5)
-
- One.id.in_([1, 2])
+++ /dev/null
---- a/base.py 2021-04-03 16:36:30.201594994 -0400
-+++ b/base.py 2021-04-03 16:38:26.404475025 -0400
-@@ -1,3 +1,15 @@
-+from sqlalchemy import Column
-+from sqlalchemy import Integer
-+from sqlalchemy import String
- from sqlalchemy.orm import declarative_base
-+from sqlalchemy.orm import declarative_mixin
-+from sqlalchemy.orm import Mapped
-
- Base = declarative_base()
-+
-+
-+@declarative_mixin
-+class Mixin:
-+ mixed = Column(String)
-+
-+ b_int: Mapped[int] = Column(Integer)
---- a/one.py 2021-04-03 16:37:17.906956282 -0400
-+++ b/one.py 2021-04-03 16:38:33.469528528 -0400
-@@ -1,13 +1,15 @@
- from sqlalchemy import Column
- from sqlalchemy import Integer
-+
- from .base import Base
-+from .base import Mixin
-
-
--class One(Base):
-+class One(Mixin, Base):
- __tablename__ = "one"
- id = Column(Integer, primary_key=True)
-
-
--o1 = One(id=5)
-+o1 = One(id=5, mixed="mixed", b_int=5)
-
- One.id.in_([1, 2])
+++ /dev/null
-import enum
-
-
-class StrEnum(enum.Enum):
- one = "one"
- two = "two"
-
-
-class IntEnum(enum.Enum):
- one = 1
- two = 2
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Enum
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import Mapped
-from . import enum_col_import1
-from .enum_col_import1 import IntEnum
-from .enum_col_import1 import StrEnum
-
-Base = declarative_base()
-
-
-class TestEnum(Base):
- __tablename__ = "test_enum"
-
- e1: Mapped[StrEnum] = Column(Enum(StrEnum))
- e2: StrEnum = Column(Enum(StrEnum))
-
- e3: Mapped[IntEnum] = Column(Enum(IntEnum))
- e4: IntEnum = Column(Enum(IntEnum))
-
- e5: Mapped[enum_col_import1.StrEnum] = Column(
- Enum(enum_col_import1.StrEnum)
- )
- e6: enum_col_import1.StrEnum = Column(Enum(enum_col_import1.StrEnum))
-
- e7: Mapped[enum_col_import1.IntEnum] = Column(
- Enum(enum_col_import1.IntEnum)
- )
- e8: enum_col_import1.IntEnum = Column(Enum(enum_col_import1.IntEnum))
+++ /dev/null
-from sqlalchemy.ext.declarative import declarative_base
-
-
-class CustomBase:
- x = 5
-
-
-sql_base = declarative_base(cls=CustomBase)
+++ /dev/null
---- a/table.py 2021-06-21 15:52:47.733131711 -0400
-+++ b/table.py 2021-06-21 16:32:25.437594701 -0400
-@@ -3,3 +3,5 @@
-
- class Table(sql_base):
- pass
-+
-+x = Table.x
+++ /dev/null
-from .base import sql_base
-
-
-class Table(sql_base):
- pass
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-
-
-Base = declarative_base()
-
-
-class FooBase(Base):
- __abstract__ = True
-
- updated_at = Column(Integer)
-
-
-class Foo(FooBase):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-Foo.updated_at.in_([1, 2, 3])
-
-f1 = Foo(name="name", updated_at=5)
-
-# test that we read the __abstract__ flag and don't apply a constructor
-# EXPECTED_MYPY: Unexpected keyword argument "updated_at" for "FooBase"
-FooBase(updated_at=5)
+++ /dev/null
-from typing import List
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import as_declarative
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import relationship
-from sqlalchemy.sql.schema import ForeignKey
-
-
-@as_declarative()
-class Base:
- updated_at = Column(Integer)
-
-
-class Foo(Base):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: Mapped[str] = Column(String)
-
- bar: List["Bar"] = relationship("Bar")
-
-
-class Bar(Base):
- __tablename__ = "bar"
- id: int = Column(Integer(), primary_key=True)
- foo_id: int = Column(ForeignKey("foo.id"))
-
- foo: Optional[Foo] = relationship(Foo)
-
-
-f1 = Foo()
-
-val: int = f1.id
-
-p: str = f1.name
-
-Foo.id.property
-
-f2 = Foo(name="some name", updated_at=5)
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.as_declarative_base()
-class Base:
- updated_at = Column(Integer)
-
-
-class Foo(Base):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-f1 = Foo()
-
-val: int = f1.id
-
-p: str = f1.name
-
-Foo.id.property
-
-f2 = Foo(name="some name", updated_at=5)
+++ /dev/null
-from typing import Optional
-
-from sqlalchemy import Boolean
-from sqlalchemy import Column
-from sqlalchemy.orm import declarative_base
-
-Base = declarative_base()
-
-
-class TestBoolean(Base):
- __tablename__ = "test_boolean"
-
- flag = Column(Boolean)
-
- bflag: bool = Column(Boolean(create_constraint=True))
-
-
-expr = TestBoolean.flag.is_(True)
-
-t1 = TestBoolean(flag=True)
-
-x: Optional[bool] = t1.flag
-
-y: bool = t1.bflag
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-
-reg: registry = registry()
-
-
-@reg.mapped
-class Foo:
- id: int = Column(Integer())
- name: str = Column(String)
- other_name: str = Column(String(50))
-
- # has a string key in it
- third_name = Column("foo", String(50))
-
- some_name = "fourth_name"
-
- fourth_name = Column(some_name, String(50))
-
-
-f1 = Foo()
-
-# This needs to work, e.g., value is "int" at the instance level
-val: int = f1.id # noqa
-
-# also, the type are not optional, since we used an explicit
-# type without Optional
-p: str = f1.name
-
-Foo.id.property
-
-
-Foo(name="n", other_name="on", third_name="tn", fourth_name="fn")
+++ /dev/null
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
- name = Column(String)
-
-
-@reg.mapped
-class Address:
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: Mapped[int] = Column(ForeignKey("user.id"))
- email_address = Column(String)
-
-
-ad1 = Address()
-
-p: Optional[int] = ad1.user_id
-
-# it's not optional because we called it Mapped[int]
-# and not Mapped[Optional[int]]
-p2: int = ad1.user_id
-
-
-# class-level descriptor access
-User.name.in_(["x", "y"])
-
-
-# class-level descriptor access
-Address.user_id.in_([1, 2])
+++ /dev/null
-from typing import Any
-from typing import Tuple
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import select
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import composite
-
-Base = declarative_base()
-
-
-class Point:
- def __init__(self, x: int, y: int):
- self.x = x
- self.y = y
-
- def __composite_values__(self) -> Tuple[int, int]:
- return self.x, self.y
-
- def __repr__(self) -> str:
- return "Point(x=%r, y=%r)" % (self.x, self.y)
-
- def __eq__(self, other: Any) -> bool:
- return (
- isinstance(other, Point)
- and other.x == self.x
- and other.y == self.y
- )
-
- def __ne__(self, other: Any) -> bool:
- return not self.__eq__(other)
-
-
-class Vertex(Base):
- __tablename__ = "vertices"
-
- id = Column(Integer, primary_key=True)
- x1 = Column(Integer)
- y1 = Column(Integer)
- x2 = Column(Integer)
- y2 = Column(Integer)
-
- # inferred from right hand side
- start = composite(Point, x1, y1)
-
- # taken from left hand side
- end: Point = composite(Point, x2, y2)
-
-
-v1 = Vertex(start=Point(3, 4), end=Point(5, 6))
-
-# I'm not even sure composites support this but it should work from a
-# typing perspective
-stmt = select(Vertex).where(Vertex.start.in_([Point(3, 4)]))
-
-p1: Point = v1.start
-p2: Point = v1.end
-
-y3: int = v1.end.y
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
-
-Base = declarative_base()
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
- data = Column(String)
- x = Column(Integer)
- y = Column(Integer)
-
-
-a1 = A(data="d", x=5, y=4)
-
-
-# EXPECTED_MYPY: Argument "data" to "A" has incompatible type "int"; expected "Optional[str]" # noqa
-a2 = A(data=5)
-
-# EXPECTED_MYPY: Unexpected keyword argument "nonexistent" for "A"
-a3 = A(nonexistent="hi")
-
-print(a1)
-print(a2)
-print(a3)
+++ /dev/null
-from __future__ import annotations
-
-from dataclasses import dataclass
-from dataclasses import field
-from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import TYPE_CHECKING
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import select
-from sqlalchemy import String
-from sqlalchemy import Table
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import relationship
-
-mapper_registry: registry = registry()
-
-
-@mapper_registry.mapped
-@dataclass
-class User:
- __table__ = Table(
- "user",
- mapper_registry.metadata,
- Column("id", Integer, primary_key=True),
- Column("name", String(50)),
- Column("fullname", String(50)),
- Column("nickname", String(12)),
- )
- id: int = field(init=False)
- name: Optional[str] = None
- fullname: Optional[str] = None
- nickname: Optional[str] = None
- addresses: List[Address] = field(default_factory=list)
-
- if TYPE_CHECKING:
- _mypy_mapped_attrs = [id, name, fullname, nickname, addresses]
-
- __mapper_args__: Dict[str, Any] = {
- "properties": {"addresses": relationship("Address")}
- }
-
-
-@mapper_registry.mapped
-@dataclass
-class Address:
- __table__ = Table(
- "address",
- mapper_registry.metadata,
- Column("id", Integer, primary_key=True),
- Column("user_id", Integer, ForeignKey("user.id")),
- Column("email_address", String(50)),
- )
-
- id: int = field(init=False)
- user_id: int = field(init=False)
- email_address: Optional[str] = None
-
- if TYPE_CHECKING:
- _mypy_mapped_attrs = [id, user_id, email_address]
-
-
-stmt1 = select(User.name).where(User.id.in_([1, 2, 3]))
-stmt2 = select(Address).where(Address.email_address.contains(["foo"]))
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-from sqlalchemy.sql.schema import ForeignKey
-from sqlalchemy.sql.schema import MetaData
-from sqlalchemy.sql.schema import Table
-
-
-reg: registry = registry()
-
-
-@reg.mapped
-class Foo:
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-@reg.mapped
-class Bar(Foo):
- __tablename__ = "bar"
- id: int = Column(ForeignKey("foo.id"), primary_key=True)
-
-
-@reg.mapped
-class Bat(Foo):
- pass
-
-
-m1: MetaData = reg.metadata
-
-t1: Table = Foo.__table__
-
-t2: Table = Bar.__table__
-
-t3: Table = Bat.__table__
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import registry
-from sqlalchemy.sql.schema import ForeignKey
-from sqlalchemy.sql.schema import MetaData
-from sqlalchemy.sql.schema import Table
-
-
-Base = declarative_base()
-
-
-class Foo(Base):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-class Bar(Foo):
- __tablename__ = "bar"
- id: int = Column(ForeignKey("foo.id"), primary_key=True)
-
-
-class Bat(Foo):
- pass
-
-
-m0: MetaData = Base.metadata
-r0: registry = Base.registry
-
-t1: Table = Foo.__table__
-m1: MetaData = Foo.metadata
-
-t2: Table = Bar.__table__
-m2: MetaData = Bar.metadata
-
-t3: Table = Bat.__table__
-m3: MetaData = Bat.metadata
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-
-
-class _Base:
- updated_at = Column(Integer)
-
-
-Base = declarative_base(cls=_Base)
-
-
-class Foo(Base):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-class Bar(Base):
- __tablename__ = "bar"
- id = Column(Integer(), primary_key=True)
- num = Column(Integer)
-
-
-Foo.updated_at.in_([1, 2, 3])
-
-f1 = Foo(name="name", updated_at=5)
-
-b1 = Bar(num=5, updated_at=6)
+++ /dev/null
-from typing import List
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm.decl_api import declared_attr
-from sqlalchemy.sql.schema import ForeignKey
-from sqlalchemy.sql.sqltypes import Integer
-from sqlalchemy.sql.sqltypes import String
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String(50))
-
- name3 = Column(String(50))
-
- addresses: List["Address"] = relationship("Address")
-
-
-@reg.mapped
-class SubUser(User):
- __tablename__ = "subuser"
-
- id: int = Column(ForeignKey("user.id"), primary_key=True)
-
- @declared_attr
- def name(cls) -> Column[String]:
- return Column(String(50))
-
- @declared_attr
- def name2(cls) -> Mapped[Optional[str]]:
- return Column(String(50))
-
- @declared_attr
- def name3(cls) -> Mapped[str]:
- return Column(String(50))
-
- subname = Column(String)
-
-
-@reg.mapped
-class Address:
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
- email = Column(String(50))
-
- user = relationship(User, uselist=False)
-
-
-s1 = SubUser()
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str" # noqa
-x1: str = s1.name
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") # noqa
-x2: str = s1.name2
-
-x3: str = s1.name3
-
-u1 = User()
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") # noqa
-x4: str = u1.name3
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
-
-# this is actually in orm now
-
-
-Base = declarative_base()
-
-
-class Foo(Base):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
- other_name: str = Column(String(50))
-
-
-f1 = Foo()
-
-val: int = f1.id
-
-p: str = f1.name
-
-Foo.id.property
-
-# TODO: getitem checker? this should raise
-Foo.id.property_nonexistent
-
-
-f2 = Foo(name="some name", other_name="some other name")
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-from sqlalchemy.orm.decl_api import DeclarativeMeta
-
-
-class Base(metaclass=DeclarativeMeta):
- __abstract__ = True
- registry = registry()
- metadata = registry.metadata
-
-
-class Foo(Base):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
- other_name: str = Column(String(50))
-
-
-f1 = Foo()
-
-val: int = f1.id
-
-p: str = f1.name
-
-Foo.id.property
-
-# TODO: getitem checker? this should raise
-Foo.id.property_nonexistent
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
- name = Column(String, nullable=False)
-
-
-u1 = User()
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") # noqa: E501
-p: str = u1.name
+++ /dev/null
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
- name: Mapped[Optional[str]] = Column(String)
-
-
-u1 = User()
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "Optional[int]") # noqa: E501
-p: Optional[int] = u1.name
+++ /dev/null
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
-
- # we will call this "semi-inferred", since the real
- # type will be Mapped[Optional[str]], but the Optional[str]
- # which is not inferred, we use that to create it
- name: Optional[str] = Column(String)
-
-
-u1 = User()
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") # noqa: E501
-p: str = u1.name
+++ /dev/null
-import enum
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import Enum
-from sqlalchemy.orm import declarative_base
-
-
-class MyEnum(enum.Enum):
- one = 1
- two = 2
- three = 3
-
-
-Base = declarative_base()
-
-one, two, three = "one", "two", "three"
-
-
-class TestEnum(Base):
- __tablename__ = "test_enum"
-
- e1: str = Column(Enum("one", "two", "three"))
-
- e2: MyEnum = Column(Enum(MyEnum))
-
- e3 = Column(Enum(one, two, three))
-
- e4 = Column(Enum(MyEnum))
-
-
-t1 = TestEnum(e1="two", e2=MyEnum.three, e3="one", e4=MyEnum.one)
-
-x: str = t1.e1
-
-y: MyEnum = t1.e2
-
-z: Optional[str] = t1.e3
-
-z2: Optional[MyEnum] = t1.e4
+++ /dev/null
-import datetime
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import DateTime
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy import Table
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import Mapped
-
-
-Base = declarative_base()
-
-
-class MyMappedClass(Base):
- __table_ = Table(
- "some_table",
- Base.metadata,
- Column("id", Integer, primary_key=True),
- Column("data", String(50)),
- Column("created_at", DateTime),
- )
-
- id: Mapped[int]
- data: Mapped[Optional[str]]
- created_at: Mapped[datetime.datetime]
-
-
-m1 = MyMappedClass(id=5, data="string", created_at=datetime.datetime.now())
-
-# EXPECTED_MYPY: Argument "created_at" to "MyMappedClass" has incompatible type "int"; expected "datetime" # noqa
-m2 = MyMappedClass(id=5, data="string", created_at=12)
-
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "str") # noqa
-x: str = MyMappedClass().data
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
- # EXPECTED: Left hand assignment 'name: "int"' not compatible with ORM mapped expression # noqa: E501
- name: int = Column(String())
+++ /dev/null
-from typing import Any
-from typing import Dict
-
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import declared_attr
-
-
-Base = declarative_base()
-
-
-class Foo(Base):
- @declared_attr
- def __tablename__(cls) -> str:
- return "name"
-
- @declared_attr
- def __mapper_args__(cls) -> Dict[Any, Any]:
- return {}
-
- @declared_attr
- def __table_args__(cls) -> Dict[Any, Any]:
- return {}
+++ /dev/null
-from typing import Any
-from typing import Dict
-from typing import Type
-
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import declared_attr
-
-
-Base = declarative_base()
-
-
-class Foo(Base):
- # no mypy error emitted regarding the
- # Type[Foo] part
- @declared_attr
- def __tablename__(cls: Type["Foo"]) -> str:
- return "name"
-
- @declared_attr
- def __mapper_args__(cls: Type["Foo"]) -> Dict[Any, Any]:
- return {}
-
- # this was a workaround that works if there's no plugin present, make
- # sure that doesn't crash anything
- @classmethod
- @declared_attr
- def __table_args__(cls: Type["Foo"]) -> Dict[Any, Any]:
- return {}
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy.orm import registry
-
-
-class BackendMeta:
- __abstract__ = True
- mapped_registry: registry = registry()
- metadata = mapped_registry.metadata
-
-
-# this decorator is not picked up now, but at least it doesn't crash
-@BackendMeta.mapped_registry.mapped
-class User:
- __tablename__ = "user"
-
- # EXPECTED_MYPY: Incompatible types in assignment (expression has type "Column[int]", variable has type "int")
- id: int = Column(Integer(), primary_key=True)
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy.orm import registry
-
-
-class BackendMeta:
- __abstract__ = True
- mapped_registry: registry = registry()
- metadata = mapped_registry.metadata
-
-
-reg: registry = BackendMeta.mapped_registry
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id: int = Column(Integer(), primary_key=True)
+++ /dev/null
-from typing import Any
-from typing import Type
-
-from sqlalchemy.sql.elements import ColumnElement
-from sqlalchemy.sql.type_api import TypeEngine
-
-col: ColumnElement[Any]
-type_: Type[TypeEngine[Any]]
-obj: TypeEngine[Any]
-
-col.cast(type_)
-col.cast(obj)
+++ /dev/null
-import uuid
-
-from sqlalchemy import Column
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-
-Base = declarative_base()
-
-
-class MyClass(Base):
- id = Column(String, default=lambda: uuid.uuid4(), primary_key=True)
+++ /dev/null
-"""Test patterns that can be used for assignment of mapped attributes
-after the mapping is complete
-
-
-"""
-
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import inspect
-from sqlalchemy import Integer
-from sqlalchemy import select
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import column_property
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class B(Base):
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
- a_id: int = Column(ForeignKey("a.id"))
-
- # to attach attrs after the fact, declare them with Mapped
- # on the class...
- data: Mapped[str]
-
- a: Mapped[Optional["A"]]
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
- data = Column(String)
- bs = relationship(B, uselist=True, back_populates="a")
-
-
-# There's no way to intercept the __setattr__() from the metaclass
-# here, and also when @reg.mapped() is used there is no metaclass.
-# so have them do it the old way
-inspect(B).add_property(
- "data",
- column_property(select(A.data).where(A.id == B.a_id).scalar_subquery()),
-)
-inspect(B).add_property("a", relationship(A))
-
-
-# the constructor will pick them up
-a1 = A()
-b1 = B(data="b", a=a1)
-
-# and it's mapped
-B.data.in_(["x", "y"])
-B.a.any()
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import registry
-
-
-reg: registry = registry()
-
-Base = declarative_base()
-
-
-class SomeAbstract(Base):
- __abstract__ = True
-
-
-class HasUpdatedAt:
- updated_at = Column(Integer)
-
-
-@reg.mapped
-class Foo(SomeAbstract):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-class Bar(HasUpdatedAt, Base):
- __tablename__ = "bar"
- id = Column(Integer(), primary_key=True)
- num = Column(Integer)
-
-
-Bar.__mapper__
-
-# EXPECTED_MYPY: "type[HasUpdatedAt]" has no attribute "__mapper__"
-HasUpdatedAt.__mapper__
-
-
-# EXPECTED_MYPY: "type[SomeAbstract]" has no attribute "__mapper__"
-SomeAbstract.__mapper__
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import registry
-
-
-reg: registry = registry()
-
-# TODO: also reg.as_declarative_base()
-Base = declarative_base()
-
-
-class HasUpdatedAt:
- updated_at = Column(Integer)
-
-
-@reg.mapped
-class Foo(HasUpdatedAt):
- __tablename__ = "foo"
- id: int = Column(Integer(), primary_key=True)
- name: str = Column(String)
-
-
-class Bar(HasUpdatedAt, Base):
- __tablename__ = "bar"
- id = Column(Integer(), primary_key=True)
- num = Column(Integer)
-
-
-Foo.updated_at.in_([1, 2, 3])
-Bar.updated_at.in_([1, 2, 3])
-
-f1 = Foo(name="name", updated_at=5)
-
-b1 = Bar(num=5, updated_at=6)
-
-
-# test that we detected this as an unmapped mixin
-# EXPECTED_MYPY: Unexpected keyword argument "updated_at" for "HasUpdatedAt"
-HasUpdatedAt(updated_at=5)
+++ /dev/null
-from typing import Callable
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import deferred
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm.decl_api import declarative_mixin
-from sqlalchemy.orm.decl_api import declared_attr
-from sqlalchemy.orm.interfaces import MapperProperty
-
-
-def some_other_decorator(fn: Callable[..., None]) -> Callable[..., None]:
- return fn
-
-
-@declarative_mixin
-class HasAMixin:
- x: Mapped[int] = Column(Integer)
-
- y = Column(String)
-
- @declared_attr
- def data(cls) -> Column[String]:
- return Column(String)
-
- @declared_attr
- def data2(cls) -> MapperProperty[str]:
- return deferred(Column(String))
-
- @some_other_decorator
- def q(cls) -> None:
- return None
+++ /dev/null
-from typing import Callable
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import deferred
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm import RelationshipProperty
-from sqlalchemy.orm.decl_api import declared_attr
-from sqlalchemy.orm.interfaces import MapperProperty
-from sqlalchemy.sql.schema import ForeignKey
-
-
-reg: registry = registry()
-
-
-@reg.mapped
-class C:
- __tablename__ = "c"
- id = Column(Integer, primary_key=True)
-
-
-def some_other_decorator(fn: Callable[..., None]) -> Callable[..., None]:
- return fn
-
-
-class HasAMixin:
- @declared_attr
- def a(cls) -> Mapped["A"]:
- return relationship("A", back_populates="bs")
-
- # EXPECTED: Can't infer type from @declared_attr on function 'a2';
- @declared_attr
- def a2(cls):
- return relationship("A", back_populates="bs")
-
- @declared_attr
- def a3(cls) -> RelationshipProperty["A"]:
- return relationship("A", back_populates="bs")
-
- @declared_attr
- def c1(cls) -> RelationshipProperty[C]:
- return relationship(C, back_populates="bs")
-
- @declared_attr
- def c2(cls) -> Mapped[C]:
- return relationship(C, back_populates="bs")
-
- @declared_attr
- def data(cls) -> Column[String]:
- return Column(String)
-
- @declared_attr
- def data2(cls) -> MapperProperty[str]:
- return deferred(Column(String))
-
- @some_other_decorator
- def q(cls) -> None:
- return None
-
-
-@reg.mapped
-class B(HasAMixin):
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
- a_id: int = Column(ForeignKey("a.id"))
- c_id: int = Column(ForeignKey("c.id"))
-
-
-@reg.mapped
-class A:
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
-
- @declared_attr
- def data(cls) -> Column[String]:
- return Column(String)
-
- # EXPECTED: Can't infer type from @declared_attr on function 'data2';
- @declared_attr
- def data2(cls):
- return Column(String)
-
- bs = relationship(B, uselist=True, back_populates="a")
-
-
-a1 = A(id=1, data="d1", data2="d2")
-
-
-b1 = B(a=A(), a2=A(), c1=C(), c2=C(), data="d1", data2="d2")
-
-# descriptor access as Mapped[<type>]
-B.a.any()
-B.a2.any()
-B.c1.any()
-B.c2.any()
-
-# sanity check against another fn that isn't mapped
-# EXPECTED_MYPY: "Callable[..., None]" has no attribute "any"
-B.q.any()
-
-B.data.in_(["a", "b"])
-B.data2.in_(["a", "b"])
+++ /dev/null
-# test #6937
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import declared_attr
-from sqlalchemy.orm import Mapped
-
-
-Base = declarative_base()
-
-
-class UpdatedCls:
- @declared_attr
- def __tablename__(cls) -> Mapped[str]:
- return cls.__name__.lower()
-
- updated_at = Column(Integer)
-
-
-class Bar(UpdatedCls, Base):
- id = Column(Integer(), primary_key=True)
- num = Column(Integer)
-
-
-Bar.updated_at.in_([1, 2, 3])
-
-b1 = Bar(num=5, updated_at=6)
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy.ext.orderinglist import ordering_list
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import relationship
-
-mapper_registry: registry = registry()
-
-
-@mapper_registry.mapped
-class A:
- __tablename__ = "a"
- id = Column(Integer, primary_key=True)
-
- # EXPECTED: Can't infer type from ORM mapped expression assigned to attribute 'parents'; please specify a Python type or Mapped[<python type>] on the left hand side. # noqa
- parents = relationship("A", collection_class=ordering_list("ordering"))
- parent_id = Column(Integer, ForeignKey("a.id"))
- ordering = Column(Integer)
-
-
-a1 = A(id=5, ordering=10)
-
-# EXPECTED_MYPY: Argument "parents" to "A" has incompatible type "list[A]"; expected "Mapped[Any]" # noqa
-a2 = A(parents=[a1])
+++ /dev/null
-from typing import List
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy.ext.orderinglist import ordering_list
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import relationship
-
-mapper_registry: registry = registry()
-
-
-@mapper_registry.mapped
-class B:
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
- parent_id = Column(Integer, ForeignKey("a.id"))
- ordering = Column(Integer)
-
-
-@mapper_registry.mapped
-class C:
- __tablename__ = "c"
- id = Column(Integer, primary_key=True)
- parent_id = Column(Integer, ForeignKey("a.id"))
- ordering = Column(Integer)
-
-
-@mapper_registry.mapped
-class A:
- __tablename__ = "a"
- id = Column(Integer, primary_key=True)
-
- bs = relationship(B, collection_class=ordering_list("ordering"))
-
- bs_w_list: List[B] = relationship(
- B, collection_class=ordering_list("ordering")
- )
-
- # EXPECTED: Left hand assignment 'cs: "list[B]"' not compatible with ORM mapped expression of type "Mapped[list[C]]" # noqa
- cs: List[B] = relationship(C, uselist=True)
-
- # EXPECTED: Left hand assignment 'cs_2: "B"' not compatible with ORM mapped expression of type "Mapped[list[C]]" # noqa
- cs_2: B = relationship(C, uselist=True)
-
-
-b1 = B(ordering=10)
-
-# in this case, the plugin infers OrderingList as the type. not great
-a1 = A()
-a1.bs.append(b1)
-
-# so we want to support being able to override it at least
-a2 = A(bs_w_list=[b1])
+++ /dev/null
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import column_property
-from sqlalchemy.orm import deferred
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import Session
-from sqlalchemy.orm import synonym
-from sqlalchemy.sql.functions import func
-from sqlalchemy.sql.sqltypes import Text
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
- name = Column(String)
-
- # this gets inferred
- big_col = deferred(Column(Text))
-
- # this gets inferred
- explicit_col = column_property(Column(Integer))
-
- # EXPECTED: Can't infer type from ORM mapped expression assigned to attribute 'lower_name'; # noqa
- lower_name = column_property(func.lower(name))
-
- # EXPECTED: Can't infer type from ORM mapped expression assigned to attribute 'syn_name'; # noqa
- syn_name = synonym("name")
-
- # this uses our type
- lower_name_exp: str = column_property(func.lower(name))
-
- # this uses our type
- syn_name_exp: Optional[str] = synonym("name")
-
-
-s = Session()
-
-u1: Optional[User] = s.get(User, 5)
-assert u1
-
-q1: Optional[str] = u1.big_col
-
-q2: Optional[int] = u1.explicit_col
-
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "str", variable has type "int") # noqa
-x: int = u1.lower_name_exp
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "Optional[str]", variable has type "int") # noqa
-y: int = u1.syn_name_exp
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class Foo:
- pass
- id: int = Column(Integer())
- name: str = Column(String)
-
-
-f1 = Foo()
-
-
-# EXPECTED_MYPY: Name 'u1' is not defined
-p: str = u1.name # noqa
+++ /dev/null
-from typing import List
-from typing import Optional
-
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import select
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import mapped_column
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class User(Base):
- __tablename__ = "user"
-
- id = mapped_column(Integer, primary_key=True)
- name: Mapped[Optional[str]] = mapped_column(String, nullable=True)
-
- addresses: Mapped[List["Address"]] = relationship(
- "Address", back_populates="user"
- )
-
- @property
- def some_property(self) -> List[Optional[int]]:
- return [i.id for i in self.addresses]
-
-
-class Address(Base):
- __tablename__ = "address"
-
- id = mapped_column(Integer, primary_key=True)
- user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
-
- user: Mapped["User"] = relationship("User", back_populates="addresses")
-
- @property
- def some_other_property(self) -> Optional[str]:
- return self.user.name
-
-
-# it's in the constructor, correct type
-u1 = User(addresses=[Address()])
-
-# knows it's an iterable
-[x for x in u1.addresses]
-
-# knows it's Mapped
-stmt = select(User).where(User.addresses.any(id=5))
+++ /dev/null
-from typing import List
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import select
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
- addresses: List["Address"] = relationship("Address", back_populates="user")
-
- @property
- def some_property(self) -> List[Optional[int]]:
- return [i.id for i in self.addresses]
-
-
-class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
-
- user: "User" = relationship("User", back_populates="addresses")
-
- @property
- def some_other_property(self) -> Optional[str]:
- return self.user.name
-
-
-# it's in the constructor, correct type
-u1 = User(addresses=[Address()])
-
-# knows it's an iterable
-[x for x in u1.addresses]
-
-# knows it's Mapped
-stmt = select(User).where(User.addresses.any(id=5))
+++ /dev/null
-from typing import List
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import select
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
- name = Column(String)
-
- addresses: List["Address"] = relationship("Address", back_populates="user")
-
- @property
- def some_property(self) -> List[Optional[int]]:
- return [i.id for i in self.addresses]
-
-
-class Address(Base):
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- user_id: int = Column(ForeignKey("user.id"))
-
- user: "User" = relationship("User", back_populates="addresses")
-
- @property
- def some_other_property(self) -> Optional[str]:
- return self.user.name
-
-
-# it's in the constructor, correct type
-u1 = User(addresses=[Address()])
-
-# knows it's an iterable
-[x for x in u1.addresses]
-
-# knows it's Mapped
-stmt = select(User).where(User.addresses.any(id=5))
+++ /dev/null
-from typing import List
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class B(Base):
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
- a_id: int = Column(ForeignKey("a.id"))
- data = Column(String)
-
- a: Optional["A"] = relationship("A", back_populates="bs")
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
- data = Column(String)
- bs = relationship(B, uselist=True, back_populates="a")
-
-
-a1 = A(bs=[B(data="b"), B(data="b")])
-
-x: List[B] = a1.bs
-
-
-b1 = B(a=A())
+++ /dev/null
-from typing import List
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class B(Base):
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
-
- # EXPECTED: Expected Python collection type for collection_class parameter # noqa
- as_: List["A"] = relationship("A", collection_class=None)
-
- # EXPECTED: Can't infer type from ORM mapped expression assigned to attribute 'another_as_'; # noqa
- another_as_ = relationship("A", uselist=True)
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
- b_id: int = Column(ForeignKey("b.id"))
-
- # EXPECTED: Sending uselist=False and collection_class at the same time does not make sense # noqa
- b: B = relationship(B, uselist=False, collection_class=set)
+++ /dev/null
-from typing import Set
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class B(Base):
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
- a_id: int = Column(ForeignKey("a.id"))
- data = Column(String)
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
- data = Column(String)
- bs = relationship(B, uselist=True)
-
-
-# EXPECTED_MYPY: List item 1 has incompatible type "A"; expected "B"
-a1 = A(bs=[B(data="b"), A()])
-
-# EXPECTED_MYPY: Incompatible types in assignment (expression has type "list[B]", variable has type "set[B]") # noqa
-x: Set[B] = a1.bs
+++ /dev/null
-from typing import Optional
-from typing import Set
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class B(Base):
- __tablename__ = "b"
- id = Column(Integer, primary_key=True)
- a_id: int = Column(ForeignKey("a.id"))
- data = Column(String)
- a: Optional["A"] = relationship("A", back_populates="bs")
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = Column(Integer, primary_key=True)
- data = Column(String)
-
- bs: Set[B] = relationship(B, uselist=True, back_populates="a")
-
- # EXPECTED: Left hand assignment 'another_bs: "set[B]"' not compatible with ORM mapped expression of type "Mapped[B]" # noqa
- another_bs: Set[B] = relationship(B, viewonly=True)
-
-
-# EXPECTED_MYPY: Argument "a" to "B" has incompatible type "str"; expected "Optional[A]" # noqa
-b1 = B(a="not an a")
+++ /dev/null
-from typing import List
-from typing import Optional
-
-import sqlalchemy as sa
-from sqlalchemy import orm as saorm
-
-
-Base = saorm.declarative_base()
-
-
-class B(Base):
- __tablename__ = "b"
- id = sa.Column(sa.Integer, primary_key=True)
- a_id: int = sa.Column(sa.ForeignKey("a.id"))
- data = sa.Column(sa.String)
-
- a: Optional["A"] = saorm.relationship("A", back_populates="bs")
-
-
-class A(Base):
- __tablename__ = "a"
-
- id = sa.Column(sa.Integer, primary_key=True)
- data = sa.Column(sa.String)
- bs = saorm.relationship(B, uselist=True, back_populates="a")
-
-
-a1 = A(bs=[B(data="b"), B(data="b")])
-
-x: List[B] = a1.bs
-
-
-b1 = B(a=A())
+++ /dev/null
-from typing import cast
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import query_expression
-from sqlalchemy.orm import Session
-from sqlalchemy.orm import with_expression
-
-Base = declarative_base()
-
-
-class User(Base):
- __tablename__ = "users"
-
- id = Column(Integer, primary_key=True)
-
- foo = Column(Integer)
-
- question_count: Mapped[int] = query_expression()
- answer_count: int = query_expression()
-
-
-s = Session()
-
-q = s.query(User).options(with_expression(User.question_count, User.foo + 5))
-
-u1: User = cast(User, q.first())
-
-qc: int = u1.question_count
-print(qc)
+++ /dev/null
-from typing import Any
-from typing import Optional
-
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy import TypeDecorator
-from sqlalchemy.ext.declarative import declarative_base
-
-Base = declarative_base()
-
-
-class IntToStr(TypeDecorator[int]):
- impl = String
- cache_ok = True
-
- def process_bind_param(
- self,
- value: Any,
- dialect: Any,
- ) -> Optional[str]:
- return str(value) if value is not None else value
-
- def process_result_value(
- self,
- value: Any,
- dialect: Any,
- ) -> Optional[int]:
- return int(value) if value is not None else value
-
- def copy(self, **kwargs: Any) -> "IntToStr":
- return IntToStr(self.impl.length)
-
-
-class Thing(Base):
- __tablename__ = "things"
-
- id: int = Column(Integer, primary_key=True)
- intToStr: int = Column(IntToStr)
-
-
-t1 = Thing(intToStr=5)
-
-i5: int = t1.intToStr
-
-t1.intToStr = 8
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import registry
-
-reg: registry = registry()
-
-
-@reg.mapped
-class User:
- __tablename__ = "user"
-
- id = Column(Integer(), primary_key=True)
- name = Column(String)
-
-
-@reg.mapped
-class Address:
- __tablename__ = "address"
-
- id = Column(Integer, primary_key=True)
- # EXPECTED: Can't infer type from ORM mapped expression assigned to attribute 'user_id'; # noqa: E501
- user_id = Column(ForeignKey("user.id"))
- email_address = Column(String)
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy.orm import registry
-from sqlalchemy.types import TypeEngine
-
-
-# EXPECTED_MYPY: Missing type parameters for generic type "TypeEngine"
-class MyCustomType(TypeEngine):
- pass
-
-
-# correct way
-class MyOtherCustomType(TypeEngine[str]):
- pass
-
-
-reg: registry = registry()
-
-
-@reg.mapped
-class Foo:
- id: int = Column(Integer())
-
- name = Column(MyCustomType())
- other_name: str = Column(MyCustomType())
-
- name2 = Column(MyOtherCustomType())
- other_name2: str = Column(MyOtherCustomType())
-
-
-Foo(name="x", other_name="x", name2="x", other_name2="x")
+++ /dev/null
-from sqlalchemy import Column
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declared_attr
-from sqlalchemy.orm import registry
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm import RelationshipProperty
-
-reg: registry = registry()
-
-
-@reg.mapped
-class Foo:
- id: int = Column(Integer())
-
- # EXPECTED: Can't infer type from @declared_attr on function 'name'; # noqa
- @declared_attr
- # EXPECTED: Column type should be a TypeEngine subclass not 'builtins.str'
- def name(cls) -> Column[str]:
- return Column(String)
-
- # EXPECTED: Left hand assignment 'other_name: "Column[String]"' not compatible with ORM mapped expression of type "Mapped[str]" # noqa
- other_name: Column[String] = Column(String)
-
- # EXPECTED: Can't infer type from @declared_attr on function 'third_name';
- @declared_attr
- # EXPECTED_MYPY: Missing type parameters for generic type "Column"
- def third_name(cls) -> Column:
- return Column(String)
-
- # EXPECTED: Can't infer type from @declared_attr on function 'some_relationship' # noqa
- @declared_attr
- # EXPECTED_MYPY: Missing type parameters for generic type "RelationshipProperty"
- def some_relationship(cls) -> RelationshipProperty:
- return relationship("Bar")
-
-
-Foo(name="x")
+++ /dev/null
-"""Test that the right-hand expressions we normally "replace" are actually
-type checked.
-
-"""
-
-from typing import List
-
-from sqlalchemy import Column
-from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.orm import declarative_base
-from sqlalchemy.orm import Mapped
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm.decl_api import declared_attr
-
-
-Base = declarative_base()
-
-
-class User(Base):
- __tablename__ = "user"
-
- id = Column(Integer, primary_key=True)
-
- addresses: Mapped[List["Address"]] = relationship(
- "Address", wrong_arg="imwrong"
- )
-
-
-class SubUser(User):
- __tablename__ = "subuser"
-
- id: int = Column(Integer, ForeignKey("user.id"), primary_key=True)
-
-
-class Address(Base):
- __tablename__ = "address"
-
- id: int = Column(Integer, primary_key=True)
-
- user_id: int = Column(ForeignKey("user.id"))
-
- @declared_attr
- def email_address(cls) -> Column[String]:
- # EXPECTED_MYPY: Argument 1 to "Column" has incompatible type "bool";
- return Column(True)
-
- @declared_attr
- # EXPECTED_MYPY: Invalid type comment or annotation
- def thisisweird(cls) -> Column(String):
- # EXPECTED_MYPY: Argument 1 to "Column" has incompatible type "bool";
- return Column(False)
+++ /dev/null
-import os
-import pathlib
-import shutil
-
-try:
- from mypy.version import __version__ as _mypy_version_str
-except ImportError:
- _mypy_version = None
-else:
- _mypy_version = tuple(int(x) for x in _mypy_version_str.split("."))
-
-from sqlalchemy import testing
-from sqlalchemy.testing import eq_
-from sqlalchemy.testing import fixtures
-
-
-def _incremental_dirs():
- path = os.path.join(os.path.dirname(__file__), "incremental")
- files = []
- for d in os.listdir(path):
- if os.path.isdir(os.path.join(path, d)):
- files.append(
- os.path.join(os.path.dirname(__file__), "incremental", d)
- )
-
- for extra_dir in testing.config.options.mypy_extra_test_paths:
- if extra_dir and os.path.isdir(extra_dir):
- for d in os.listdir(os.path.join(extra_dir, "incremental")):
- if os.path.isdir(os.path.join(path, d)):
- files.append(os.path.join(extra_dir, "incremental", d))
- return files
-
-
-def _mypy_missing_or_incompatible():
- return not _mypy_version or _mypy_version > (1, 10, 1)
-
-
-class MypyPluginTest(fixtures.MypyTest):
- @testing.skip_if(
- _mypy_missing_or_incompatible,
- "Mypy must be present and compatible (<= 1.10.1)",
- )
- @testing.combinations(
- *[
- (pathlib.Path(pathname).name, pathname)
- for pathname in _incremental_dirs()
- ],
- argnames="pathname",
- id_="ia",
- )
- @testing.requires.patch_library
- def test_incremental(self, mypy_runner, per_func_cachedir, pathname):
- import patch
-
- cachedir = per_func_cachedir
-
- dest = os.path.join(cachedir, "mymodel")
- os.mkdir(dest)
-
- patches = set()
-
- print("incremental test: %s" % pathname)
-
- for fname in os.listdir(pathname):
- if fname.endswith(".py"):
- shutil.copy(
- os.path.join(pathname, fname), os.path.join(dest, fname)
- )
- print("copying to: %s" % os.path.join(dest, fname))
- elif fname.endswith(".testpatch"):
- patches.add(fname)
-
- for patchfile in [None] + sorted(patches):
- if patchfile is not None:
- print("Applying patchfile %s" % patchfile)
- patch_obj = patch.fromfile(os.path.join(pathname, patchfile))
- assert patch_obj.apply(1, dest), (
- "pathfile %s failed" % patchfile
- )
- print("running mypy against %s" % dest)
- result = mypy_runner(
- dest,
- use_plugin=True,
- use_cachedir=cachedir,
- )
- eq_(
- result[2],
- 0,
- msg="Failure after applying patch %s: %s"
- % (patchfile, result[0]),
- )
-
- @testing.skip_if(
- _mypy_missing_or_incompatible,
- "Mypy must be present and compatible (<= 1.10.1)",
- )
- @testing.combinations(
- *(
- (os.path.basename(path), path, True)
- for path in fixtures.MypyTest.file_combinations("plugin_files")
- ),
- argnames="path",
- id_="ia",
- )
- def test_plugin_files(self, mypy_typecheck_file, path):
- mypy_typecheck_file(path, use_plugin=True)
pytest>=7.0.0rc1,<8
pytest-xdist
greenlet != 0.4.17
- mypy >= 1.7.0,<1.11.0
- patch==1.*
+ mypy >= 1.14
types-greenlet
extras=
{[greenletextras]extras}