From 2dd29c9c529c4cde1132762cd1fbe447c32ac3cb Mon Sep 17 00:00:00 2001 From: KotlinIsland Date: Sat, 7 Sep 2024 17:10:32 +1000 Subject: [PATCH] 3.8 fell off --- .github/workflows/check.yaml | 6 +- pyproject.toml | 4 +- src/basedtyping/__init__.py | 92 +++++++------------- src/basedtyping/transformer.py | 66 ++++++-------- tests/test_runtime_only/test_literal_type.py | 19 ++-- 5 files changed, 69 insertions(+), 118 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 1f0215b..f4785cc 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -8,8 +8,6 @@ jobs: strategy: matrix: include: - - python-version: "3.8" - usable-python-version: "3.8" - python-version: "3.9" usable-python-version: "3.9" - python-version: "3.10" @@ -18,6 +16,8 @@ jobs: usable-python-version: "3.11" - python-version: "3.12" usable-python-version: "3.12" + - python-version: "3.13-dev" + usable-python-version: "3.12" steps: - uses: actions/checkout@v4 @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: "3.9" - run: ./pw poetry config virtualenvs.in-project true - name: Set up cache uses: actions/cache@v3 diff --git a/pyproject.toml b/pyproject.toml index 3030223..03a0750 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ name = "basedtyping" version = "0.1.5" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.9" typing_extensions = "^4.12.2" [tool.poetry.group.dev.dependencies] @@ -24,7 +24,7 @@ requires = ["poetry-core>=1.0.8"] main = ["poetry==1.7.1"] [tool.mypy] -python_version = 3.8 +python_version = 3.9 packages = ["basedtyping", "tests"] # we can't use override until we bump the minimum typing_extensions or something disable_error_code = ["explicit-override"] diff --git a/src/basedtyping/__init__.py b/src/basedtyping/__init__.py index 81673c0..cf5407f 100644 --- a/src/basedtyping/__init__.py +++ b/src/basedtyping/__init__.py @@ -12,7 +12,6 @@ TYPE_CHECKING, Any, Callable, - Final, Generic, NoReturn, Sequence, @@ -85,12 +84,6 @@ def __and__(self, other: object) -> object: def __rand__(self, other: object) -> object: return Intersection[other, self] - if sys.version_info < (3, 9): - - @_tp_cache_typed - def __getitem__(self, item: object) -> object: - return self.alias(self, item) # type: ignore[operator] - class _BasedGenericAlias(_GenericAlias, _root=True): def __and__(self, other: object) -> object: @@ -464,7 +457,7 @@ def issubform(form: _Forms, forminfo: _Forms) -> bool: # We pretend that it's an alias to Any so that it's slightly more compatible with # other tools, basedmypy will still utilize the SpecialForm over the TypeAlias. Untyped: TypeAlias = Any # type: ignore[no-any-explicit] -elif sys.version_info >= (3, 9): +else: @_BasedSpecialForm def Untyped( # noqa: N802 @@ -477,15 +470,6 @@ def Untyped( # noqa: N802 """ raise TypeError(f"{self} is not subscriptable") -else: - Untyped: Final = _BasedSpecialForm( - "Untyped", - doc=( - "Special type indicating that something isn't typed.\nThis is more" - " specialized than ``Any`` and can help with gradually typing modules." - ), - ) - class _IntersectionGenericAlias(_BasedGenericAlias, _root=True): def copy_with(self, args: object) -> Self: # type: ignore[override] # TODO: put in the overloads # noqa: TD003 @@ -510,51 +494,46 @@ def __reduce__(self) -> (object, object): return func, (Intersection, args) -if sys.version_info > (3, 9): +@_BasedSpecialForm +def Intersection(self: _BasedSpecialForm, parameters: object) -> object: # noqa: N802 + """Intersection type; Intersection[X, Y] means both X and Y. - @_BasedSpecialForm - def Intersection(self: _BasedSpecialForm, parameters: object) -> object: # noqa: N802 - """Intersection type; Intersection[X, Y] means both X and Y. - - To define an intersection: - - If using __future__.annotations, shortform can be used e.g. A & B - - otherwise the fullform must be used e.g. Intersection[A, B]. - - Details: - - The arguments must be types and there must be at least one. - - None as an argument is a special case and is replaced by - type(None). - - Intersections of intersections are flattened, e.g.:: + To define an intersection: + - If using __future__.annotations, shortform can be used e.g. A & B + - otherwise the fullform must be used e.g. Intersection[A, B]. - Intersection[Intersection[int, str], float] == Intersection[int, str, float] + Details: + - The arguments must be types and there must be at least one. + - None as an argument is a special case and is replaced by + type(None). + - Intersections of intersections are flattened, e.g.:: - - Intersections of a single argument vanish, e.g.:: + Intersection[Intersection[int, str], float] == Intersection[int, str, float] - Intersection[int] == int # The constructor actually returns int + - Intersections of a single argument vanish, e.g.:: - - Redundant arguments are skipped, e.g.:: + Intersection[int] == int # The constructor actually returns int - Intersection[int, str, int] == Intersection[int, str] + - Redundant arguments are skipped, e.g.:: - - When comparing intersections, the argument order is ignored, e.g.:: + Intersection[int, str, int] == Intersection[int, str] - Intersection[int, str] == Intersection[str, int] + - When comparing intersections, the argument order is ignored, e.g.:: - - You cannot subclass or instantiate an intersection. - """ - if parameters == (): - raise TypeError("Cannot take an Intersection of no types.") - if not isinstance(parameters, tuple): - parameters = (parameters,) - msg = "Intersection[arg, ...]: each arg must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) # type: ignore[no-any-expr] - parameters = _remove_dups_flatten(parameters) # type: ignore[no-any-expr] - if len(parameters) == 1: # type: ignore[no-any-expr] - return parameters[0] # type: ignore[no-any-expr] - return _IntersectionGenericAlias(self, parameters) # type: ignore[arg-type, no-any-expr] + Intersection[int, str] == Intersection[str, int] -else: - Intersection = _BasedSpecialForm("Intersection", doc="", alias=_IntersectionGenericAlias) + - You cannot subclass or instantiate an intersection. + """ + if parameters == (): + raise TypeError("Cannot take an Intersection of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + msg = "Intersection[arg, ...]: each arg must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) # type: ignore[no-any-expr] + parameters = _remove_dups_flatten(parameters) # type: ignore[no-any-expr] + if len(parameters) == 1: # type: ignore[no-any-expr] + return parameters[0] # type: ignore[no-any-expr] + return _IntersectionGenericAlias(self, parameters) # type: ignore[arg-type, no-any-expr] class _TypeFormForm(_BasedSpecialForm, _root=True): # type: ignore[misc] @@ -672,7 +651,7 @@ def _evaluate( recursive_guard: frozenset[str], ) -> object | None: ... - elif sys.version_info >= (3, 9): + else: def _evaluate( self, @@ -681,10 +660,3 @@ def _evaluate( recursive_guard: frozenset[str], # noqa: ARG002 ) -> object | None: return transformer._eval_direct(self, globalns, localns) - - else: - - def _evaluate( - self, globalns: dict[str, object] | None, localns: dict[str, object] | None - ) -> object | None: - return transformer._eval_direct(self, globalns, localns) diff --git a/src/basedtyping/transformer.py b/src/basedtyping/transformer.py index 9766ac1..292eb84 100644 --- a/src/basedtyping/transformer.py +++ b/src/basedtyping/transformer.py @@ -1,7 +1,6 @@ from __future__ import annotations import ast -import sys import types import typing import uuid @@ -95,10 +94,7 @@ def _literal(self, value: ast.Constant | ast.Name | ast.Attribute) -> ast.Subscr return self.subscript(self._typing("Literal"), value) def subscript(self, value: ast.expr, slice_: ast.expr) -> ast.Subscript: - if sys.version_info < (3, 9): - result = ast.Subscript(value=value, slice=ast.Index(slice_), ctx=ast.Load()) - else: - result = ast.Subscript(value=value, slice=slice_, ctx=ast.Load()) + result = ast.Subscript(value=value, slice=slice_, ctx=ast.Load()) return ast.fix_missing_locations(result) _implicit_tuple = False @@ -115,22 +111,15 @@ def implicit_tuple(self) -> typing.Iterator[None]: def visit_Subscript(self, node: ast.Subscript) -> ast.AST: node_type = self.eval_type(node.value) if node_type is typing_extensions.Annotated: - if sys.version_info < (3, 9): - slice_ = cast(ast.Index, node.slice) - if isinstance(slice_.value, ast.Tuple): - slice_.value.elts[0] = cast(ast.expr, self.visit(slice_.value.elts[0])) - else: - slice_.value = cast(ast.expr, self.visit(slice_.value)) + slice_ = node.slice + if isinstance(slice_, ast.Tuple): + temp = self.visit(slice_.elts[0]) + assert isinstance(temp, ast.expr) + slice_.elts[0] = temp else: - slice_ = node.slice - if isinstance(slice_, ast.Tuple): - temp = self.visit(slice_.elts[0]) - assert isinstance(temp, ast.expr) - slice_.elts[0] = temp - else: - temp = self.visit(slice_) - assert isinstance(temp, ast.expr) - node.slice = temp + temp = self.visit(slice_) + assert isinstance(temp, ast.expr) + node.slice = temp return node with self.implicit_tuple(): result = self.generic_visit(node) @@ -139,10 +128,7 @@ def visit_Subscript(self, node: ast.Subscript) -> ast.AST: node_type = self.eval_type(node.value) if node_type is types.FunctionType: - if sys.version_info < (3, 9): - slice2_ = cast(ast.Index, node.slice).value - else: - slice2_ = node.slice + slice2_ = node.slice node = self.subscript(self._typing("Callable"), slice2_) return node @@ -219,24 +205,22 @@ def _eval_direct( return eval_type_based(value, globalns, localns, string_literals=False) -if sys.version_info >= (3, 9): - - def crifigy_type( - value: str, - globalns: dict[str, object] | None = None, - localns: dict[str, object] | None = None, - *, - string_literals: bool, - ) -> object: - tree: ast.AST - try: - tree = ast.parse(value, mode="eval") - except SyntaxError: - tree = ast.parse(value.removeprefix("def").lstrip(), mode="func_type") +def crifigy_type( + value: str, + globalns: dict[str, object] | None = None, + localns: dict[str, object] | None = None, + *, + string_literals: bool, +) -> object: + tree: ast.AST + try: + tree = ast.parse(value, mode="eval") + except SyntaxError: + tree = ast.parse(value.removeprefix("def").lstrip(), mode="func_type") - transformer = CringeTransformer(globalns, localns, string_literals=string_literals) - tree = transformer.visit(tree) - return ast.unparse(tree) + transformer = CringeTransformer(globalns, localns, string_literals=string_literals) + tree = transformer.visit(tree) + return ast.unparse(tree) def eval_type_based( diff --git a/tests/test_runtime_only/test_literal_type.py b/tests/test_runtime_only/test_literal_type.py index 7d8effd..eb9f6e9 100644 --- a/tests/test_runtime_only/test_literal_type.py +++ b/tests/test_runtime_only/test_literal_type.py @@ -1,22 +1,17 @@ from __future__ import annotations -import sys from typing import Union -import pytest -if sys.version_info >= (3, 9): # prevent mypy errors +def test_literal_type_positive(): + from typing import Literal - @pytest.mark.skipif(sys.version_info < (3, 9), reason="need 3.9 for LiteralType") - def test_literal_type_positive(): - from typing import Literal + from basedtyping.runtime_only import LiteralType - from basedtyping.runtime_only import LiteralType + assert isinstance(Literal[1, 2], LiteralType) - assert isinstance(Literal[1, 2], LiteralType) - @pytest.mark.skipif(sys.version_info < (3, 9), reason="need 3.9 for LiteralType") - def test_literal_type_negative(): - from basedtyping.runtime_only import LiteralType +def test_literal_type_negative(): + from basedtyping.runtime_only import LiteralType - assert not isinstance(Union[int, str], LiteralType) + assert not isinstance(Union[int, str], LiteralType)