Skip to content

Commit de2e7fb

Browse files
committed
Add close_code and close_reason to new implementations.
Also add state to threading implementation. Fix #1537.
1 parent 9a2f39f commit de2e7fb

File tree

11 files changed

+145
-9
lines changed

11 files changed

+145
-9
lines changed

docs/reference/asyncio/client.rst

+7
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,10 @@ Using a connection
5757
.. autoattribute:: response
5858

5959
.. autoproperty:: subprotocol
60+
61+
The following attributes are available after the closing handshake,
62+
once the WebSocket connection is closed:
63+
64+
.. autoproperty:: close_code
65+
66+
.. autoproperty:: close_reason

docs/reference/asyncio/common.rst

+7
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,10 @@ Both sides (new :mod:`asyncio`)
4545
.. autoattribute:: response
4646

4747
.. autoproperty:: subprotocol
48+
49+
The following attributes are available after the closing handshake,
50+
once the WebSocket connection is closed:
51+
52+
.. autoproperty:: close_code
53+
54+
.. autoproperty:: close_reason

docs/reference/asyncio/server.rst

+7
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ Using a connection
7979

8080
.. autoproperty:: subprotocol
8181

82+
The following attributes are available after the closing handshake,
83+
once the WebSocket connection is closed:
84+
85+
.. autoproperty:: close_code
86+
87+
.. autoproperty:: close_reason
88+
8289
Broadcast
8390
---------
8491

docs/reference/sync/client.rst

+9
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Using a connection
3939

4040
.. autoproperty:: remote_address
4141

42+
.. autoproperty:: state
43+
4244
The following attributes are available after the opening handshake,
4345
once the WebSocket connection is open:
4446

@@ -47,3 +49,10 @@ Using a connection
4749
.. autoattribute:: response
4850

4951
.. autoproperty:: subprotocol
52+
53+
The following attributes are available after the closing handshake,
54+
once the WebSocket connection is closed:
55+
56+
.. autoproperty:: close_code
57+
58+
.. autoproperty:: close_reason

docs/reference/sync/common.rst

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Both sides (:mod:`threading`)
3131

3232
.. autoproperty:: remote_address
3333

34+
.. autoproperty:: state
35+
3436
The following attributes are available after the opening handshake,
3537
once the WebSocket connection is open:
3638

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

4143
.. autoproperty:: subprotocol
44+
45+
The following attributes are available after the closing handshake,
46+
once the WebSocket connection is closed:
47+
48+
.. autoproperty:: close_code
49+
50+
.. autoproperty:: close_reason

docs/reference/sync/server.rst

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Using a connection
5252

5353
.. autoproperty:: remote_address
5454

55+
.. autoproperty:: state
56+
5557
The following attributes are available after the opening handshake,
5658
once the WebSocket connection is open:
5759

@@ -61,6 +63,13 @@ Using a connection
6163

6264
.. autoproperty:: subprotocol
6365

66+
The following attributes are available after the closing handshake,
67+
once the WebSocket connection is closed:
68+
69+
.. autoproperty:: close_code
70+
71+
.. autoproperty:: close_reason
72+
6473
HTTP Basic Authentication
6574
-------------------------
6675

src/websockets/asyncio/connection.py

+24
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,30 @@ def subprotocol(self) -> Subprotocol | None:
185185
"""
186186
return self.protocol.subprotocol
187187

188+
@property
189+
def close_code(self) -> int | None:
190+
"""
191+
State of the WebSocket connection, defined in :rfc:`6455`.
192+
193+
This attribute is provided for completeness. Typical applications
194+
shouldn't check its value. Instead, they should inspect attributes
195+
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
196+
197+
"""
198+
return self.protocol.close_code
199+
200+
@property
201+
def close_reason(self) -> str | None:
202+
"""
203+
State of the WebSocket connection, defined in :rfc:`6455`.
204+
205+
This attribute is provided for completeness. Typical applications
206+
shouldn't check its value. Instead, they should inspect attributes
207+
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
208+
209+
"""
210+
return self.protocol.close_reason
211+
188212
# Public methods
189213

190214
async def __aenter__(self) -> Connection:

src/websockets/protocol.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,12 @@ def state(self) -> State:
159159
"""
160160
State of the WebSocket connection.
161161
162-
Defined in 4.1, 4.2, 7.1.3, and 7.1.4 of :rfc:`6455`.
162+
Defined in 4.1_, 4.2_, 7.1.3_, and 7.1.4_ of :rfc:`6455`.
163+
164+
.. _4.1: https://datatracker.ietf.org/doc/html/rfc6455#section-4.1
165+
.. _4.2: https://datatracker.ietf.org/doc/html/rfc6455#section-4.2
166+
.. _7.1.3: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.3
167+
.. _7.1.4: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4
163168
164169
"""
165170
return self._state
@@ -173,10 +178,11 @@ def state(self, state: State) -> None:
173178
@property
174179
def close_code(self) -> int | None:
175180
"""
176-
`WebSocket close code`_.
181+
WebSocket close code received from the remote endpoint.
182+
183+
Defined in 7.1.5_ of :rfc:`6455`.
177184
178-
.. _WebSocket close code:
179-
https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
185+
.. _7.1.5: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5
180186
181187
:obj:`None` if the connection isn't closed yet.
182188
@@ -191,10 +197,11 @@ def close_code(self) -> int | None:
191197
@property
192198
def close_reason(self) -> str | None:
193199
"""
194-
`WebSocket close reason`_.
200+
WebSocket close reason received from the remote endpoint.
201+
202+
Defined in 7.1.6_ of :rfc:`6455`.
195203
196-
.. _WebSocket close reason:
197-
https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6
204+
.. _7.1.6: https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6
198205
199206
:obj:`None` if the connection isn't closed yet.
200207

src/websockets/sync/connection.py

+37
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ def remote_address(self) -> Any:
140140
"""
141141
return self.socket.getpeername()
142142

