|
18 | 18 | from typing import no_type_check, no_type_check_decorator |
19 | 19 | from typing import Type |
20 | 20 | from typing import NewType |
21 | | -from typing import NamedTuple |
| 21 | +from typing import NamedTuple, TypedDict |
22 | 22 | from typing import IO, TextIO, BinaryIO |
23 | 23 | from typing import Pattern, Match |
24 | 24 | import abc |
@@ -1883,6 +1883,18 @@ def __str__(self): |
1883 | 1883 | def __add__(self, other): |
1884 | 1884 | return 0 |
1885 | 1885 |
|
| 1886 | +Label = TypedDict('Label', [('label', str)]) |
| 1887 | + |
| 1888 | +class Point2D(TypedDict): |
| 1889 | + x: int |
| 1890 | + y: int |
| 1891 | + |
| 1892 | +class LabelPoint2D(Point2D, Label): ... |
| 1893 | + |
| 1894 | +class Options(TypedDict, total=False): |
| 1895 | + log_level: int |
| 1896 | + log_path: str |
| 1897 | + |
1886 | 1898 | class HasForeignBaseClass(mod_generics_cache.A): |
1887 | 1899 | some_xrepr: 'XRepr' |
1888 | 1900 | other_a: 'mod_generics_cache.A' |
@@ -2658,6 +2670,97 @@ def test_pickle(self): |
2658 | 2670 | self.assertEqual(jane2, jane) |
2659 | 2671 |
|
2660 | 2672 |
|
| 2673 | +class TypedDictTests(BaseTestCase): |
| 2674 | + def test_basics_functional_syntax(self): |
| 2675 | + Emp = TypedDict('Emp', {'name': str, 'id': int}) |
| 2676 | + self.assertIsSubclass(Emp, dict) |
| 2677 | + self.assertIsSubclass(Emp, typing.MutableMapping) |
| 2678 | + self.assertNotIsSubclass(Emp, collections.abc.Sequence) |
| 2679 | + jim = Emp(name='Jim', id=1) |
| 2680 | + self.assertIs(type(jim), dict) |
| 2681 | + self.assertEqual(jim['name'], 'Jim') |
| 2682 | + self.assertEqual(jim['id'], 1) |
| 2683 | + self.assertEqual(Emp.__name__, 'Emp') |
| 2684 | + self.assertEqual(Emp.__module__, __name__) |
| 2685 | + self.assertEqual(Emp.__bases__, (dict,)) |
| 2686 | + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) |
| 2687 | + self.assertEqual(Emp.__total__, True) |
| 2688 | + |
| 2689 | + def test_basics_keywords_syntax(self): |
| 2690 | + Emp = TypedDict('Emp', name=str, id=int) |
| 2691 | + self.assertIsSubclass(Emp, dict) |
| 2692 | + self.assertIsSubclass(Emp, typing.MutableMapping) |
| 2693 | + self.assertNotIsSubclass(Emp, collections.abc.Sequence) |
| 2694 | + jim = Emp(name='Jim', id=1) |
| 2695 | + self.assertIs(type(jim), dict) |
| 2696 | + self.assertEqual(jim['name'], 'Jim') |
| 2697 | + self.assertEqual(jim['id'], 1) |
| 2698 | + self.assertEqual(Emp.__name__, 'Emp') |
| 2699 | + self.assertEqual(Emp.__module__, __name__) |
| 2700 | + self.assertEqual(Emp.__bases__, (dict,)) |
| 2701 | + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) |
| 2702 | + self.assertEqual(Emp.__total__, True) |
| 2703 | + |
| 2704 | + def test_typeddict_errors(self): |
| 2705 | + Emp = TypedDict('Emp', {'name': str, 'id': int}) |
| 2706 | + self.assertEqual(TypedDict.__module__, 'typing') |
| 2707 | + jim = Emp(name='Jim', id=1) |
| 2708 | + with self.assertRaises(TypeError): |
| 2709 | + isinstance({}, Emp) |
| 2710 | + with self.assertRaises(TypeError): |
| 2711 | + isinstance(jim, Emp) |
| 2712 | + with self.assertRaises(TypeError): |
| 2713 | + issubclass(dict, Emp) |
| 2714 | + with self.assertRaises(TypeError): |
| 2715 | + TypedDict('Hi', x=1) |
| 2716 | + with self.assertRaises(TypeError): |
| 2717 | + TypedDict('Hi', [('x', int), ('y', 1)]) |
| 2718 | + with self.assertRaises(TypeError): |
| 2719 | + TypedDict('Hi', [('x', int)], y=int) |
| 2720 | + |
| 2721 | + def test_py36_class_syntax_usage(self): |
| 2722 | + self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') |
| 2723 | + self.assertEqual(LabelPoint2D.__module__, __name__) |
| 2724 | + self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str}) |
| 2725 | + self.assertEqual(LabelPoint2D.__bases__, (dict,)) |
| 2726 | + self.assertEqual(LabelPoint2D.__total__, True) |
| 2727 | + self.assertNotIsSubclass(LabelPoint2D, typing.Sequence) |
| 2728 | + not_origin = Point2D(x=0, y=1) |
| 2729 | + self.assertEqual(not_origin['x'], 0) |
| 2730 | + self.assertEqual(not_origin['y'], 1) |
| 2731 | + other = LabelPoint2D(x=0, y=1, label='hi') |
| 2732 | + self.assertEqual(other['label'], 'hi') |
| 2733 | + |
| 2734 | + def test_pickle(self): |
| 2735 | + global EmpD # pickle wants to reference the class by name |
| 2736 | + EmpD = TypedDict('EmpD', name=str, id=int) |
| 2737 | + jane = EmpD({'name': 'jane', 'id': 37}) |
| 2738 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): |
| 2739 | + z = pickle.dumps(jane, proto) |
| 2740 | + jane2 = pickle.loads(z) |
| 2741 | + self.assertEqual(jane2, jane) |
| 2742 | + self.assertEqual(jane2, {'name': 'jane', 'id': 37}) |
| 2743 | + ZZ = pickle.dumps(EmpD, proto) |
| 2744 | + EmpDnew = pickle.loads(ZZ) |
| 2745 | + self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) |
| 2746 | + |
| 2747 | + def test_optional(self): |
| 2748 | + EmpD = TypedDict('EmpD', name=str, id=int) |
| 2749 | + |
| 2750 | + self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) |
| 2751 | + self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) |
| 2752 | + |
| 2753 | + def test_total(self): |
| 2754 | + D = TypedDict('D', {'x': int}, total=False) |
| 2755 | + self.assertEqual(D(), {}) |
| 2756 | + self.assertEqual(D(x=1), {'x': 1}) |
| 2757 | + self.assertEqual(D.__total__, False) |
| 2758 | + |
| 2759 | + self.assertEqual(Options(), {}) |
| 2760 | + self.assertEqual(Options(log_level=2), {'log_level': 2}) |
| 2761 | + self.assertEqual(Options.__total__, False) |
| 2762 | + |
| 2763 | + |
2661 | 2764 | class IOTests(BaseTestCase): |
2662 | 2765 |
|
2663 | 2766 | def test_io(self): |
|
0 commit comments