Skip to content

Commit 2fdba9e

Browse files
committed
use dataclasses for options instead of typeddict
1 parent b069dd8 commit 2fdba9e

File tree

12 files changed

+100
-108
lines changed

12 files changed

+100
-108
lines changed

docs/app.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from idom import component
88
from idom.core.types import ComponentConstructor
9-
from idom.server.sanic import configure, use_request
9+
from idom.server.sanic import Options, configure, use_request
1010

1111
from .examples import load_examples
1212

@@ -26,10 +26,10 @@ def run():
2626
configure(
2727
app,
2828
Example(),
29-
{
30-
"redirect_root_to_index": False,
31-
"url_prefix": IDOM_MODEL_SERVER_URL_PREFIX,
32-
},
29+
Options(
30+
redirect_root=False,
31+
url_prefix=IDOM_MODEL_SERVER_URL_PREFIX,
32+
),
3333
)
3434

3535
app.run(

docs/source/guides/getting-started/running-idom.rst

+21-3
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,29 @@ Tornado is run using it's own builtin server rather than an external WSGI or ASG
9999
server.
100100

101101

102-
Configuration Options
103-
---------------------
102+
Server Configuration Options
103+
----------------------------
104104

105105
IDOM's various server implementations come with ``Options`` that can be passed to their
106-
respective ``configure()`` functions. Each provides a similar set of key value pairs
106+
respective ``configure()`` functions. Those which are common amongst the options are:
107+
108+
- ``url_prefix`` - prefix all routes configured by IDOM
109+
- ``redirect_root`` - whether to redirect the root of the application to the IDOM view
110+
- ``serve_static_files`` - whether to server IDOM's static files from it's default route
111+
112+
You'd then pass these options to ``configure()`` in the following way:
113+
114+
.. code-block::
115+
116+
configure(app, MyComponent, Options(...))
117+
118+
To learn more read the description for your chosen server implementation:
119+
120+
- :class:`idom.server.fastapi.Options`
121+
- :class:`idom.server.flask.Options`
122+
- :class:`idom.server.sanic.Options`
123+
- :class:`idom.server.starlette.Options`
124+
- :class:`idom.server.tornado.Options`
107125

108126

109127
Running IDOM in Debug Mode

scripts/live_docs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
)
1515

1616
from docs.app import IDOM_MODEL_SERVER_URL_PREFIX, Example, make_app, reload_examples
17-
from idom.server.sanic import configure, serve_development_app
17+
from idom.server.sanic import Options, configure, serve_development_app
1818
from idom.testing import clear_idom_web_modules_dir
1919

2020

@@ -33,7 +33,7 @@ def wrap_builder(old_builder):
3333
configure(
3434
app,
3535
Example,
36-
{"cors": True, "url_prefix": IDOM_MODEL_SERVER_URL_PREFIX},
36+
Options(cors=True, url_prefix=IDOM_MODEL_SERVER_URL_PREFIX),
3737
)
3838

3939
thread_started = threading.Event()

setup.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ warn_unused_ignores = True
99

