Skip to content

Commit a5dd2c8

Browse files
committed
Add some TS style "utility types"
1 parent cfac3ae commit a5dd2c8

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

scripts/update-examples.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ scripts/py2rst.py tests/test_fastapilike_2.py --start "Begin PEP section: Automa
1414

1515
scripts/py2rst.py tests/test_nplike.py --start "Begin PEP section" --end "End PEP section" \
1616
| scripts/rst_replace_section.py "$PEP" pep827-numpy-impl -i
17+
18+
scripts/py2rst.py tests/test_ts_utility.py --start "Begin PEP section" --end "End PEP section" \
19+
| scripts/rst_replace_section.py "$PEP" pep827-ts-utils -i

tests/test_ts_utility.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# mypy: ignore-errors
2+
"""
3+
TypeScript-style utility type operators: Pick, Omit, Partial.
4+
5+
See https://www.typescriptlang.org/docs/handbook/utility-types.html
6+
"""
7+
8+
import textwrap
9+
from typing import Literal, NotRequired, TypedDict
10+
11+
import typemap_extensions as typing
12+
from typemap.type_eval import eval_typing
13+
14+
from . import format_helper
15+
16+
17+
# The "Todo" type from the TypeScript utility-types examples
18+
class Todo:
19+
title: str
20+
description: str
21+
completed: bool
22+
23+
24+
class TodoTD(TypedDict):
25+
title: str
26+
description: str
27+
completed: bool
28+
29+
30+
# Begin PEP section: Utility types
31+
"""
32+
TypeScript defines a number of `utility types
33+
<https://www.typescriptlang.org/docs/handbook/utility-types.html>`__
34+
for performing common type operations.
35+
36+
We present implementations of a selection of them::
37+
38+
"""
39+
40+
41+
# Pick<T, Keys>
42+
# Constructs a type by picking the set of properties Keys from T.
43+
type Pick[T, Keys] = typing.NewProtocol[
44+
*[
45+
p
46+
for p in typing.Iter[typing.Members[T]]
47+
if typing.IsAssignable[p.name, Keys]
48+
]
49+
]
50+
51+
# Omit<T, Keys>
52+
# Constructs a type by picking all properties from T and then removing Keys.
53+
type Omit[T, Keys] = typing.NewProtocol[
54+
*[
55+
p
56+
for p in typing.Iter[typing.Members[T]]
57+
if not typing.IsAssignable[p.name, Keys]
58+
]
59+
]
60+
61+
# Partial<T>
62+
# Constructs a type with all properties of T set to optional (T | None).
63+
type Partial[T] = typing.NewProtocol[
64+
*[
65+
typing.Member[p.name, p.type | None, p.quals]
66+
for p in typing.Iter[typing.Attrs[T]]
67+
]
68+
]
69+
70+
# PartialTD<T>
71+
# Like Partial, but for TypedDicts: wraps all fields in NotRequired
72+
# rather than making them T | None.
73+
type PartialTD[T] = typing.NewProtocol[
74+
*[
75+
typing.Member[p.name, NotRequired[p.type], p.quals]
76+
for p in typing.Iter[typing.Attrs[T]]
77+
]
78+
]
79+
# End PEP section: Utility types
80+
81+
82+
def test_pick():
83+
tgt = eval_typing(Pick[Todo, Literal["title"] | Literal["completed"]])
84+
fmt = format_helper.format_class(tgt)
85+
assert fmt == textwrap.dedent("""\
86+
class Pick[tests.test_ts_utility.Todo, typing.Literal['title'] | typing.Literal['completed']]:
87+
title: str
88+
completed: bool
89+
""")
90+
91+
92+
def test_omit():
93+
tgt = eval_typing(Omit[Todo, Literal["description"]])
94+
fmt = format_helper.format_class(tgt)
95+
assert fmt == textwrap.dedent("""\
96+
class Omit[tests.test_ts_utility.Todo, typing.Literal['description']]:
97+
title: str
98+
completed: bool
99+
""")
100+
101+
102+
def test_partial():
103+
tgt = eval_typing(Partial[Todo])
104+
fmt = format_helper.format_class(tgt)
105+
assert fmt == textwrap.dedent("""\
106+
class Partial[tests.test_ts_utility.Todo]:
107+
title: str | None
108+
description: str | None
109+
completed: bool | None
110+
""")
111+
112+
113+
def test_partial_td():
114+
tgt = eval_typing(PartialTD[TodoTD])
115+
fmt = format_helper.format_class(tgt)
116+
assert fmt == textwrap.dedent("""\
117+
class PartialTD[tests.test_ts_utility.TodoTD]:
118+
title: typing.NotRequired[str]
119+
description: typing.NotRequired[str]
120+
completed: typing.NotRequired[bool]
121+
""")

0 commit comments

Comments
 (0)