Skip to content

Commit 48b2fc9

Browse files
committed
get sanic and flask to work
1 parent 55527bd commit 48b2fc9

File tree

6 files changed

+55
-21
lines changed

6 files changed

+55
-21
lines changed

requirements/pkg-extras.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ fastapi >=0.63.0
1111
uvicorn[standard] >=0.13.4
1212

1313
# extra=flask
14-
flask<2.0
14+
flask
1515
markupsafe<2.1
1616
flask-cors
17-
flask-sockets
17+
flask-sock
1818

1919
# extra=tornado
2020
tornado

src/idom/server/flask.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
url_for,
2323
)
2424
from flask_cors import CORS
25-
from flask_sockets import Sockets
25+
from flask_sock import Sock
2626
from gevent import pywsgi
2727
from geventwebsocket.handler import WebSocketHandler
28-
from geventwebsocket.websocket import WebSocket
28+
from simple_websocket import Server as WebSocket
2929

3030
import idom
3131
from idom.config import IDOM_WEB_MODULES_DIR
@@ -185,9 +185,10 @@ def redirect_to_index() -> Any:
185185
def _setup_single_view_dispatcher_route(
186186
app: Flask, options: Options, constructor: RootComponentConstructor
187187
) -> None:
188-
sockets = Sockets(app)
188+
sockets = Sock(app)
189189

190-
@sockets.route(_join_url_paths(options.url_prefix, "/app<path:path>/_stream")) # type: ignore
190+
@sockets.route(_join_url_paths(options.url_prefix, "/app/_stream"))
191+
@sockets.route(_join_url_paths(options.url_prefix, "/app/<path:path>/_stream"))
191192
def model_stream(ws: WebSocket) -> None:
192193
def send(value: Any) -> None:
193194
ws.send(json.dumps(value))

src/idom/server/sanic.py

+36-9
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
from idom.core.types import RootComponentConstructor
2626

2727
from ._asgi import serve_development_asgi
28-
from .utils import CLIENT_BUILD_DIR
28+
from .utils import CLIENT_BUILD_DIR, client_build_dir_path
2929

3030

3131
logger = logging.getLogger(__name__)
3232

