|
102 | 102 | reveal_type, |
103 | 103 | runtime, |
104 | 104 | runtime_checkable, |
| 105 | + sentinel, |
105 | 106 | type_repr, |
106 | 107 | ) |
107 | 108 |
|
@@ -4642,6 +4643,47 @@ class ChildWithInlineAndOptional(Untotal, Inline): |
4642 | 4643 | class Wrong(*bases): |
4643 | 4644 | pass |
4644 | 4645 |
|
| 4646 | + def test_keys_inheritance_with_same_name(self): |
| 4647 | + class NotTotal(TypedDict, total=False): |
| 4648 | + a: int |
| 4649 | + |
| 4650 | + class Total(NotTotal): |
| 4651 | + a: int |
| 4652 | + |
| 4653 | + self.assertEqual(NotTotal.__required_keys__, frozenset()) |
| 4654 | + self.assertEqual(NotTotal.__optional_keys__, frozenset(['a'])) |
| 4655 | + self.assertEqual(Total.__required_keys__, frozenset(['a'])) |
| 4656 | + self.assertEqual(Total.__optional_keys__, frozenset()) |
| 4657 | + |
| 4658 | + class Base(TypedDict): |
| 4659 | + a: NotRequired[int] |
| 4660 | + b: Required[int] |
| 4661 | + |
| 4662 | + class Child(Base): |
| 4663 | + a: Required[int] |
| 4664 | + b: NotRequired[int] |
| 4665 | + |
| 4666 | + self.assertEqual(Base.__required_keys__, frozenset(['b'])) |
| 4667 | + self.assertEqual(Base.__optional_keys__, frozenset(['a'])) |
| 4668 | + self.assertEqual(Child.__required_keys__, frozenset(['a'])) |
| 4669 | + self.assertEqual(Child.__optional_keys__, frozenset(['b'])) |
| 4670 | + |
| 4671 | + def test_multiple_inheritance_with_same_key(self): |
| 4672 | + class Base1(TypedDict): |
| 4673 | + a: NotRequired[int] |
| 4674 | + |
| 4675 | + class Base2(TypedDict): |
| 4676 | + a: Required[str] |
| 4677 | + |
| 4678 | + class Child(Base1, Base2): |
| 4679 | + pass |
| 4680 | + |
| 4681 | + # Last base wins |
| 4682 | + self.assertEqual(Child.__annotations__, {'a': Required[str]}) |
| 4683 | + self.assertEqual(Child.__required_keys__, frozenset(['a'])) |
| 4684 | + self.assertEqual(Child.__optional_keys__, frozenset()) |
| 4685 | + |
| 4686 | + |
4645 | 4687 | def test_closed_values(self): |
4646 | 4688 | class Implicit(TypedDict): ... |
4647 | 4689 | class ExplicitTrue(TypedDict, closed=True): ... |
@@ -9542,42 +9584,71 @@ def test_invalid_special_forms(self): |
9542 | 9584 |
|
9543 | 9585 |
|
9544 | 9586 | class TestSentinels(BaseTestCase): |
| 9587 | + SENTINEL = sentinel("TestSentinels.SENTINEL") |
| 9588 | + |
9545 | 9589 | def test_sentinel_no_repr(self): |
9546 | | - sentinel_no_repr = Sentinel('sentinel_no_repr') |
| 9590 | + sentinel_no_repr = sentinel('sentinel_no_repr') |
9547 | 9591 |
|
9548 | | - self.assertEqual(sentinel_no_repr._name, 'sentinel_no_repr') |
9549 | | - self.assertEqual(repr(sentinel_no_repr), '<sentinel_no_repr>') |
| 9592 | + self.assertEqual(sentinel_no_repr.__name__, 'sentinel_no_repr') |
| 9593 | + self.assertEqual(repr(sentinel_no_repr), 'sentinel_no_repr') |
9550 | 9594 |
|
9551 | | - def test_sentinel_explicit_repr(self): |
9552 | | - sentinel_explicit_repr = Sentinel('sentinel_explicit_repr', repr='explicit_repr') |
| 9595 | + def test_sentinel_deprecated_explicit_repr(self): |
| 9596 | + with self.assertWarnsRegex(DeprecationWarning, r"'repr' parameter is deprecated and will be removed"): |
| 9597 | + sentinel_explicit_repr = sentinel('sentinel_explicit_repr', repr='explicit_repr') |
9553 | 9598 |
|
9554 | 9599 | self.assertEqual(repr(sentinel_explicit_repr), 'explicit_repr') |
9555 | 9600 |
|
9556 | 9601 | @skipIf(sys.version_info < (3, 10), reason='New unions not available in 3.9') |
9557 | 9602 | def test_sentinel_type_expression_union(self): |
9558 | | - sentinel = Sentinel('sentinel') |
| 9603 | + sentinel_type = sentinel('sentinel') |
9559 | 9604 |
|
9560 | | - def func1(a: int | sentinel = sentinel): pass |
9561 | | - def func2(a: sentinel | int = sentinel): pass |
| 9605 | + def func1(a: int | sentinel_type = sentinel_type): pass |
| 9606 | + def func2(a: sentinel_type | int = sentinel_type): pass |
9562 | 9607 |
|
9563 | | - self.assertEqual(func1.__annotations__['a'], Union[int, sentinel]) |
9564 | | - self.assertEqual(func2.__annotations__['a'], Union[sentinel, int]) |
| 9608 | + self.assertEqual(func1.__annotations__['a'], Union[int, sentinel_type]) |
| 9609 | + self.assertEqual(func2.__annotations__['a'], Union[sentinel_type, int]) |
9565 | 9610 |
|
9566 | 9611 | def test_sentinel_not_callable(self): |
9567 | | - sentinel = Sentinel('sentinel') |
| 9612 | + sentinel_ = sentinel('sentinel') |
9568 | 9613 | with self.assertRaisesRegex( |
9569 | 9614 | TypeError, |
9570 | | - "'Sentinel' object is not callable" |
| 9615 | + "'sentinel' object is not callable" |
9571 | 9616 | ): |
| 9617 | + sentinel_() |
| 9618 | + |
| 9619 | + def test_sentinel_copy_identity(self): |
| 9620 | + self.assertIs(self.SENTINEL, copy.copy(self.SENTINEL)) |
| 9621 | + self.assertIs(self.SENTINEL, copy.deepcopy(self.SENTINEL)) |
| 9622 | + |
| 9623 | + anonymous_sentinel = sentinel("anonymous_sentinel") |
| 9624 | + self.assertIs(anonymous_sentinel, copy.copy(anonymous_sentinel)) |
| 9625 | + self.assertIs(anonymous_sentinel, copy.deepcopy(anonymous_sentinel)) |
| 9626 | + |
| 9627 | + def test_sentinel_picklable_qualified(self): |
| 9628 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): |
| 9629 | + self.assertIs(self.SENTINEL, pickle.loads(pickle.dumps(self.SENTINEL, protocol=proto))) |
| 9630 | + |
| 9631 | + def test_sentinel_picklable_anonymous(self): |
| 9632 | + anonymous_sentinel = sentinel("anonymous_sentinel") # Anonymous sentinel can not be pickled |
| 9633 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): |
| 9634 | + with self.assertRaisesRegex( |
| 9635 | + pickle.PicklingError, |
| 9636 | + r"attribute lookup anonymous_sentinel on \w+ failed|not found as \w+.anonymous_sentinel" |
| 9637 | + ): |
| 9638 | + self.assertIs(anonymous_sentinel, pickle.loads(pickle.dumps(anonymous_sentinel, protocol=proto))) |
| 9639 | + |
| 9640 | + def test_sentinel_deprecated(self): |
| 9641 | + with self.assertWarnsRegex(DeprecationWarning, r"Subclassing sentinel is deprecated"): |
| 9642 | + class SentinelSubclass(Sentinel): |
| 9643 | + pass |
| 9644 | + with self.assertRaisesRegex(TypeError, r"First parameter 'name' is required"): |
9572 | 9645 | sentinel() |
9573 | 9646 |
|
9574 | | - def test_sentinel_not_picklable(self): |
9575 | | - sentinel = Sentinel('sentinel') |
9576 | | - with self.assertRaisesRegex( |
9577 | | - TypeError, |
9578 | | - "Cannot pickle 'Sentinel' object" |
9579 | | - ): |
9580 | | - pickle.dumps(sentinel) |
| 9647 | + with self.assertWarnsRegex(DeprecationWarning, r"Passing 'name' as a keyword argument is deprecated"): |
| 9648 | + my_sentinel = Sentinel(name="my_sentinel") |
| 9649 | + with self.assertWarnsRegex(DeprecationWarning, r"Setting attribute 'foo' on sentinel objects is deprecated"): |
| 9650 | + my_sentinel.foo = "bar" |
| 9651 | + |
9581 | 9652 |
|
9582 | 9653 | def load_tests(loader, tests, pattern): |
9583 | 9654 | import doctest |
|
0 commit comments