Skip to content

Commit 66f36b2

Browse files
committed
fixes #790
1 parent 3d2a979 commit 66f36b2

File tree

4 files changed

+76
-44
lines changed

4 files changed

+76
-44
lines changed

fasthtml/core.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,13 +650,17 @@ def f(func=noop): return self._add_ws(func, path, conn, disconn, name=name, midd
650650
return f
651651

652652
# %% ../nbs/api/00_core.ipynb
653-
def _mk_locfunc(f,p):
653+
def _mk_locfunc(f, p, app=None):
654654
"Create a location function wrapper with route path and to() method"
655655
class _lf:
656-
def __init__(self): update_wrapper(self, f)
656+
def __init__(self):
657+
update_wrapper(self, f)
658+
self.app = app
659+
657660
def __call__(self, *args, **kw): return f(*args, **kw)
658661
def to(self, **kw): return qp(p, **kw)
659662
def __str__(self): return p
663+
660664
return _lf()
661665

662666
# %% ../nbs/api/00_core.ipynb
@@ -676,7 +680,7 @@ def _add_route(self:FastHTML, func, path, methods, name, include_in_schema, body
676680
if not p: p = '/'+('' if fn=='index' else fn)
677681
route = Route(p, endpoint=self._endp(func, body_wrap or self.body_wrap), methods=m, name=n, include_in_schema=include_in_schema)
678682
self.add_route(route)
679-
lf = _mk_locfunc(func, p)
683+
lf = _mk_locfunc(func, p, app=self)
680684
lf.__routename__ = n
681685
return lf
682686

fasthtml/jupyter.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,13 @@ def stop(self):
110110
self.server.should_exit = True
111111
wait_port_free(self.port)
112112

113+
# %% ../nbs/api/06_jupyter.ipynb
114+
from starlette.testclient import TestClient
115+
from html import escape
116+
113117
# %% ../nbs/api/06_jupyter.ipynb
114118
def HTMX(path="/", host='localhost', app=None, port=8000, height="auto", link=False, iframe=True):
115119
"An iframe which displays the HTMX application in a notebook."
116-
if isinstance(path, (FT,tuple,Safe)):
117-
assert app, 'Need an app to render a component'
118-
route = f'/{unqid()}'
119-
res = path
120-
app.get(route)(lambda: res)
121-
path = route
122120
if isinstance(height, int): height = f"{height}px"
123121
scr = """{
124122
let frame = this;
@@ -129,9 +127,17 @@ def HTMX(path="/", host='localhost', app=None, port=8000, height="auto", link=Fa
129127
}""" if height == "auto" else ""
130128
proto = 'http' if host=='localhost' else 'https'
131129
fullpath = f"{proto}://{host}:{port}{path}" if host else path
130+
src = f'src="{fullpath}"'
132131
if link: display(HTML(f'<a href="{fullpath}" target="_blank">Open in new tab</a>'))
132+
if isinstance(path, (FT,tuple,Safe)):
133+
assert app, 'Need an app to render a component'
134+
route = f'/{unqid()}'
135+
res = path
136+
app.get(route)(lambda: res)
137+
page = TestClient(app).get(route).text
138+
src = f'srcdoc="{escape(page)}"'
133139
if iframe:
134-
return HTML(f'<iframe src="{fullpath}" style="width: 100%; height: {height}; border: none;" onload="{scr}" ' + """allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> """)
140+
return HTML(f'<iframe {src} style="width: 100%; height: {height}; border: none;" onload="{scr}" ' + """allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> """)
135141

136142
# %% ../nbs/api/06_jupyter.ipynb
137143
def ws_client(app, nm='', host='localhost', port=8000, ws_connect='/ws', frame=True, link=True, **kwargs):

nbs/api/00_core.ipynb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,13 +1778,17 @@
17781778
"outputs": [],
17791779
"source": [
17801780
"#| export\n",
1781-
"def _mk_locfunc(f,p):\n",
1781+
"def _mk_locfunc(f, p, app=None):\n",
17821782
" \"Create a location function wrapper with route path and to() method\"\n",
17831783
" class _lf:\n",
1784-
" def __init__(self): update_wrapper(self, f)\n",
1784+
" def __init__(self):\n",
1785+
" update_wrapper(self, f)\n",
1786+
" self.app = app\n",
1787+
"\n",
17851788
" def __call__(self, *args, **kw): return f(*args, **kw)\n",
17861789
" def to(self, **kw): return qp(p, **kw)\n",
17871790
" def __str__(self): return p\n",
1791+
"\n",
17881792
" return _lf()"
17891793
]
17901794
},
@@ -1854,7 +1858,7 @@
18541858
" if not p: p = '/'+('' if fn=='index' else fn)\n",
18551859
" route = Route(p, endpoint=self._endp(func, body_wrap or self.body_wrap), methods=m, name=n, include_in_schema=include_in_schema)\n",
18561860
" self.add_route(route)\n",
1857-
" lf = _mk_locfunc(func, p)\n",
1861+
" lf = _mk_locfunc(func, p, app=self)\n",
18581862
" lf.__routename__ = n\n",
18591863
" return lf"
18601864
]