33-
RequestContext: type[Context[request.Request | None]] = create_context(
33+
ConnectionContext: type[Context[request.Request | None]] = create_context(
3434
None, "RequestContext"
3535
)
3636

@@ -65,9 +65,9 @@ async def serve_development_app(
6565
await serve_development_asgi(app, host, port, started)
6666

6767

68-
def use_request() -> request.Request:
68+
def use_connection() -> Connection:
6969
"""Get the current ``Request``"""
70-
request = use_context(RequestContext)
70+
request = use_context(ConnectionContext)
7171
if request is None:
7272
raise RuntimeError( # pragma: no cover
7373
"No request. Are you running with a Sanic server?"
@@ -77,7 +77,7 @@ def use_request() -> request.Request:
7777

7878
def use_scope() -> ASGIScope:
7979
"""Get the current ASGI scope"""
80-
app = use_request().app
80+
app = use_connection().request.app
8181
try:
8282
asgi_app = app._asgi_app
8383
except AttributeError: # pragma: no cover
@@ -112,7 +112,18 @@ def _setup_common_routes(blueprint: Blueprint, options: Options) -> None:
112112
CORS(blueprint, **cors_params)
113113

114114
if options.serve_static_files:
115-
blueprint.static("/app", str(CLIENT_BUILD_DIR))
115+
116+
@blueprint.route("/app")
117+
@blueprint.route("/app/<path>")
118+
async def single_page_app_files(
119+
request: request.Request, path: str
120+
) -> response.HTTPResponse:
121+
try:
122+
path = client_build_dir_path(path)
123+
except ValueError:
124+
return response.HTTPResponse("File not found", status=404)
125+
return await response.file(str(CLIENT_BUILD_DIR / path))
126+
116127
blueprint.static("/modules", str(IDOM_WEB_MODULES_DIR.current))
117128

118129
if options.redirect_root:
@@ -129,18 +140,34 @@ def redirect_to_index(
129140
def _setup_single_view_dispatcher_route(
130141
blueprint: Blueprint, constructor: RootComponentConstructor
131142
) -> None:
132-
@blueprint.websocket("/app<path:path>/_stream") # type: ignore
143+
@blueprint.websocket("/app/_stream")
144+
@blueprint.websocket("/app/<path:path>/_stream") # type: ignore
133145
async def model_stream(
134-
request: request.Request, socket: WebSocketCommonProtocol
146+
request: request.Request, socket: WebSocketCommonProtocol, path: str = ""
135147
) -> None:
136148
send, recv = _make_send_recv_callbacks(socket)
149+
conn = Connection(request, socket, path)
137150
await serve_json_patch(
138-
Layout(RequestContext(constructor(), value=request)),
151+
Layout(ConnectionContext(constructor(), value=conn)),
139152
send,
140153
recv,
141154
)
142155

143156

157+
@dataclass
158+
class Connection:
159+
"""A simple wrapper for holding"""
160+
161+
request: request.Request
162+
"""The current request object"""
163+
164+
socket: WebSocketCommonProtocol
165+
"""A handle to the current websocket"""
166+
167+
path: str
168+
"""The current path being served"""
169+
170+
144171
def _make_send_recv_callbacks(
145172
socket: WebSocketCommonProtocol,
146173
) -> Tuple[SendCoroutine, RecvCoroutine]:

src/idom/server/starlette.py

+9-5
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, Dict, Tuple, Union
7+
from typing import Any, Awaitable, Callable, Dict, Tuple, Union
88

99
from starlette.applications import Starlette
1010
from starlette.middleware.cors import CORSMiddleware
@@ -122,7 +122,7 @@ def _setup_common_routes(options: Options, app: Starlette) -> None:
122122

123123
if options.serve_static_files:
124124

125-
mount_serve_single_page_app_files(app, url_prefix)
125+
app.mount(url_prefix + "/app", single_page_app_files())
126126

127127
app.mount(
128128
f"{url_prefix}/modules",
@@ -145,7 +145,7 @@ def redirect_to_index(request: Request) -> RedirectResponse:
145145
return RedirectResponse(redirect_url)
146146

147147

148-
def mount_serve_single_page_app_files(app: Starlette, url_prefix: str) -> None:
148+
def single_page_app_files() -> Callable[..., Awaitable[None]]:
149149
asgi_app = StaticFiles(
150150
directory=CLIENT_BUILD_DIR,
151151
html=True,
@@ -159,14 +159,18 @@ async def spa_app(scope: Scope, receive: Receive, send: Send) -> None:
159159
send,
160160
)
161161

162-
app.mount(url_prefix + "/app", spa_app)
162+
return spa_app
163163

164164

165165
def _setup_single_view_dispatcher_route(
166166
url_prefix: str, app: Starlette, constructor: RootComponentConstructor
167167
) -> None:
168-
@app.websocket_route(url_prefix + "/app{path:path}/_stream")
168+
@app.websocket_route(url_prefix + "/app/_stream")
169+
@app.websocket_route(url_prefix + "/app/{path:path}/_stream")
169170
async def model_stream(socket: WebSocket) -> None:
171+
path_params: dict[str, str] = socket.scope["path_params"]
172+
path_params.setdefault("path", "")
173+
170174
await socket.accept()
171175
send, recv = _make_send_recv_callbacks(socket)
172176
try:

src/idom/server/utils.py

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828

2929
def client_build_dir_path(path: str) -> str:
30+
if "./" in path or "../" in path:
31+
raise ValueError("Paths may not be relative")
3032
start, _, end = path.rpartition("/")
3133
file = end or start
3234
return file if (CLIENT_BUILD_DIR / file).is_file() else "index.html"

temp.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import idom
2-
from idom.server import tornado as server
2+
from idom.server import sanic as server
33

44

55
@idom.component

0 commit comments

Comments
 (0)