Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ include = ["typemap", "typemap.*", "typemap_extensions"]
test = [
"pytest>=7.0",
"ruff",
"mypy @ git+https://github.com/msullivan/mypy-typemap@fbc5d6c16834379307857318e6c32326b5d8a201",
"mypy @ git+https://github.com/msullivan/mypy-typemap@f127ae7a0b79b0d6b3fee9c82e75e62a12ac39e3",
]

[tool.uv]
Expand Down
14 changes: 5 additions & 9 deletions tests/test_dataclass_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Literal,
ReadOnly,
TypedDict,
Never,
)

import typemap_extensions as typing
Expand Down Expand Up @@ -70,13 +69,10 @@ def _check_hero_init() -> None:
p.name,
p.type,
# All arguments are keyword-only
# It takes a default if a default is specified in the class
Literal["keyword"]
if typing.IsAssignable[
GetDefault[p.init],
Never,
]
else Literal["keyword", "default"],
Literal["keyword"],
# GetDefault is Never when there's no default, so use it
# directly as D.
GetDefault[p.init],
]
for p in typing.Iter[typing.Attrs[T]]
],
Expand Down Expand Up @@ -156,5 +152,5 @@ class Hero:
secret_name: str
@classmethod
def __init_subclass__[T](cls: type[T]) -> typemap.typing.UpdateClass[InitFnType[T]]: ...
def __init__(self: Self, *, id: int | None = ..., name: str, age: int | None = ..., secret_name: str) -> None: ...
def __init__(self: Self, *, id: int | None = None, name: str, age: int | None = None, secret_name: str) -> None: ...
""")
7 changes: 4 additions & 3 deletions tests/test_fastapilike_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import enum
import textwrap

from typing import Annotated, Callable, Literal, Union, Self
from typing import Annotated, Callable, Literal, Never, Union, Self

from typemap.type_eval import eval_typing
from typemap_extensions import (
Expand Down Expand Up @@ -57,12 +57,13 @@ class _Default:
Param[
p.name,
DropAnnotations[p.type],
Literal["keyword", "default"]
Literal["keyword"],
DropAnnotations[p.type]
if IsAssignable[
Literal[PropQuals.HAS_DEFAULT],
GetAnnotations[p.type],
]
else Literal["keyword"],
else Never,
]
for p in Iter[Attrs[T]]
],
Expand Down
18 changes: 7 additions & 11 deletions tests/test_fastapilike_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
Union,
ReadOnly,
TypedDict,
Never,
Self,
)

Expand Down Expand Up @@ -150,13 +149,10 @@ class Field[T: FieldArgs](typing.InitField[T]):
p.name,
p.type,
# All arguments are keyword-only
# It takes a default if a default is specified in the class
Literal["keyword"]
if typing.IsAssignable[
GetDefault[p.init],
Never,
]
else Literal["keyword", "default"],
Literal["keyword"],
# GetDefault is Never when there's no default, so use it
# directly as D.
GetDefault[p.init],
]
for p in typing.Iter[typing.Attrs[T]]
],
Expand Down Expand Up @@ -267,7 +263,7 @@ class AddInit[tests.test_fastapilike_2.Hero]:
name: str = Field(index=True)
age: int | None = Field(default=None, index=True)
secret_name: str = Field(hidden=True)
def __init__(self: Self, *, id: int | None = ..., name: str, age: int | None = ..., secret_name: str) -> None: ...
def __init__(self: Self, *, id: int | None = None, name: str, age: int | None = None, secret_name: str) -> None: ...
""")


Expand All @@ -293,7 +289,7 @@ class AddInit[tests.test_fastapilike_2.Create[tests.test_fastapilike_2.Hero]]:
name: str
age: int | None = None
secret_name: str
def __init__(self: Self, *, name: str, age: int | None = ..., secret_name: str) -> None: ...
def __init__(self: Self, *, name: str, age: int | None = None, secret_name: str) -> None: ...
""")


Expand All @@ -306,5 +302,5 @@ class AddInit[tests.test_fastapilike_2.Update[tests.test_fastapilike_2.Hero]]:
name: str | None = None
age: int | None = None
secret_name: str | None = None
def __init__(self: Self, *, name: str | None = ..., age: int | None = ..., secret_name: str | None = ...) -> None: ...
def __init__(self: Self, *, name: str | None = None, age: int | None = None, secret_name: str | None = None) -> None: ...
""")
13 changes: 6 additions & 7 deletions tests/test_type_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class Final:
fin: typing.Final[int]
x: tests.test_type_dir.Wrapper[int | None]
ordinary: str
def foo(self: Self, a: int | None, *, b: int = ...) -> dict[str, int]: ...
def foo(self: Self, a: int | None, *, b: int = 0) -> dict[str, int]: ...
def base[Z](self: Self, a: int | Z | None, b: ~K) -> dict[str, int | Z]: ...
@classmethod
def cbase(cls: type[typing.Self], a: int | None, b: ~K) -> dict[str, int]: ...
Expand Down Expand Up @@ -385,10 +385,9 @@ def test_type_members_func_1():
str(typ)
== "\
typing.Callable[\
typemap.typing.Params[typemap.typing.Param[typing.Literal['self'], tests.test_type_dir.Base[int], typing.Never], \
typemap.typing.Param[typing.Literal['a'], int | None, typing.Never], \
typemap.typing.Param[typing.Literal['b'], int, typing.Literal['keyword', \
'default']]], \
typemap.typing.Params[typemap.typing.Param[typing.Literal['self'], tests.test_type_dir.Base[int], typing.Literal['positional_or_keyword'], typing.Never], \
typemap.typing.Param[typing.Literal['a'], int | None, typing.Literal['positional_or_keyword'], typing.Never], \
typemap.typing.Param[typing.Literal['b'], int, typing.Literal['keyword'], typing.Literal[0]]], \
dict[str, int]]"
)

