Skip to content
Merged
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
3 changes: 3 additions & 0 deletions docs/project/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ Improvements

* Connections are now garbage collected immediately once closed.

* :func:`~sync.client.unix_connect` and :func:`~sync.server.unix_serve` now accept
path-like objects, such as :class:`pathlib.Path`, in the ``path`` argument.

.. _17.0.1:

17.0.1
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Types

.. autodata:: LoggerLike

.. autodata:: PathLike

.. autodata:: StatusLike

.. autodata:: Origin
Expand Down
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dev
django
Dockerfile
dyno
filesystem
formatter
fractalideas
github
Expand Down
4 changes: 2 additions & 2 deletions src/websockets/asyncio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from ..protocol import CONNECTING, Event
from ..proxy import Proxy, get_proxy, parse_proxy, prepare_connect_request
from ..streams import StreamReader
from ..typing import LoggerLike, Origin, Subprotocol
from ..typing import LoggerLike, Origin, PathLike, Subprotocol
from ..uri import WebSocketURI, parse_uri
from .connection import Connection

Expand Down Expand Up @@ -630,7 +630,7 @@ async def __aiter__(self) -> AsyncIterator[ClientConnection]:


def unix_connect(
path: str | None = None,
path: PathLike | None = None,
uri: str | None = None,
**kwargs: Any,
) -> connect:
Expand Down
5 changes: 3 additions & 2 deletions src/websockets/asyncio/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any, Awaitable, Callable, Literal

from ..http11 import Request, Response
from ..typing import PathLike
from .server import Server, ServerConnection, serve


