Skip to content

add use_connection hook #823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/source/_custom_js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion docs/source/about/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ more info, see the :ref:`Contributor Guide <Creating a Changelog Entry>`.
Unreleased
----------

No changes.
**Changed**

- :pull:`823` - The hooks ``use_location`` and ``use_scope`` are no longer
implementation specific and are now available as top-level imports. Instead of each
backend defining these hooks, backends establish a ``ConnectionContext`` with this
information.

**Added**

- :pull:`823` - There is a new ``use_connection`` hook which returns a ``Connection``
object. This ``Connection`` object contains a ``location`` and ``scope``, along with
a ``carrier`` which is unique to each backend implementation.


v0.40.2
Expand Down
4 changes: 2 additions & 2 deletions requirements/pkg-extras.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# extra=starlette
starlette >=0.13.6
uvicorn[standard] >=0.13.4
uvicorn[standard] >=0.19.0

# extra=sanic
sanic >=21
sanic-cors

# extra=fastapi
fastapi >=0.63.0
uvicorn[standard] >=0.13.4
uvicorn[standard] >=0.19.0

# extra=flask
flask
Expand Down
6 changes: 5 additions & 1 deletion src/idom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import backend, config, html, logging, sample, types, web
from .backend.hooks import use_connection, use_location, use_scope
from .backend.utils import run
from .core import hooks
from .core.component import component
Expand All @@ -25,6 +26,7 @@
__version__ = "0.40.2" # DO NOT MODIFY

__all__ = [
"backend",
"component",
"config",
"create_context",
Expand All @@ -38,16 +40,18 @@
"Ref",
"run",
"sample",
"backend",
"Stop",
"types",
"use_callback",
"use_connection",
"use_context",
"use_debug_value",
"use_effect",
"use_location",
"use_memo",
"use_reducer",
"use_ref",
"use_scope",
"use_state",
"vdom",
"web",
Expand Down
2 changes: 1 addition & 1 deletion src/idom/backend/_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async def serve_development_asgi(
host=host,
port=port,
loop="asyncio",
debug=True,
reload=True,
)
)

Expand Down
12 changes: 1 addition & 11 deletions src/idom/backend/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from idom.types import RootComponentConstructor

from .types import BackendImplementation, Location
from .types import BackendImplementation
from .utils import all_implementations


Expand Down Expand Up @@ -35,16 +35,6 @@ async def serve_development_app(
)


def use_scope() -> Any:
"""Return the current ASGI/WSGI scope"""
return _default_implementation().use_scope()


def use_location() -> Location:
"""Return the current route as a string"""
return _default_implementation().use_location()


_DEFAULT_IMPLEMENTATION: BackendImplementation[Any] | None = None


Expand Down
10 changes: 2 additions & 8 deletions src/idom/backend/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@
serve_development_app = starlette.serve_development_app
"""Alias for :func:`idom.backend.starlette.serve_development_app`"""

# see: https://github.com/idom-team/flake8-idom-hooks/issues/12
use_location = starlette.use_location # noqa: ROH101
use_connection = starlette.use_connection
"""Alias for :func:`idom.backend.starlette.use_location`"""

# see: https://github.com/idom-team/flake8-idom-hooks/issues/12
use_scope = starlette.use_scope # noqa: ROH101
"""Alias for :func:`idom.backend.starlette.use_scope`"""

# see: https://github.com/idom-team/flake8-idom-hooks/issues/12
use_websocket = starlette.use_websocket # noqa: ROH101
use_websocket = starlette.use_websocket
"""Alias for :func:`idom.backend.starlette.use_websocket`"""

Options = starlette.Options
Expand Down
73 changes: 36 additions & 37 deletions src/idom/backend/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
from werkzeug.serving import BaseWSGIServer, make_server

import idom
from idom.backend.types import Location
from idom.core.hooks import Context, create_context, use_context
from idom.backend.hooks import ConnectionContext
from idom.backend.hooks import use_connection as _use_connection
from idom.backend.types import Connection, Location
from idom.core.layout import LayoutEvent, LayoutUpdate
from idom.core.serve import serve_json_patch
from idom.core.types import ComponentType, RootComponentConstructor
Expand All @@ -37,8 +38,6 @@

logger = logging.getLogger(__name__)

ConnectionContext: Context[Connection | None] = create_context(None)


def configure(
app: Flask, component: RootComponentConstructor, options: Options | None = None
Expand Down Expand Up @@ -107,45 +106,25 @@ def run_server() -> None:
raise RuntimeError("Failed to shutdown server.")


def use_location() -> Location:
"""Get the current route as a string"""
conn = use_connection()
search = conn.request.query_string.decode()
return Location(pathname="/" + conn.path, search="?" + search if search else "")


def use_scope() -> dict[str, Any]:
"""Get the current WSGI environment"""
return use_request().environ
def use_websocket() -> WebSocket:
"""A handle to the current websocket"""
return use_connection().carrier.websocket


def use_request() -> Request:
"""Get the current ``Request``"""
return use_connection().request
return use_connection().carrier.request


def use_connection() -> Connection:
def use_connection() -> Connection[_FlaskCarrier]:
"""Get the current :class:`Connection`"""
connection = use_context(ConnectionContext)
if connection is None:
raise RuntimeError( # pragma: no cover
"No connection. Are you running with a Flask server?"
conn = _use_connection()
if not isinstance(conn.carrier, _FlaskCarrier):
raise TypeError( # pragma: no cover
f"Connection has unexpected carrier {conn.carrier}. "
"Are you running with a Flask server?"
)
return connection


@dataclass
class Connection:
"""A simple wrapper for holding connection information"""

request: Request
"""The current request object"""

websocket: WebSocket
"""A handle to the current websocket"""

path: str
"""The current path being served"""
return conn


@dataclass
Expand Down Expand Up @@ -230,11 +209,20 @@ async def recv_coro() -> Any:
return await async_recv_queue.get()

async def main() -> None:
search = request.query_string.decode()
await serve_json_patch(
idom.Layout(
ConnectionContext(
component, value=Connection(request, websocket, path)
)
component,
value=Connection(
scope=request.environ,
location=Location(
pathname=f"/{path}",
search=f"?{search}" if search else "",
),
carrier=_FlaskCarrier(request, websocket),
),
),
),
send_coro,
recv_coro,
Expand Down Expand Up @@ -283,3 +271,14 @@ class _DispatcherThreadInfo(NamedTuple):
dispatch_future: "asyncio.Future[Any]"
thread_send_queue: "ThreadQueue[LayoutUpdate]"
async_recv_queue: "AsyncQueue[LayoutEvent]"


@dataclass
class _FlaskCarrier:
"""A simple wrapper for holding a Flask request and WebSocket"""

request: Request
"""The current request object"""

websocket: WebSocket
"""A handle to the current websocket"""
26 changes: 26 additions & 0 deletions src/idom/backend/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from __future__ import annotations

from typing import Any, MutableMapping

from idom.core.hooks import Context, create_context, use_context

from .types import Connection, Location


# backend implementations should establish this context at the root of an app
ConnectionContext: Context[Connection[Any] | None] = create_context(None)


def use_connection() -> Connection[Any]:
conn = use_context(ConnectionContext)
if conn is None:
raise RuntimeError("No backend established a connection.") # pragma: no cover
return conn


def use_scope() -> MutableMapping[str, Any]:
return use_connection().scope


def use_location() -> Location:
return use_connection().location
Loading