143+
@property
144+
def state(self) -> State:
145+
"""
146+
State of the WebSocket connection, defined in :rfc:`6455`.
147+
148+
This attribute is provided for completeness. Typical applications
149+
shouldn't check its value. Instead, they should call :meth:`~recv` or
150+
:meth:`send` and handle :exc:`~websockets.exceptions.ConnectionClosed`
151+
exceptions.
152+
153+
"""
154+
return self.protocol.state
155+
143156
@property
144157
def subprotocol(self) -> Subprotocol | None:
145158
"""
@@ -150,6 +163,30 @@ def subprotocol(self) -> Subprotocol | None:
150163
"""
151164
return self.protocol.subprotocol
152165

166+
@property
167+
def close_code(self) -> int | None:
168+
"""
169+
State of the WebSocket connection, defined in :rfc:`6455`.
170+
171+
This attribute is provided for completeness. Typical applications
172+
shouldn't check its value. Instead, they should inspect attributes
173+
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
174+
175+
"""
176+
return self.protocol.close_code
177+
178+
@property
179+
def close_reason(self) -> str | None:
180+
"""
181+
State of the WebSocket connection, defined in :rfc:`6455`.
182+
183+
This attribute is provided for completeness. Typical applications
184+
shouldn't check its value. Instead, they should inspect attributes
185+
of :exc:`~websockets.exceptions.ConnectionClosed` exceptions.
186+
187+
"""
188+
return self.protocol.close_reason
189+
153190
# Public methods
154191

155192
def __enter__(self) -> Connection:

tests/asyncio/test_connection.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ async def test_remote_address(self, get_extra_info):
11391139

11401140
async def test_state(self):
11411141
"""Connection has a state attribute."""
1142-
self.assertEqual(self.connection.state, State.OPEN)
1142+
self.assertIs(self.connection.state, State.OPEN)
11431143

11441144
async def test_request(self):
11451145
"""Connection has a request attribute."""
@@ -1153,6 +1153,14 @@ async def test_subprotocol(self):
11531153
"""Connection has a subprotocol attribute."""
11541154
self.assertIsNone(self.connection.subprotocol)
11551155

1156+
async def test_close_code(self):
1157+
"""Connection has a close_code attribute."""
1158+
self.assertIsNone(self.connection.close_code)
1159+
1160+
async def test_close_reason(self):
1161+
"""Connection has a close_reason attribute."""
1162+
self.assertIsNone(self.connection.close_reason)
1163+
11561164
# Test reporting of network errors.
11571165

11581166
async def test_writing_in_data_received_fails(self):

tests/sync/test_connection.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
ConnectionClosedOK,
1616
)
1717
from websockets.frames import CloseCode, Frame, Opcode
18-
from websockets.protocol import CLIENT, SERVER, Protocol
18+
from websockets.protocol import CLIENT, SERVER, Protocol, State
1919
from websockets.sync.connection import *
2020

2121
from ..protocol import RecordingProtocol
@@ -808,6 +808,10 @@ def test_remote_address(self, getpeername):
808808
self.assertEqual(self.connection.remote_address, ("peer", 1234))
809809
getpeername.assert_called_with()
810810

811+
def test_state(self):
812+
"""Connection has a state attribute."""
813+
self.assertIs(self.connection.state, State.OPEN)
814+
811815
def test_request(self):
812816
"""Connection has a request attribute."""
813817
self.assertIsNone(self.connection.request)
@@ -820,6 +824,14 @@ def test_subprotocol(self):
820824
"""Connection has a subprotocol attribute."""
821825
self.assertIsNone(self.connection.subprotocol)
822826

827+
def test_close_code(self):
828+
"""Connection has a close_code attribute."""
829+
self.assertIsNone(self.connection.close_code)
830+
831+
def test_close_reason(self):
832+
"""Connection has a close_reason attribute."""
833+
self.assertIsNone(self.connection.close_reason)
834+
823835
# Test reporting of network errors.
824836

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

0 commit comments

Comments
 (0)