Skip to content

Commit 9520bf1

Browse files
committed
Fix type hints
1 parent 2fe399b commit 9520bf1

File tree

8 files changed

+82
-58
lines changed

8 files changed

+82
-58
lines changed

src/js/packages/@reactpy/client/src/mount.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ export function mountReactPy(props: MountProps) {
77
// WebSocket route for component rendering
88
const wsProtocol = `ws${window.location.protocol === "https:" ? "s" : ""}:`;
99
const wsOrigin = `${wsProtocol}//${window.location.host}`;
10-
const componentUrl = new URL(`${wsOrigin}${props.pathPrefix}${props.appendComponentPath || ""}`);
10+
const componentUrl = new URL(
11+
`${wsOrigin}${props.pathPrefix}${props.appendComponentPath || ""}`,
12+
);
1113

1214
// Embed the initial HTTP path into the WebSocket URL
1315
componentUrl.searchParams.append("http_pathname", window.location.pathname);
1416
if (window.location.search) {
15-
componentUrl.searchParams.append("http_query_string", window.location.search);
17+
componentUrl.searchParams.append(
18+
"http_query_string",
19+
window.location.search,
20+
);
1621
}
1722

1823
// Configure a new ReactPy client

src/reactpy/asgi/middleware.py

+39-32
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
import re
66
import traceback
77
import urllib.parse
8-
from collections.abc import Coroutine, Iterable
8+
from collections.abc import Iterable
99
from dataclasses import dataclass
1010
from pathlib import Path
11-
from typing import Any, Callable
11+
from typing import Any
1212

1313
import orjson
14+
from asgiref import typing as asgi_types
1415
from asgiref.compatibility import guarantee_single_callable
1516
from servestatic import ServeStaticASGI
1617
from 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)

src/reactpy/asgi/standalone.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
import hashlib
44
import re
5-
from collections.abc import Coroutine
65
from dataclasses import dataclass
76
from datetime import datetime, timezone
87
from email.utils import formatdate
98
from logging import getLogger
10-
from typing import Any, Callable
119

10+
from asgiref import typing as asgi_types
1211
from typing_extensions import Unpack
1312

1413
from reactpy import html
@@ -61,9 +60,9 @@ class ReactPyApp:
6160

6261
async def __call__(
6362
self,
64-
scope: dict[str, Any],
65-
receive: Callable[..., Coroutine],
66-
send: Callable[..., Coroutine],
63+
scope: asgi_types.Scope,
64+
receive: asgi_types.ASGIReceiveCallable,
65+
send: asgi_types.ASGISendCallable,
6766
) -> None:
6867
if scope["type"] != "http":
6968
if scope["type"] != "lifespan":
@@ -121,7 +120,7 @@ async def __call__(
121120
headers=dict_to_byte_list(response_headers),
122121
)
123122

124-
def match_dispatch_path(self, scope: dict) -> bool:
123+
def match_dispatch_path(self, scope: asgi_types.WebSocketScope) -> bool:
125124
"""Method override to remove `dotted_path` from the dispatcher URL."""
126125
return str(scope["path"]) == self.parent.dispatcher_path
127126

src/reactpy/asgi/utils.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

33
import logging
4-
from collections.abc import Coroutine, Iterable, Sequence
4+
from collections.abc import Iterable
55
from importlib import import_module
6-
from typing import Any, Callable
6+
from typing import Any
7+
8+
from asgiref import typing as asgi_types
79

810
from reactpy._option import Option
911
from reactpy.types import ReactPyConfig, VdomDict
@@ -69,15 +71,24 @@ def vdom_head_to_html(head: VdomDict) -> str:
6971

7072
async def http_response(
7173
*,
72-
send: Callable[[dict[str, Any]], Coroutine],
74+
send: asgi_types.ASGISendCallable,
7375
method: str,
7476
code: int = 200,
7577
message: str = "",
76-
headers: Sequence = (),
78+
headers: Iterable[tuple[bytes, bytes]] = (),
7779
) -> None:
7880
"""Sends a HTTP response using the ASGI `send` API."""
79-
start_msg = {"type": "http.response.start", "status": code, "headers": [*headers]}
80-
body_msg: dict[str, str | bytes] = {"type": "http.response.body"}
81+
start_msg: asgi_types.HTTPResponseStartEvent = {
82+
"type": "http.response.start",
83+
"status": code,
84+
"headers": [*headers],
85+
"trailers": False,
86+
}
87+
body_msg: asgi_types.HTTPResponseBodyEvent = {
88+
"type": "http.response.body",
89+
"body": b"",
90+
"more_body": False,
91+
}
8192

