55import re
66import traceback
77import urllib .parse
8- from collections .abc import Coroutine , Iterable
8+ from collections .abc import Iterable
99from dataclasses import dataclass
1010from pathlib import Path
11- from typing import Any , Callable
11+ from typing import Any
1212
1313import orjson
14+ from asgiref import typing as asgi_types
1415from asgiref .compatibility import guarantee_single_callable
1516from servestatic import ServeStaticASGI
1617from typing_extensions import Unpack
@@ -33,7 +34,7 @@ class ReactPyMiddleware:
3334
3435 def __init__ (
3536 self ,
36- app : Callable [..., Coroutine ] ,
37+ app : asgi_types . ASGIApplication ,
3738 root_components : Iterable [str ],
3839 ** settings : Unpack [ReactPyConfig ],
3940 ) -> None :
@@ -61,7 +62,7 @@ def __init__(
6162 self .static_pattern = re .compile (f"^{ self .static_path } .*" )
6263
6364 # Component attributes
64- self .user_app = guarantee_single_callable (app )
65+ self .user_app : asgi_types . ASGI3Application = guarantee_single_callable (app ) # type: ignore
6566 self .root_components = import_components (root_components )
6667
6768 # Directory attributes
@@ -84,9 +85,9 @@ def __init__(
8485
8586 async def __call__ (
8687 self ,
87- scope : dict [ str , Any ] ,
88- receive : Callable [..., Coroutine ] ,
89- send : Callable [..., Coroutine ] ,
88+ scope : asgi_types . Scope ,
89+ receive : asgi_types . ASGIReceiveCallable ,
90+ send : asgi_types . ASGISendCallable ,
9091 ) -> None :
9192 """The ASGI entrypoint that determines whether ReactPy should route the
9293 request to ourselves or to the user application."""
@@ -105,13 +106,13 @@ async def __call__(
105106 # Serve the user's application
106107 await self .user_app (scope , receive , send )
107108
108- def match_dispatch_path (self , scope : dict ) -> bool :
109+ def match_dispatch_path (self , scope : asgi_types . WebSocketScope ) -> bool :
109110 return bool (re .match (self .dispatcher_pattern , scope ["path" ]))
110111
111- def match_static_path (self , scope : dict ) -> bool :
112+ def match_static_path (self , scope : asgi_types . HTTPScope ) -> bool :
112113 return bool (re .match (self .static_pattern , scope ["path" ]))
113114
114- def match_web_modules_path (self , scope : dict ) -> bool :
115+ def match_web_modules_path (self , scope : asgi_types . HTTPScope ) -> bool :
115116 return bool (re .match (self .js_modules_pattern , scope ["path" ]))
116117
117118
@@ -121,19 +122,21 @@ class ComponentDispatchApp:
121122
122123 async def __call__ (
123124 self ,
124- scope : dict [ str , Any ] ,
125- receive : Callable [..., Coroutine ] ,
126- send : Callable [..., Coroutine ] ,
125+ scope : asgi_types . WebSocketScope ,
126+ receive : asgi_types . ASGIReceiveCallable ,
127+ send : asgi_types . ASGISendCallable ,
127128 ) -> None :
128129 """ASGI app for rendering ReactPy Python components."""
129- dispatcher : asyncio .Task | None = None
130- recv_queue : asyncio .Queue = asyncio .Queue ()
130+ dispatcher : asyncio .Task [ Any ] | None = None
131+ recv_queue : asyncio .Queue [ dict [ str , Any ]] = asyncio .Queue ()
131132
132133 # Start a loop that handles ASGI websocket events
133134 while True :
134135 event = await receive ()
135136 if event ["type" ] == "websocket.connect" :
136- await send ({"type" : "websocket.accept" })
137+ await send (
138+ {"type" : "websocket.accept" , "subprotocol" : None , "headers" : []}
139+ )
137140 dispatcher = asyncio .create_task (
138141 self .run_dispatcher (scope , receive , send , recv_queue )
139142 )
@@ -143,16 +146,16 @@ async def __call__(
143146 dispatcher .cancel ()
144147 break
145148
146- elif event ["type" ] == "websocket.receive" :
149+ elif event ["type" ] == "websocket.receive" and event [ "text" ] :
147150 queue_put_func = recv_queue .put (orjson .loads (event ["text" ]))
148151 await queue_put_func
149152
150153 async def run_dispatcher (
151154 self ,
152- scope : dict [ str , Any ] ,
153- receive : Callable [..., Coroutine ] ,
154- send : Callable [..., Coroutine ] ,
155- recv_queue : asyncio .Queue ,
155+ scope : asgi_types . WebSocketScope ,
156+ receive : asgi_types . ASGIReceiveCallable ,
157+ send : asgi_types . ASGISendCallable ,
158+ recv_queue : asyncio .Queue [ dict [ str , Any ]] ,
156159 ) -> None :
157160 """Asyncio background task that renders and transmits layout updates of ReactPy components."""
158161 try :
@@ -187,11 +190,15 @@ async def run_dispatcher(
187190
188191 # Start the ReactPy component rendering loop
189192 await serve_layout (
190- Layout (ConnectionContext (component (), value = connection )), # type: ignore
193+ Layout (ConnectionContext (component (), value = connection )),
191194 lambda msg : send (
192- {"type" : "websocket.send" , "text" : orjson .dumps (msg ).decode ()}
195+ {
196+ "type" : "websocket.send" ,
197+ "text" : orjson .dumps (msg ).decode (),
198+ "bytes" : None ,
199+ }
193200 ),
194- recv_queue .get ,
201+ recv_queue .get , # type: ignore
195202 )
196203
197204 # Manually log exceptions since this function is running in a separate asyncio task.
@@ -206,9 +213,9 @@ class StaticFileApp:
206213
207214 async def __call__ (
208215 self ,
209- scope : dict [ str , Any ] ,
210- receive : Callable [..., Coroutine ] ,
211- send : Callable [..., Coroutine ] ,
216+ scope : asgi_types . HTTPScope ,
217+ receive : asgi_types . ASGIReceiveCallable ,
218+ send : asgi_types . ASGISendCallable ,
212219 ) -> None :
213220 """ASGI app for ReactPy static files."""
214221 if not self ._static_file_server :
@@ -218,7 +225,7 @@ async def __call__(
218225 prefix = self .parent .static_path ,
219226 )
220227
221- return await self ._static_file_server (scope , receive , send )
228+ await self ._static_file_server (scope , receive , send )
222229
223230
224231@dataclass
@@ -228,9 +235,9 @@ class WebModuleApp:
228235
229236 async def __call__ (
230237 self ,
231- scope : dict [ str , Any ] ,
232- receive : Callable [..., Coroutine ] ,
233- send : Callable [..., Coroutine ] ,
238+ scope : asgi_types . HTTPScope ,
239+ receive : asgi_types . ASGIReceiveCallable ,
240+ send : asgi_types . ASGISendCallable ,
234241 ) -> None :
235242 """ASGI app for ReactPy web modules."""
236243 if not self ._static_file_server :
@@ -241,4 +248,4 @@ async def __call__(
241248 autorefresh = True ,
242249 )
243250
244- return await self ._static_file_server (scope , receive , send )
251+ await self ._static_file_server (scope , receive , send )
0 commit comments