Skip to content

Commit 3c86b58

Browse files
committed
WIP
1 parent ae8f200 commit 3c86b58

File tree

5 files changed

+50
-321
lines changed

5 files changed

+50
-321
lines changed

chanx/core/websocket.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,9 @@ async def websocket_connect(self, message: WebSocketConnectEvent) -> None:
268268
if self.authenticator:
269269
auth_result = await self.authenticator.authenticate(self.scope)
270270

271-
if not auth_result:
272-
await self.close()
273-
return
271+
if not auth_result:
272+
await self.close()
273+
return
274274

275275
await self.post_authentication()
276276

chanx/ext/fast_channels/__init__.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,15 @@
99
- AsyncAPI documentation generation
1010
- Interactive WebSocket playground
1111
- FastAPI integration utilities
12-
13-
Usage:
14-
from chanx.ext.fast_channels import add_asyncapi_routes
15-
from fastapi import FastAPI
16-
17-
app = FastAPI()
18-
add_asyncapi_routes(app)
1912
"""
2013

21-
from .discovery import auto_discover_and_register, discover_consumers_in_module
2214
from .views import (
23-
add_asyncapi_routes,
2415
asyncapi_docs,
2516
asyncapi_spec_json,
2617
asyncapi_spec_yaml,
2718
)
2819

2920
__all__ = [
30-
"auto_discover_and_register",
31-
"discover_consumers_in_module",
32-
"add_asyncapi_routes",
3321
"asyncapi_spec_json",
3422
"asyncapi_spec_yaml",
3523
"asyncapi_docs",

chanx/ext/fast_channels/discovery.py

Lines changed: 2 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -5,190 +5,11 @@
55
in FastAPI applications, similar to Django's app discovery.
66
"""
77

8-
import importlib
9-
import inspect
108
from typing import Any
119

12-
from chanx.asyncapi.generator import AsyncAPIGenerator
13-
from chanx.core.registry import message_registry
14-
from chanx.core.websocket import AsyncJsonWebsocketConsumer
15-
from chanx.routing.discovery import RouteDiscovery, RouteInfo
16-
10+
from starlette.routing import Mount, WebSocketRoute
1711

18-
def discover_consumers_in_module(
19-
module_name: str,
20-
) -> list[type[AsyncJsonWebsocketConsumer]]:
21-
"""
22-
Discover chanx consumers in a given module.
23-
24-
Args:
25-
module_name: The module name to scan for consumers (e.g., 'app.consumers')
26-
27-
Returns:
28-
List of discovered consumer classes
29-
"""
30-
try:
31-
module = importlib.import_module(module_name)
32-
consumers = []
33-
34-
for name in dir(module):
35-
obj = getattr(module, name)
36-
if (
37-
inspect.isclass(obj)
38-
and issubclass(obj, AsyncJsonWebsocketConsumer)
39-
and obj != AsyncJsonWebsocketConsumer
40-
):
41-
consumers.append(obj)
42-
43-
return consumers
44-
except ImportError:
45-
return []
46-
47-
48-
def discover_consumers_in_packages(
49-
package_names: list[str],
50-
) -> list[type[AsyncJsonWebsocketConsumer]]:
51-
"""
52-
Discover chanx consumers in multiple packages.
53-
54-
Args:
55-
package_names: List of package names to scan
56-
57-
Returns:
58-
List of all discovered consumer classes
59-
"""
60-
all_consumers = []
61-
62-
for package in package_names:
63-
# Try common consumer module names
64-
for module_suffix in ["consumers", "consumer", "websockets", "ws"]:
65-
module_name = f"{package}.{module_suffix}"
66-
consumers = discover_consumers_in_module(module_name)
67-
all_consumers.extend(consumers)
68-
69-
return all_consumers
70-
71-
72-
def register_consumers_with_message_registry(
73-
consumers: list[type[AsyncJsonWebsocketConsumer]],
74-
) -> None:
75-
"""
76-
Register discovered consumers with the message registry.
77-
78-
Args:
79-
consumers: List of consumer classes to register
80-
"""
81-
for consumer_class in consumers:
82-
# Process message handlers
83-
for attr_name in dir(consumer_class):
84-
if attr_name.startswith("_"):
85-
continue
86-
87-
try:
88-
attr = getattr(consumer_class, attr_name)
89-
if hasattr(attr, "_ws_handler_info"):
90-
handler_info = attr._ws_handler_info
91-
input_type = handler_info.get("input_type")
92-
output_type = handler_info.get("output_type")
93-
94-
if input_type:
95-
message_registry.add(input_type, consumer_class.__name__)
96-
if output_type:
97-
message_registry.add(output_type, consumer_class.__name__)
98-
99-
elif hasattr(attr, "_event_handler_info"):
100-
handler_info = attr._event_handler_info
101-
input_type = handler_info.get("input_type")
102-
output_type = handler_info.get("output_type")
103-
104-
if input_type:
105-
message_registry.add(input_type, consumer_class.__name__)
106-
if output_type:
107-
message_registry.add(output_type, consumer_class.__name__)
108-
109-
except (AttributeError, TypeError):
110-
continue
111-
112-
113-
def auto_discover_and_register(package_names: list[str] = None) -> dict[str, Any]:
114-
"""
115-
Auto-discover consumers and register them with the message registry.
116-
117-
Args:
118-
package_names: List of package names to scan. If None, uses common patterns.
119-
120-
Returns:
121-
Dictionary with discovery results and statistics
122-
"""
123-
if package_names is None:
124-
# Try to auto-detect common package structures
125-
import sys
126-
127-
package_names = []
128-
129-
# Look for common app structures
130-
for module_name in sys.modules:
131-
if (
132-
"." not in module_name
133-
and module_name not in ["chanx", "fast_channels", "channels"]
134-
and not module_name.startswith("_")
135-
):
136-
package_names.append(module_name)
137-
138-
consumers = discover_consumers_in_packages(package_names)
139-
register_consumers_with_message_registry(consumers)
140-
141-
return {
142-
"discovered_consumers": len(consumers),
143-
"consumer_classes": [c.__name__ for c in consumers],
144-
"scanned_packages": package_names,
145-
}
146-
147-
148-
def get_consumer_info(
149-
consumer_class: type[AsyncJsonWebsocketConsumer],
150-
) -> dict[str, Any]:
151-
"""
152-
Get detailed information about a consumer class.
153-
154-
Args:
155-
consumer_class: The consumer class to analyze
156-
157-
Returns:
158-
Dictionary with consumer information
159-
"""
160-
info = {
161-
"name": consumer_class.__name__,
162-
"module": consumer_class.__module__,
163-
"doc": consumer_class.__doc__,
164-
"handlers": [],
165-
"channel_info": getattr(consumer_class, "_channel_info", None),
166-
}
167-
168-
# Analyze handlers
169-
for attr_name in dir(consumer_class):
170-
if attr_name.startswith("_"):
171-
continue
172-
173-
try:
174-
attr = getattr(consumer_class, attr_name)
175-
176-
if hasattr(attr, "_ws_handler_info"):
177-
handler_info = attr._ws_handler_info.copy()
178-
handler_info["type"] = "websocket"
179-
handler_info["method"] = attr_name
180-
info["handlers"].append(handler_info)
181-
182-
elif hasattr(attr, "_event_handler_info"):
183-
handler_info = attr._event_handler_info.copy()
184-
handler_info["type"] = "event"
185-
handler_info["method"] = attr_name
186-
info["handlers"].append(handler_info)
187-
188-
except (AttributeError, TypeError):
189-
continue
190-
191-
return info
12+
from chanx.routing.discovery import RouteDiscovery, RouteInfo
19213

