44import json
55import logging
66from dataclasses import dataclass
7- from typing import Any , Dict , Tuple , Union
7+ from typing import Any , Dict , MutableMapping , Tuple , Union
88from urllib import parse as urllib_parse
99from uuid import uuid4
1010
1111from sanic import Blueprint , Sanic , request , response
1212from sanic .config import Config
1313from sanic .models .asgi import ASGIScope
14+ from sanic .server .websockets .connection import WebSocketConnection
1415from sanic_cors import CORS
15- from websockets .legacy .protocol import WebSocketCommonProtocol
1616
17- from idom .backend .types import Location
18- from idom .core .hooks import Context , create_context , use_context
17+ from idom .backend .types import Connection , Location
1918from idom .core .layout import Layout , LayoutEvent
2019from idom .core .serve import (
2120 RecvCoroutine ,
2726from idom .core .types import RootComponentConstructor
2827
2928from ._asgi import serve_development_asgi
29+ from .hooks import ConnectionContext
30+ from .hooks import use_connection as _use_connection
3031from .utils import safe_client_build_dir_path , safe_web_modules_dir_path
3132
3233
3334logger = logging .getLogger (__name__ )
3435
35- ConnectionContext : Context [Connection | None ] = create_context (None )
36-
3736
3837def configure (
3938 app : Sanic , component : RootComponentConstructor , options : Options | None = None
@@ -65,50 +64,25 @@ async def serve_development_app(
6564 await serve_development_asgi (app , host , port , started )
6665
6766
68- def use_location () -> Location :
69- """Get the current route as a string"""
70- conn = use_connection ()
71- search = conn .request .query_string
72- return Location (pathname = "/" + conn .path , search = "?" + search if search else "" )
73-
74-
75- def use_scope () -> ASGIScope :
76- """Get the current ASGI scope"""
77- app = use_request ().app
78- try :
79- asgi_app = app ._asgi_app
80- except AttributeError : # pragma: no cover
81- raise RuntimeError ("No scope. Sanic may not be running with an ASGI server" )
82- return asgi_app .transport .scope
83-
84-
8567def use_request () -> request .Request :
8668 """Get the current ``Request``"""
87- return use_connection ().request
88-
89-
90- def use_connection () -> Connection :
91- """Get the current :class:`Connection`"""
92- connection = use_context (ConnectionContext )
93- if connection is None :
94- raise RuntimeError ( # pragma: no cover
95- "No connection. Are you running with a Sanic server?"
96- )
97- return connection
69+ return use_connection ().carrier .request
9870
9971
100- @ dataclass
101- class Connection :
102- """A simple wrapper for holding connection information"""
72+ def use_websocket () -> WebSocketConnection :
73+ """Get the current websocket"""
74+ return use_connection (). carrier . websocket
10375
104- request : request .Request
105- """The current request object"""
10676
107- websocket : WebSocketCommonProtocol
108- """A handle to the current websocket"""
109-
110- path : str
111- """The current path being served"""
77+ def use_connection () -> Connection [_SanicCarrier ]:
78+ """Get the current :class:`Connection`"""
79+ conn = _use_connection ()
80+ if not isinstance (conn .carrier , _SanicCarrier ):
81+ raise TypeError ( # pragma: no cover
82+ f"Connection has unexpected carrier { conn .carrier } . "
83+ "Are you running with a Sanic server?"
84+ )
85+ return conn
11286
11387
11488@dataclass
@@ -165,12 +139,36 @@ def _setup_single_view_dispatcher_route(
165139 blueprint : Blueprint , constructor : RootComponentConstructor
166140) -> None :
167141 async def model_stream (
168- request : request .Request , socket : WebSocketCommonProtocol , path : str = ""
142+ request : request .Request , socket : WebSocketConnection , path : str = ""
169143 ) -> None :
144+ app = request .app
145+ try :
146+ asgi_app = app ._asgi_app
147+ except AttributeError : # pragma: no cover
148+ logger .warning ("No scope. Sanic may not be running with an ASGI server" )
149+ scope : MutableMapping [str , Any ] = {}
150+ else :
151+ scope = asgi_app .transport .scope
152+
170153 send , recv = _make_send_recv_callbacks (socket )
171- conn = Connection (request , socket , path )
172154 await serve_json_patch (
173- Layout (ConnectionContext (constructor (), value = conn )),
155+ Layout (
156+ ConnectionContext (
157+ constructor (),
158+ value = Connection (
159+ scope = scope ,
160+ location = Location (
161+ pathname = f"/{ path } " ,
162+ search = (
163+ f"?{ request .query_string } "
164+ if request .query_string
165+ else ""
166+ ),
167+ ),
168+ carrier = _SanicCarrier (request , socket ),
169+ ),
170+ )
171+ ),
174172 send ,
175173 recv ,
176174 )
@@ -180,7 +178,7 @@ async def model_stream(
180178
181179
182180def _make_send_recv_callbacks (
183- socket : WebSocketCommonProtocol ,
181+ socket : WebSocketConnection ,
184182) -> Tuple [SendCoroutine , RecvCoroutine ]:
185183 async def sock_send (value : VdomJsonPatch ) -> None :
186184 await socket .send (json .dumps (value ))
@@ -192,3 +190,14 @@ async def sock_recv() -> LayoutEvent:
192190 return LayoutEvent (** json .loads (data ))
193191
194192 return sock_send , sock_recv
193+
194+
195+ @dataclass
196+ class _SanicCarrier :
197+ """A simple wrapper for holding connection information"""
198+
199+ request : request .Request
200+ """The current request object"""
201+
202+ websocket : WebSocketConnection
203+ """A handle to the current websocket"""
0 commit comments