Skip to content

Commit 0653d70

Browse files
committed
Reduce Sentinel as singletons
Use pickles method of handing singleton objects. This is simpler and more predictable than a custom unpickle function. Anonymous sentinels can no longer be pickled and will raise PicklingError instead of TypeError.
1 parent 5c0e8a7 commit 0653d70

File tree

2 files changed

+14
-24
lines changed

2 files changed

+14
-24
lines changed

src/test_typing_extensions.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9324,10 +9324,19 @@ def test_sentinel_import(self):
93249324
self.assertIs(Sentinel._import_sentinel("nonexistent", ""), None)
93259325
self.assertIs(Sentinel._import_sentinel("nonexistent", "nonexistent.nonexistent.nonexistent"), None)
93269326

9327-
def test_sentinel_picklable(self):
9327+
def test_sentinel_picklable_qualified(self):
93289328
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
93299329
self.assertIs(self.SENTINEL, pickle.loads(pickle.dumps(self.SENTINEL, protocol=proto)))
93309330

9331+
def test_sentinel_picklable_anonymous(self):
9332+
anonymous_sentinel = Sentinel("anonymous_sentinel") # Anonymous sentinel can not be pickled
9333+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
9334+
with self.assertRaisesRegex(
9335+
pickle.PicklingError,
9336+
r"attribute lookup anonymous_sentinel on test_typing_extensions failed"
9337+
):
9338+
self.assertIs(anonymous_sentinel, pickle.loads(pickle.dumps(anonymous_sentinel, protocol=proto)))
9339+
93319340

93329341

93339342
if __name__ == '__main__':

src/typing_extensions.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4158,16 +4158,6 @@ def evaluate_forward_ref(
41584158

41594159
_sentinel_registry = {}
41604160

4161-
def _unpickle_sentinel(
4162-
name: str,
4163-
module_name: str,
4164-
config: typing.Dict[str, typing.Any],
4165-
/,
4166-
):
4167-
"""Stable Sentinel unpickling function, get Sentinel at 'module_name.name'."""
4168-
# Explicit repr=name because a saved module_name is known to be valid
4169-
return Sentinel(name, module_name, repr=config.get("repr", name))
4170-
41714161
class Sentinel:
41724162
"""A sentinel object.
41734163
@@ -4229,7 +4219,7 @@ def __new__(
42294219
# Create initial or anonymous sentinel
42304220
sentinel = super().__new__(cls)
42314221
sentinel._name = name
4232-
sentinel._module_name = module_name
4222+
sentinel.__module__ = module_name # Assign which module defined this instance
42334223
sentinel._repr = repr if repr is not None else name
42344224
return _sentinel_registry.setdefault(registry_key, sentinel)
42354225

@@ -4262,18 +4252,9 @@ def __or__(self, other):
42624252
def __ror__(self, other):
42634253
return typing.Union[other, self]
42644254

4265-
def __reduce__(self):
4266-
"""Record where this sentinel is defined and its current parameters."""
4267-
config = {"repr": self._repr}
4268-
# Reduce callable must be at the top-level to be stable whenever Sentinel changes
4269-
return (
4270-
_unpickle_sentinel,
4271-
(
4272-
self._name,
4273-
self._module_name,
4274-
config,
4275-
),
4276-
)
4255+
def __reduce__(self) -> str:
4256+
"""Reduce this sentinel to a singleton."""
4257+
return self._name # Module is set from __module__ attribute
42774258

42784259

42794260
# Aliases for items that are in typing in all supported versions.

0 commit comments

Comments
 (0)