Expand All @@ -406,7 +405,7 @@ def test_type_members_func_2():
assert (
str(typ)
== "\
classmethod[tests.test_type_dir.Base[int], typemap.typing.Params[typemap.typing.Param[typing.Literal['a'], int | None, typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Never]], dict[str, int]]"
classmethod[tests.test_type_dir.Base[int], typemap.typing.Params[typemap.typing.Param[typing.Literal['a'], int | None, typing.Literal['positional_or_keyword'], typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Literal['positional_or_keyword'], typing.Never]], dict[str, int]]"
)


Expand All @@ -425,7 +424,7 @@ def test_type_members_func_3():
)
assert (
str(evaled)
== "staticmethod[typemap.typing.Params[typemap.typing.Param[typing.Literal['a'], int | typing.Literal['gotcha!'] | Z | None, typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Never]], dict[str, int | Z]]"
== "staticmethod[typemap.typing.Params[typemap.typing.Param[typing.Literal['a'], int | typing.Literal['gotcha!'] | Z | None, typing.Literal['positional_or_keyword'], typing.Never], typemap.typing.Param[typing.Literal['b'], ~K, typing.Literal['positional_or_keyword'], typing.Never]], dict[str, int | Z]]"
)


Expand Down
38 changes: 32 additions & 6 deletions tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ def test_eval_types_4():
[
Param[Literal["a"], int, Literal["positional"]],
Param[Literal["b"], int],
Param[Literal["c"], int, Literal["default"]],
Param[Literal["c"], int, Literal["positional_or_keyword"], int],
Param[None, int, Literal["*"]],
Param[Literal["d"], int, Literal["keyword"]],
Param[Literal["e"], int, Literal["default", "keyword"]],
Param[Literal["e"], int, Literal["keyword"], int],
Param[None, int, Literal["**"]],
],
int,
Expand All @@ -172,10 +172,10 @@ def test_eval_types_4():
[
Param[Literal["a"], int, Literal["positional"]],
Param[Literal["b"], int],
Param[Literal["c"], int, Literal["default"]],
Param[Literal["c"], int, Literal["positional_or_keyword"], int],
Param[None, int, Literal["*"]],
Param[Literal["d"], int, Literal["keyword"]],
Param[Literal["e"], int, Literal["default", "keyword"]],
Param[Literal["e"], int, Literal["keyword"], int],
Param[None, int, Literal["**"]],
],
int,
Expand Down Expand Up @@ -1827,10 +1827,10 @@ def test_callable_to_signature_01():
Params[
Param[None, int],
Param[Literal["b"], int],
Param[Literal["c"], int, Literal["default"]],
Param[Literal["c"], int, Literal["positional_or_keyword"], int],
Param[None, int, Literal["*"]],
Param[Literal["d"], int, Literal["keyword"]],
Param[Literal["e"], int, Literal["default", "keyword"]],
Param[Literal["e"], int, Literal["keyword"], int],
Param[None, int, Literal["**"]],
],
int,
Expand All @@ -1847,6 +1847,32 @@ def test_callable_to_signature_01():
)


def test_callable_to_signature_multi_kind_error():
from typemap.type_eval._eval_operators import _callable_type_to_signature

# Param can carry at most one kind; combining "positional" with
# "keyword" is nonsense and should be rejected.
callable_type = Callable[
Params[Param[Literal["x"], int, Literal["positional", "keyword"]]],
int,
]
with pytest.raises(TypeError, match="at most one"):
_callable_type_to_signature(callable_type)


def test_callable_to_signature_never_kind_error():
from typemap.type_eval._eval_operators import _callable_type_to_signature

# Never is not a valid kind; the explicit "normal" kind is
# Literal["positional_or_keyword"].
callable_type = Callable[
Params[Param[Literal["x"], int, Never]],
int,
]
with pytest.raises(TypeError, match="cannot be Never"):
_callable_type_to_signature(callable_type)


def test_new_protocol_with_methods_01():
class C:
def member_method(self, x: int) -> int: ...
Expand Down
Loading
Loading