Skip to content

Commit 8caf973

Browse files
authored
Fix idom.run uvicorn self.servers exception (#943)
1 parent b15b43d commit 8caf973

File tree

7 files changed

+27
-32
lines changed

7 files changed

+27
-32
lines changed

Diff for: docs/source/about/contributor-guide.rst

+2-8
Original file line numberDiff line numberDiff line change
@@ -140,23 +140,17 @@ followed the `earlier instructions <Development Environment>`_. The suite covers
140140

141141
3. Client-side Javascript code with UVU_
142142

143-
Before running the test suite you'll need to install the required browsers by running:
144-
145-
.. code-block:: bash
146-
147-
playwright install
148-
149143
Once you've installed them you'll be able to run:
150144

151145
.. code-block:: bash
152146
153-
nox -s test
147+
nox -s check-python-tests
154148
155149
You can observe the browser as the tests are running by passing an extra flag:
156150

157151
.. code-block:: bash
158152
159-
nox -s test -- --headed
153+
nox -s check-python-tests -- --headed
160154
161155
To see a full list of available commands (e.g. ``nox -s <your-command>``) run:
162156

Diff for: src/reactpy/backend/_common.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
from pathlib import Path, PurePosixPath
77
from typing import Any, Awaitable, Sequence, cast
88

9+
import uvicorn
910
from asgiref.typing import ASGIApplication
10-
from uvicorn.config import Config as UvicornConfig
11-
from uvicorn.server import Server as UvicornServer
1211

1312
from reactpy import __file__ as _reactpy_file_path
1413
from reactpy import html
@@ -31,29 +30,35 @@ async def serve_development_asgi(
3130
port: int,
3231
started: asyncio.Event | None,
3332
) -> None:
34-
"""Run a development server for starlette"""
35-
server = UvicornServer(
36-
UvicornConfig(
33+
"""Run a development server for an ASGI application"""
34+
server = uvicorn.Server(
35+
uvicorn.Config(
3736
app,
3837
host=host,
3938
port=port,
4039
loop="asyncio",
4140
reload=True,
4241
)
4342
)
44-
43+
server.config.setup_event_loop()
4544
coros: list[Awaitable[Any]] = [server.serve()]
4645

46+
# If a started event is provided, then use it signal based on `server.started`
4747
if started:
4848
coros.append(_check_if_started(server, started))
4949

5050
try:
5151
await asyncio.gather(*coros)
5252
finally:
53+
# Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's
54+
# order of operations. So we need to make sure `shutdown()` always has an initialized
55+
# list of `self.servers` to use.
56+
if not hasattr(server, "servers"): # pragma: no cover
57+
server.servers = []
5358
await asyncio.wait_for(server.shutdown(), timeout=3)
5459

5560

56-
async def _check_if_started(server: UvicornServer, started: asyncio.Event) -> None:
61+
async def _check_if_started(server: uvicorn.Server, started: asyncio.Event) -> None:
5762
while not server.started:
5863
await asyncio.sleep(0.2)
5964
started.set()

Diff for: src/reactpy/backend/flask.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ def use_request() -> Request:
129129
def use_connection() -> Connection[_FlaskCarrier]:
130130
"""Get the current :class:`Connection`"""
131131
conn = _use_connection()
132-
if not isinstance(conn.carrier, _FlaskCarrier):
133-
raise TypeError( # pragma: no cover
132+
if not isinstance(conn.carrier, _FlaskCarrier): # pragma: no cover
133+
raise TypeError(
134134
f"Connection has unexpected carrier {conn.carrier}. "
135135
"Are you running with a Flask server?"
136136
)

Diff for: src/reactpy/backend/sanic.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import json
55
import logging
66
from dataclasses import dataclass
7-
from typing import Any, MutableMapping, Tuple
7+
from typing import Any, Tuple
88
from urllib import parse as urllib_parse
99
from uuid import uuid4
1010

@@ -82,8 +82,8 @@ def use_websocket() -> WebSocketConnection:
8282
def use_connection() -> Connection[_SanicCarrier]:
8383
"""Get the current :class:`Connection`"""
8484
conn = _use_connection()
85-
if not isinstance(conn.carrier, _SanicCarrier):
86-
raise TypeError( # pragma: no cover
85+
if not isinstance(conn.carrier, _SanicCarrier): # pragma: no cover
86+
raise TypeError(
8787
f"Connection has unexpected carrier {conn.carrier}. "
8888
"Are you running with a Sanic server?"
8989
)
@@ -162,11 +162,9 @@ async def model_stream(
162162
request: request.Request, socket: WebSocketConnection, path: str = ""
163163
) -> None:
164164
asgi_app = getattr(request.app, "_asgi_app", None)
165-
if asgi_app is None: # pragma: no cover
165+
scope = asgi_app.transport.scope if asgi_app else {}
166+
if not scope: # pragma: no cover
166167
logger.warning("No scope. Sanic may not be running with an ASGI server")
167-
scope: MutableMapping[str, Any] = {}
168-
else:
169-
scope = asgi_app.transport.scope
170168

171169
send, recv = _make_send_recv_callbacks(socket)
172170
await serve_layout(

Diff for: src/reactpy/backend/starlette.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ def use_websocket() -> WebSocket:
7878

7979
def use_connection() -> Connection[WebSocket]:
8080
conn = _use_connection()
81-
if not isinstance(conn.carrier, WebSocket):
82-
raise TypeError( # pragma: no cover
81+
if not isinstance(conn.carrier, WebSocket): # pragma: no cover
82+
raise TypeError(
8383
f"Connection has unexpected carrier {conn.carrier}. "
8484
"Are you running with a Flask server?"
8585
)

Diff for: src/reactpy/backend/tornado.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def use_request() -> HTTPServerRequest:
100100

101101
def use_connection() -> Connection[HTTPServerRequest]:
102102
conn = _use_connection()
103-
if not isinstance(conn.carrier, HTTPServerRequest):
104-
raise TypeError( # pragma: no cover
103+
if not isinstance(conn.carrier, HTTPServerRequest): # pragma: no cover
104+
raise TypeError(
105105
f"Connection has unexpected carrier {conn.carrier}. "
106106
"Are you running with a Flask server?"
107107
)

Diff for: src/reactpy/backend/utils.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,8 @@ def all_implementations() -> Iterator[BackendImplementation[Any]]:
8585
logger.debug(f"Failed to import {name!r}", exc_info=True)
8686
continue
8787

88-
if not isinstance(module, BackendImplementation):
89-
raise TypeError( # pragma: no cover
90-
f"{module.__name__!r} is an invalid implementation"
91-
)
88+
if not isinstance(module, BackendImplementation): # pragma: no cover
89+
raise TypeError(f"{module.__name__!r} is an invalid implementation")
9290

9391
yield module
9492

0 commit comments

Comments
 (0)