From 92345b25de308d89773b819f09abcfc9bc0f5801 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind Date: Wed, 13 Aug 2025 20:43:20 +0530 Subject: [PATCH 1/2] Refactor endpoint parsing for better extensibility --- daphne/endpoints.py | 50 +++++++++++++++++++++++++++++++-------------- tests/test_cli.py | 23 ++++++++++++++++++++- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/daphne/endpoints.py b/daphne/endpoints.py index b97364f..6b89953 100644 --- a/daphne/endpoints.py +++ b/daphne/endpoints.py @@ -1,22 +1,42 @@ -def build_endpoint_description_strings( - host=None, port=None, unix_socket=None, file_descriptor=None -): +from abc import ABC, abstractmethod + +class Endpoint(ABC): + @abstractmethod + def parse(self, options): + pass + +class TCPEndpoint(Endpoint): + def parse(self, options): + if options.get("port") and options.get("host"): + host = options["host"].strip("[]").replace(":", r"\:") + return f"tcp:port={int(options['port'])}:interface={host}" + elif options.get("port") or options.get("host"): + raise ValueError("TCP binding requires both port and host kwargs.") + return None + +class UNIXEndpoint(Endpoint): + def parse(self, options): + if options.get("unix_socket"): + return f"unix:{options['unix_socket']}" + return None + +class FileDescriptorEndpoint(Endpoint): + def parse(self, options): + if options.get("file_descriptor") is not None: + return f"fd:fileno={int(options['file_descriptor'])}" + return None + +endpoint_parsers = [TCPEndpoint(), UNIXEndpoint(), FileDescriptorEndpoint()] + +def build_endpoint_description_strings(**kwargs): """ Build a list of twisted endpoint description strings that the server will listen on. This is to streamline the generation of twisted endpoint description strings from easier to use command line args such as host, port, unix sockets etc. """ socket_descriptions = [] - if host and port is not None: - host = host.strip("[]").replace(":", r"\:") - socket_descriptions.append("tcp:port=%d:interface=%s" % (int(port), host)) - elif any([host, port]): - raise ValueError("TCP binding requires both port and host kwargs.") - - if unix_socket: - socket_descriptions.append("unix:%s" % unix_socket) - - if file_descriptor is not None: - socket_descriptions.append("fd:fileno=%d" % int(file_descriptor)) - + for parser in endpoint_parsers: + description = parser.parse(kwargs) + if description: + socket_descriptions.append(description) return socket_descriptions diff --git a/tests/test_cli.py b/tests/test_cli.py index 59f44e7..aee7f8b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,7 +4,11 @@ from argparse import ArgumentError from unittest import TestCase, skipUnless from daphne.cli import CommandLineInterface -from daphne.endpoints import build_endpoint_description_strings as build +from daphne.endpoints import ( + build_endpoint_description_strings as build, + endpoint_parsers, + Endpoint, +) class TestEndpointDescriptions(TestCase): @@ -64,6 +68,23 @@ class TestEndpointDescriptions(TestCase): ), ) + def test_custom_endpoint(self): + class CustomEndpoint(Endpoint): + def parse(self, options): + if options.get("custom"): + return f"custom:{options['custom']}" + return None + + endpoint_parsers.append(CustomEndpoint()) + + self.assertEqual( + build(custom="myprotocol"), + ["custom:myprotocol"], + ) + + # Cleanup custom endpoint parser + endpoint_parsers.pop() + class TestCLIInterface(TestCase): """ From 13beeeeffbcbadfb53ee09373aae5cd740105584 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:16:20 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- daphne/endpoints.py | 6 ++++++ tests/test_cli.py | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/daphne/endpoints.py b/daphne/endpoints.py index 6b89953..b25a7e1 100644 --- a/daphne/endpoints.py +++ b/daphne/endpoints.py @@ -1,10 +1,12 @@ from abc import ABC, abstractmethod + class Endpoint(ABC): @abstractmethod def parse(self, options): pass + class TCPEndpoint(Endpoint): def parse(self, options): if options.get("port") and options.get("host"): @@ -14,20 +16,24 @@ class TCPEndpoint(Endpoint): raise ValueError("TCP binding requires both port and host kwargs.") return None + class UNIXEndpoint(Endpoint): def parse(self, options): if options.get("unix_socket"): return f"unix:{options['unix_socket']}" return None + class FileDescriptorEndpoint(Endpoint): def parse(self, options): if options.get("file_descriptor") is not None: return f"fd:fileno={int(options['file_descriptor'])}" return None + endpoint_parsers = [TCPEndpoint(), UNIXEndpoint(), FileDescriptorEndpoint()] + def build_endpoint_description_strings(**kwargs): """ Build a list of twisted endpoint description strings that the server will listen on. diff --git a/tests/test_cli.py b/tests/test_cli.py index aee7f8b..f6c6f78 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,10 +5,12 @@ from unittest import TestCase, skipUnless from daphne.cli import CommandLineInterface from daphne.endpoints import ( - build_endpoint_description_strings as build, - endpoint_parsers, Endpoint, ) +from daphne.endpoints import build_endpoint_description_strings as build +from daphne.endpoints import ( + endpoint_parsers, +) class TestEndpointDescriptions(TestCase):