Expand All @@ -30,7 +31,7 @@ def route(

def unix_route(
url_map: Map,
path: str | None = None,
path: PathLike | None = None,
**kwargs: Any,
) -> Server:
raise ImportError("unix_route() requires werkzeug")
Expand Down Expand Up @@ -155,7 +156,7 @@ async def process_request(

def unix_route(
url_map: Map,
path: str | None = None,
path: PathLike | None = None,
**kwargs: Any,
) -> Server:
"""
Expand Down
4 changes: 2 additions & 2 deletions src/websockets/asyncio/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ..http11 import SERVER, Request, Response
from ..protocol import CONNECTING, OPEN, Event
from ..server import ServerProtocol
from ..typing import LoggerLike, Origin, StatusLike, Subprotocol
from ..typing import LoggerLike, Origin, PathLike, StatusLike, Subprotocol
from ..utils import get_socket_name
from .connection import Connection, broadcast

Expand Down Expand Up @@ -769,7 +769,7 @@ async def protocol_handler(connection: ServerConnection) -> None:

def unix_serve(
handler: Callable[[ServerConnection], Awaitable[None]],
path: str | None = None,
path: PathLike | None = None,
**kwargs: Any,
) -> Server:
"""
Expand Down
13 changes: 7 additions & 6 deletions src/websockets/sync/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from ..protocol import CONNECTING, Event
from ..proxy import Proxy, get_proxy, parse_proxy, prepare_connect_request
from ..streams import StreamReader
from ..typing import BytesLike, LoggerLike, Origin, Subprotocol
from ..typing import BytesLike, LoggerLike, Origin, PathLike, Subprotocol
from ..uri import WebSocketURI, parse_uri
from .connection import Connection
from .utils import Deadline
Expand Down Expand Up @@ -308,7 +308,7 @@ def open_socket(self, deadline: Deadline) -> socket.socket:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.settimeout(deadline.timeout())
sock.connect(kwargs.pop("path"))
sock.connect(os.fspath(kwargs.pop("path")))
except Exception:
sock.close()
raise
Expand Down Expand Up @@ -463,6 +463,7 @@ def process_redirect(self, exc: Exception) -> Exception | str:
f"cannot follow cross-origin redirect to {new_uri} "
f"with a Unix socket"
)

# Cross-origin redirects when host and port are overridden are ill-defined.
if self.open_socket_kwargs.get("address") is not None:
return ValueError(
Expand Down Expand Up @@ -821,7 +822,7 @@ def connect(


def unix_reconnect(
path: str | None = None,
path: PathLike | None = None,
uri: str | None = None,
**kwargs: Any,
) -> reconnect:
Expand Down Expand Up @@ -849,7 +850,7 @@ def unix_reconnect(

@overload
def unix_connect(
path: str | None = ...,
path: PathLike | None = ...,
uri: str | None = ...,
*,
legacy: Literal[True] | None = ...,
Expand All @@ -859,7 +860,7 @@ def unix_connect(

@overload
def unix_connect(
path: str | None = ...,
path: PathLike | None = ...,
uri: str | None = ...,
*,
legacy: Literal[False],
Expand All @@ -868,7 +869,7 @@ def unix_connect(


def unix_connect(
path: str | None = None,
path: PathLike | None = None,
uri: str | None = None,
*,
legacy: bool | None = None,
Expand Down
5 changes: 3 additions & 2 deletions src/websockets/sync/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any, Callable, Literal

from ..http11 import Request, Response
from ..typing import PathLike
from .server import Server, ServerConnection, serve


Expand All @@ -30,7 +31,7 @@ def route(

def unix_route(
url_map: Map,
path: str | None = None,
path: PathLike | None = None,
**kwargs: Any,
) -> Server:
raise ImportError("unix_route() requires werkzeug")
Expand Down Expand Up @@ -141,7 +142,7 @@ def process_request(

def unix_route(
url_map: Map,
path: str | None = None,
path: PathLike | None = None,
Comment thread
aaugustin marked this conversation as resolved.
**kwargs: Any,
) -> Server:
"""
Expand Down
7 changes: 4 additions & 3 deletions src/websockets/sync/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import hmac
import http
import logging
import os
import re
import selectors
import socket
Expand All @@ -28,7 +29,7 @@
from ..http11 import SERVER, Request, Response
from ..protocol import CONNECTING, OPEN, Event
from ..server import ServerProtocol
from ..typing import LoggerLike, Origin, StatusLike, Subprotocol
from ..typing import LoggerLike, Origin, PathLike, StatusLike, Subprotocol
from ..utils import get_socket_name
from .connection import Connection, broadcast
from .utils import Deadline
Expand Down Expand Up @@ -645,7 +646,7 @@ def handler(websocket):
if path is None:
raise ValueError("missing path argument")
kwargs.setdefault("family", socket.AF_UNIX)
sock = socket.create_server(path, **kwargs)
sock = socket.create_server(os.fspath(path), **kwargs)
else:
sock = socket.create_server((host, port), **kwargs)
else:
Expand Down Expand Up @@ -793,7 +794,7 @@ def protocol_select_subprotocol(

def unix_serve(
handler: Callable[[ServerConnection], None],
path: str | None = None,
path: PathLike | None = None,
**kwargs: Any,
) -> Server:
"""
Expand Down
5 changes: 3 additions & 2 deletions src/websockets/trio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from ..protocol import CONNECTING, Event
from ..proxy import Proxy, get_proxy, parse_proxy, prepare_connect_request
from ..streams import StreamReader
from ..typing import LoggerLike, Origin, Subprotocol
from ..typing import LoggerLike, Origin, PathLike, Subprotocol
from ..uri import WebSocketURI, parse_uri
from .connection import Connection
from .utils import race_events
Expand Down Expand Up @@ -482,6 +482,7 @@ def process_redirect(self, exc: Exception) -> Exception | str:
f"cannot follow cross-origin redirect to {new_uri} "
f"with a Unix socket"
)

# Cross-origin redirects when host and port are overridden are ill-defined.
if (
self.open_tcp_stream_kwargs.get("host") is not None
Expand Down Expand Up @@ -640,7 +641,7 @@ async def __aiter__(self) -> AsyncIterator[ClientConnection]:


def unix_connect(
path: str | None = None,
path: PathLike | None = None,
uri: str | None = None,
**kwargs: Any,
) -> connect:
Expand Down
6 changes: 4 additions & 2 deletions src/websockets/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import http
import logging
import os
from typing import Any, NewType, Sequence


Expand Down Expand Up @@ -36,10 +37,11 @@
LoggerLike = logging.Logger | logging.LoggerAdapter[Any]
"""Types accepted where a :class:`~logging.Logger` is expected."""

PathLike = str | bytes | os.PathLike[str] | os.PathLike[bytes]
"""Types accepted where a filesystem path is expected."""

StatusLike = http.HTTPStatus | int
"""
Types accepted where an :class:`~http.HTTPStatus` is expected."""
"""Types accepted where an :class:`~http.HTTPStatus` is expected."""


Origin = NewType("Origin", str)
Expand Down
8 changes: 8 additions & 0 deletions tests/asyncio/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import http
import logging
import os
import pathlib
import socket
import ssl
import sys
Expand Down Expand Up @@ -993,6 +994,13 @@ async def test_set_server_hostname(self):
ssl_object = client.transport.get_extra_info("ssl_object")
self.assertEqual(ssl_object.server_hostname, "overridden")

async def test_pathlib_path(self):
"""Client accepts a pathlib.Path object as the path argument."""
with temp_unix_socket_path() as path:
async with unix_serve(handler, path):
async with unix_connect(pathlib.Path(path)) as client:
self.assertEqual(client.protocol.state.name, "OPEN")

async def test_non_existing_path(self):
"""Client attempts to connect to a non-existing Unix socket path."""
with temp_unix_socket_path() as path:
Expand Down
8 changes: 8 additions & 0 deletions tests/asyncio/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import hmac
import http
import logging
import pathlib
import socket
import unittest

Expand Down Expand Up @@ -742,6 +743,13 @@ async def test_connection(self):
async with unix_connect(path) as client:
await self.assertEval(client, "ws.protocol.state.name", "OPEN")

async def test_pathlib_path(self):
"""Server accepts a pathlib.Path object as the path argument."""
with temp_unix_socket_path() as path:
async with unix_serve(handler, pathlib.Path(path)):
async with unix_connect(path) as client:
await self.assertEval(client, "ws.protocol.state.name", "OPEN")


@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets")
class SecureUnixServerTests(EvalShellMixin, unittest.IsolatedAsyncioTestCase):
Expand Down
8 changes: 8 additions & 0 deletions tests/sync/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import http
import logging
import os
import pathlib
import socket
import socketserver
import ssl
Expand Down Expand Up @@ -1033,6 +1034,13 @@ def test_set_server_hostname(self):
) as client:
self.assertEqual(client.socket.server_hostname, "overridden")

def test_pathlib_path(self):
"""Client accepts a pathlib.Path object as the path argument."""
with temp_unix_socket_path() as path:
with run_unix_server(path):
with unix_connect(pathlib.Path(path)) as client:
self.assertEqual(client.protocol.state.name, "OPEN")

def test_non_existing_path(self):
"""Client attempts to connect to a non-existing Unix socket path."""
with temp_unix_socket_path() as path:
Expand Down
8 changes: 8 additions & 0 deletions tests/sync/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import hmac
import http
import logging
import pathlib
import socket
import threading
import time
Expand Down Expand Up @@ -524,6 +525,13 @@ def test_connection(self):
with unix_connect(path) as client:
self.assertEval(client, "ws.protocol.state.name", "OPEN")

def test_pathlib_path(self):
"""Server accepts a pathlib.Path object as the path argument."""
with temp_unix_socket_path() as path:
with run_unix_server(pathlib.Path(path)):
with unix_connect(path) as client:
self.assertEval(client, "ws.protocol.state.name", "OPEN")


@unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets")
class SecureUnixServerTests(EvalShellMixin, unittest.TestCase):
Expand Down
8 changes: 8 additions & 0 deletions tests/trio/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import http
import logging
import os
import pathlib
import socket
import ssl
import sys
Expand Down Expand Up @@ -978,6 +979,13 @@ async def test_set_server_hostname(self):
ssl_object = client.stream._ssl_object
self.assertEqual(ssl_object.server_hostname, "overridden")

async def test_pathlib_path(self):
"""Client accepts a pathlib.Path object as the path argument."""
with temp_unix_socket_path() as path:
with run_unix_server(path):
async with unix_connect(pathlib.Path(path)) as client:
self.assertEqual(client.protocol.state.name, "OPEN")

async def test_non_existing_path(self):
"""Client attempts to connect to a non-existing Unix socket path."""
with temp_unix_socket_path() as path:
Expand Down