1010
[flake8]
1111
ignore = E203, E266, E501, W503, F811, N802, N806
12+
per-file-ignores =
13+
# sometimes this is required in order to hide setup for an example
14+
docs/*/_examples/*.py:E402
1215
max-line-length = 88
1316
max-complexity = 18
1417
select = B,C,E,F,W,T4,B9,N,ROH

src/idom/server/default.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
from .utils import all_implementations
1010

1111

12-
def configure(app: Any, component: RootComponentConstructor) -> None:
12+
def configure(
13+
app: Any, component: RootComponentConstructor, options: None = None
14+
) -> None:
1315
"""Configure the given app instance to display the given component"""
16+
if options is not None: # pragma: no cover
17+
raise ValueError("Default implementation cannot be configured with options")
1418
return _default_implementation().configure(app, component)
1519

1620

src/idom/server/fastapi.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
use_websocket = starlette.use_websocket # noqa: ROH101
2020
"""Alias for :func:`starlette.use_websocket`"""
2121

22+
Options = starlette.Options
23+
"""Alias for :class:`starlette.Options`"""
24+
2225

2326
def configure(
2427
app: FastAPI,
@@ -34,9 +37,7 @@ def configure(
3437
"""
3538
options = starlette._setup_options(options)
3639
starlette._setup_common_routes(options, app)
37-
starlette._setup_single_view_dispatcher_route(
38-
options["url_prefix"], app, constructor
39-
)
40+
starlette._setup_single_view_dispatcher_route(options.url_prefix, app, constructor)
4041

4142

4243
def create_development_app() -> FastAPI:

src/idom/server/flask.py

+13-28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import logging
66
from asyncio import Queue as AsyncQueue
7+
from dataclasses import dataclass
78
from queue import Queue as ThreadQueue
89
from threading import Event as ThreadEvent
910
from threading import Thread
@@ -24,7 +25,6 @@
2425
from gevent import pywsgi
2526
from geventwebsocket.handler import WebSocketHandler
2627
from geventwebsocket.websocket import WebSocket
27-
from typing_extensions import TypedDict
2828

2929
import idom
3030
from idom.config import IDOM_WEB_MODULES_DIR
@@ -53,8 +53,8 @@ def configure(
5353
options: Options for configuring server behavior
5454
app: An application instance (otherwise a default instance is created)
5555
"""
56-
options = _setup_options(options)
57-
blueprint = Blueprint("idom", __name__, url_prefix=options["url_prefix"])
56+
options = options or Options()
57+
blueprint = Blueprint("idom", __name__, url_prefix=options.url_prefix)
5858
_setup_common_routes(blueprint, options)
5959
_setup_single_view_dispatcher_route(app, options, component)
6060
app.register_blueprint(blueprint)
@@ -126,48 +126,33 @@ def use_scope() -> dict[str, Any]:
126126
return use_request().environ
127127

128128

129-
class Options(TypedDict, total=False):
129+
@dataclass
130+
class Options:
130131
"""Render server config for :class:`FlaskRenderServer`"""
131132

132-
cors: Union[bool, Dict[str, Any]]
133+
cors: Union[bool, Dict[str, Any]] = False
133134
"""Enable or configure Cross Origin Resource Sharing (CORS)
134135
135136
For more information see docs for ``flask_cors.CORS``
136137
"""
137138

138-
import_name: str
139-
"""The module where the application instance was created
140-
141-
For more info see :class:`flask.Flask`.
142-
"""
143-
144-
redirect_root_to_index: bool
139+
redirect_root: bool = True
145140
"""Whether to redirect the root URL (with prefix) to ``index.html``"""
146141

147-
serve_static_files: bool
142+
serve_static_files: bool = True
148143
"""Whether or not to serve static files (i.e. web modules)"""
149144

150-
url_prefix: str
145+
url_prefix: str = ""
151146
"""The URL prefix where IDOM resources will be served from"""
152147

153148

154-
def _setup_options(options: Options | None) -> Options:
155-
return {
156-
"url_prefix": "",
157-
"cors": False,
158-
"serve_static_files": True,
159-
"redirect_root_to_index": True,
160-
**(options or {}), # type: ignore
161-
}
162-
163-
164149
def _setup_common_routes(blueprint: Blueprint, options: Options) -> None:
165-
cors_options = options["cors"]
150+
cors_options = options.cors
166151
if cors_options: # pragma: no cover
167152
cors_params = cors_options if isinstance(cors_options, dict) else {}
168153
CORS(blueprint, **cors_params)
169154

170-
if options["serve_static_files"]:
155+
if options.serve_static_files:
171156

172157
@blueprint.route("/client/<path:path>")
173158
def send_client_dir(path: str) -> Any:
@@ -177,7 +162,7 @@ def send_client_dir(path: str) -> Any:
177162
def send_modules_dir(path: str) -> Any:
178163
return send_from_directory(str(IDOM_WEB_MODULES_DIR.current), path)
179164

180-
if options["redirect_root_to_index"]:
165+
if options.redirect_root:
181166

182167
@blueprint.route("/")
183168
def redirect_to_index() -> Any:
@@ -195,7 +180,7 @@ def _setup_single_view_dispatcher_route(
195180
) -> None:
196181
sockets = Sockets(app)
197182

198-
@sockets.route(_join_url_paths(options["url_prefix"], "/stream")) # type: ignore
183+
@sockets.route(_join_url_paths(options.url_prefix, "/stream")) # type: ignore
199184
def model_stream(ws: WebSocket) -> None:
200185
def send(value: Any) -> None:
201186
ws.send(json.dumps(value))

src/idom/server/sanic.py

+12-23
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import asyncio
44
import json
55
import logging
6+
from dataclasses import dataclass
67
from typing import Any, Dict, Tuple, Union
78
from uuid import uuid4
89

9-
from mypy_extensions import TypedDict
1010
from sanic import Blueprint, Sanic, request, response
1111
from sanic.config import Config
1212
from sanic.models.asgi import ASGIScope
@@ -39,10 +39,8 @@ def configure(
3939
app: Sanic, component: RootComponentConstructor, options: Options | None = None
4040
) -> None:
4141
"""Configure an application instance to display the given component"""
42-
options = _setup_options(options)
43-
blueprint = Blueprint(
44-
f"idom_dispatcher_{id(app)}", url_prefix=options["url_prefix"]
45-
)
42+
options = options or Options()
43+
blueprint = Blueprint(f"idom_dispatcher_{id(app)}", url_prefix=options.url_prefix)
4644
_setup_common_routes(blueprint, options)
4745
_setup_single_view_dispatcher_route(blueprint, component)
4846
app.blueprint(blueprint)
@@ -83,46 +81,37 @@ def use_scope() -> ASGIScope:
8381
return asgi_app.transport.scope
8482

8583

86-
class Options(TypedDict, total=False):
84+
@dataclass
85+
class Options:
8786
"""Options for :class:`SanicRenderServer`"""
8887

89-
cors: Union[bool, Dict[str, Any]]
88+
cors: Union[bool, Dict[str, Any]] = False
9089
"""Enable or configure Cross Origin Resource Sharing (CORS)
9190
9291
For more information see docs for ``sanic_cors.CORS``
9392
"""
9493

95-
redirect_root_to_index: bool
94+
redirect_root: bool = True
9695
"""Whether to redirect the root URL (with prefix) to ``index.html``"""
9796

98-
serve_static_files: bool
97+
serve_static_files: bool = True
9998
"""Whether or not to serve static files (i.e. web modules)"""
10099

101-
url_prefix: str
100+
url_prefix: str = ""
102101
"""The URL prefix where IDOM resources will be served from"""
103102

104103

105-
def _setup_options(options: Options | None) -> Options:
106-
return {
107-
"cors": False,
108-
"url_prefix": "",
109-
"serve_static_files": True,
110-
"redirect_root_to_index": True,
111-
**(options or {}), # type: ignore
112-
}
113-
114-
115104
def _setup_common_routes(blueprint: Blueprint, options: Options) -> None:
116-
cors_options = options["cors"]
105+
cors_options = options.cors
117106
if cors_options: # pragma: no cover
118107
cors_params = cors_options if isinstance(cors_options, dict) else {}
119108
CORS(blueprint, **cors_params)
120109

121-
if options["serve_static_files"]:
110+
if options.serve_static_files:
122111
blueprint.static("/client", str(CLIENT_BUILD_DIR))
123112
blueprint.static("/modules", str(IDOM_WEB_MODULES_DIR.current))
124113

125-
if options["redirect_root_to_index"]:
114+
if options.redirect_root:
126115

127116
@blueprint.route("/") # type: ignore
128117
def redirect_to_index(

src/idom/server/starlette.py

+13-22
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import asyncio
44
import json
55
import logging
6+
from dataclasses import dataclass
67
from typing import Any, Dict, Tuple, Union
78

8-
from mypy_extensions import TypedDict
99
from starlette.applications import Starlette
1010
from starlette.middleware.cors import CORSMiddleware
1111
from starlette.requests import Request
@@ -50,9 +50,9 @@ def configure(
5050
constructor: A component constructor
5151
options: Options for configuring server behavior
5252
"""
53-
options = _setup_options(options)
53+
options = options or Options()
5454
_setup_common_routes(options, app)
55-
_setup_single_view_dispatcher_route(options["url_prefix"], app, constructor)
55+
_setup_single_view_dispatcher_route(options.url_prefix, app, constructor)
5656

5757

5858
def create_development_app() -> Starlette:
@@ -85,37 +85,28 @@ def use_scope() -> Scope:
8585
return use_websocket().scope
8686

8787

88-
class Options(TypedDict, total=False):
88+
@dataclass
89+
class Options:
8990
"""Optionsuration options for :class:`StarletteRenderServer`"""
9091

91-
cors: Union[bool, Dict[str, Any]]
92+
cors: Union[bool, Dict[str, Any]] = False
9293
"""Enable or configure Cross Origin Resource Sharing (CORS)
9394
9495
For more information see docs for ``starlette.middleware.cors.CORSMiddleware``
9596
"""
9697

97-
redirect_root_to_index: bool
98+
redirect_root: bool = True
9899
"""Whether to redirect the root URL (with prefix) to ``index.html``"""
99100

100-
serve_static_files: bool
101+
serve_static_files: bool = True
101102
"""Whether or not to serve static files (i.e. web modules)"""
102103

103-
url_prefix: str
104+
url_prefix: str = ""
104105
"""The URL prefix where IDOM resources will be served from"""
105106

106107

107-
def _setup_options(options: Options | None) -> Options:
108-
return {
109-
"cors": False,
110-
"url_prefix": "",
111-
"serve_static_files": True,
112-
"redirect_root_to_index": True,
113-
**(options or {}), # type: ignore
114-
}
115-
116-
117108
def _setup_common_routes(options: Options, app: Starlette) -> None:
118-
cors_options = options["cors"]
109+
cors_options = options.cors
119110
if cors_options: # pragma: no cover
120111
cors_params = (
121112
cors_options if isinstance(cors_options, dict) else {"allow_origins": ["*"]}
@@ -124,8 +115,8 @@ def _setup_common_routes(options: Options, app: Starlette) -> None:
124115

125116
# This really should be added to the APIRouter, but there's a bug in Starlette
126117
# BUG: https://github.com/tiangolo/fastapi/issues/1469
127-
url_prefix = options["url_prefix"]
128-
if options["serve_static_files"]:
118+
url_prefix = options.url_prefix
119+
if options.serve_static_files:
129120
app.mount(
130121
f"{url_prefix}/client",
131122
StaticFiles(
@@ -145,7 +136,7 @@ def _setup_common_routes(options: Options, app: Starlette) -> None:
145136
name="idom_web_module_files",
146137
)
147138

148-
if options["redirect_root_to_index"]:
139+
if options.redirect_root:
149140

150141
@app.route(f"{url_prefix}/")
151142
def redirect_to_index(request: Request) -> RedirectResponse:

0 commit comments

Comments
 (0)