diff --git a/doc/index.rst b/doc/index.rst index e8da24dc..8e577db5 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1075,12 +1075,12 @@ Capsule objects Sentinel objects ~~~~~~~~~~~~~~~~ -.. class:: sentinel(name, /) +.. 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. - Assigning attributes to a sentinel is deprecated. + Assigning attributes to a sentinel is deprecated (except for __module__). Example:: @@ -1106,7 +1106,7 @@ Sentinel objects Now supports pickle and will be reduced as a singleton. Renamed from `Sentinel` to `sentinel`, `Sentinel` is deprecated. Automatic `repr` string no longer has angle brackets. - `repr` parameter was deprecated. + `repr` as a positional argument is deprecated. `name` as a keyword is deprecated. Subclassing and attribute assignment are deprecated. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 6dd0264a..4149eebe 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -9619,10 +9619,18 @@ def test_sentinel_no_repr(self): self.assertEqual(sentinel_no_repr.__name__, 'sentinel_no_repr') self.assertEqual(repr(sentinel_no_repr), 'sentinel_no_repr') + @skipIf(TYPING_3_15_0, reason="'Passing 'repr' as a positional argument was removed in 3.15") + def test_sentinel_deprecated_argument_repr(self): + with self.assertWarnsRegex(DeprecationWarning, r"Passing 'repr' as a positional argument is deprecated; pass it by keyword instead."): + sentinel_argument_repr = sentinel('sentinel_argument_repr', 'argument_repr') + + self.assertEqual(repr(sentinel_argument_repr), 'argument_repr') + @skipIf(TYPING_3_15_0_BETA_1, reason="'repr' parameter is not yet available in 3.15.0b1") - def test_sentinel_explicit_repr(self): - sentinel_explicit_repr = sentinel('sentinel_explicit_repr', repr='explicit_repr') - self.assertEqual(repr(sentinel_explicit_repr), 'explicit_repr') + def test_sentinel_keyword_repr(self): + sentinel_keyword_repr = sentinel('sentinel_keyword_repr', repr='keyword_repr') + + self.assertEqual(repr(sentinel_keyword_repr), 'keyword_repr') @skipIf(sys.version_info < (3, 10), reason='New unions not available in 3.9') def test_sentinel_type_expression_union(self): @@ -9675,6 +9683,8 @@ class SentinelSubclass(Sentinel): my_sentinel = Sentinel(name="my_sentinel") with self.assertWarnsRegex(DeprecationWarning, r"Setting attribute 'foo' on sentinel objects is deprecated"): my_sentinel.foo = "bar" + with self.assertWarnsRegex(DeprecationWarning, r"Setting attribute '__name__' on sentinel objects is deprecated"): + my_sentinel.__name__ = "bar" @skipUnless(TYPING_3_15_0, reason='Deprecated sentinel APIs are available before 3.15') def test_sentinel_removed_deprecated_apis(self): @@ -9687,6 +9697,8 @@ class SentinelSubclass(Sentinel): Sentinel(name="my_sentinel") with self.assertRaises(AttributeError): sentinel('my_sentinel').foo = "bar" + with self.assertRaises(AttributeError): + sentinel('my_sentinel').__name__ = "bar" def load_tests(loader, tests, pattern): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 31c37e55..64b2676b 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -189,9 +189,10 @@ class sentinel: def __init__( self, __name: str = _sentinel_placeholder, + __repr: typing.Optional[str] = _sentinel_placeholder, /, - repr: typing.Optional[str] = None, *, + repr: typing.Optional[str] = None, name: str = _sentinel_placeholder, ) -> None: if name is not _sentinel_placeholder: @@ -204,8 +205,16 @@ def __init__( __name = name if __name is _sentinel_placeholder: raise TypeError("First parameter 'name' is required") + if __repr is not _sentinel_placeholder: + warnings.warn( + "Passing 'repr' as a positional argument is deprecated; " + "pass it by keyword instead.", + DeprecationWarning, + stacklevel=2, + ) + repr = __repr - self.__name__ = __name + self._name = __name self._repr = repr if repr is not None else __name # For pickling as a singleton: @@ -221,7 +230,7 @@ def __init_subclass__(cls): super().__init_subclass__() def __setattr__(self, attr: str, value: object) -> None: - if attr not in {"__name__", "_repr", "__module__"}: + if attr not in {"_name", "_repr", "__module__"}: warnings.warn( f"Setting attribute {attr!r} on sentinel objects is deprecated " "and will be disallowed in Python 3.15.", @@ -230,7 +239,15 @@ def __setattr__(self, attr: str, value: object) -> None: ) super().__setattr__(attr, value) - def __repr__(self): + @property + def __name__(self) -> str: + return self._name + + @__name__.setter + def __name__(self, value: str) -> None: + self._name = value + + def __repr__(self) -> str: return self._repr if sys.version_info < (3, 11):