diff --git a/docs/source/about/contributor-guide.rst b/docs/source/about/contributor-guide.rst index 11ff79a43..79cd2fb4c 100644 --- a/docs/source/about/contributor-guide.rst +++ b/docs/source/about/contributor-guide.rst @@ -140,23 +140,17 @@ followed the `earlier instructions `_. The suite covers 3. Client-side Javascript code with UVU_ -Before running the test suite you'll need to install the required browsers by running: - -.. code-block:: bash - - playwright install - Once you've installed them you'll be able to run: .. code-block:: bash - nox -s test + nox -s check-python-tests You can observe the browser as the tests are running by passing an extra flag: .. code-block:: bash - nox -s test -- --headed + nox -s check-python-tests -- --headed To see a full list of available commands (e.g. ``nox -s ``) run: diff --git a/src/reactpy/backend/_common.py b/src/reactpy/backend/_common.py index 555e2c7f6..41bff8512 100644 --- a/src/reactpy/backend/_common.py +++ b/src/reactpy/backend/_common.py @@ -6,9 +6,8 @@ from pathlib import Path, PurePosixPath from typing import Any, Awaitable, Sequence, cast +import uvicorn from asgiref.typing import ASGIApplication -from uvicorn.config import Config as UvicornConfig -from uvicorn.server import Server as UvicornServer from reactpy import __file__ as _reactpy_file_path from reactpy import html @@ -31,9 +30,9 @@ async def serve_development_asgi( port: int, started: asyncio.Event | None, ) -> None: - """Run a development server for starlette""" - server = UvicornServer( - UvicornConfig( + """Run a development server for an ASGI application""" + server = uvicorn.Server( + uvicorn.Config( app, host=host, port=port, @@ -41,19 +40,25 @@ async def serve_development_asgi( reload=True, ) ) - + server.config.setup_event_loop() coros: list[Awaitable[Any]] = [server.serve()] + # If a started event is provided, then use it signal based on `server.started` if started: coros.append(_check_if_started(server, started)) try: await asyncio.gather(*coros) finally: + # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's + # order of operations. So we need to make sure `shutdown()` always has an initialized + # list of `self.servers` to use. + if not hasattr(server, "servers"): # pragma: no cover + server.servers = [] await asyncio.wait_for(server.shutdown(), timeout=3) -async def _check_if_started(server: UvicornServer, started: asyncio.Event) -> None: +async def _check_if_started(server: uvicorn.Server, started: asyncio.Event) -> None: while not server.started: await asyncio.sleep(0.2) started.set() diff --git a/src/reactpy/backend/flask.py b/src/reactpy/backend/flask.py index 1e07159c9..19dfbe1c3 100644 --- a/src/reactpy/backend/flask.py +++ b/src/reactpy/backend/flask.py @@ -129,8 +129,8 @@ def use_request() -> Request: def use_connection() -> Connection[_FlaskCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() - if not isinstance(conn.carrier, _FlaskCarrier): - raise TypeError( # pragma: no cover + if not isinstance(conn.carrier, _FlaskCarrier): # pragma: no cover + raise TypeError( f"Connection has unexpected carrier {conn.carrier}. " "Are you running with a Flask server?" ) diff --git a/src/reactpy/backend/sanic.py b/src/reactpy/backend/sanic.py index ab0f3bb67..91d20b838 100644 --- a/src/reactpy/backend/sanic.py +++ b/src/reactpy/backend/sanic.py @@ -4,7 +4,7 @@ import json import logging from dataclasses import dataclass -from typing import Any, MutableMapping, Tuple +from typing import Any, Tuple from urllib import parse as urllib_parse from uuid import uuid4 @@ -82,8 +82,8 @@ def use_websocket() -> WebSocketConnection: def use_connection() -> Connection[_SanicCarrier]: """Get the current :class:`Connection`""" conn = _use_connection() - if not isinstance(conn.carrier, _SanicCarrier): - raise TypeError( # pragma: no cover + if not isinstance(conn.carrier, _SanicCarrier): # pragma: no cover + raise TypeError( f"Connection has unexpected carrier {conn.carrier}. " "Are you running with a Sanic server?" ) @@ -162,11 +162,9 @@ async def model_stream( request: request.Request, socket: WebSocketConnection, path: str = "" ) -> None: asgi_app = getattr(request.app, "_asgi_app", None) - if asgi_app is None: # pragma: no cover + scope = asgi_app.transport.scope if asgi_app else {} + if not scope: # pragma: no cover logger.warning("No scope. Sanic may not be running with an ASGI server") - scope: MutableMapping[str, Any] = {} - else: - scope = asgi_app.transport.scope send, recv = _make_send_recv_callbacks(socket) await serve_layout( diff --git a/src/reactpy/backend/starlette.py b/src/reactpy/backend/starlette.py index 234737cf1..4ab16e91b 100644 --- a/src/reactpy/backend/starlette.py +++ b/src/reactpy/backend/starlette.py @@ -78,8 +78,8 @@ def use_websocket() -> WebSocket: def use_connection() -> Connection[WebSocket]: conn = _use_connection() - if not isinstance(conn.carrier, WebSocket): - raise TypeError( # pragma: no cover + if not isinstance(conn.carrier, WebSocket): # pragma: no cover + raise TypeError( f"Connection has unexpected carrier {conn.carrier}. " "Are you running with a Flask server?" ) diff --git a/src/reactpy/backend/tornado.py b/src/reactpy/backend/tornado.py index 30fd174f7..c2a034ab2 100644 --- a/src/reactpy/backend/tornado.py +++ b/src/reactpy/backend/tornado.py @@ -100,8 +100,8 @@ def use_request() -> HTTPServerRequest: def use_connection() -> Connection[HTTPServerRequest]: conn = _use_connection() - if not isinstance(conn.carrier, HTTPServerRequest): - raise TypeError( # pragma: no cover + if not isinstance(conn.carrier, HTTPServerRequest): # pragma: no cover + raise TypeError( f"Connection has unexpected carrier {conn.carrier}. " "Are you running with a Flask server?" ) diff --git a/src/reactpy/backend/utils.py b/src/reactpy/backend/utils.py index 5105cf9c0..b7576cfb0 100644 --- a/src/reactpy/backend/utils.py +++ b/src/reactpy/backend/utils.py @@ -85,10 +85,8 @@ def all_implementations() -> Iterator[BackendImplementation[Any]]: logger.debug(f"Failed to import {name!r}", exc_info=True) continue - if not isinstance(module, BackendImplementation): - raise TypeError( # pragma: no cover - f"{module.__name__!r} is an invalid implementation" - ) + if not isinstance(module, BackendImplementation): # pragma: no cover + raise TypeError(f"{module.__name__!r} is an invalid implementation") yield module