Coverage for src/lexigram/graphql/subscriptions/transport/handler.py: 55%
31 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""GraphQL WebSocket Transport for subscriptions.
3Provides WebSocket transport implementation using graphql-transport-ws protocol.
4"""
6from __future__ import annotations
8import asyncio
9from dataclasses import dataclass, field
10from typing import TYPE_CHECKING, Any, cast
12from lexigram import serialization as json
13from lexigram.graphql.subscriptions.protocol import GQLWSMessageType
14from lexigram.graphql.subscriptions.transport._transport import GraphQLWSTransport
15from lexigram.graphql.types import SubscriptionInfo
16from lexigram.logging import get_logger
18if TYPE_CHECKING:
19 from collections.abc import AsyncIterator, Awaitable, Callable
21 from lexigram.contracts.graphql.protocols import SubscriptionAuthHandlerProtocol
22 from lexigram.contracts.web import WebSocketProtocol
24logger = get_logger(__name__)
27async def graphql_ws_endpoint(websocket: Any) -> None:
28 """ASGI-compatible endpoint for GraphQL WebSocket connections.
30 This is a simple endpoint that can be used with Starlette.
31 For production use, configure with proper execute/subscribe handlers.
33 Example:
34 from starlette.routing import Route
35 from lexigram.graphql import constants as const
37 async def get_transport():
38 from lexigram.graphql.schema import build_schema
39 # Configure with your schema
40 return GraphQLWSTransport(
41 execute=execute_fn,
42 subscribe=subscribe_fn,
43 )
45 routes = [
46 Route(const.DEFAULT_SUBSCRIPTIONS_PATH, graphql_ws_endpoint),
47 ]
48 """
49 transport = GraphQLWSTransport()
50 await transport.handle(websocket)
53def create_ws_route(path: str) -> Any:
54 """Create a WebSocket route for GraphQL subscriptions.
56 Returns a Starlette ``Route`` when ``starlette`` is installed. The return
57 type is ``Any`` so callers are not forced to depend on Starlette directly.
59 Args:
60 path: The WebSocket path.
62 Returns:
63 A Starlette Route object.
64 """
65 from starlette.routing import Route # deferred — Starlette is optional
67 return Route(path, graphql_ws_endpoint)
70class GraphQLWSHandler:
71 """Handler class for GraphQL WebSocket subscriptions.
73 This class provides a more configurable way to handle subscriptions.
74 """
76 def __init__(
77 self,
78 execute: Callable[..., Awaitable[Any]] | None = None,
79 subscribe: Callable[..., Awaitable[Any]] | None = None,
80 connection_init_timeout: float = 10.0,
81 keepalive_interval: float = 30.0,
82 context_factory: Any | None = None,
83 auth_handler: Any | None = None,
84 ):
85 """Initialize the handler.
87 Args:
88 execute: Function to execute GraphQL operations.
89 subscribe: Function to subscribe to GraphQL operations.
90 connection_init_timeout: Timeout for connection_init in seconds.
91 keepalive_interval: Interval for keep-alive messages in seconds.
92 context_factory: Optional factory to create GraphQL context.
93 auth_handler: Optional handler for connection authentication.
94 """
95 self._transport = GraphQLWSTransport(
96 execute=execute,
97 subscribe=subscribe,
98 connection_init_timeout=connection_init_timeout,
99 keepalive_interval=keepalive_interval,
100 context_factory=context_factory,
101 auth_handler=auth_handler,
102 )
104 async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
105 """Handle WebSocket connection.
107 Args:
108 scope: ASGI scope
109 receive: ASGI receive
110 send: ASGI send
111 """
112 from starlette.websockets import WebSocket
114 websocket = WebSocket(scope=scope, receive=receive, send=send)
115 await self._transport.handle(websocket, app=scope.get("app"))
117 @classmethod
118 def create_from_schema(
119 cls,
120 schema: Any,
121 execute: Callable[..., Awaitable[Any]] | None = None,
122 context_factory: Any | None = None,
123 auth_handler: Any | None = None,
124 ) -> GraphQLWSHandler:
125 """Create a handler from a Strawberry schema.
127 Args:
128 schema: The Strawberry schema.
129 execute: Optional custom execute function.
130 context_factory: Optional factory to create GraphQL context.
131 auth_handler: Optional handler for connection authentication.
133 Returns:
134 A configured GraphQLWSHandler.
135 """
136 # Get execute/subscribe from schema if not provided
137 if execute is None and hasattr(schema, "execute"):
138 execute = schema.execute
140 subscribe = None
141 if hasattr(schema, "subscribe"):
142 subscribe = schema.subscribe
144 return cls(
145 execute=execute,
146 subscribe=subscribe,
147 context_factory=context_factory,
148 auth_handler=auth_handler,
149 )