From 156fef61135a55c6ad17765b64155801f1dbea66 Mon Sep 17 00:00:00 2001 From: Federico Caselli Date: Mon, 22 Jul 2024 23:17:45 +0200 Subject: [PATCH] update typing for mypy 1.11; pin plugin to <1.11 Fixed internal typing issues to establish compatibility with mypy 1.11.0. Note that this does not include issues which have arisen with the deprecated mypy plugin used by SQLAlchemy 1.4-style code; see the addiional change note for this plugin indicating revised compatibility. The legacy mypy plugin is no longer fully functional with the latest series of mypy 1.11.0, as changes in the mypy interpreter are no longer compatible with the approach used by the plugin. If code is dependent on the legacy mypy plugin with sqlalchemy2-stubs, it's recommended to pin mypy to be below the 1.11.0 series. Seek upgrading to the 2.0 series of SQLAlchemy and migrating to the modern type annotations. Change-Id: Ib8fef93ede588430dc0f7ed44ef887649a415821 --- .../changelog/unreleased_14/mypy1110.rst | 14 ++++ .../changelog/unreleased_20/mypy1110.rst | 7 ++ doc/build/orm/extensions/mypy.rst | 9 ++- lib/sqlalchemy/engine/interfaces.py | 11 +++- lib/sqlalchemy/ext/mypy/util.py | 21 +++++- lib/sqlalchemy/orm/descriptor_props.py | 4 +- lib/sqlalchemy/orm/mapped_collection.py | 27 ++++---- lib/sqlalchemy/orm/query.py | 2 +- lib/sqlalchemy/orm/util.py | 2 +- lib/sqlalchemy/sql/base.py | 2 +- lib/sqlalchemy/sql/coercions.py | 64 ++++++++++++------- lib/sqlalchemy/sql/compiler.py | 8 ++- lib/sqlalchemy/sql/crud.py | 2 +- lib/sqlalchemy/sql/elements.py | 2 +- lib/sqlalchemy/sql/sqltypes.py | 14 ++-- lib/sqlalchemy/util/compat.py | 2 +- lib/sqlalchemy/util/langhelpers.py | 8 +++ test/ext/mypy/test_mypy_plugin_py3k.py | 7 +- tox.ini | 2 +- 19 files changed, 145 insertions(+), 63 deletions(-) create mode 100644 doc/build/changelog/unreleased_14/mypy1110.rst create mode 100644 doc/build/changelog/unreleased_20/mypy1110.rst diff --git a/doc/build/changelog/unreleased_14/mypy1110.rst b/doc/build/changelog/unreleased_14/mypy1110.rst new file mode 100644 index 0000000000..1dc5e0dc3e --- /dev/null +++ b/doc/build/changelog/unreleased_14/mypy1110.rst @@ -0,0 +1,14 @@ +.. change:: + :tags: bug, mypy + :versions: 2.0 + + The deprecated mypy plugin is no longer fully functional with the latest + series of mypy 1.11.0, as changes in the mypy interpreter are no longer + compatible with the approach used by the plugin. If code is dependent on + the mypy plugin with sqlalchemy2-stubs, it's recommended to pin mypy to be + below the 1.11.0 series. Seek upgrading to the 2.0 series of SQLAlchemy + and migrating to the modern type annotations. + + .. seealso:: + + :ref:`mypy_toplevel` diff --git a/doc/build/changelog/unreleased_20/mypy1110.rst b/doc/build/changelog/unreleased_20/mypy1110.rst new file mode 100644 index 0000000000..f722c407f2 --- /dev/null +++ b/doc/build/changelog/unreleased_20/mypy1110.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: bug, mypy + + Fixed internal typing issues to establish compatibility with mypy 1.11.0. + Note that this does not include issues which have arisen with the + deprecated mypy plugin used by SQLAlchemy 1.4-style code; see the addiional + change note for this plugin indicating revised compatibility. diff --git a/doc/build/orm/extensions/mypy.rst b/doc/build/orm/extensions/mypy.rst index afd34929af..dbca3f35f9 100644 --- a/doc/build/orm/extensions/mypy.rst +++ b/doc/build/orm/extensions/mypy.rst @@ -13,7 +13,8 @@ the :func:`_orm.mapped_column` construct introduced in SQLAlchemy 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.** + 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. @@ -24,7 +25,11 @@ the :func:`_orm.mapped_column` construct introduced in SQLAlchemy 2.0. .. topic:: SQLAlchemy Mypy Plugin Status Update - **Updated July 2023** + **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 diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py index dc793d0ec8..58d79cdd94 100644 --- a/lib/sqlalchemy/engine/interfaces.py +++ b/lib/sqlalchemy/engine/interfaces.py @@ -1254,8 +1254,7 @@ class Dialect(EventTarget): """ raise NotImplementedError() - @classmethod - def type_descriptor(cls, typeobj: TypeEngine[_T]) -> TypeEngine[_T]: + def type_descriptor(self, typeobj: TypeEngine[_T]) -> TypeEngine[_T]: """Transform a generic type to a dialect-specific type. Dialect classes will usually use the @@ -1317,6 +1316,7 @@ class Dialect(EventTarget): def get_multi_columns( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1365,6 +1365,7 @@ class Dialect(EventTarget): def get_multi_pk_constraint( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1411,6 +1412,7 @@ class Dialect(EventTarget): def get_multi_foreign_keys( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1570,6 +1572,7 @@ class Dialect(EventTarget): def get_multi_indexes( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1616,6 +1619,7 @@ class Dialect(EventTarget): def get_multi_unique_constraints( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1663,6 +1667,7 @@ class Dialect(EventTarget): def get_multi_check_constraints( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1705,6 +1710,7 @@ class Dialect(EventTarget): def get_multi_table_options( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, @@ -1756,6 +1762,7 @@ class Dialect(EventTarget): def get_multi_table_comment( self, connection: Connection, + *, schema: Optional[str] = None, filter_names: Optional[Collection[str]] = None, **kw: Any, diff --git a/lib/sqlalchemy/ext/mypy/util.py b/lib/sqlalchemy/ext/mypy/util.py index 7f04c481d3..af0882bc30 100644 --- a/lib/sqlalchemy/ext/mypy/util.py +++ b/lib/sqlalchemy/ext/mypy/util.py @@ -80,7 +80,7 @@ class SQLAlchemyAttribute: "name": self.name, "line": self.line, "column": self.column, - "type": self.type.serialize(), + "type": serialize_type(self.type), } def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None: @@ -336,3 +336,22 @@ def info_for_cls( 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() diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index d82a33d0a3..b43824e2ef 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -784,7 +784,9 @@ class CompositeProperty( elif isinstance(self.prop.composite_class, type) and isinstance( value, self.prop.composite_class ): - values = self.prop._composite_values_from_instance(value) + values = self.prop._composite_values_from_instance( + value # type: ignore[arg-type] + ) else: raise sa_exc.ArgumentError( "Can't UPDATE composite attribute %s to %r" diff --git a/lib/sqlalchemy/orm/mapped_collection.py b/lib/sqlalchemy/orm/mapped_collection.py index 13c6b689e1..0d3079fb5a 100644 --- a/lib/sqlalchemy/orm/mapped_collection.py +++ b/lib/sqlalchemy/orm/mapped_collection.py @@ -29,6 +29,8 @@ from .. import util from ..sql import coercions from ..sql import expression from ..sql import roles +from ..util.langhelpers import Missing +from ..util.langhelpers import MissingOr from ..util.typing import Literal if TYPE_CHECKING: @@ -40,8 +42,6 @@ if TYPE_CHECKING: _KT = TypeVar("_KT", bound=Any) _VT = TypeVar("_VT", bound=Any) -_F = TypeVar("_F", bound=Callable[[Any], Any]) - class _PlainColumnGetter(Generic[_KT]): """Plain column getter, stores collection of Column objects @@ -70,7 +70,7 @@ class _PlainColumnGetter(Generic[_KT]): def _cols(self, mapper: Mapper[_KT]) -> Sequence[ColumnElement[_KT]]: return self.cols - def __call__(self, value: _KT) -> Union[_KT, Tuple[_KT, ...]]: + def __call__(self, value: _KT) -> MissingOr[Union[_KT, Tuple[_KT, ...]]]: state = base.instance_state(value) m = base._state_mapper(state) @@ -83,7 +83,7 @@ class _PlainColumnGetter(Generic[_KT]): else: obj = key[0] if obj is None: - return _UNMAPPED_AMBIGUOUS_NONE + return Missing else: return obj @@ -198,9 +198,6 @@ def column_keyed_dict( ) -_UNMAPPED_AMBIGUOUS_NONE = object() - - class _AttrGetter: __slots__ = ("attr_name", "getter") @@ -217,9 +214,9 @@ class _AttrGetter: dict_ = state.dict obj = dict_.get(self.attr_name, base.NO_VALUE) if obj is None: - return _UNMAPPED_AMBIGUOUS_NONE + return Missing else: - return _UNMAPPED_AMBIGUOUS_NONE + return Missing return obj @@ -277,7 +274,7 @@ def attribute_keyed_dict( def keyfunc_mapping( - keyfunc: _F, + keyfunc: Callable[[Any], Any], *, ignore_unpopulated_attribute: bool = False, ) -> Type[KeyFuncDict[_KT, Any]]: @@ -353,7 +350,7 @@ class KeyFuncDict(Dict[_KT, _VT]): def __init__( self, - keyfunc: _F, + keyfunc: Callable[[Any], Any], *dict_args: Any, ignore_unpopulated_attribute: bool = False, ) -> None: @@ -377,7 +374,7 @@ class KeyFuncDict(Dict[_KT, _VT]): @classmethod def _unreduce( cls, - keyfunc: _F, + keyfunc: Callable[[Any], Any], values: Dict[_KT, _KT], adapter: Optional[CollectionAdapter] = None, ) -> "KeyFuncDict[_KT, _KT]": @@ -464,7 +461,7 @@ class KeyFuncDict(Dict[_KT, _VT]): ) else: return - elif key is _UNMAPPED_AMBIGUOUS_NONE: + elif key is Missing: if not self.ignore_unpopulated_attribute: self._raise_for_unpopulated( value, _sa_initiator, warn_only=True @@ -492,7 +489,7 @@ class KeyFuncDict(Dict[_KT, _VT]): value, _sa_initiator, warn_only=False ) return - elif key is _UNMAPPED_AMBIGUOUS_NONE: + elif key is Missing: if not self.ignore_unpopulated_attribute: self._raise_for_unpopulated( value, _sa_initiator, warn_only=True @@ -514,7 +511,7 @@ class KeyFuncDict(Dict[_KT, _VT]): def _mapped_collection_cls( - keyfunc: _F, ignore_unpopulated_attribute: bool + keyfunc: Callable[[Any], Any], ignore_unpopulated_attribute: bool ) -> Type[KeyFuncDict[_KT, _KT]]: class _MKeyfuncMapped(KeyFuncDict[_KT, _KT]): def __init__(self, *dict_args: Any) -> None: diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index b535b9db2d..88b4862e47 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -745,7 +745,7 @@ class Query( ) @overload - def as_scalar( + def as_scalar( # type: ignore[overload-overlap] self: Query[Tuple[_MAYBE_ENTITY]], ) -> ScalarSelect[_MAYBE_ENTITY]: ... diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 957b934dda..d4dff11e45 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1683,7 +1683,7 @@ class Bundle( c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]] """An alias for :attr:`.Bundle.columns`.""" - def _clone(self): + def _clone(self, **kw): cloned = self.__class__.__new__(self.__class__) cloned.__dict__.update(self.__dict__) return cloned diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index dcb00e16a5..970d0dd754 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -2137,7 +2137,7 @@ class ColumnSet(util.OrderedSet["ColumnClause[Any]"]): l.append(c == local) return elements.and_(*l) - def __hash__(self): + def __hash__(self): # type: ignore[override] return hash(tuple(x for x in self)) diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 22d6091552..0c998c667f 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -493,6 +493,7 @@ class RoleImpl: element: Any, argname: Optional[str] = None, resolved: Optional[Any] = None, + *, advice: Optional[str] = None, code: Optional[str] = None, err: Optional[Exception] = None, @@ -595,7 +596,7 @@ def _no_text_coercion( class _NoTextCoercion(RoleImpl): __slots__ = () - def _literal_coercion(self, element, argname=None, **kw): + def _literal_coercion(self, element, *, argname=None, **kw): if isinstance(element, str) and issubclass( elements.TextClause, self._role_class ): @@ -613,7 +614,7 @@ class _CoerceLiterals(RoleImpl): def _text_coercion(self, element, argname=None): return _no_text_coercion(element, argname) - def _literal_coercion(self, element, argname=None, **kw): + def _literal_coercion(self, element, *, argname=None, **kw): if isinstance(element, str): if self._coerce_star and element == "*": return elements.ColumnClause("*", is_literal=True) @@ -641,7 +642,8 @@ class LiteralValueImpl(RoleImpl): self, element, resolved, - argname, + argname=None, + *, type_=None, literal_execute=False, **kw, @@ -659,7 +661,7 @@ class LiteralValueImpl(RoleImpl): literal_execute=literal_execute, ) - def _literal_coercion(self, element, argname=None, type_=None, **kw): + def _literal_coercion(self, element, **kw): return element @@ -671,6 +673,7 @@ class _SelectIsNotFrom(RoleImpl): element: Any, argname: Optional[str] = None, resolved: Optional[Any] = None, + *, advice: Optional[str] = None, code: Optional[str] = None, err: Optional[Exception] = None, @@ -745,7 +748,7 @@ class ExpressionElementImpl(_ColumnCoercions, RoleImpl): __slots__ = () def _literal_coercion( - self, element, name=None, type_=None, argname=None, is_crud=False, **kw + self, element, *, name=None, type_=None, is_crud=False, **kw ): if ( element is None @@ -787,15 +790,22 @@ class ExpressionElementImpl(_ColumnCoercions, RoleImpl): class BinaryElementImpl(ExpressionElementImpl, RoleImpl): __slots__ = () - def _literal_coercion( - self, element, expr, operator, bindparam_type=None, argname=None, **kw + def _literal_coercion( # type: ignore[override] + self, + element, + *, + expr, + operator, + bindparam_type=None, + argname=None, + **kw, ): try: return expr._bind_param(operator, element, type_=bindparam_type) except exc.ArgumentError as err: self._raise_for_expected(element, err=err) - def _post_coercion(self, resolved, expr, bindparam_type=None, **kw): + def _post_coercion(self, resolved, *, expr, bindparam_type=None, **kw): if resolved.type._isnull and not expr.type._isnull: resolved = resolved._with_binary_element_type( bindparam_type if bindparam_type is not None else expr.type @@ -833,7 +843,9 @@ class InElementImpl(RoleImpl): % (elem.__class__.__name__) ) - def _literal_coercion(self, element, expr, operator, **kw): + def _literal_coercion( # type: ignore[override] + self, element, *, expr, operator, **kw + ): if util.is_non_string_iterable(element): non_literal_expressions: Dict[ Optional[operators.ColumnOperators], @@ -867,7 +879,7 @@ class InElementImpl(RoleImpl): else: self._raise_for_expected(element, **kw) - def _post_coercion(self, element, expr, operator, **kw): + def _post_coercion(self, element, *, expr, operator, **kw): if element._is_select_base: # for IN, we are doing scalar_subquery() coercion without # a warning @@ -893,12 +905,10 @@ class OnClauseImpl(_ColumnCoercions, RoleImpl): _coerce_consts = True - def _literal_coercion( - self, element, name=None, type_=None, argname=None, is_crud=False, **kw - ): + def _literal_coercion(self, element, **kw): self._raise_for_expected(element) - def _post_coercion(self, resolved, original_element=None, **kw): + def _post_coercion(self, resolved, *, original_element=None, **kw): # this is a hack right now as we want to use coercion on an # ORM InstrumentedAttribute, but we want to return the object # itself if it is one, not its clause element. @@ -983,7 +993,7 @@ class GroupByImpl(ByOfImpl, RoleImpl): class DMLColumnImpl(_ReturnsStringKey, RoleImpl): __slots__ = () - def _post_coercion(self, element, as_key=False, **kw): + def _post_coercion(self, element, *, as_key=False, **kw): if as_key: return element.key else: @@ -993,7 +1003,7 @@ class DMLColumnImpl(_ReturnsStringKey, RoleImpl): class ConstExprImpl(RoleImpl): __slots__ = () - def _literal_coercion(self, element, argname=None, **kw): + def _literal_coercion(self, element, *, argname=None, **kw): if element is None: return elements.Null() elif element is False: @@ -1019,7 +1029,7 @@ class TruncatedLabelImpl(_StringOnly, RoleImpl): else: self._raise_for_expected(element, argname, resolved) - def _literal_coercion(self, element, argname=None, **kw): + def _literal_coercion(self, element, **kw): """coerce the given value to :class:`._truncated_label`. Existing :class:`._truncated_label` and @@ -1069,7 +1079,9 @@ class LimitOffsetImpl(RoleImpl): else: self._raise_for_expected(element, argname, resolved) - def _literal_coercion(self, element, name, type_, **kw): + def _literal_coercion( # type: ignore[override] + self, element, *, name, type_, **kw + ): if element is None: return None else: @@ -1111,7 +1123,7 @@ class ColumnsClauseImpl(_SelectIsNotFrom, _CoerceLiterals, RoleImpl): _guess_straight_column = re.compile(r"^\w\S*$", re.I) def _raise_for_expected( - self, element, argname=None, resolved=None, advice=None, **kw + self, element, argname=None, resolved=None, *, advice=None, **kw ): if not advice and isinstance(element, list): advice = ( @@ -1149,7 +1161,9 @@ class ReturnsRowsImpl(RoleImpl): class StatementImpl(_CoerceLiterals, RoleImpl): __slots__ = () - def _post_coercion(self, resolved, original_element, argname=None, **kw): + def _post_coercion( + self, resolved, *, original_element, argname=None, **kw + ): if resolved is not original_element and not isinstance( original_element, str ): @@ -1215,7 +1229,7 @@ class JoinTargetImpl(RoleImpl): _skip_clauseelement_for_target_match = True - def _literal_coercion(self, element, argname=None, **kw): + def _literal_coercion(self, element, *, argname=None, **kw): self._raise_for_expected(element, argname) def _implicit_coercions( @@ -1223,6 +1237,7 @@ class JoinTargetImpl(RoleImpl): element: Any, resolved: Any, argname: Optional[str] = None, + *, legacy: bool = False, **kw: Any, ) -> Any: @@ -1256,6 +1271,7 @@ class FromClauseImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl): element: Any, resolved: Any, argname: Optional[str] = None, + *, explicit_subquery: bool = False, allow_select: bool = True, **kw: Any, @@ -1277,7 +1293,7 @@ class FromClauseImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl): else: self._raise_for_expected(element, argname, resolved) - def _post_coercion(self, element, deannotate=False, **kw): + def _post_coercion(self, element, *, deannotate=False, **kw): if deannotate: return element._deannotate() else: @@ -1292,7 +1308,7 @@ class StrictFromClauseImpl(FromClauseImpl): element: Any, resolved: Any, argname: Optional[str] = None, - explicit_subquery: bool = False, + *, allow_select: bool = False, **kw: Any, ) -> Any: @@ -1312,7 +1328,7 @@ class StrictFromClauseImpl(FromClauseImpl): class AnonymizedFromClauseImpl(StrictFromClauseImpl): __slots__ = () - def _post_coercion(self, element, flat=False, name=None, **kw): + def _post_coercion(self, element, *, flat=False, name=None, **kw): assert name is None return element._anonymous_fromclause(flat=flat) diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 18baf0f8e7..3eb412e6d7 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -6477,8 +6477,10 @@ class StrSQLCompiler(SQLCompiler): def visit_json_path_getitem_op_binary(self, binary, operator, **kw): return self.visit_getitem_binary(binary, operator, **kw) - def visit_sequence(self, seq, **kw): - return "" % self.preparer.format_sequence(seq) + def visit_sequence(self, sequence, **kw): + return ( + f"" + ) def returning_clause( self, @@ -6512,7 +6514,7 @@ class StrSQLCompiler(SQLCompiler): for t in extra_froms ) - def visit_empty_set_expr(self, type_, **kw): + def visit_empty_set_expr(self, element_types, **kw): return "SELECT 1 WHERE 1!=1" def get_from_hint_text(self, table, text): diff --git a/lib/sqlalchemy/sql/crud.py b/lib/sqlalchemy/sql/crud.py index 499a19d97c..d142665823 100644 --- a/lib/sqlalchemy/sql/crud.py +++ b/lib/sqlalchemy/sql/crud.py @@ -1286,7 +1286,7 @@ class _multiparam_column(elements.ColumnElement[Any]): def compare(self, other, **kw): raise NotImplementedError() - def _copy_internals(self, other, **kw): + def _copy_internals(self, **kw): raise NotImplementedError() def __eq__(self, other): diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 3271acd60d..64b686fc03 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -4009,7 +4009,7 @@ class Slice(ColumnElement[Any]): self.type = type_api.NULLTYPE def self_group(self, against: Optional[OperatorType] = None) -> Self: - assert against is operator.getitem # type: ignore[comparison-overlap] + assert against is operator.getitem return self diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 8bd036551c..0a411ce349 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -1012,7 +1012,7 @@ class SchemaType(SchemaEventTarget, TypeEngineMixin): if _adapted_from: self.dispatch = self.dispatch._join(_adapted_from.dispatch) - def _set_parent(self, column, **kw): + def _set_parent(self, parent, **kw): # set parent hook is when this type is associated with a column. # Column calls it for all SchemaEventTarget instances, either the # base type and/or variants in _variant_mapping. @@ -1026,7 +1026,7 @@ class SchemaType(SchemaEventTarget, TypeEngineMixin): # on_table/metadata_create/drop in this method, which is used by # "native" types with a separate CREATE/DROP e.g. Postgresql.ENUM - column._on_table_attach(util.portable_instancemethod(self._set_table)) + parent._on_table_attach(util.portable_instancemethod(self._set_table)) def _variant_mapping_for_set_table(self, column): if column.type._variant_mapping: @@ -1670,10 +1670,10 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]): assert "_enums" in kw return impltype(**kw) - def adapt(self, impltype, **kw): + def adapt(self, cls, **kw): kw["_enums"] = self._enums_argument kw["_disable_warnings"] = True - return super().adapt(impltype, **kw) + return super().adapt(cls, **kw) def _should_create_constraint(self, compiler, **kw): if not self._is_impl_for_variant(compiler.dialect, kw): @@ -3066,13 +3066,13 @@ class ARRAY( def compare_values(self, x, y): return x == y - def _set_parent(self, column, outer=False, **kw): + def _set_parent(self, parent, outer=False, **kw): """Support SchemaEventTarget""" if not outer and isinstance(self.item_type, SchemaEventTarget): - self.item_type._set_parent(column, **kw) + self.item_type._set_parent(parent, **kw) - def _set_parent_with_dispatch(self, parent): + def _set_parent_with_dispatch(self, parent, **kw): """Support SchemaEventTarget""" super()._set_parent_with_dispatch(parent, outer=True) diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index c843024579..c637e19cd1 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -54,7 +54,7 @@ class FullArgSpec(typing.NamedTuple): varkw: Optional[str] defaults: Optional[Tuple[Any, ...]] kwonlyargs: List[str] - kwonlydefaults: Dict[str, Any] + kwonlydefaults: Optional[Dict[str, Any]] annotations: Dict[str, Any] diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 9a02e7d71a..632e6a0a56 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -2232,3 +2232,11 @@ def load_uncompiled_module(module: _M) -> _M: assert py_spec.loader py_spec.loader.exec_module(py_module) return cast(_M, py_module) + + +class _Missing(enum.Enum): + Missing = enum.auto() + + +Missing = _Missing.Missing +MissingOr = Union[_T, Literal[_Missing.Missing]] diff --git a/test/ext/mypy/test_mypy_plugin_py3k.py b/test/ext/mypy/test_mypy_plugin_py3k.py index f1b36ac52b..e1aa1f9655 100644 --- a/test/ext/mypy/test_mypy_plugin_py3k.py +++ b/test/ext/mypy/test_mypy_plugin_py3k.py @@ -1,4 +1,5 @@ import os +import pathlib import shutil from sqlalchemy import testing @@ -25,8 +26,12 @@ def _incremental_dirs(): class MypyPluginTest(fixtures.MypyTest): @testing.combinations( - *[(pathname) for pathname in _incremental_dirs()], + *[ + (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): diff --git a/tox.ini b/tox.ini index c13ee761dc..f1146007dd 100644 --- a/tox.ini +++ b/tox.ini @@ -192,7 +192,7 @@ commands= [testenv:pep484] deps= greenlet != 0.4.17 - mypy >= 1.7.0,<1.11.0 # temporary, REMOVE upper bound + mypy >= 1.7.0 types-greenlet commands = mypy {env:MYPY_COLOR} ./lib/sqlalchemy -- 2.47.2