diff --git a/CHANGELOG.md b/CHANGELOG.md index ba1a6d78..92a19a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ New features: Patch by [Victorien Plot](https://github.com/Viicos). - Add `typing_extensions.Reader` and `typing_extensions.Writer`. Patch by Sebastian Rittau. +- Add support for sentinels ([PEP 661](https://peps.python.org/pep-0661/)). Patch by + [Victorien Plot](https://github.com/Viicos). # Release 4.13.2 (April 10, 2025) diff --git a/doc/index.rst b/doc/index.rst index 68402faf..21d6fa60 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1027,6 +1027,34 @@ Capsule objects .. versionadded:: 4.12.0 +Sentinel objects +~~~~~~~~~~~~~~~~ + +.. class:: Sentinel(name, repr=None) + + A type used to define sentinel values. The *name* argument should be the + name of the variable to which the return value shall be assigned. + + If *repr* is provided, it will be used for the :meth:`~object.__repr__` + of the sentinel object. If not provided, ``""`` will be used. + + Example:: + + >>> from typing_extensions import Sentinel, assert_type + >>> MISSING = Sentinel('MISSING') + >>> def func(arg: int | MISSING = MISSING) -> None: + ... if arg is MISSING: + ... assert_type(arg, MISSING) + ... else: + ... assert_type(arg, int) + ... + >>> func(MISSING) + + .. versionadded:: 4.14.0 + + See :pep:`661` + + Pure aliases ~~~~~~~~~~~~ diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index a7953dc5..c23e94b7 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -65,6 +65,7 @@ ReadOnly, Required, Self, + Sentinel, Set, Tuple, Type, @@ -9096,5 +9097,44 @@ def test_invalid_special_forms(self): self.assertIs(evaluate_forward_ref(typing.ForwardRef("ClassVar", is_argument=False), globals=vars(typing)), ClassVar) +class TestSentinels(BaseTestCase): + def test_sentinel_no_repr(self): + sentinel_no_repr = Sentinel('sentinel_no_repr') + + self.assertEqual(sentinel_no_repr._name, 'sentinel_no_repr') + self.assertEqual(repr(sentinel_no_repr), '') + + def test_sentinel_explicit_repr(self): + sentinel_explicit_repr = Sentinel('sentinel_explicit_repr', repr='explicit_repr') + + self.assertEqual(repr(sentinel_explicit_repr), 'explicit_repr') + + @skipIf(sys.version_info < (3, 10), reason='New unions not available in 3.9') + def test_sentinel_type_expression_union(self): + sentinel = Sentinel('sentinel') + + def func1(a: int | sentinel = sentinel): pass + def func2(a: sentinel | int = sentinel): pass + + self.assertEqual(func1.__annotations__['a'], Union[int, sentinel]) + self.assertEqual(func2.__annotations__['a'], Union[sentinel, int]) + + def test_sentinel_not_callable(self): + sentinel = Sentinel('sentinel') + with self.assertRaisesRegex( + TypeError, + "'Sentinel' object is not callable" + ): + sentinel() + + def test_sentinel_not_picklable(self): + sentinel = Sentinel('sentinel') + with self.assertRaisesRegex( + TypeError, + "Cannot pickle 'Sentinel' object" + ): + pickle.dumps(sentinel) + + if __name__ == '__main__': main() diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 1ab6220d..d4e92a4c 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -89,6 +89,7 @@ 'overload', 'override', 'Protocol', + 'Sentinel', 'reveal_type', 'runtime', 'runtime_checkable', @@ -4210,6 +4211,42 @@ def evaluate_forward_ref( ) +class Sentinel: + """Create a unique sentinel object. + + *name* should be the name of the variable to which the return value shall be assigned. + + *repr*, if supplied, will be used for the repr of the sentinel object. + If not provided, "" will be used. + """ + + def __init__( + self, + name: str, + repr: typing.Optional[str] = None, + ): + self._name = name + self._repr = repr if repr is not None else f'<{name}>' + + def __repr__(self): + return self._repr + + if sys.version_info < (3, 11): + # The presence of this method convinces typing._type_check + # that Sentinels are types. + def __call__(self, *args, **kwargs): + raise TypeError(f"{type(self).__name__!r} object is not callable") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __getstate__(self): + raise TypeError(f"Cannot pickle {type(self).__name__!r} object") + + # Aliases for items that are in typing in all supported versions. # We use hasattr() checks so this library will continue to import on # future versions of Python that may remove these names. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy