Skip to content

Commit

Permalink
3.8 fell off
Browse files Browse the repository at this point in the history
  • Loading branch information
KotlinIsland committed Sep 7, 2024
1 parent 64611f6 commit 2dd29c9
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 118 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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"]
Expand Down
92 changes: 32 additions & 60 deletions src/basedtyping/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
TYPE_CHECKING,
Any,
Callable,
Final,
Generic,
NoReturn,
Sequence,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -672,7 +651,7 @@ def _evaluate(
recursive_guard: frozenset[str],
) -> object | None:
...
elif sys.version_info >= (3, 9):
else:

def _evaluate(
self,
Expand All @@ -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)
66 changes: 25 additions & 41 deletions src/basedtyping/transformer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import ast
import sys
import types
import typing
import uuid
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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(
Expand Down
19 changes: 7 additions & 12 deletions tests/test_runtime_only/test_literal_type.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 2dd29c9

Please sign in to comment.