19314

19415
class FastAPIRouteDiscovery(RouteDiscovery):
@@ -229,7 +50,6 @@ def _walk_routes(
22950
base_url: Base WebSocket URL
23051
prefix: Current path prefix
23152
"""
232-
from starlette.routing import Mount, WebSocketRoute
23353

23454
# Skip if application doesn't have routes (e.g., StaticFiles)
23555
if not hasattr(application, "routes"):
@@ -305,21 +125,6 @@ def _extract_path_params(self, path: str) -> dict[str, str] | None:
305125

306126
return params if params else None
307127

308-
def _discover_from_consumers(self, routes: list[RouteInfo], base_url: str) -> None:
309-
"""Create routes from discovered consumers (fallback method)."""
310-
discovery_result = auto_discover_and_register()
311-
312-
# Create basic routes for each consumer (simplified)
313-
for consumer_name in discovery_result["consumer_classes"]:
314-
# Create a basic route - in practice you'd want more sophisticated discovery
315-
route = RouteInfo(
316-
path=f"/ws/{consumer_name.lower().replace('consumer', '')}",
317-
handler=None, # Would be the actual consumer instance
318-
base_url=base_url,
319-
consumer=None, # Would be the consumer class
320-
)
321-
routes.append(route)
322-
323128
def get_websocket_application(self) -> Any:
324129
"""Get the FastAPI WebSocket application."""
325130
return self.app
@@ -329,44 +134,3 @@ def extract_routes_from_router(
329134
) -> None:
330135
"""Extract routes from FastAPI router (using the walk method instead)."""
331136
self._walk_routes(router, routes, base_url, prefix)
332-
333-
334-
def generate_fastapi_asyncapi_spec(
335-
consumers: list[type[AsyncJsonWebsocketConsumer]] = None,
336-
app=None,
337-
title: str = "FastAPI WebSocket API",
338-
version: str = "1.0.0",
339-
description: str = "AsyncAPI specification for FastAPI application using chanx framework",
340-
) -> dict[str, Any]:
341-
"""
342-
Generate AsyncAPI specification for FastAPI application.
343-
344-
Args:
345-
consumers: List of consumer classes. If None, auto-discovers.
346-
app: FastAPI application instance
347-
title: API title
348-
version: API version
349-
description: API description
350-
351-
Returns:
352-
AsyncAPI specification dictionary
353-
"""
354-
# Auto-discover consumers if not provided
355-
if consumers is None:
356-
auto_discover_and_register()
357-
358-
# Create route discovery
359-
discovery = FastAPIRouteDiscovery(app)
360-
routes = discovery.discover_routes()
361-
362-
# Generate AsyncAPI spec using the existing generator
363-
generator = AsyncAPIGenerator(
364-
routes=routes,
365-
title=title,
366-
version=version,
367-
description=description,
368-
server_url="localhost:8000",
369-
server_protocol="ws",
370-
)
371-
372-
return generator.generate()

0 commit comments

Comments
 (0)