Skip to content

Commit dcdda61

Browse files
committed
initial work to move away from run func
1 parent f96538c commit dcdda61

File tree

7 files changed

+139
-270
lines changed

7 files changed

+139
-270
lines changed

src/idom/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .core.layout import Layout
1717
from .core.vdom import vdom
1818
from .sample import run_sample_app
19-
from .server.prefab import run
19+
from .server.develop import develop
2020
from .utils import Ref, html_to_vdom
2121
from .widgets import hotswap, multiview
2222

@@ -38,7 +38,6 @@
3838
"multiview",
3939
"Ref",
4040
"run_sample_app",
41-
"run",
4241
"Stop",
4342
"types",
4443
"use_callback",

src/idom/sample.py

+4-24
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@
33
import webbrowser
44
from typing import Any
55

6-
from idom.server.types import ServerType
7-
86
from . import html
97
from .core.component import component
108
from .core.types import VdomDict
11-
from .server.utils import find_available_port, find_builtin_server_type
9+
from .server.develop import develop
1210

1311

1412
@component
1513
def App() -> VdomDict:
16-
return html.div(
14+
return html._(
1715
{"style": {"padding": "15px"}},
1816
html.h1("Sample Application"),
1917
html.p(
@@ -31,30 +29,12 @@ def run_sample_app(
3129
host: str = "127.0.0.1",
3230
port: int | None = None,
3331
open_browser: bool = False,
34-
run_in_thread: bool | None = None,
35-
) -> ServerType[Any]:
32+
) -> None:
3633
"""Run a sample application.
3734
3835
Args:
3936
host: host where the server should run
4037
port: the port on the host to serve from
4138
open_browser: whether to open a browser window after starting the server
4239
"""
43-
port = port or find_available_port(host)
44-
server_type = find_builtin_server_type("PerClientStateServer")
45-
server = server_type(App)
46-
47-
run_in_thread = open_browser or run_in_thread
48-
49-
if not run_in_thread: # pragma: no cover
50-
server.run(host=host, port=port)
51-
return server
52-
53-
thread = server.run_in_thread(host=host, port=port)
54-
server.wait_until_started(5)
55-
56-
if open_browser: # pragma: no cover
57-
webbrowser.open(f"http://{host}:{port}")
58-
thread.join()
59-
60-
return server
40+
develop(App, None, host, port, open_browser=open_browser)

src/idom/server/__init__.py

-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +0,0 @@
1-
from .prefab import hotswap_server, multiview_server, run
2-
3-
4-
__all__ = [
5-
"hotswap_server",
6-
"multiview_server",
7-
"run",
8-
]

src/idom/server/develop.py

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
import warnings
5+
import webbrowser
6+
from importlib import import_module
7+
from typing import Any, Awaitable, TypeVar, runtime_checkable
8+
9+
from typing_extensions import Protocol
10+
11+
from idom.types import ComponentConstructor
12+
13+
from .utils import find_available_port
14+
15+
16+
SUPPORTED_PACKAGES = (
17+
"starlette",
18+
"fastapi",
19+
"sanic",
20+
"flask",
21+
"tornado",
22+
)
23+
24+
25+
def develop(
26+
component: ComponentConstructor,
27+
app: Any | None = None,
28+
host: str = "127.0.0.1",
29+
port: int | None = None,
30+
open_browser: bool = True,
31+
) -> None:
32+
"""Run a component with a development server"""
33+
34+
warnings.warn(
35+
"You are running a development server, be sure to change this before deploying in production!",
36+
UserWarning,
37+
stacklevel=2,
38+
)
39+
40+
implementation = _get_implementation(app)
41+
42+
if app is None:
43+
app = implementation.create_development_app()
44+
45+
implementation.configure_development_view(component)
46+
47+
coros: list[Awaitable] = []
48+
49+
host = host
50+
port = port or find_available_port(host)
51+
server_did_start = asyncio.Event()
52+
53+
coros.append(
54+
implementation.serve_development_app(
55+
app,
56+
host=host,
57+
port=port,
58+
did_start=server_did_start,
59+
)
60+
)
61+
62+
if open_browser:
63+
coros.append(_open_browser_after_server(host, port, server_did_start))
64+
65+
asyncio.get_event_loop().run_forever(asyncio.gather(*coros))
66+
67+
68+
async def _open_browser_after_server(
69+
host: str,
70+
port: int,
71+
server_did_start: asyncio.Event,
72+
) -> None:
73+
await server_did_start.wait()
74+
webbrowser.open(f"http://{host}:{port}")
75+
76+
77+
def _get_implementation(app: _App | None) -> _Implementation:
78+
implementations = _all_implementations()
79+
80+
if app is None:
81+
return next(iter(implementations.values()))
82+
83+
for cls in type(app).mro():
84+
if cls in implementations:
85+
return implementations[cls]
86+
else:
87+
raise TypeError(f"No built-in and installed implementation supports {app}")
88+
89+
90+
def _all_implementations() -> dict[type[Any], _Implementation]:
91+
if not _INSTALLED_IMPLEMENTATIONS:
92+
for name in SUPPORTED_PACKAGES:
93+
try:
94+
module = import_module(f"idom.server.{name}")
95+
except ImportError: # pragma: no cover
96+
continue
97+
98+
if not isinstance(module, _Implementation):
99+
raise TypeError(f"{module.__name__!r} is an invalid implementation")
100+
101+
_INSTALLED_IMPLEMENTATIONS[module.SERVER_TYPE] = module
102+
103+
if not _INSTALLED_IMPLEMENTATIONS:
104+
raise RuntimeError("No built-in implementations are installed")
105+
106+
return _INSTALLED_IMPLEMENTATIONS
107+
108+
109+
_App = TypeVar("_App")
110+
111+
112+
@runtime_checkable
113+
class _Implementation(Protocol):
114+
115+
APP_TYPE = type[Any]
116+
117+
def create_development_app(self) -> Any:
118+
...
119+
120+
def configure_development_view(self, component: ComponentConstructor) -> None:
121+
...
122+
123+
async def serve_development_app(
124+
self,
125+
app: Any,
126+
host: str,
127+
port: int,
128+
did_start: asyncio.Event,
129+
) -> None:
130+
...
131+
132+
133+
_INSTALLED_IMPLEMENTATIONS: dict[type[Any], _Implementation[Any]] = {}

src/idom/server/prefab.py

-151
This file was deleted.

0 commit comments

Comments
 (0)