Skip to content

Commit e282bbc

Browse files
committed
fix/simplify introduction example notebook
1 parent 3fecd97 commit e282bbc

File tree

7 files changed

+158
-123
lines changed

7 files changed

+158
-123
lines changed

examples/example_utils.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,45 @@
11
import os
2-
from typing import Mapping, Any, Optional
2+
from IPython import display
3+
from typing import Mapping, Any, Optional, Callable, Tuple, Type
34

5+
from idom.server.base import AbstractRenderServer
6+
from idom.server import imperative_server_mount
47

5-
def example_uri_root(protocol: str, port: int) -> str:
6-
"""Returns the IDOM root URI for example notebooks
78

8-
When examples are running on mybinder.org or in a container created by
9-
jupyter-repo2docker this is not simply "localhost" or "127.0.0.1".
10-
Instead we use a route produced by ``jupyter_server_proxy`` instead.
11-
"""
12-
if "JUPYTERHUB_OAUTH_CALLBACK_URL" in os.environ:
13-
auth = os.environ["JUPYTERHUB_OAUTH_CALLBACK_URL"].rsplit("/", 1)[0]
14-
return "%s/proxy/%s" % (auth, port)
15-
elif "JUPYTER_SERVER_URL" in os.environ:
16-
return "%s/proxy/%s" % (os.environ["JUPYTER_SERVER_URL"], port)
17-
else:
18-
return "%s://127.0.0.1:%s" % (protocol, port)
9+
def setup_example_server(
10+
server: Type[AbstractRenderServer], host: str, port: int
11+
) -> Tuple[str, AbstractRenderServer, Callable[..., Any]]:
12+
server_instance, mount = imperative_server_mount(
13+
server, host, port, {"access_log": False}, {"cors": True},
14+
)
1915

16+
localhost_idom_path = f"http://{host}:{port}"
17+
jupyterhub_idom_path = path_to_jupyterhub_proxy(port)
2018

21-
def is_on_jupyterhub() -> bool:
22-
return (
23-
"JUPYTER_SERVER_URL" in os.environ
24-
or "JUPYTERHUB_OAUTH_CALLBACK_URL" in os.environ
25-
)
19+
path_to_idom = jupyterhub_idom_path or localhost_idom_path
2620

21+
return path_to_idom, server_instance, mount
2722

28-
class HtmlLink:
29-
def __init__(self, href: str, text: Optional[str] = None):
30-
self.href, self.text = href, text
3123

32-
def __str__(self) -> str:
33-
return self.href
24+
def path_to_jupyterhub_proxy(port: int) -> Optional[str]:
25+
"""If running on Jupyterhub return the path from the host's root to a proxy server
26+
27+
This is used when examples are running on mybinder.org or in a container created by
28+
jupyter-repo2docker. For this to work a ``jupyter_server_proxy`` must have been
29+
instantiated.
30+
"""
31+
if "JUPYTERHUB_OAUTH_CALLBACK_URL" in os.environ:
32+
url = os.environ["JUPYTERHUB_OAUTH_CALLBACK_URL"].rsplit("/", 1)[0]
33+
return f"{url}/proxy/{port}"
34+
elif "JUPYTER_SERVER_URL" in os.environ:
35+
return f"{os.environ['JUPYTER_SERVER_URL']}/proxy/{port}"
36+
else:
37+
return None
38+
3439

35-
def _repr_html_(self) -> str:
36-
return f"<a href='{self.href}' target='_blank'>{self.text or self.href}</a>"
40+
def display_href(href: str) -> None:
41+
display.display_html(f"<a href='{href}' target='_blank'>{href}</a>", raw=True)
42+
return None
3743

3844

