11"""
2- Utility functions for WebSocket route discovery and inspection .
2+ WebSocket playground utilities .
33
4- This module provides tools to dynamically discover WebSocket routes
5- in a Django Channels application and generate example messages for
6- WebSocket consumer endpoints.
4+ This module provides specialized utilities for the WebSocket playground,
5+ transforming route information into a format suitable for display and interaction.
76"""
87
98import inspect
1716 get_type_hints ,
1817)
1918
20- from channels .routing import URLRouter , get_default_application
2119from django .http import HttpRequest
2220
2321from polyfactory .factories .pydantic_factory import ModelFactory
2422from pydantic import BaseModel
2523
2624from chanx .messages .base import BaseIncomingMessage
2725from chanx .utils .logging import logger
26+ from chanx .utils .websocket import RouteInfo , get_websocket_routes , transform_routes
2827
2928
3029class MessageExample (TypedDict ):
@@ -51,185 +50,51 @@ class WebSocketRoute(TypedDict, total=False):
5150 message_examples : list [MessageExample ]
5251
5352
54- def get_websocket_routes (request : HttpRequest | None = None ) -> list [WebSocketRoute ]:
53+ def get_playground_websocket_routes (
54+ request : HttpRequest | None = None ,
55+ ) -> list [WebSocketRoute ]:
5556 """
56- Extract all WebSocket routes from the ASGI application .
57+ Get WebSocket routes formatted for the playground .
5758
58- This function traverses the Django Channels routing configuration
59- to discover all available WebSocket endpoints, their paths, and
60- generates example messages for each endpoint based on the
61- message schema defined in the consumer.
59+ Uses the core WebSocket route discovery mechanism and transforms the routes
60+ into a format suitable for the playground UI, including example messages.
6261
6362 Args:
6463 request: The HTTP request object, used to determine the current domain.
6564 If None, defaults to localhost:8000.
6665
6766 Returns:
68- A list of dictionaries containing route information including URL,
69- description, and example messages.
67+ A list of WebSocketRoute objects with UI-friendly information.
7068 """
71- endpoints : list [WebSocketRoute ] = []
69+ # Get raw routes from the core utility
70+ raw_routes = get_websocket_routes (request )
7271
73- # Determine the WebSocket base URL based on the request
74- ws_base_url : str = _get_websocket_base_url ( request )
72+ # Transform routes into the format needed for the playground
73+ return transform_routes ( raw_routes , _transform_route_for_playground )
7574
76- # Extract the WebSocket protocol handler from the ASGI application
77- application = get_default_application ()
78- if hasattr (application , "application_mapping" ):
79- # If it's a ProtocolTypeRouter
80- ws_app : Any = application .application_mapping .get ("websocket" )
8175
82- # Extract routes
83- if ws_app :
84- _traverse_middleware (ws_app , "" , endpoints , ws_base_url )
85-
86- return endpoints
87-
88-
89- def _get_websocket_base_url (request : HttpRequest | None ) -> str :
90- """
91- Determine the WebSocket base URL based on the request.
92-
93- Constructs a WebSocket URL (ws:// or wss://) based on the
94- domain in the request object.
95-
96- Args:
97- request: The HTTP request object.
98-
99- Returns:
100- The WebSocket base URL (ws:// or wss:// followed by domain).
101- """
102- if request is None :
103- return "ws://localhost:8000"
104-
105- # Get the current domain from the request
106- domain : str = request .get_host ()
107-
108- # Determine if we should use secure WebSockets (wss://) based on the request
109- is_secure : bool = request .is_secure ()
110- protocol : str = "wss://" if is_secure else "ws://"
111-
112- return f"{ protocol } { domain } "
113-
114-
115- def _traverse_middleware (
116- app : Any , prefix : str , endpoints : list [WebSocketRoute ], ws_base_url : str
117- ) -> None :
118- """
119- Traverse through middleware layers to find the URLRouter.
120-
121- Recursively explores the middleware stack to find URLRouter instances
122- and extract route information from them. It uses the fact that
123- middleware typically stores the inner app as self.inner.
124-
125- Args:
126- app: The current application or middleware to traverse.
127- prefix: URL prefix accumulated so far.
128- endpoints: List to store discovered endpoints.
129- ws_base_url: Base URL for WebSocket connections.
76+ def _transform_route_for_playground (route : RouteInfo ) -> WebSocketRoute :
13077 """
131- # Skip if app is None
132- if app is None :
133- return
134-
135- # If it's a URLRouter, extract routes
136- if isinstance (app , URLRouter ):
137- _extract_routes_from_router (app , prefix , endpoints , ws_base_url )
138- return
139-
140- # Try to access the inner application (standard middleware pattern)
141- inner_app : Any | None = getattr (app , "inner" , None )
78+ Transform a raw route into a playground-friendly format.
14279
143- # If inner isn't found, try other common attributes that might hold the next app
144- if inner_app is None :
145- for attr_name in ["app" , "application" ]:
146- inner_app = getattr (app , attr_name , None )
147- if inner_app is not None :
148- break
149-
150- # If we found an inner app, continue traversal
151- if inner_app is not None :
152- _traverse_middleware (inner_app , prefix , endpoints , ws_base_url )
153-
154-
155- def _extract_routes_from_router (
156- router : URLRouter , prefix : str , endpoints : list [WebSocketRoute ], ws_base_url : str
157- ) -> None :
158- """
159- Extract routes from a URLRouter object.
160-
161- Processes each route in the router, extracting path patterns and
162- handler information, and recursively traversing nested routers.
163-
164- Args:
165- router: The router to extract routes from.
166- prefix: URL prefix accumulated so far.
167- endpoints: List to store discovered endpoints.
168- ws_base_url: Base URL for WebSocket connections.
169- """
170- for route in router .routes :
171- try :
172- # Get the pattern string
173- pattern : str = _get_pattern_string (route )
174-
175- # Build the full path
176- full_path : str = f"{ prefix } { pattern } "
177-
178- # Get the handler
179- handler : Any = route .callback
180-
181- # If it's another router, recurse into it
182- if isinstance (handler , URLRouter ):
183- _extract_routes_from_router (handler , full_path , endpoints , ws_base_url )
184- else :
185- # For consumers, get info with message examples
186- endpoint_info : WebSocketRoute = _get_handler_info (
187- handler , full_path , ws_base_url
188- )
189- endpoints .append (endpoint_info )
190- except AttributeError as e :
191- # More specific error for attribute issues
192- logger .exception (
193- f"AttributeError while parsing route: { ws_base_url } /{ prefix } . Error: { str (e )} "
194- )
195- except Exception as e :
196- # For other unexpected errors
197- logger .exception (
198- f"Error parsing route: { ws_base_url } /{ prefix } . Error: { str (e )} "
199- )
200-
201-
202- def _get_pattern_string (route : Any ) -> str :
203- """
204- Extract pattern string from a route object.
205-
206- Handles different route pattern implementations to extract
207- the URL pattern string.
80+ This function extracts metadata from a WebSocket consumer and formats it
81+ for display in the playground UI, including generating example messages.
20882
20983 Args:
210- route: The route object to extract pattern from.
84+ route: The RouteInfo dataclass instance
21185
21286 Returns:
213- The cleaned URL pattern string.
87+ A WebSocketRoute with UI-friendly information
21488 """
215- if hasattr (route , "pattern" ):
216- # For URLRoute
217- if hasattr (route .pattern , "pattern" ):
218- pattern : str = route .pattern .pattern
219- else :
220- # For RoutePattern
221- pattern = str (route .pattern )
222- else :
223- pattern = str (route )
224-
225- # Clean up the pattern string
226- pattern = pattern .replace ("^" , "" ).replace ("$" , "" )
227- return pattern
89+ # Get handler info with examples for the playground
90+ return _get_handler_info (
91+ handler = route .handler , path = route .path , ws_base_url = route .base_url
92+ )
22893
22994
23095def _get_handler_info (handler : Any , path : str , ws_base_url : str ) -> WebSocketRoute :
23196 """
232- Extract information about a route handler.
97+ Extract information about a route handler for the playground .
23398
23499 Extracts metadata from a WebSocket consumer including its name,
235100 description, and message schema, and generates example messages.
@@ -240,8 +105,7 @@ def _get_handler_info(handler: Any, path: str, ws_base_url: str) -> WebSocketRou
240105 ws_base_url: Base URL for WebSocket connections.
241106
242107 Returns:
243- Information about the handler including name, URL, description,
244- and example messages.
108+ Information about the handler formatted for the playground.
245109 """
246110 # Default values
247111 name : str = getattr (handler , "__name__" , "Unknown" )
@@ -283,7 +147,7 @@ def _create_example(msg_type: type[BaseModel]) -> MessageExample:
283147 Returns:
284148 A formatted example with name, description, and sample data.
285149 """
286- description = inspect .getdoc (msg_type ) or f"Example of { msg_type .__name__ } "
150+ description : str = inspect .getdoc (msg_type ) or f"Example of { msg_type .__name__ } "
287151
288152 # Create the example using the factory
289153 factory = ModelFactory .create_factory (model = msg_type )
0 commit comments