From fee0e7fc973ddca17642da43e36e410d8fea9483 Mon Sep 17 00:00:00 2001 From: KotlinIsland <65446343+kotlinisland@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:25:33 +1000 Subject: [PATCH] add `FunctionType` and rework `TypeVars` --- basedtyping/__init__.py | 63 +++++++++++++++++++++-------------- poetry.lock | 66 ++++++++++++++++++++----------------- pyproject.toml | 4 +-- tests/test_function_type.py | 4 +-- tests/test_intersection.py | 12 +++---- tests/test_is_subform.py | 1 - 6 files changed, 84 insertions(+), 66 deletions(-) diff --git a/basedtyping/__init__.py b/basedtyping/__init__.py index 6ef6adf..bfd6c24 100644 --- a/basedtyping/__init__.py +++ b/basedtyping/__init__.py @@ -6,15 +6,16 @@ import ast import sys +import types import typing import warnings -from types import FunctionType from typing import ( # type: ignore[attr-defined] TYPE_CHECKING, Any, Callable, Final, Generic, + Mapping, NoReturn, Sequence, Tuple, @@ -37,10 +38,11 @@ # TODO: `Final[Literal[False]]` but basedmypy will whinge on usages # https://github.com/KotlinIsland/basedmypy/issues/782 BASEDMYPY_TYPE_CHECKING: Final = False -"""a special constant, is always `False`, but basedmypy will always assume it to be true +BASEDPYRIGHT_TYPE_CHECKING: Final = False +"""special constants, atr always `False`, but basedmypy will always assume it to be true -if you aren't using basedmypy, you may have to configure your type checker to consider - this variable "always false" +you may have to configure your type checker to consider + these variables "always true" or "always false" """ if not TYPE_CHECKING: @@ -50,7 +52,10 @@ from typing import _collect_type_vars as _collect_parameters __all__ = ( - "Function", + "AnyCallable", + "FunctionType", + "TCallable", + "TFunction", "T", "in_T", "out_T", @@ -106,18 +111,6 @@ def __rand__(self, other: object) -> object: return Intersection[other, self] -if TYPE_CHECKING: - Function = Callable[..., object] # type: ignore[no-any-explicit] - """Any ``Callable``. useful when using mypy with ``disallow-any-explicit`` - due to https://github.com/python/mypy/issues/9496 - - Cannot actually be called unless it's narrowed, so it should only really be used as - a bound in a ``TypeVar``. - """ -else: - # for isinstance checks - Function = Callable - # Unlike the generics in other modules, these are meant to be imported to save you # from the boilerplate T = TypeVar("T") @@ -125,7 +118,28 @@ def __rand__(self, other: object) -> object: out_T = TypeVar("out_T", covariant=True) Ts = TypeVarTuple("Ts") P = ParamSpec("P") -Fn = TypeVar("Fn", bound=Function) + +AnyCallable = Callable[..., object] # type: ignore[no-any-explicit] +"""Any ``Callable``. useful when using mypy with ``disallow-any-explicit`` +due to https://github.com/python/mypy/issues/9496 + +Cannot actually be called unless it's narrowed, so it should only really be used as +a bound in a ``TypeVar``. +""" + + +if not BASEDMYPY_TYPE_CHECKING and TYPE_CHECKING: + FunctionType: TypeAlias = Callable[P, T] +else: + # TODO: BasedSpecialGenericAlias # noqa: TD003 + FunctionType: _SpecialForm = typing._CallableType(types.FunctionType, 2) # type: ignore[attr-defined] + +AnyFunction = FunctionType[..., object] # type: ignore[no-any-explicit] + +TCallable = TypeVar("TCallable", bound=AnyCallable) +TFunction = TypeVar("TFunction", bound=AnyFunction) +Fn = TypeVar("Fn", bound=AnyCallable) +"""this is deprecated, use `TCallable` or `TFunction` instead""" def _type_convert(arg: object) -> object: @@ -583,8 +597,6 @@ def f[T](t: TypeForm[T]) -> T: ... ) -# TODO: conditionally declare FunctionType with a BASEDMYPY so that this doesn't break everyone else -# https://github.com/KotlinIsland/basedmypy/issues/524 def as_functiontype(fn: Callable[P, T]) -> FunctionType[P, T]: """Asserts that a ``Callable`` is a ``FunctionType`` and returns it @@ -596,10 +608,9 @@ def deco(fn: Callable[[], None]) -> Callable[[], None]: ... @deco def foo(): ... """ - if not isinstance(fn, FunctionType): + if not isinstance(fn, types.FunctionType): raise TypeError(f"{fn} is not a FunctionType") - # https://github.com/KotlinIsland/basedmypy/issues/745 - return cast("FunctionType[P, T]", fn) + return cast(FunctionType[P, T], fn) class ForwardRef(typing.ForwardRef, _root=True): # type: ignore[call-arg,misc] @@ -681,10 +692,12 @@ def _evaluate( def _evaluate( self, globalns: dict[str, object] | None, - localns: dict[str, object] | None, + localns: Mapping[str, object] | None, recursive_guard: frozenset[str], ) -> object | None: - return transformer._eval_direct(self, globalns, localns) + return transformer._eval_direct( + self, globalns, localns if localns is None else dict(localns) + ) def _type_check(arg: object, msg: str) -> object: diff --git a/poetry.lock b/poetry.lock index 45261d5..2f59511 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,39 +1,44 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "basedmypy" -version = "2.6.0" +version = "2.7.0" description = "Based static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "basedmypy-2.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2dde708c6e130df4d0ae5931095844cc4157ac0e1a99c676a023b1c31dbdccd4"}, - {file = "basedmypy-2.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bfaef963805a02c24153c3d8af89b508248cdedec63c937af01e94237902dbdd"}, - {file = "basedmypy-2.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf928660d50ce1a2182b9162d15dca3b370a82a054a54c9cb7ec90f4b1f1cdc2"}, - {file = "basedmypy-2.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1cadba3f48ce22e82b7f31ab6dcad1d74c47f7aaf25a4aa094b1468923dd1292"}, - {file = "basedmypy-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:5ee0b49c577d93d401b029eeeb028f130340ba05d6048ce6e6adcbc609846d3b"}, - {file = "basedmypy-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5bfcd0cd73aa51cf263fa88d6225f4dce8ddf24789ac7007f3b4f7ab410edf3"}, - {file = "basedmypy-2.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e2dcade460f9837a84907c8dc2bb6186e3792779f482f09bd84ec160129b5ed"}, - {file = "basedmypy-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12829f2619b733013d7fc9dfca76cdeb8c33724eab9d54ac5db7e23b902a0e3"}, - {file = "basedmypy-2.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c16a1c36ba528d5922be8fbaa918616cfe82f041a6a8fb95ef0c580761818cc"}, - {file = "basedmypy-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb22d5814601883451539ef83d2e362f6cad02a109afcf2af2628320cfc5198f"}, - {file = "basedmypy-2.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:590688b27afec8303b8e42043cd7afaa6b60d0ac0bb894a49e16f541378adca8"}, - {file = "basedmypy-2.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0dbf81111b710ab8fc4e6ba014061d71bcf5951c405d73220c783bc283691bff"}, - {file = "basedmypy-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e36eeb0f63e02b5ff9828208803b711d755ba493aba5b237832e38a8f4837ad1"}, - {file = "basedmypy-2.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9819ea425a769b9bdfd8568d8ef6451e4f169f8df39fdb9d5014ee0a4ba15f4"}, - {file = "basedmypy-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:74cecf63520469d49f360d4226701b4e563bfc911dc84747a9abf67a00133f8b"}, - {file = "basedmypy-2.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5adf4a2b095002ce976d44cf4a7740bc6e500227f393b9ad4f93996cb175904e"}, - {file = "basedmypy-2.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:974dd7432e0d4e1571aae6575d46745f5f4d86e22bd8aa638ec8eb37b7cd084b"}, - {file = "basedmypy-2.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c5990f1ceaf7d76f25fb10c63f26148e25ca53400f52bc1a380d3a73318fea"}, - {file = "basedmypy-2.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e67e45ebd5c97754302be36bce4c5c2d45176407e39151e3984d952f88e5224"}, - {file = "basedmypy-2.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:143db931ddca9cac9aafb4fc055acd2bb900320380723e2bd448b2070a88adbc"}, - {file = "basedmypy-2.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce962c7f2d63bf9e370a04ebd8b269de8f1d029199dbf1111623aea7b1e5e825"}, - {file = "basedmypy-2.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f32b1281a2b52bb10a3db07c474e90d244cc423345b5586869f8b4baca3a91"}, - {file = "basedmypy-2.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03369993451fed0f26df2705b8aa2cfe9c8ef5e8750f217acf5d08c726dc4749"}, - {file = "basedmypy-2.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ac52f6b5f9ac9d9c2ec8873ba5f40140fd1dcbb47eb24977173fa4469752898"}, - {file = "basedmypy-2.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4ec34e2f6de049854399e7ca66bd0b4fe0f54ca76e96bc5573897d81ecb159ea"}, - {file = "basedmypy-2.6.0-py3-none-any.whl", hash = "sha256:329244dfbdd0507e83f6813e1509849e60b75321d193fa156f6151fb564faa17"}, - {file = "basedmypy-2.6.0.tar.gz", hash = "sha256:6ff3607d6e0ef776b9c0c9fdb24706f96783686ec7805c3bc28dd19eb07e5dbd"}, + {file = "basedmypy-2.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:283635cb57c917c7f88146e801005dc0034a74c25d48397bb65699e2359d1085"}, + {file = "basedmypy-2.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5158709f8c04fdb0f62d97551ad75366f3838e5dd3db0ffd83c32c3b64867e7d"}, + {file = "basedmypy-2.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d76a8ee5e755024dcfb4e1ec0309ae6f95a0330cd71151cbe956a6740da5bc8"}, + {file = "basedmypy-2.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0794aa3281bae1e88922223da6da5db9c6e495694bdc65cfc03acd97d1a94602"}, + {file = "basedmypy-2.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:58a75892e74a8679a54a18b8e5959ae8c1612e925ccb8ccc07a5e767c44eaf8d"}, + {file = "basedmypy-2.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f7a467856b12fc5b8085f065eaa36e1c08ea4e048a11777f47715dc4a9108e39"}, + {file = "basedmypy-2.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94988835eec3c886daeae717b5dc16befbd339b520bd3d1d005b562836530d21"}, + {file = "basedmypy-2.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cab30d2bfbba4e8bbd36b7bc1ea8811b40a062796edc653a8e4fd0881d4899e6"}, + {file = "basedmypy-2.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:40f2324c82e7befc94ad722fc3e0fe9362b7607b93d29ae7930831e30e22ff6a"}, + {file = "basedmypy-2.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:9470812c9a977d832bee52ebdd9b76bd30cea8f37f42ab1ce509e871b79996d3"}, + {file = "basedmypy-2.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:678fc29448b87c92df4c035fac2ae53a8e675a3b29d46d08d6328147f95b4832"}, + {file = "basedmypy-2.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5a770fb4e4714ef359b766dc29f44212f562ca387883c6ee5997cad2f4b6a838"}, + {file = "basedmypy-2.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91caccc79132091e62dce7d6927003dda5b6930deb50c18bdae2b19c22c1c924"}, + {file = "basedmypy-2.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1e3f32699843071df47445e71d53495e4e120b2df6469cc7efb3837080b770ab"}, + {file = "basedmypy-2.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:f7f0e7d2f5bd943a7f3dfa6b314e0684d38fd7665f560bcec0c4958d2dd710d1"}, + {file = "basedmypy-2.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4269027ee62f6ed4ded804e523a69a71eaec888ced8b6bc94c05b902b4dc970f"}, + {file = "basedmypy-2.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee773922a22a3aba0eb2ce8f9b8733e1b6086b7b75b885f887cff0f9746bf607"}, + {file = "basedmypy-2.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa155eb6506c2caf7296e97eb57e3870bc65e271af2582f2bb245b49acd283b0"}, + {file = "basedmypy-2.7.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f938bc49ebe8ae81c1091f1934ac014b03464ead3cf93893b69f5da98c3ffbde"}, + {file = "basedmypy-2.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:0758a45dc099f57110b21fd480dae82d289620501acbbb2854db5d22d6dd7b2a"}, + {file = "basedmypy-2.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:594205870794dc8f1f0b86654949d283edfa6bf1197bb9c6097bd6ea153e763a"}, + {file = "basedmypy-2.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c9141b0b7230032f930db1f2e1e60b3aa0e4df379f574ce38be680ac330cb7a"}, + {file = "basedmypy-2.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c777c29cdbbb8f522347a2505d76f3b71a7d209e4690d1593192235d2850d682"}, + {file = "basedmypy-2.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e61e51db7c7ab335d88b43d703bd85638edeb59f1ecfbb9292cab03f7ba0d19b"}, + {file = "basedmypy-2.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:34ddbd6cf0ec678a501d32423fbbfd0a9966cb2016184f3e4f9a66b05ccd0e94"}, + {file = "basedmypy-2.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e256804e471d7591109a87f7eadb632fece7184987ab7f15b8c75db60e20ab81"}, + {file = "basedmypy-2.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73c195a9bb7ddb94dc8763c90fb04bae3f8d84531fba54ec1d2ce7d0bfbe4caa"}, + {file = "basedmypy-2.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a9b1caecc78d5bb570a1bf17e5bdf7a48e4e374f9f5b272515e976077937162"}, + {file = "basedmypy-2.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:388b458f2b8d2720506fa5cdb925470f089ecf373fc7eb9d8e491dca202418de"}, + {file = "basedmypy-2.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:d42880ebb9b8da5b9aa526bfc74c2272c0014a9db5f71dee41e92c92f1863abf"}, + {file = "basedmypy-2.7.0-py3-none-any.whl", hash = "sha256:59661555a2637e0ece7ac65aca774451d60c3a9c1bd2613a96a067418fc381a7"}, + {file = "basedmypy-2.7.0.tar.gz", hash = "sha256:a8ff08c667d8ca06c6ab5acd574e683b557ce64671b2a0e0c2503979f1186411"}, ] [package.dependencies] @@ -44,6 +49,7 @@ typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -194,4 +200,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "373319d480ce46b0a60e4c71b84a00e007ec2b264ec7e3d8608ae2238dd2e4f2" +content-hash = "d4378014c1883091f0ae89ab8140b49d6d7aeb8afae5a06899c068747968ed78" diff --git a/pyproject.toml b/pyproject.toml index bdc315b..28616b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,14 @@ authors = [ ] description = "Utilities for basedmypy" name = "basedtyping" -version = "0.1.5" +version = "0.1.6" [tool.poetry.dependencies] python = "^3.9" typing_extensions = "^4.12.2" [tool.poetry.group.dev.dependencies] -basedmypy = "^2" +basedmypy = "^2.7" pytest = "^8" ruff = "~0.2.1" diff --git a/tests/test_function_type.py b/tests/test_function_type.py index 6b7cdc3..704ce08 100644 --- a/tests/test_function_type.py +++ b/tests/test_function_type.py @@ -5,10 +5,10 @@ if TYPE_CHECKING: # these are just type-time tests, not real life pytest tests. they are only run by mypy - from basedtyping import Function + from basedtyping import AnyCallable from basedtyping.typetime_only import assert_type - assert_function = assert_type[Function] + assert_function = assert_type[AnyCallable] def test_lambda_type(): assert_function(lambda: ...) diff --git a/tests/test_intersection.py b/tests/test_intersection.py index 3f8c8d4..fca29f7 100644 --- a/tests/test_intersection.py +++ b/tests/test_intersection.py @@ -39,15 +39,15 @@ def test_intersection_eq_hash(): def test_intersection_instancecheck(): - assert isinstance(C(), value) # type: ignore[arg-type, misc] - assert not isinstance(A(), value) # type: ignore[arg-type, misc] - assert not isinstance(B(), value) # type: ignore[arg-type, misc] + assert isinstance(C(), value) # type: ignore[misc, arg-type, misc] + assert not isinstance(A(), value) # type: ignore[misc, arg-type, misc] + assert not isinstance(B(), value) # type: ignore[misc, arg-type, misc] def test_intersection_subclasscheck(): - assert issubclass(C, value) # type: ignore[arg-type, misc] - assert not issubclass(A, value) # type: ignore[arg-type, misc] - assert not issubclass(B, value) # type: ignore[arg-type, misc] + assert issubclass(C, value) # type: ignore[misc, arg-type, misc] + assert not issubclass(A, value) # type: ignore[misc, arg-type, misc] + assert not issubclass(B, value) # type: ignore[misc, arg-type, misc] def test_intersection_reduce(): diff --git a/tests/test_is_subform.py b/tests/test_is_subform.py index a41c841..511fffe 100644 --- a/tests/test_is_subform.py +++ b/tests/test_is_subform.py @@ -20,7 +20,6 @@ def test_union_first_arg(): def test_old_union(): - # TODO: fix the mypy error # noqa: TD003 assert not issubform(Union[int, str], int) assert issubform(Union[int, str], object) assert issubform(Union[int, str], Union[str, int])