3945
def pretty_dict_string(

examples/introduction.ipynb

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
},
4848
{
4949
"cell_type": "code",
50-
"execution_count": null,
50+
"execution_count": 1,
5151
"metadata": {},
5252
"outputs": [],
5353
"source": [
@@ -65,30 +65,26 @@
6565
},
6666
{
6767
"cell_type": "code",
68-
"execution_count": null,
69-
"metadata": {},
70-
"outputs": [],
68+
"execution_count": 2,
69+
"metadata": {},
70+
"outputs": [
71+
{
72+
"name": "stdout",
73+
"output_type": "stream",
74+
"text": [
75+
"[2020-04-22 17:37:27 -0700] [3083] [INFO] Goin' Fast @ http://127.0.0.1:8765\n"
76+
]
77+
}
78+
],
7179
"source": [
7280
"from idom.server.sanic import PerClientState\n",
73-
"from example_utils import (\n",
74-
" example_uri_root,\n",
75-
" HtmlLink,\n",
76-
" pretty_dict_string,\n",
77-
" is_on_jupyterhub,\n",
78-
")\n",
79-
"\n",
80-
"webpage_url = HtmlLink(example_uri_root(\"http\", 8765))\n",
81-
"\n",
82-
"mount, element = idom.hotswap()\n",
83-
"PerClientState(element).configure({\"cors\": not is_on_jupyterhub()}).daemon(\n",
84-
" \"127.0.0.1\", 8765, access_log=False\n",
85-
")\n",
81+
"from example_utils import setup_example_server, display_href, pretty_dict_string\n",
8682
"\n",
83+
"server_url, server, mount = setup_example_server(PerClientState, \"127.0.0.1\", 8765)\n",
8784
"\n",
88-
"def display(*args, **kwargs):\n",
89-
" element, *args = args\n",
85+
"def display(element, *args, **kwargs):\n",
9086
" mount(element, *args, **kwargs)\n",
91-
" return idom.display(\"jupyter\", str(webpage_url), secure=is_on_jupyterhub())"
87+
" return idom.display(\"jupyter\", server_url)"
9288
]
9389
},
9490
{
@@ -102,7 +98,7 @@
10298
},
10399
{
104100
"cell_type": "code",
105-
"execution_count": null,
101+
"execution_count": 3,
106102
"metadata": {},
107103
"outputs": [],
108104
"source": [
@@ -127,9 +123,61 @@
127123
},
128124
{
129125
"cell_type": "code",
130-
"execution_count": null,
131-
"metadata": {},
132-
"outputs": [],
126+
"execution_count": 4,
127+
"metadata": {},
128+
"outputs": [
129+
{
130+
"name": "stdout",
131+
"output_type": "stream",
132+
"text": [
133+
"Try clicking the image! 🖱️\n"
134+
]
135+
},
136+
{
137+
"data": {
138+
"text/html": [
139+
"<script>document.idomServerExists = true;</script>"
140+
]
141+
},
142+
"metadata": {},
143+
"output_type": "display_data"
144+
},
145+
{
146+
"data": {
147+
"text/html": [
148+
"\n",
149+
" <div id=\"idom-2dbb91f1d1c948adab63c95854c5b9db\" class=\"idom-widget\"/>\n",
150+
" \n",
151+
" <script type=\"module\">\n",
152+
" // we want to avoid making this request (in case of CORS)\n",
153+
" // unless we know an IDOM server is expected to respond\n",
154+
" if (document.idomServerExists) {\n",
155+
" const loc = {\"host\": \"127.0.0.1:8765\", \"protocol\": \"http:\"};\n",
156+
" const idom_url = \"//\" + loc.host + \"\";\n",
157+
" const http_proto = loc.protocol;\n",
158+
" const ws_proto = (http_proto === \"https:\") ? \"wss:\" : \"ws:\";\n",
159+
" import(http_proto + idom_url + \"/client/core_modules/layout.js\").then(\n",
160+
" (module) => {\n",
161+
" module.renderLayout(\n",
162+
" document.getElementById(\"idom-2dbb91f1d1c948adab63c95854c5b9db\"),\n",
163+
" ws_proto + idom_url + \"/stream\"\n",
164+
" );\n",
165+
" }\n",
166+
" );\n",
167+
" }\n",
168+
" </script>\n",
169+
" \n",
170+
" "
171+
],
172+
"text/plain": [
173+
"JupyterWigdet('')"
174+
]
175+
},
176+
"execution_count": 4,
177+
"metadata": {},
178+
"output_type": "execute_result"
179+
}
180+
],
133181
"source": [
134182
"print(\"Try clicking the image! 🖱️\")\n",
135183
"\n",
@@ -151,7 +199,7 @@
151199
"metadata": {},
152200
"outputs": [],
153201
"source": [
154-
"webpage_url"
202+
"display_href(server_url)"
155203
]
156204
},
157205
{
@@ -770,19 +818,13 @@
770818
"outputs": [],
771819
"source": [
772820
"from idom.server.sanic import SharedClientState\n",
821+
"from example_utils import setup_example_server, display_href, pretty_dict_string\n",
773822
"\n",
774-
"shared_webpage_url = HtmlLink(example_uri_root(\"http\", 5678))\n",
775-
"\n",
776-
"mount_shared, element = idom.hotswap()\n",
777-
"SharedClientState(element).configure({\"cors\": not is_on_jupyterhub()}).daemon(\n",
778-
" \"127.0.0.1\", 5678, access_log=False\n",
779-
")\n",
780-
"\n",
823+
"shared_server_url, shared_server, mount_shared = setup_example_server(SharedClientState, \"127.0.0.1\", 5678)\n",
781824
"\n",
782-
"def shared_display(*args, **kwargs):\n",
783-
" element, *args = args\n",
825+
"def display_shared(element, *args, **kwargs):\n",
784826
" mount_shared(element, *args, **kwargs)\n",
785-
" return idom.display(\"jupyter\", str(shared_webpage_url), secure=is_on_jupyterhub())"
827+
" return idom.display(\"jupyter\", shared_server_url)"
786828
]
787829
},
788830
{
@@ -798,7 +840,7 @@
798840
"metadata": {},
799841
"outputs": [],
800842
"source": [
801-
"display = shared_display(DragDropBoxes)"
843+
"display = display_shared(DragDropBoxes)"
802844
]
803845
},
804846
{

idom/client/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
node_modules
22
web_modules
3-
etc_modules
3+
user_modules

idom/client/user_modules/chart.js

Lines changed: 0 additions & 39 deletions
This file was deleted.

idom/server/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
from importlib import import_module
2+
from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar
3+
4+
from idom.core.element import ElementConstructor
5+
from idom.widgets.common import hotswap
6+
7+
from .base import AbstractRenderServer
8+
29

310
__all__ = []
411

@@ -11,3 +18,19 @@
1118
else:
1219
import_module(__name__ + "." + name)
1320
__all__.append(name)
21+
22+
23+
_S = TypeVar("_S", bound=AbstractRenderServer[Any, Any])
24+
25+
26+
def imperative_server_mount(
27+
server: Type[_S],
28+
host: str,
29+
port: int,
30+
run_options: Optional[Dict[str, Any]] = None,
31+
server_options: Optional[Dict[str, Any]] = None,
32+
) -> Tuple[_S, Callable[[ElementConstructor], None]]:
33+
mount, element = hotswap()
34+
server_instance = server(element).configure(server_options or {})
35+
server_instance.daemon(host, port, **(run_options or {}))
36+
return server_instance, mount

idom/server/sanic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _setup_application(self, app: Sanic, config: Config) -> None:
5656
app.route("/favicon.ico")(
5757
lambda r: response.redirect("/client/favicon.ico")
5858
)
59-
app.route(url_prefix + "/")(lambda r: response.redirect("/client/index.html"))
59+
app.route(url_prefix + "/")(lambda r: response.redirect("./client/index.html"))
6060
app.websocket(url_prefix + "/stream")(self._stream_route)
6161

6262
def _run_application(

idom/widgets/display.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import uuid
2+
import json
23
from typing import Any
3-
from urllib.parse import urlsplit, urlunsplit
4+
from urllib.parse import urlparse
45
from IPython import display as _ipy_display
56

67

@@ -19,20 +20,17 @@ def display(kind: str, *args: Any, **kwargs: Any) -> Any:
1920
class JupyterWigdet:
2021
"""Output for IDOM within a Jupyter Notebook."""
2122

22-
__slots__ = ("_ws", "_http")
23+
__slots__ = ("_location", "_path")
2324

24-
def __init__(self, url: str, secure=True) -> None:
25-
uri = urlunsplit(("",) + urlsplit(url)[1:])
26-
if uri.endswith("/"):
27-
uri = uri[:-1]
28-
if secure:
29-
ws_proto = "wss"
30-
http_proto = "https"
25+
def __init__(self, url: str) -> None:
26+
parsed_url = urlparse(url)
27+
if not parsed_url.netloc:
28+
self._location = "window.location"
3129
else:
32-
ws_proto = "ws"
33-
http_proto = "http"
34-
self._ws = ws_proto + ":" + uri
35-
self._http = http_proto + ":" + uri
30+
self._location = json.dumps(
31+
{"host": parsed_url.netloc, "protocol": parsed_url.scheme + ":"}
32+
)
33+
self._path = parsed_url.path
3634
_ipy_display.display_html(
3735
"<script>document.idomServerExists = true;</script>", raw=True,
3836
)
@@ -43,13 +41,18 @@ def _script(self, mount_id):
4341
// we want to avoid making this request (in case of CORS)
4442
// unless we know an IDOM server is expected to respond
4543
if (document.idomServerExists) {{
46-
import("{self._http}/client/core_modules/layout.js").then(
47-
module => {{
48-
module.renderLayout(
49-
document.getElementById("{mount_id}"), "{self._ws}/stream"
50-
)
44+
const loc = {self._location};
45+
const idom_url = "//" + loc.host + "{self._path}";
46+
const http_proto = loc.protocol;
47+
const ws_proto = (http_proto === "https:") ? "wss:" : "ws:";
48+
import(http_proto + idom_url + "/client/core_modules/layout.js").then(
49+
(module) => {{
50+
module.renderLayout(
51+
document.getElementById("{mount_id}"),
52+
ws_proto + idom_url + "/stream"
53+
);
5154
}}
52-
)
55+
);
5356
}}
5457
</script>
5558
"""
@@ -63,4 +66,4 @@ def _repr_html_(self) -> str:
6366
"""
6467

6568
def __repr__(self) -> str:
66-
return "%s(%r)" % (type(self).__name__, self._http)
69+
return "%s(%r)" % (type(self).__name__, self._path)

0 commit comments

Comments
 (0)