Skip to content

Commit a86652c

Browse files
committed
Unique types for sentinel objects
Resolves python-hyper#8.
1 parent 0f0c468 commit a86652c

File tree

4 files changed

+32
-14
lines changed

4 files changed

+32
-14
lines changed

h11/_connection.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
# Import all state sentinels
77
from ._state import *
88
# Import the internal things we need
9-
from ._util import LocalProtocolError, RemoteProtocolError, Sentinel
9+
from ._util import (
10+
LocalProtocolError, RemoteProtocolError, Sentinel, make_sentinel)
1011
from ._state import ConnectionState, _SWITCH_UPGRADE, _SWITCH_CONNECT
1112
from ._headers import (
1213
get_comma_header, set_comma_header, has_expect_100_continue,
@@ -18,8 +19,8 @@
1819
# Everything in __all__ gets re-exported as part of the h11 public API.
1920
__all__ = ["Connection", "NEED_DATA", "PAUSED"]
2021

21-
NEED_DATA = Sentinel("NEED_DATA")
22-
PAUSED = Sentinel("PAUSED")
22+
NEED_DATA = make_sentinel("NEED_DATA")
23+
PAUSED = make_sentinel("PAUSED")
2324

2425
# If we ever have this much buffered without it making a complete parseable
2526
# event, we error out. The only time we really buffer is when reading the
@@ -418,7 +419,7 @@ def next_event(self):
418419
"Can't receive data when peer state is ERROR")
419420
try:
420421
event = self._extract_next_receive_event()
421-
if type(event) is not Sentinel:
422+
if not isinstance(event, Sentinel):
422423
self._process_event(self.their_role, event)
423424
self._receive_buffer.compress()
424425
if event is NEED_DATA:

h11/_state.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
# script to keep it in sync!
113113

114114
from ._events import *
115-
from ._util import LocalProtocolError, Sentinel
115+
from ._util import LocalProtocolError, make_sentinel
116116

117117
# Everything in __all__ gets re-exported as part of the h11 public API.
118118
__all__ = []
@@ -125,7 +125,7 @@
125125
# Switch types
126126
"_SWITCH_UPGRADE _SWITCH_CONNECT").split()
127127
for token in sentinels:
128-
globals()[token] = Sentinel(token)
128+
globals()[token] = make_sentinel(token)
129129

130130
__all__ += [s for s in sentinels if not s.startswith("_")]
131131

h11/_util.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys
22

33
__all__ = ["ProtocolError", "LocalProtocolError", "RemoteProtocolError",
4-
"validate", "Sentinel", "bytesify"]
4+
"validate", "Sentinel", "bytesify", "make_sentinel"]
55

66
class ProtocolError(Exception):
77
"""Exception indicating a violation of the HTTP/1.1 protocol.
@@ -91,14 +91,29 @@ def validate(regex, data, msg="malformed data"):
9191
raise LocalProtocolError(msg)
9292
return match.groupdict()
9393

94-
# Sentinel values
95-
# Inherits identity-based comparison and hashing from object
9694
class Sentinel(object):
97-
def __init__(self, name):
98-
self._name = name
95+
"""Sentinel type for constructed sentinel types to inherit from.
96+
97+
The class inherits identity-based comparison and hashing from object.
98+
"""
99+
100+
def make_sentinel(name):
101+
"""Return a sentinel value of a newly constructed type.
102+
103+
The constructed class is equivalent to the following:
104+
105+
.. code-block:: python
106+
107+
class <name>(object):
108+
def __repr__(self):
109+
return <name>
110+
"""
99111

100112
def __repr__(self):
101-
return self._name
113+
return name
114+
115+
cls = type(name, (Sentinel,), dict(__repr__=__repr__))
116+
return cls()
102117

103118
# Used for methods, request targets, HTTP versions, header names, and header
104119
# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always

h11/tests/test_util.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ def test_validate():
5353
with pytest.raises(LocalProtocolError):
5454
validate(my_re, b"0.1\n")
5555

56-
def test_Sentinel():
57-
S = Sentinel("S")
56+
def test_make_sentinel():
57+
S = make_sentinel("S")
5858
assert repr(S) == "S"
59+
assert type(S).__name__ == "S"
60+
assert isinstance(S, Sentinel)
5961
assert S == S
6062
assert S in {S}
6163

0 commit comments

Comments
 (0)