diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst index 30d595b94..b683ab4a4 100644 --- a/docs/source/about/changelog.rst +++ b/docs/source/about/changelog.rst @@ -40,11 +40,15 @@ v1.0.1 **Changed** - :pull:`1050` - Warn and attempt to fix missing mime types, which can result in ``reactpy.run`` not working as expected. +- :pull:`1051` - Rename ``reactpy.backend.BackendImplementation`` to ``reactpy.backend.BackendType`` +- :pull:`1051` - Allow ``reactpy.run`` to fail in more predictable ways **Fixed** - :issue:`930` - better traceback for JSON serialization errors (via :pull:`1008`) - :issue:`437` - explain that JS component attributes must be JSON (via :pull:`1008`) +- :pull:`1051` - Fix ``reactpy.run`` port assignment sometimes attaching to in-use ports on Windows +- :pull:`1051` - Fix ``reactpy.run`` not recognizing ``fastapi`` v1.0.0 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index dab76855e..000000000 --- a/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ --r requirements/build-docs.txt --r requirements/build-pkg.txt --r requirements/check-style.txt --r requirements/check-types.txt --r requirements/make-release.txt --r requirements/pkg-deps.txt --r requirements/pkg-extras.txt --r requirements/test-env.txt --r requirements/nox-deps.txt diff --git a/src/py/reactpy/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py index 17983a033..b4d6af19c 100644 --- a/src/py/reactpy/reactpy/backend/_common.py +++ b/src/py/reactpy/reactpy/backend/_common.py @@ -14,53 +14,49 @@ from reactpy.utils import vdom_to_html if TYPE_CHECKING: + import uvicorn from asgiref.typing import ASGIApplication PATH_PREFIX = PurePosixPath("/_reactpy") MODULES_PATH = PATH_PREFIX / "modules" ASSETS_PATH = PATH_PREFIX / "assets" STREAM_PATH = PATH_PREFIX / "stream" - CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_static" / "app" / "dist" -try: + +async def serve_with_uvicorn( + app: ASGIApplication | Any, + host: str, + port: int, + started: asyncio.Event | None, +) -> None: + """Run a development server for an ASGI application""" import uvicorn -except ImportError: # nocov - pass -else: - - async def serve_development_asgi( - app: ASGIApplication | Any, - host: str, - port: int, - started: asyncio.Event | None, - ) -> None: - """Run a development server for an ASGI application""" - server = uvicorn.Server( - uvicorn.Config( - app, - host=host, - port=port, - loop="asyncio", - reload=True, - ) + + server = uvicorn.Server( + uvicorn.Config( + app, + host=host, + port=port, + loop="asyncio", ) - server.config.setup_event_loop() - coros: list[Awaitable[Any]] = [server.serve()] + ) + 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)) + # 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"): # nocov - server.servers = [] - await asyncio.wait_for(server.shutdown(), timeout=3) + 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"): # nocov + server.servers = [] + await asyncio.wait_for(server.shutdown(), timeout=3) async def _check_if_started(server: uvicorn.Server, started: asyncio.Event) -> None: @@ -72,8 +68,7 @@ async def _check_if_started(server: uvicorn.Server, started: asyncio.Event) -> N def safe_client_build_dir_path(path: str) -> Path: """Prevent path traversal out of :data:`CLIENT_BUILD_DIR`""" return traversal_safe_path( - CLIENT_BUILD_DIR, - *("index.html" if path in ("", "/") else path).split("/"), + CLIENT_BUILD_DIR, *("index.html" if path in {"", "/"} else path).split("/") ) @@ -140,6 +135,9 @@ class CommonOptions: url_prefix: str = "" """The URL prefix where ReactPy resources will be served from""" + serve_index_route: bool = True + """Automatically generate and serve the index route (``/``)""" + def __post_init__(self) -> None: if self.url_prefix and not self.url_prefix.startswith("/"): msg = "Expected 'url_prefix' to start with '/'" diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py index 4ca192c1c..37aad31af 100644 --- a/src/py/reactpy/reactpy/backend/default.py +++ b/src/py/reactpy/reactpy/backend/default.py @@ -5,13 +5,26 @@ from sys import exc_info from typing import Any, NoReturn -from reactpy.backend.types import BackendImplementation -from reactpy.backend.utils import SUPPORTED_PACKAGES, all_implementations +from reactpy.backend.types import BackendType +from reactpy.backend.utils import SUPPORTED_BACKENDS, all_implementations from reactpy.types import RootComponentConstructor logger = getLogger(__name__) +_DEFAULT_IMPLEMENTATION: BackendType[Any] | None = None +# BackendType.Options +class Options: # nocov + """Configuration options that can be provided to the backend. + This definition should not be used/instantiated. It exists only for + type hinting purposes.""" + + def __init__(self, *args: Any, **kwds: Any) -> NoReturn: + msg = "Default implementation has no options." + raise ValueError(msg) + + +# BackendType.configure def configure( app: Any, component: RootComponentConstructor, options: None = None ) -> None: @@ -22,17 +35,13 @@ def configure( return _default_implementation().configure(app, component) +# BackendType.create_development_app def create_development_app() -> Any: """Create an application instance for development purposes""" return _default_implementation().create_development_app() -def Options(*args: Any, **kwargs: Any) -> NoReturn: # nocov - """Create configuration options""" - msg = "Default implementation has no options." - raise ValueError(msg) - - +# BackendType.serve_development_app async def serve_development_app( app: Any, host: str, @@ -45,10 +54,7 @@ async def serve_development_app( ) -_DEFAULT_IMPLEMENTATION: BackendImplementation[Any] | None = None - - -def _default_implementation() -> BackendImplementation[Any]: +def _default_implementation() -> BackendType[Any]: """Get the first available server implementation""" global _DEFAULT_IMPLEMENTATION # noqa: PLW0603 @@ -59,7 +65,7 @@ def _default_implementation() -> BackendImplementation[Any]: implementation = next(all_implementations()) except StopIteration: # nocov logger.debug("Backend implementation import failed", exc_info=exc_info()) - supported_backends = ", ".join(SUPPORTED_PACKAGES) + supported_backends = ", ".join(SUPPORTED_BACKENDS) msg = ( "It seems you haven't installed a backend. To resolve this issue, " "you can install a backend by running:\n\n" diff --git a/src/py/reactpy/reactpy/backend/fastapi.py b/src/py/reactpy/reactpy/backend/fastapi.py index 575fce1fe..a0137a3dc 100644 --- a/src/py/reactpy/reactpy/backend/fastapi.py +++ b/src/py/reactpy/reactpy/backend/fastapi.py @@ -4,22 +4,22 @@ from reactpy.backend import starlette -serve_development_app = starlette.serve_development_app -"""Alias for :func:`reactpy.backend.starlette.serve_development_app`""" - -use_connection = starlette.use_connection -"""Alias for :func:`reactpy.backend.starlette.use_location`""" - -use_websocket = starlette.use_websocket -"""Alias for :func:`reactpy.backend.starlette.use_websocket`""" - +# BackendType.Options Options = starlette.Options -"""Alias for :class:`reactpy.backend.starlette.Options`""" +# BackendType.configure configure = starlette.configure -"""Alias for :class:`reactpy.backend.starlette.configure`""" +# BackendType.create_development_app def create_development_app() -> FastAPI: """Create a development ``FastAPI`` application instance.""" return FastAPI(debug=True) + + +# BackendType.serve_development_app +serve_development_app = starlette.serve_development_app + +use_connection = starlette.use_connection + +use_websocket = starlette.use_websocket diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py index 46aed3c46..2e00e8f64 100644 --- a/src/py/reactpy/reactpy/backend/flask.py +++ b/src/py/reactpy/reactpy/backend/flask.py @@ -45,6 +45,19 @@ logger = logging.getLogger(__name__) +# BackendType.Options +@dataclass +class Options(CommonOptions): + """Render server config for :func:`reactpy.backend.flask.configure`""" + + cors: bool | dict[str, Any] = False + """Enable or configure Cross Origin Resource Sharing (CORS) + + For more information see docs for ``flask_cors.CORS`` + """ + + +# BackendType.configure def configure( app: Flask, component: RootComponentConstructor, options: Options | None = None ) -> None: @@ -69,20 +82,21 @@ def configure( app.register_blueprint(spa_bp) +# BackendType.create_development_app def create_development_app() -> Flask: """Create an application instance for development purposes""" os.environ["FLASK_DEBUG"] = "true" - app = Flask(__name__) - return app + return Flask(__name__) +# BackendType.serve_development_app async def serve_development_app( app: Flask, host: str, port: int, started: asyncio.Event | None = None, ) -> None: - """Run an application using a development server""" + """Run a development server for FastAPI""" loop = asyncio.get_running_loop() stopped = asyncio.Event() @@ -135,17 +149,6 @@ def use_connection() -> Connection[_FlaskCarrier]: return conn -@dataclass -class Options(CommonOptions): - """Render server config for :func:`reactpy.backend.flask.configure`""" - - cors: bool | dict[str, Any] = False - """Enable or configure Cross Origin Resource Sharing (CORS) - - For more information see docs for ``flask_cors.CORS`` - """ - - def _setup_common_routes( api_blueprint: Blueprint, spa_blueprint: Blueprint, @@ -166,10 +169,12 @@ def send_modules_dir(path: str = "") -> Any: index_html = read_client_index_html(options) - @spa_blueprint.route("/") - @spa_blueprint.route("/<path:_>") - def send_client_dir(_: str = "") -> Any: - return index_html + if options.serve_index_route: + + @spa_blueprint.route("/") + @spa_blueprint.route("/<path:_>") + def send_client_dir(_: str = "") -> Any: + return index_html def _setup_single_view_dispatcher_route( diff --git a/src/py/reactpy/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py index 53dd0ce68..3fd48db85 100644 --- a/src/py/reactpy/reactpy/backend/sanic.py +++ b/src/py/reactpy/reactpy/backend/sanic.py @@ -22,7 +22,7 @@ read_client_index_html, safe_client_build_dir_path, safe_web_modules_dir_path, - serve_development_asgi, + serve_with_uvicorn, ) from reactpy.backend.hooks import ConnectionContext from reactpy.backend.hooks import use_connection as _use_connection @@ -34,6 +34,19 @@ logger = logging.getLogger(__name__) +# BackendType.Options +@dataclass +class Options(CommonOptions): + """Render server config for :func:`reactpy.backend.sanic.configure`""" + + cors: bool | dict[str, Any] = False + """Enable or configure Cross Origin Resource Sharing (CORS) + + For more information see docs for ``sanic_cors.CORS`` + """ + + +# BackendType.configure def configure( app: Sanic, component: RootComponentConstructor, options: Options | None = None ) -> None: @@ -49,14 +62,15 @@ def configure( app.blueprint([spa_bp, api_bp]) +# BackendType.create_development_app def create_development_app() -> Sanic: """Return a :class:`Sanic` app instance in test mode""" Sanic.test_mode = True logger.warning("Sanic.test_mode is now active") - app = Sanic(f"reactpy_development_app_{uuid4().hex}", Config()) - return app + return Sanic(f"reactpy_development_app_{uuid4().hex}", Config()) +# BackendType.serve_development_app async def serve_development_app( app: Sanic, host: str, @@ -64,7 +78,7 @@ async def serve_development_app( started: asyncio.Event | None = None, ) -> None: """Run a development server for :mod:`sanic`""" - await serve_development_asgi(app, host, port, started) + await serve_with_uvicorn(app, host, port, started) def use_request() -> request.Request: @@ -86,17 +100,6 @@ def use_connection() -> Connection[_SanicCarrier]: return conn -@dataclass -class Options(CommonOptions): - """Render server config for :func:`reactpy.backend.sanic.configure`""" - - cors: bool | dict[str, Any] = False - """Enable or configure Cross Origin Resource Sharing (CORS) - - For more information see docs for ``sanic_cors.CORS`` - """ - - def _setup_common_routes( api_blueprint: Blueprint, spa_blueprint: Blueprint, @@ -115,16 +118,17 @@ async def single_page_app_files( ) -> response.HTTPResponse: return response.html(index_html) - spa_blueprint.add_route( - single_page_app_files, - "/", - name="single_page_app_files_root", - ) - spa_blueprint.add_route( - single_page_app_files, - "/<_:path>", - name="single_page_app_files_path", - ) + if options.serve_index_route: + spa_blueprint.add_route( + single_page_app_files, + "/", + name="single_page_app_files_root", + ) + spa_blueprint.add_route( + single_page_app_files, + "/<_:path>", + name="single_page_app_files_path", + ) async def asset_files( request: request.Request, diff --git a/src/py/reactpy/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py index 3a9695b33..2953b97b3 100644 --- a/src/py/reactpy/reactpy/backend/starlette.py +++ b/src/py/reactpy/reactpy/backend/starlette.py @@ -21,7 +21,7 @@ STREAM_PATH, CommonOptions, read_client_index_html, - serve_development_asgi, + serve_with_uvicorn, ) from reactpy.backend.hooks import ConnectionContext from reactpy.backend.hooks import use_connection as _use_connection @@ -34,6 +34,19 @@ logger = logging.getLogger(__name__) +# BackendType.Options +@dataclass +class Options(CommonOptions): + """Render server config for :func:`reactpy.backend.starlette.configure`""" + + cors: bool | dict[str, Any] = False + """Enable or configure Cross Origin Resource Sharing (CORS) + + For more information see docs for ``starlette.middleware.cors.CORSMiddleware`` + """ + + +# BackendType.configure def configure( app: Starlette, component: RootComponentConstructor, @@ -54,11 +67,13 @@ def configure( _setup_common_routes(options, app) +# BackendType.create_development_app def create_development_app() -> Starlette: """Return a :class:`Starlette` app instance in debug mode""" return Starlette(debug=True) +# BackendType.serve_development_app async def serve_development_app( app: Starlette, host: str, @@ -66,7 +81,7 @@ async def serve_development_app( started: asyncio.Event | None = None, ) -> None: """Run a development server for starlette""" - await serve_development_asgi(app, host, port, started) + await serve_with_uvicorn(app, host, port, started) def use_websocket() -> WebSocket: @@ -82,17 +97,6 @@ def use_connection() -> Connection[WebSocket]: return conn -@dataclass -class Options(CommonOptions): - """Render server config for :func:`reactpy.backend.starlette.configure`""" - - cors: bool | dict[str, Any] = False - """Enable or configure Cross Origin Resource Sharing (CORS) - - For more information see docs for ``starlette.middleware.cors.CORSMiddleware`` - """ - - def _setup_common_routes(options: Options, app: Starlette) -> None: cors_options = options.cors if cors_options: # nocov @@ -115,8 +119,10 @@ def _setup_common_routes(options: Options, app: Starlette) -> None: ) # register this last so it takes least priority index_route = _make_index_route(options) - app.add_route(url_prefix + "/", index_route) - app.add_route(url_prefix + "/{path:path}", index_route) + + if options.serve_index_route: + app.add_route(f"{url_prefix}/", index_route) + app.add_route(url_prefix + "/{path:path}", index_route) def _make_index_route(options: Options) -> Callable[[Request], Awaitable[HTMLResponse]]: diff --git a/src/py/reactpy/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py index 5ec877532..8f540ddb4 100644 --- a/src/py/reactpy/reactpy/backend/tornado.py +++ b/src/py/reactpy/reactpy/backend/tornado.py @@ -32,10 +32,11 @@ from reactpy.core.serve import serve_layout from reactpy.core.types import ComponentConstructor +# BackendType.Options Options = CommonOptions -"""Render server config for :func:`reactpy.backend.tornado.configure`""" +# BackendType.configure def configure( app: Application, component: ComponentConstructor, @@ -60,10 +61,12 @@ def configure( ) +# BackendType.create_development_app def create_development_app() -> Application: return Application(debug=True) +# BackendType.serve_development_app async def serve_development_app( app: Application, host: str, @@ -119,12 +122,17 @@ def _setup_common_routes(options: Options) -> _RouteHandlerSpecs: StaticFileHandler, {"path": str(CLIENT_BUILD_DIR / "assets")}, ), - ( - r"/(.*)", - IndexHandler, - {"index_html": read_client_index_html(options)}, - ), - ] + ] + ( + [ + ( + r"/(.*)", + IndexHandler, + {"index_html": read_client_index_html(options)}, + ), + ] + if options.serve_index_route + else [] + ) def _add_handler( diff --git a/src/py/reactpy/reactpy/backend/types.py b/src/py/reactpy/reactpy/backend/types.py index fbc4addc0..51e7bef04 100644 --- a/src/py/reactpy/reactpy/backend/types.py +++ b/src/py/reactpy/reactpy/backend/types.py @@ -11,11 +11,11 @@ @runtime_checkable -class BackendImplementation(Protocol[_App]): +class BackendType(Protocol[_App]): """Common interface for built-in web server/framework integrations""" Options: Callable[..., Any] - """A constructor for options passed to :meth:`BackendImplementation.configure`""" + """A constructor for options passed to :meth:`BackendType.configure`""" def configure( self, diff --git a/src/py/reactpy/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py index 3d9be13a4..183e801f5 100644 --- a/src/py/reactpy/reactpy/backend/utils.py +++ b/src/py/reactpy/reactpy/backend/utils.py @@ -3,22 +3,23 @@ import asyncio import logging import socket +import sys from collections.abc import Iterator from contextlib import closing from importlib import import_module from typing import Any -from reactpy.backend.types import BackendImplementation +from reactpy.backend.types import BackendType from reactpy.types import RootComponentConstructor logger = logging.getLogger(__name__) -SUPPORTED_PACKAGES = ( - "starlette", +SUPPORTED_BACKENDS = ( "fastapi", "sanic", "tornado", "flask", + "starlette", ) @@ -26,43 +27,37 @@ def run( component: RootComponentConstructor, host: str = "127.0.0.1", port: int | None = None, - implementation: BackendImplementation[Any] | None = None, + implementation: BackendType[Any] | None = None, ) -> None: """Run a component with a development server""" logger.warning(_DEVELOPMENT_RUN_FUNC_WARNING) implementation = implementation or import_module("reactpy.backend.default") - app = implementation.create_development_app() implementation.configure(app, component) - - host = host port = port or find_available_port(host) - app_cls = type(app) + logger.info( - f"Running with {app_cls.__module__}.{app_cls.__name__} at http://{host}:{port}" + "ReactPy is running with '%s.%s' at http://%s:%s", + app_cls.__module__, + app_cls.__name__, + host, + port, ) - asyncio.run(implementation.serve_development_app(app, host, port)) -def find_available_port( - host: str, - port_min: int = 8000, - port_max: int = 9000, - allow_reuse_waiting_ports: bool = True, -) -> int: +def find_available_port(host: str, port_min: int = 8000, port_max: int = 9000) -> int: """Get a port that's available for the given host and port range""" for port in range(port_min, port_max): with closing(socket.socket()) as sock: try: - if allow_reuse_waiting_ports: - # As per this answer: https://stackoverflow.com/a/19247688/3159288 - # setting can be somewhat unreliable because we allow the use of - # ports that are stuck in TIME_WAIT. However, not setting the option - # means we're overly cautious and almost always use a different addr - # even if it could have actually been used. + if sys.platform == "linux": + # Fixes bug where every time you restart the server you'll + # get a different port on Linux. This cannot be set on Windows + # otherwise address will always be reused. + # Ref: https://stackoverflow.com/a/19247688/3159288 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((host, port)) except OSError: @@ -73,26 +68,20 @@ def find_available_port( raise RuntimeError(msg) -def all_implementations() -> Iterator[BackendImplementation[Any]]: +def all_implementations() -> Iterator[BackendType[Any]]: """Yield all available server implementations""" - for name in SUPPORTED_PACKAGES: + for name in SUPPORTED_BACKENDS: try: - relative_import_name = f"{__name__.rsplit('.', 1)[0]}.{name}" - module = import_module(relative_import_name) + import_module(name) except ImportError: # nocov - logger.debug(f"Failed to import {name!r}", exc_info=True) + logger.debug("Failed to import %s", name, exc_info=True) continue - if not isinstance(module, BackendImplementation): # nocov - msg = f"{module.__name__!r} is an invalid implementation" - raise TypeError(msg) - - yield module + reactpy_backend_name = f"{__name__.rsplit('.', 1)[0]}.{name}" + yield import_module(reactpy_backend_name) -_DEVELOPMENT_RUN_FUNC_WARNING = f"""\ -The `run()` function is only intended for testing during development! To run in \ -production, consider selecting a supported backend and importing its associated \ -`configure()` function from `reactpy.backend.<package>` where `<package>` is one of \ -{list(SUPPORTED_PACKAGES)}. For details refer to the docs on how to run each package.\ +_DEVELOPMENT_RUN_FUNC_WARNING = """\ +The `run()` function is only intended for testing during development! To run \ +in production, refer to the docs on how to use reactpy.backend.*.configure.\ """ diff --git a/src/py/reactpy/reactpy/testing/backend.py b/src/py/reactpy/reactpy/testing/backend.py index 549e16056..b699f3071 100644 --- a/src/py/reactpy/reactpy/testing/backend.py +++ b/src/py/reactpy/reactpy/testing/backend.py @@ -2,13 +2,13 @@ import asyncio import logging -from contextlib import AsyncExitStack +from contextlib import AsyncExitStack, suppress from types import TracebackType from typing import Any, Callable from urllib.parse import urlencode, urlunparse from reactpy.backend import default as default_server -from reactpy.backend.types import BackendImplementation +from reactpy.backend.types import BackendType from reactpy.backend.utils import find_available_port from reactpy.config import REACTPY_TESTING_DEFAULT_TIMEOUT from reactpy.core.component import component @@ -43,21 +43,20 @@ def __init__( host: str = "127.0.0.1", port: int | None = None, app: Any | None = None, - implementation: BackendImplementation[Any] | None = None, + implementation: BackendType[Any] | None = None, options: Any | None = None, timeout: float | None = None, ) -> None: self.host = host - self.port = port or find_available_port(host, allow_reuse_waiting_ports=False) + self.port = port or find_available_port(host) self.mount, self._root_component = _hotswap() self.timeout = ( REACTPY_TESTING_DEFAULT_TIMEOUT.current if timeout is None else timeout ) - if app is not None: - if implementation is None: - msg = "If an application instance its corresponding server implementation must be provided too." - raise ValueError(msg) + if app is not None and implementation is None: + msg = "If an application instance its corresponding server implementation must be provided too." + raise ValueError(msg) self._app = app self.implementation = implementation or default_server @@ -124,10 +123,8 @@ async def __aenter__(self) -> BackendFixture: async def stop_server() -> None: server_future.cancel() - try: + with suppress(asyncio.CancelledError): await asyncio.wait_for(server_future, timeout=self.timeout) - except asyncio.CancelledError: - pass self._exit_stack.push_async_callback(stop_server) diff --git a/src/py/reactpy/reactpy/types.py b/src/py/reactpy/reactpy/types.py index 715b66fff..4766fe801 100644 --- a/src/py/reactpy/reactpy/types.py +++ b/src/py/reactpy/reactpy/types.py @@ -4,7 +4,7 @@ - :mod:`reactpy.backend.types` """ -from reactpy.backend.types import BackendImplementation, Connection, Location +from reactpy.backend.types import BackendType, Connection, Location from reactpy.core.component import Component from reactpy.core.hooks import Context from reactpy.core.types import ( @@ -27,7 +27,7 @@ ) __all__ = [ - "BackendImplementation", + "BackendType", "Component", "ComponentConstructor", "ComponentType", diff --git a/src/py/reactpy/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py index 11b9693a2..d697e5d3f 100644 --- a/src/py/reactpy/tests/test_backend/test_all.py +++ b/src/py/reactpy/tests/test_backend/test_all.py @@ -6,7 +6,7 @@ from reactpy import html from reactpy.backend import default as default_implementation from reactpy.backend._common import PATH_PREFIX -from reactpy.backend.types import BackendImplementation, Connection, Location +from reactpy.backend.types import BackendType, Connection, Location from reactpy.backend.utils import all_implementations from reactpy.testing import BackendFixture, DisplayFixture, poll @@ -17,7 +17,7 @@ scope="module", ) async def display(page, request): - imp: BackendImplementation = request.param + imp: BackendType = request.param # we do this to check that route priorities for each backend are correct if imp is default_implementation: @@ -158,7 +158,7 @@ def ShowRoute(): @pytest.mark.parametrize("imp", all_implementations()) -async def test_customized_head(imp: BackendImplementation, page): +async def test_customized_head(imp: BackendType, page): custom_title = f"Custom Title for {imp.__name__}" @reactpy.component