Skip to content

Commit

Permalink
Add close_code and close_reason to new implementations.
Browse files Browse the repository at this point in the history
Also add state to threading implementation.

Fix #1537.
  • Loading branch information
aaugustin committed Nov 11, 2024
1 parent 9a2f39f commit de2e7fb
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 9 deletions.
7 changes: 7 additions & 0 deletions docs/reference/asyncio/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@ Using a connection
.. autoattribute:: response

.. autoproperty:: subprotocol

The following attributes are available after the closing handshake,
once the WebSocket connection is closed:

.. autoproperty:: close_code

.. autoproperty:: close_reason
7 changes: 7 additions & 0 deletions docs/reference/asyncio/common.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ Both sides (new :mod:`asyncio`)
.. autoattribute:: response

.. autoproperty:: subprotocol

The following attributes are available after the closing handshake,
once the WebSocket connection is closed:

.. autoproperty:: close_code

.. autoproperty:: close_reason
7 changes: 7 additions & 0 deletions docs/reference/asyncio/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ Using a connection

.. autoproperty:: subprotocol

The following attributes are available after the closing handshake,
once the WebSocket connection is closed:

.. autoproperty:: close_code

.. autoproperty:: close_reason

Broadcast
---------

Expand Down
9 changes: 9 additions & 0 deletions docs/reference/sync/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Using a connection

.. autoproperty:: remote_address

.. autoproperty:: state

The following attributes are available after the opening handshake,
once the WebSocket connection is open:

Expand All @@ -47,3 +49,10 @@ Using a connection
.. autoattribute:: response

.. autoproperty:: subprotocol

The following attributes are available after the closing handshake,
once the WebSocket connection is closed:

.. autoproperty:: close_code

.. autoproperty:: close_reason
9 changes: 9 additions & 0 deletions docs/reference/sync/common.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Both sides (:mod:`threading`)

.. autoproperty:: remote_address

.. autoproperty:: state

The following attributes are available after the opening handshake,
once the WebSocket connection is open:

Expand All @@ -39,3 +41,10 @@ Both sides (:mod:`threading`)
.. autoattribute:: response

.. autoproperty:: subprotocol

The following attributes are available after the closing handshake,
once the WebSocket connection is closed:

.. autoproperty:: close_code

.. autoproperty:: close_reason
9 changes: 9 additions & 0 deletions docs/reference/sync/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Using a connection

.. autoproperty:: remote_address

.. autoproperty:: state

The following attributes are available after the opening handshake,
once the WebSocket connection is open:

Expand All @@ -61,6 +63,13 @@ Using a connection

.. autoproperty:: subprotocol

The following attributes are available after the closing handshake,
once the WebSocket connection is closed:

.. autoproperty:: close_code

.. autoproperty:: close_reason

HTTP Basic Authentication
-------------------------

Expand Down
24 changes: 24 additions & 0 deletions src/websockets/asyncio/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,30 @@ def subprotocol(self) -> Subprotocol | None:
"""
return self.protocol.subprotocol

@property
def close_code(self) -> int | None:
"""
State of the WebSocket connection, defined in :rfc:`6455`.
This attribute is provided for completeness. Typical applications
shouldn't check its value. Instead, they should inspect attributes
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
"""
return self.protocol.close_code

@property
def close_reason(self) -> str | None:
"""
State of the WebSocket connection, defined in :rfc:`6455`.
This attribute is provided for completeness. Typical applications
shouldn't check its value. Instead, they should inspect attributes
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
"""
return self.protocol.close_reason

# Public methods

async def __aenter__(self) -> Connection:
Expand Down
21 changes: 14 additions & 7 deletions src/websockets/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,12 @@ def state(self) -> State:
"""
State of the WebSocket connection.
Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of :rfc:`6455`.
Defined in 4.1_, 4.2_, 7.1.3_, and 7.1.4_ of :rfc:`6455`.
.. _4.1: https://datatracker.ietf.org/doc/html/rfc6455#section-4.1
.. _4.2: https://datatracker.ietf.org/doc/html/rfc6455#section-4.2
.. _7.1.3: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.3
.. _7.1.4: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
"""
return self._state
Expand All @@ -173,10 +178,11 @@ def state(self, state: State) -> None:
@property
def close_code(self) -> int | None:
"""
`WebSocket close code`_.
WebSocket close code received from the remote endpoint.
Defined in 7.1.5_ of :rfc:`6455`.
.. _WebSocket close code:
https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
.. _7.1.5: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
:obj:`None` if the connection isn't closed yet.
Expand All @@ -191,10 +197,11 @@ def close_code(self) -> int | None:
@property
def close_reason(self) -> str | None:
"""
`WebSocket close reason`_.
WebSocket close reason received from the remote endpoint.
Defined in 7.1.6_ of :rfc:`6455`.
.. _WebSocket close reason:
https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6
.. _7.1.6: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6
:obj:`None` if the connection isn't closed yet.
Expand Down
37 changes: 37 additions & 0 deletions src/websockets/sync/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ def remote_address(self) -> Any:
"""
return self.socket.getpeername()

