Skip to content

Commit 64c9e82

Browse files
authored
add use_connection hook (#823)
* add use_connection hook * add changelog entry * upgrade uvicorn * fix type annotation * remove unused imports * update docstring * increase type delay * configure default delay
1 parent 50e42c0 commit 64c9e82

20 files changed

+276
-191
lines changed

Diff for: docs/source/_custom_js/package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: docs/source/about/changelog.rst

+12-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,18 @@ more info, see the :ref:`Contributor Guide <Creating a Changelog Entry>`.
2323
Unreleased
2424
----------
2525

26-
No changes.
26+
**Changed**
27+
28+
- :pull:`823` - The hooks ``use_location`` and ``use_scope`` are no longer
29+
implementation specific and are now available as top-level imports. Instead of each
30+
backend defining these hooks, backends establish a ``ConnectionContext`` with this
31+
information.
32+
33+
**Added**
34+
35+
- :pull:`823` - There is a new ``use_connection`` hook which returns a ``Connection``
36+
object. This ``Connection`` object contains a ``location`` and ``scope``, along with
37+
a ``carrier`` which is unique to each backend implementation.
2738

2839

2940
v0.40.2

Diff for: requirements/pkg-extras.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# extra=starlette
22
starlette >=0.13.6
3-
uvicorn[standard] >=0.13.4
3+
uvicorn[standard] >=0.19.0
44

55
# extra=sanic
66
sanic >=21
77
sanic-cors
88

99
# extra=fastapi
1010
fastapi >=0.63.0
11-
uvicorn[standard] >=0.13.4
11+
uvicorn[standard] >=0.19.0
1212

1313
# extra=flask
1414
flask

Diff for: src/idom/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from . import backend, config, html, logging, sample, types, web
2+
from .backend.hooks import use_connection, use_location, use_scope
23
from .backend.utils import run
34
from .core import hooks
45
from .core.component import component
@@ -25,6 +26,7 @@
2526
__version__ = "0.40.2" # DO NOT MODIFY
2627

2728
__all__ = [
29+
"backend",
2830
"component",
2931
"config",
3032
"create_context",
@@ -38,16 +40,18 @@
3840
"Ref",
3941
"run",
4042
"sample",
41-
"backend",
4243
"Stop",
4344
"types",
4445
"use_callback",
46+
"use_connection",
4547
"use_context",
4648
"use_debug_value",
4749
"use_effect",
50+
"use_location",
4851
"use_memo",
4952
"use_reducer",
5053
"use_ref",
54+
"use_scope",
5155
"use_state",
5256
"vdom",
5357
"web",

Diff for: src/idom/backend/_asgi.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async def serve_development_asgi(
2121
host=host,
2222
port=port,
2323
loop="asyncio",
24-
debug=True,
24+
reload=True,
2525
)
2626
)
2727

Diff for: src/idom/backend/default.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from idom.types import RootComponentConstructor
77

8-
from .types import BackendImplementation, Location
8+
from .types import BackendImplementation
99
from .utils import all_implementations
1010

1111

@@ -35,16 +35,6 @@ async def serve_development_app(
3535
)
3636

3737

38-
def use_scope() -> Any:
39-
"""Return the current ASGI/WSGI scope"""
40-
return _default_implementation().use_scope()
41-
42-
43-
def use_location() -> Location:
44-
"""Return the current route as a string"""
45-
return _default_implementation().use_location()
46-
47-
4838
_DEFAULT_IMPLEMENTATION: BackendImplementation[Any] | None = None
4939

5040

Diff for: src/idom/backend/fastapi.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,10 @@
88
serve_development_app = starlette.serve_development_app
99
"""Alias for :func:`idom.backend.starlette.serve_development_app`"""
1010

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

15-
# see: https://github.com/idom-team/flake8-idom-hooks/issues/12
16-
use_scope = starlette.use_scope # noqa: ROH101
17-
"""Alias for :func:`idom.backend.starlette.use_scope`"""
18-
19-
# see: https://github.com/idom-team/flake8-idom-hooks/issues/12
20-
use_websocket = starlette.use_websocket # noqa: ROH101
14+
use_websocket = starlette.use_websocket
2115
"""Alias for :func:`idom.backend.starlette.use_websocket`"""
2216

2317
Options = starlette.Options

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

+36-37
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
from werkzeug.serving import BaseWSGIServer, make_server
2626

2727
import idom
28-
from idom.backend.types import Location
29-
from idom.core.hooks import Context, create_context, use_context
28+
from idom.backend.hooks import ConnectionContext
29+
from idom.backend.hooks import use_connection as _use_connection
30+
from idom.backend.types import Connection, Location
3031
from idom.core.layout import LayoutEvent, LayoutUpdate
3132
from idom.core.serve import serve_json_patch
3233
from idom.core.types import ComponentType, RootComponentConstructor
@@ -37,8 +38,6 @@
3738

3839
logger = logging.getLogger(__name__)
3940

40-
ConnectionContext: Context[Connection | None] = create_context(None)
41-
4241

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

109108

110-
def use_location() -> Location:
111-
"""Get the current route as a string"""
112-
conn = use_connection()
113-
search = conn.request.query_string.decode()
114-
return Location(pathname="/" + conn.path, search="?" + search if search else "")
115-
116-
117-
def use_scope() -> dict[str, Any]:
118-
"""Get the current WSGI environment"""
119-
return use_request().environ
109+
def use_websocket() -> WebSocket:
110+
"""A handle to the current websocket"""
111+
return use_connection().carrier.websocket
120112

121113

122114
def use_request() -> Request:
123115
"""Get the current ``Request``"""
124-
return use_connection().request
116+
return use_connection().carrier.request
125117

126118

127-
def use_connection() -> Connection:
119+
def use_connection() -> Connection[_FlaskCarrier]:
128120
"""Get the current :class:`Connection`"""
129-
connection = use_context(ConnectionContext)
130-
if connection is None:
131-
raise RuntimeError( # pragma: no cover
132-
"No connection. Are you running with a Flask server?"
121+
conn = _use_connection()
122+
if not isinstance(conn.carrier, _FlaskCarrier):
123+
raise TypeError( # pragma: no cover
124+
f"Connection has unexpected carrier {conn.carrier}. "
125+
"Are you running with a Flask server?"
133126
)
134-
return connection
135-
136-
137-
@dataclass
138-
class Connection:
139-
"""A simple wrapper for holding connection information"""
140-
141-
request: Request
142-
"""The current request object"""
143-
144-
websocket: WebSocket
145-
"""A handle to the current websocket"""
146-
147-
path: str
148-
"""The current path being served"""
127+
return conn
149128

150129

151130
@dataclass
@@ -230,11 +209,20 @@ async def recv_coro() -> Any:
230209
return await async_recv_queue.get()
231210

232211
async def main() -> None:
212+
search = request.query_string.decode()
233213
await serve_json_patch(
234214
idom.Layout(
235215
ConnectionContext(
236-
component, value=Connection(request, websocket, path)
237-
)
216+
component,
217+
value=Connection(
218+
scope=request.environ,
219+
location=Location(
220+
pathname=f"/{path}",
221+
search=f"?{search}" if search else "",
222+
),
223+
carrier=_FlaskCarrier(request, websocket),
224+
),
225+
),
238226
),
239227
send_coro,
240228
recv_coro,
@@ -283,3 +271,14 @@ class _DispatcherThreadInfo(NamedTuple):
283271
dispatch_future: "asyncio.Future[Any]"
284272
thread_send_queue: "ThreadQueue[LayoutUpdate]"
285273
async_recv_queue: "AsyncQueue[LayoutEvent]"
274+
275+
276+
@dataclass
277+
class _FlaskCarrier:
278+
"""A simple wrapper for holding a Flask request and WebSocket"""
279+
280+
request: Request
281+
"""The current request object"""
282+
283+
websocket: WebSocket
284+
"""A handle to the current websocket"""

Diff for: src/idom/backend/hooks.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, MutableMapping
4+
5+
from idom.core.hooks import Context, create_context, use_context
6+
7+
from .types import Connection, Location
8+
9+
10+
# backend implementations should establish this context at the root of an app
11+
ConnectionContext: Context[Connection[Any] | None] = create_context(None)
12+
13+
14+
def use_connection() -> Connection[Any]:
15+
conn = use_context(ConnectionContext)
16+
if conn is None:
17+
raise RuntimeError("No backend established a connection.") # pragma: no cover
18+
return conn
19+
20+
21+
def use_scope() -> MutableMapping[str, Any]:
22+
return use_connection().scope
23+
24+
25+
def use_location() -> Location:
26+
return use_connection().location

0 commit comments

Comments
 (0)