Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions h11/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions h11/_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = []
Expand 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("_")]

Expand Down
27 changes: 21 additions & 6 deletions h11/_util.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 <name>(Sentinel):
def __repr__(self):
return <name>
"""

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
Expand Down
6 changes: 4 additions & 2 deletions h11/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down