8293
# Add the content type and body to everything other than a HEAD request
8394
if method != "HEAD":
@@ -87,14 +98,14 @@ async def http_response(
8798
await send(body_msg)
8899

89100

90-
def process_settings(settings: ReactPyConfig):
101+
def process_settings(settings: ReactPyConfig) -> None:
91102
"""Process the settings and return the final configuration."""
92103
from reactpy import config
93104

94105
for setting in settings:
95106
config_name = f"REACTPY_{setting.upper()}"
96-
config_object: Option | None = getattr(config, config_name, None)
107+
config_object: Option[Any] | None = getattr(config, config_name, None)
97108
if config_object:
98-
config_object.set_current(settings[setting])
109+
config_object.set_current(settings[setting]) # type: ignore
99110
else:
100111
raise ValueError(f"Unknown ReactPy setting {setting!r}.")

src/reactpy/core/hooks.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import asyncio
44
import contextlib
5-
from collections.abc import Coroutine, MutableMapping, Sequence
5+
from collections.abc import Coroutine, Sequence
66
from logging import getLogger
77
from types import FunctionType
88
from typing import (
@@ -16,6 +16,7 @@
1616
overload,
1717
)
1818

19+
from asgiref import typing as asgi_types
1920
from typing_extensions import TypeAlias
2021

2122
from reactpy.config import REACTPY_DEBUG
@@ -262,7 +263,7 @@ def use_connection() -> Connection[Any]:
262263
return conn
263264

264265

265-
def use_scope() -> MutableMapping[str, Any]:
266+
def use_scope() -> asgi_types.HTTPScope | asgi_types.WebSocketScope:
266267
"""Get the current :class:`~reactpy.types.Connection`'s scope."""
267268
return use_connection().scope
268269

src/reactpy/jinja.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
from reactpy.utils import render_mount_template
77

88

9-
class ReactPyTemplateTag(StandaloneTag):
9+
class ReactPyTemplateTag(StandaloneTag): # type: ignore
1010
"""This allows enables a `component` tag to be used in any Jinja2 rendering context,
1111
as long as this template tag is registered as a Jinja2 extension."""
1212

1313
safe_output = True
1414
tags: ClassVar[set[str]] = {"component"}
1515

16-
def render(self, dotted_path: str, **kwargs):
16+
def render(self, dotted_path: str, **kwargs: str) -> str:
1717
return render_mount_template(
1818
element_id=uuid4().hex,
1919
class_=kwargs.pop("class", ""),

src/reactpy/testing/backend.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
import asyncio
44
import logging
5-
from collections.abc import Coroutine
65
from contextlib import AsyncExitStack
76
from threading import Thread
87
from types import TracebackType
98
from typing import Any, Callable
109
from urllib.parse import urlencode, urlunparse
1110

1211
import uvicorn
12+
from asgiref import typing as asgi_types
1313

1414
from reactpy.asgi.standalone import ReactPy
1515
from reactpy.config import REACTPY_TESTS_DEFAULT_TIMEOUT
@@ -43,7 +43,7 @@ class BackendFixture:
4343

4444
def __init__(
4545
self,
46-
app: Callable[..., Coroutine] | None = None,
46+
app: asgi_types.ASGIApplication | None = None,
4747
host: str = "127.0.0.1",
4848
port: int | None = None,
4949
timeout: float | None = None,

src/reactpy/types.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import sys
44
from collections import namedtuple
5-
from collections.abc import Mapping, MutableMapping, Sequence
5+
from collections.abc import Mapping, Sequence
66
from dataclasses import dataclass
77
from pathlib import Path
88
from types import TracebackType
@@ -19,6 +19,7 @@
1919
runtime_checkable,
2020
)
2121

22+
from asgiref import typing as asgi_types
2223
from typing_extensions import TypeAlias, TypedDict
2324

2425
CarrierType = TypeVar("CarrierType")
@@ -253,8 +254,8 @@ def value(self) -> _Type:
253254
class Connection(Generic[CarrierType]):
254255
"""Represents a connection with a client"""
255256

256-
scope: MutableMapping[str, Any]
257-
"""An ASGI scope dictionary"""
257+
scope: asgi_types.HTTPScope | asgi_types.WebSocketScope
258+
"""A scope dictionary related to the current connection."""
258259

259260
location: Location
260261
"""The current location (URL)"""

0 commit comments

Comments
 (0)