nbs/api/06_jupyter.ipynb

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@
291291
"metadata": {},
292292
"outputs": [],
293293
"source": [
294-
"server.stop()"
294+
"server.stop()\n",
295+
"await asyncio.sleep(0.2)"
295296
]
296297
},
297298
{
@@ -356,7 +357,8 @@
356357
"metadata": {},
357358
"outputs": [],
358359
"source": [
359-
"server.stop()"
360+
"server.stop()\n",
361+
"await asyncio.sleep(0.2)"
360362
]
361363
},
362364
{
@@ -438,7 +440,8 @@
438440
"metadata": {},
439441
"outputs": [],
440442
"source": [
441-
"server.stop()"
443+
"server.stop()\n",
444+
"await asyncio.sleep(0.2)"
442445
]
443446
},
444447
{
@@ -486,7 +489,7 @@
486489
"text/html": [
487490
"<meta charset=\"utf-8\">\n",
488491
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n",
489-
"<script src=\"https://cdn.jsdelivr.net/npm/[email protected].4/dist/htmx.min.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script><script id=\"_DHTp7k3SRbaeAjPb6FGNOw\">if (window.htmx) htmx.process(document.body)</script>"
492+
"<script src=\"https://cdn.jsdelivr.net/npm/[email protected].7/dist/htmx.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script><script id=\"_9KAKqmADTB2z5Tid0GamWQ\">if (window.htmx) htmx.process(document.body)</script>"
490493
],
491494
"text/plain": [
492495
"<IPython.core.display.HTML object>"
@@ -548,12 +551,12 @@
548551
{
549552
"data": {
550553
"text/markdown": [
551-
"<div id=\"_Uxpzz_26TZeN-kzFA6LeKg\">\n",
552-
" <div id=\"_9s4Pdqx7TxGcbe-7x7MKVQ\"></div>\n",
553-
"<script id=\"_k1VVcEJeS3e1JxBePQpAnw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
554+
"<div id=\"_g7uKZu_ARsq-n3THwmSmWw\">\n",
555+
" <div id=\"_0h9Dbvf-R0amBDsWTQQWZg\"></div>\n",
556+
"<script id=\"_EToaMaxrRDa8MVsJAH_28Q\">if (window.htmx) htmx.process(document.body)</script></div>\n"
554557
],
555558
"text/plain": [
556-
"div(('',),{'id': '_9s4Pdqx7TxGcbe-7x7MKVQ'})"
559+
"div(('',),{'id': '_0h9Dbvf-R0amBDsWTQQWZg'})"
557560
]
558561
},
559562
"execution_count": null,
@@ -593,12 +596,12 @@
593596
{
594597
"data": {
595598
"text/markdown": [
596-
"<div id=\"_evO_Voq9Qi_5b-1p0oZS0g\">\n",
597-
" <p hx-get=\"/hoho\" hx-trigger=\"load\" id=\"_gJNOsF9IQdmKesYSdh1qTA\">not loaded</p>\n",
598-
"<script id=\"_ow7qEPILT7OQmBtdrWq9xQ\">if (window.htmx) htmx.process(document.body)</script></div>\n"
599+
"<div id=\"_XgMVy0kiSoqMdapiYOvKjQ\">\n",
600+
" <p hx-get=\"/hoho\" hx-trigger=\"load\" id=\"_x4TFzZPvTtWff74RmcHGVQ\">not loaded</p>\n",
601+
"<script id=\"_bjgxEv1nSTSRImWdUhYk4Q\">if (window.htmx) htmx.process(document.body)</script></div>\n"
599602
],
600603
"text/plain": [
601-
"p(('not loaded',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'id': '_gJNOsF9IQdmKesYSdh1qTA'})"
604+
"p(('not loaded',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'id': '_x4TFzZPvTtWff74RmcHGVQ'})"
602605
]
603606
},
604607
"execution_count": null,
@@ -627,12 +630,12 @@
627630
{
628631
"data": {
629632
"text/markdown": [
630-
"<div id=\"_knZLzRRZSa_OAxB-fR4ksQ\">\n",
631-
" <div id=\"_o4AwfFRyQWmVI9xjlOzMHA\"></div>\n",
632-
"<script id=\"_y2CD7-QJS-6s7XTrdTdVZw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
633+
"<div id=\"_tLh3mf0ZRdecNW_hYz9eYA\">\n",
634+
" <div id=\"_AsIIqL_PTs_nPASliaNYGA\"></div>\n",
635+
"<script id=\"_zVvvP66UT_aabITIdgho2g\">if (window.htmx) htmx.process(document.body)</script></div>\n"
633636
],
634637
"text/plain": [
635-
"div(('',),{'id': '_o4AwfFRyQWmVI9xjlOzMHA'})"
638+
"div(('',),{'id': '_AsIIqL_PTs_nPASliaNYGA'})"
636639
]
637640
},
638641
"execution_count": null,
@@ -653,12 +656,12 @@
653656
{
654657
"data": {
655658
"text/markdown": [
656-
"<div id=\"_irfLkUpwQxeAxNgcbzO7cA\">\n",
657-
" <p hx-get=\"/foo\" hx-trigger=\"load\" hx-target=\"#_o4AwfFRyQWmVI9xjlOzMHA\" id=\"_RtkS_DHNSNWtnCdfdhuyoA\">hi</p>\n",
658-
"<script id=\"_GaeLaNuzS9ifDsTnO6pHYw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
659+
"<div id=\"_ofCzAKDvSsWqGb_i-9X95A\">\n",
660+
" <p hx-get=\"/foo\" hx-trigger=\"load\" hx-target=\"#_AsIIqL_PTs_nPASliaNYGA\" id=\"_LjH2GVPJS_6wMMIQSQpyJA\">hi</p>\n",
661+
"<script id=\"_tWo0Xu7TSQGyuYfd68xuaA\">if (window.htmx) htmx.process(document.body)</script></div>\n"
659662
],
660663
"text/plain": [
661-
"p(('hi',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'hx-target': '#_o4AwfFRyQWmVI9xjlOzMHA', 'id': '_RtkS_DHNSNWtnCdfdhuyoA'})"
664+
"p(('hi',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'hx-target': '#_AsIIqL_PTs_nPASliaNYGA', 'id': '_LjH2GVPJS_6wMMIQSQpyJA'})"
662665
]
663666
},
664667
"execution_count": null,
@@ -679,7 +682,8 @@
679682
"metadata": {},
680683
"outputs": [],
681684
"source": [
682-
"server.stop()"
685+
"server.stop()\n",
686+
"await asyncio.sleep(0.2)"
683687
]
684688
},
685689
{
@@ -701,19 +705,25 @@
701705
{
702706
"cell_type": "code",
703707
"execution_count": null,
704-
"id": "4ef1415d",
708+
"id": "a448e420",
709+
"metadata": {},
710+
"outputs": [],
711+
"source": [
712+
"#| export\n",
713+
"from starlette.testclient import TestClient\n",
714+
"from html import escape"
715+
]
716+
},
717+
{
718+
"cell_type": "code",
719+
"execution_count": null,
720+
"id": "89b8984b",
705721
"metadata": {},
706722
"outputs": [],
707723
"source": [
708724
"#| export\n",
709725
"def HTMX(path=\"/\", host='localhost', app=None, port=8000, height=\"auto\", link=False, iframe=True):\n",
710726
" \"An iframe which displays the HTMX application in a notebook.\"\n",
711-
" if isinstance(path, (FT,tuple,Safe)):\n",
712-
" assert app, 'Need an app to render a component'\n",
713-
" route = f'/{unqid()}'\n",
714-
" res = path\n",
715-
" app.get(route)(lambda: res)\n",
716-
" path = route\n",
717727
" if isinstance(height, int): height = f\"{height}px\"\n",
718728
" scr = \"\"\"{\n",
719729
" let frame = this;\n",
@@ -724,9 +734,17 @@
724734
" }\"\"\" if height == \"auto\" else \"\"\n",
725735
" proto = 'http' if host=='localhost' else 'https'\n",
726736
" fullpath = f\"{proto}://{host}:{port}{path}\" if host else path\n",
737+
" src = f'src=\"{fullpath}\"'\n",
727738
" if link: display(HTML(f'<a href=\"{fullpath}\" target=\"_blank\">Open in new tab</a>'))\n",
739+
" if isinstance(path, (FT,tuple,Safe)):\n",
740+
" assert app, 'Need an app to render a component'\n",
741+
" route = f'/{unqid()}'\n",
742+
" res = path\n",
743+
" app.get(route)(lambda: res)\n",
744+
" page = TestClient(app).get(route).text\n",
745+
" src = f'srcdoc=\"{escape(page)}\"'\n",
728746
" if iframe:\n",
729-
" return HTML(f'<iframe src=\"{fullpath}\" style=\"width: 100%; height: {height}; border: none;\" onload=\"{scr}\" ' + \"\"\"allow=\"accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking\"></iframe> \"\"\")"
747+
" return HTML(f'<iframe {src} style=\"width: 100%; height: {height}; border: none;\" onload=\"{scr}\" ' + \"\"\"allow=\"accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking\"></iframe> \"\"\")"
730748
]
731749
},
732750
{
@@ -760,13 +778,13 @@
760778
{
761779
"cell_type": "code",
762780
"execution_count": null,
763-
"id": "0063bb43",
781+
"id": "81669294",
764782
"metadata": {},
765783
"outputs": [
766784
{
767785
"data": {
768786
"text/html": [
769-
"<iframe src=\"http://localhost:8000\" style=\"width: 100%; height: auto; border: none;\" onload=\"{\n",
787+
"<iframe src=\"http://localhost:8000/\" style=\"width: 100%; height: auto; border: none;\" onload=\"{\n",
770788
" let frame = this;\n",
771789
" window.addEventListener('message', function(e) {\n",
772790
" if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe\n",

0 commit comments

Comments
 (0)