diff --git a/h11/_connection.py b/h11/_connection.py index 3b29a8c..7d6929c 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -6,7 +6,8 @@ # Import all state sentinels from ._state import * # Import the internal things we need -from ._util import LocalProtocolError, RemoteProtocolError, Sentinel +from ._util import ( + LocalProtocolError, RemoteProtocolError, Sentinel, make_sentinel) from ._state import ConnectionState, _SWITCH_UPGRADE, _SWITCH_CONNECT from ._headers import ( get_comma_header, set_comma_header, has_expect_100_continue, @@ -18,8 +19,8 @@ # Everything in __all__ gets re-exported as part of the h11 public API. __all__ = ["Connection", "NEED_DATA", "PAUSED"] -NEED_DATA = Sentinel("NEED_DATA") -PAUSED = Sentinel("PAUSED") +NEED_DATA = make_sentinel("NEED_DATA") +PAUSED = make_sentinel("PAUSED") # If we ever have this much buffered without it making a complete parseable # event, we error out. The only time we really buffer is when reading the @@ -418,7 +419,7 @@ def next_event(self): "Can't receive data when peer state is ERROR") try: event = self._extract_next_receive_event() - if type(event) is not Sentinel: + if not isinstance(event, Sentinel): self._process_event(self.their_role, event) self._receive_buffer.compress() if event is NEED_DATA: diff --git a/h11/_state.py b/h11/_state.py index 2c02ee4..c1d2e5c 100644 --- a/h11/_state.py +++ b/h11/_state.py @@ -112,7 +112,7 @@ # script to keep it in sync! from ._events import * -from ._util import LocalProtocolError, Sentinel +from ._util import LocalProtocolError, make_sentinel # Everything in __all__ gets re-exported as part of the h11 public API. __all__ = [] @@ -125,7 +125,7 @@ # Switch types "_SWITCH_UPGRADE _SWITCH_CONNECT").split() for token in sentinels: - globals()[token] = Sentinel(token) + globals()[token] = make_sentinel(token) __all__ += [s for s in sentinels if not s.startswith("_")] diff --git a/h11/_util.py b/h11/_util.py index b4d318f..c8366dd 100644 --- a/h11/_util.py +++ b/h11/_util.py @@ -1,7 +1,7 @@ import sys __all__ = ["ProtocolError", "LocalProtocolError", "RemoteProtocolError", - "validate", "Sentinel", "bytesify"] + "validate", "Sentinel", "bytesify", "make_sentinel"] class ProtocolError(Exception): """Exception indicating a violation of the HTTP/1.1 protocol. @@ -91,14 +91,29 @@ def validate(regex, data, msg="malformed data"): raise LocalProtocolError(msg) return match.groupdict() -# Sentinel values -# Inherits identity-based comparison and hashing from object class Sentinel(object): - def __init__(self, name): - self._name = name + """Sentinel type for constructed sentinel types to inherit from. + + The class inherits identity-based comparison and hashing from object. + """ + +def make_sentinel(name): + """Return a sentinel value of a newly constructed type. + + The constructed class is equivalent to the following: + + .. code-block:: python + + class (Sentinel): + def __repr__(self): + return + """ def __repr__(self): - return self._name + return name + + cls = type(name, (Sentinel,), dict(__repr__=__repr__)) + return cls() # Used for methods, request targets, HTTP versions, header names, and header # values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always diff --git a/h11/tests/test_util.py b/h11/tests/test_util.py index 30cf589..5d0dd98 100644 --- a/h11/tests/test_util.py +++ b/h11/tests/test_util.py @@ -53,9 +53,11 @@ def test_validate(): with pytest.raises(LocalProtocolError): validate(my_re, b"0.1\n") -def test_Sentinel(): - S = Sentinel("S") +def test_make_sentinel(): + S = make_sentinel("S") assert repr(S) == "S" + assert type(S).__name__ == "S" + assert isinstance(S, Sentinel) assert S == S assert S in {S}