4
4
import json
5
5
import logging
6
6
from dataclasses import dataclass
7
- from typing import Any , Dict , Tuple , Union
7
+ from typing import Any , Dict , MutableMapping , Tuple , Union
8
8
from urllib import parse as urllib_parse
9
9
from uuid import uuid4
10
10
11
11
from sanic import Blueprint , Sanic , request , response
12
12
from sanic .config import Config
13
13
from sanic .models .asgi import ASGIScope
14
+ from sanic .server .websockets .connection import WebSocketConnection
14
15
from sanic_cors import CORS
15
- from websockets .legacy .protocol import WebSocketCommonProtocol
16
16
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
19
18
from idom .core .layout import Layout , LayoutEvent
20
19
from idom .core .serve import (
21
20
RecvCoroutine ,
27
26
from idom .core .types import RootComponentConstructor
28
27
29
28
from ._asgi import serve_development_asgi
29
+ from .hooks import ConnectionContext
30
+ from .hooks import use_connection as _use_connection
30
31
from .utils import safe_client_build_dir_path , safe_web_modules_dir_path
31
32
32
33
33
34
logger = logging .getLogger (__name__ )
34
35
35
- ConnectionContext : Context [Connection | None ] = create_context (None )
36
-
37
36
38
37
def configure (
39
38
app : Sanic , component : RootComponentConstructor , options : Options | None = None
@@ -65,50 +64,25 @@ async def serve_development_app(
65
64
await serve_development_asgi (app , host , port , started )
66
65
67
66
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
-
85
67
def use_request () -> request .Request :
86
68
"""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
98
70
99
71
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
103
75
104
- request : request .Request
105
- """The current request object"""
106
76
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
112
86
113
87
114
88
@dataclass
@@ -165,12 +139,36 @@ def _setup_single_view_dispatcher_route(
165
139
blueprint : Blueprint , constructor : RootComponentConstructor
166
140
) -> None :
167
141
async def model_stream (
168
- request : request .Request , socket : WebSocketCommonProtocol , path : str = ""
142
+ request : request .Request , socket : WebSocketConnection , path : str = ""
169
143
) -> 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
+
170
153
send , recv = _make_send_recv_callbacks (socket )
171
- conn = Connection (request , socket , path )
172
154
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
+ ),
174
172
send ,
175
173
recv ,
176
174
)
@@ -180,7 +178,7 @@ async def model_stream(
180
178
181
179
182
180
def _make_send_recv_callbacks (
183
- socket : WebSocketCommonProtocol ,
181
+ socket : WebSocketConnection ,
184
182
) -> Tuple [SendCoroutine , RecvCoroutine ]:
185
183
async def sock_send (value : VdomJsonPatch ) -> None :
186
184
await socket .send (json .dumps (value ))
@@ -192,3 +190,14 @@ async def sock_recv() -> LayoutEvent:
192
190
return LayoutEvent (** json .loads (data ))
193
191
194
192
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