@property
def state(self) -> State:
"""
State of the WebSocket connection, defined in :rfc:`6455`.
This attribute is provided for completeness. Typical applications
shouldn't check its value. Instead, they should call :meth:`~recv` or
:meth:`send` and handle :exc:`~websockets.exceptions.ConnectionClosed`
exceptions.
"""
return self.protocol.state

@property
def subprotocol(self) -> Subprotocol | None:
"""
Expand All @@ -150,6 +163,30 @@ def subprotocol(self) -> Subprotocol | None:
"""
return self.protocol.subprotocol

@property
def close_code(self) -> int | None:
"""
State of the WebSocket connection, defined in :rfc:`6455`.
This attribute is provided for completeness. Typical applications
shouldn't check its value. Instead, they should inspect attributes
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
"""
return self.protocol.close_code

@property
def close_reason(self) -> str | None:
"""
State of the WebSocket connection, defined in :rfc:`6455`.
This attribute is provided for completeness. Typical applications
shouldn't check its value. Instead, they should inspect attributes
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
"""
return self.protocol.close_reason

# Public methods

def __enter__(self) -> Connection:
Expand Down
10 changes: 9 additions & 1 deletion tests/asyncio/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ async def test_remote_address(self, get_extra_info):

async def test_state(self):
"""Connection has a state attribute."""
self.assertEqual(self.connection.state, State.OPEN)
self.assertIs(self.connection.state, State.OPEN)

async def test_request(self):
"""Connection has a request attribute."""
Expand All @@ -1153,6 +1153,14 @@ async def test_subprotocol(self):
"""Connection has a subprotocol attribute."""
self.assertIsNone(self.connection.subprotocol)

async def test_close_code(self):
"""Connection has a close_code attribute."""
self.assertIsNone(self.connection.close_code)

async def test_close_reason(self):
"""Connection has a close_reason attribute."""
self.assertIsNone(self.connection.close_reason)

# Test reporting of network errors.

async def test_writing_in_data_received_fails(self):
Expand Down
14 changes: 13 additions & 1 deletion tests/sync/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ConnectionClosedOK,
)
from websockets.frames import CloseCode, Frame, Opcode
from websockets.protocol import CLIENT, SERVER, Protocol
from websockets.protocol import CLIENT, SERVER, Protocol, State
from websockets.sync.connection import *

from ..protocol import RecordingProtocol
Expand Down Expand Up @@ -808,6 +808,10 @@ def test_remote_address(self, getpeername):
self.assertEqual(self.connection.remote_address, ("peer", 1234))
getpeername.assert_called_with()

def test_state(self):
"""Connection has a state attribute."""
self.assertIs(self.connection.state, State.OPEN)

def test_request(self):
"""Connection has a request attribute."""
self.assertIsNone(self.connection.request)
Expand All @@ -820,6 +824,14 @@ def test_subprotocol(self):
"""Connection has a subprotocol attribute."""
self.assertIsNone(self.connection.subprotocol)

def test_close_code(self):
"""Connection has a close_code attribute."""
self.assertIsNone(self.connection.close_code)

def test_close_reason(self):
"""Connection has a close_reason attribute."""
self.assertIsNone(self.connection.close_reason)

# Test reporting of network errors.

@unittest.skipUnless(sys.platform == "darwin", "works only on BSD")
Expand Down

0 comments on commit de2e7fb

Please sign in to comment.