|
| 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