Skip to content

Commit

Permalink
fixes #640
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Jan 28, 2025
1 parent ce9842f commit a2508e7
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 18 deletions.
5 changes: 5 additions & 0 deletions fasthtml/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@
'fasthtml.jupyter.JupyUvi.__init__': ('api/jupyter.html#jupyuvi.__init__', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUvi.start': ('api/jupyter.html#jupyuvi.start', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUvi.stop': ('api/jupyter.html#jupyuvi.stop', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUviAsync': ('api/jupyter.html#jupyuviasync', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUviAsync.__init__': ( 'api/jupyter.html#jupyuviasync.__init__',
'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUviAsync.start': ('api/jupyter.html#jupyuviasync.start', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUviAsync.stop': ('api/jupyter.html#jupyuviasync.stop', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.htmx_config_port': ('api/jupyter.html#htmx_config_port', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.is_port_free': ('api/jupyter.html#is_port_free', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.nb_serve': ('api/jupyter.html#nb_serve', 'fasthtml/jupyter.py'),
Expand Down
15 changes: 14 additions & 1 deletion fasthtml/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# %% auto 0
__all__ = ['nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'show', 'render_ft', 'htmx_config_port', 'JupyUvi',
'HTMX', 'ws_client']
'JupyUviAsync', 'HTMX', 'ws_client']

# %% ../nbs/api/06_jupyter.ipynb
import asyncio, socket, time, uvicorn
Expand Down Expand Up @@ -92,6 +92,19 @@ def stop(self):
self.server.should_exit = True
wait_port_free(self.port)

# %% ../nbs/api/06_jupyter.ipynb
class JupyUviAsync(JupyUvi):
"Start and stop an async Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
def __init__(self, app, log_level="error", host='0.0.0.0', port=8000, **kwargs):
super().__init__(app, log_level=log_level, host=host, port=port, start=False, **kwargs)

async def start(self):
self.server = await nb_serve_async(self.app, log_level=self.log_level, host=self.host, port=self.port, **self.kwargs)

def stop(self):
self.server.should_exit = True
wait_port_free(self.port)

# %% ../nbs/api/06_jupyter.ipynb
def HTMX(path="", app=None, host='localhost', port=8000, height="auto", link=False, iframe=True):
"An iframe which displays the HTMX application in a notebook."
Expand Down
182 changes: 165 additions & 17 deletions nbs/api/06_jupyter.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,104 @@
},
{
"cell_type": "markdown",
"id": "27fba5b0",
"id": "addaa9c3",
"metadata": {},
"source": [
"You can stop the server, modify routes, and start the server again without restarting the notebook or recreating the server or application."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8ea9128a",
"metadata": {},
"outputs": [],
"source": [
"server.stop()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9134035e",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class JupyUviAsync(JupyUvi):\n",
" \"Start and stop an async Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`\"\n",
" def __init__(self, app, log_level=\"error\", host='0.0.0.0', port=8000, **kwargs):\n",
" super().__init__(app, log_level=log_level, host=host, port=port, start=False, **kwargs)\n",
"\n",
" async def start(self):\n",
" self.server = await nb_serve_async(self.app, log_level=self.log_level, host=self.host, port=self.port, **self.kwargs)\n",
"\n",
" def stop(self):\n",
" self.server.should_exit = True\n",
" wait_port_free(self.port)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "959eb254",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"<script>\n",
"document.body.addEventListener('htmx:configRequest', (event) => {\n",
" if(event.detail.path.includes('://')) return;\n",
" htmx.config.selfRequestsOnly=false;\n",
" event.detail.path = `${location.protocol}//${location.hostname}:8000${event.detail.path}`;\n",
"});\n",
"</script>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"server = JupyUviAsync(app, port=port)\n",
"await server.start()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e4550d23",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hi\n"
]
}
],
"source": [
"async with AsyncClient() as client:\n",
" r = await client.get(f'http://localhost:{port}')\n",
"print(r.text)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7869af78",
"metadata": {},
"outputs": [],
"source": [
"server.stop()"
]
},
{
"cell_type": "markdown",
"id": "f3db86f5",
Expand Down Expand Up @@ -313,6 +405,32 @@
"After importing `fasthtml.jupyter` and calling `render_ft()`, FT components render directly in the notebook."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "694cdab5",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<meta charset=\"utf-8\">\n",
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n",
"<script src=\"https://unpkg.com/[email protected]/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=\"_7KKmEIAnRE_-M4c4Lf5dJg\">if (window.htmx) htmx.process(document.body)</script>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show(*def_hdrs())\n",
"render_ft()"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -322,12 +440,12 @@
{
"data": {
"text/markdown": [
"<div id=\"_2ecQ0yY4QSu5jZSem8p43w\">\n",
" <div id=\"_kTd5d5FwSHuVKeOD7z-yUw\">Cogito ergo sum</div>\n",
"<script id=\"_MUmKg5uVRBq4dFYAAcqLXw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
"<div id=\"_NNvXojeGS-eH1SZLG-pE4Q\">\n",
" <div id=\"_vzHRxQNEQiaSQnLdUFq2Mg\">Cogito ergo sum</div>\n",
"<script id=\"_SN7to4-bQ5O09J6vs2UvNA\">if (window.htmx) htmx.process(document.body)</script></div>\n"
],
"text/plain": [
"div(('Cogito ergo sum',),{'id': '_kTd5d5FwSHuVKeOD7z-yUw'})"
"div(('Cogito ergo sum',),{'id': '_vzHRxQNEQiaSQnLdUFq2Mg'})"
]
},
"execution_count": null,
Expand All @@ -347,6 +465,36 @@
"Handlers are written just like a regular web app:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "78d40711",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"<script>\n",
"document.body.addEventListener('htmx:configRequest', (event) => {\n",
" if(event.detail.path.includes('://')) return;\n",
" htmx.config.selfRequestsOnly=false;\n",
" event.detail.path = `${location.protocol}//${location.hostname}:8000${event.detail.path}`;\n",
"});\n",
"</script>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"server = JupyUvi(app, port=port)"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -375,12 +523,12 @@
{
"data": {
"text/markdown": [
"<div id=\"_EWcF9CkYSxm1qdl-bSk8xQ\">\n",
" <p hx-get=\"/hoho\" hx-trigger=\"load\" id=\"_azwI1xARSnCZmBU4kc0x7w\">not loaded</p>\n",
"<script id=\"_8z_lLeeTT5_d553Ug7alkQ\">if (window.htmx) htmx.process(document.body)</script></div>\n"
"<div id=\"_l0IdURqdSRu46RNQQPmMvA\">\n",
" <p hx-get=\"/hoho\" hx-trigger=\"load\" id=\"_WUPc1vlnQUStT30OWNXAHQ\">not loaded</p>\n",
"<script id=\"_o4Z4wNOxQXum9LI9N-YhRw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
],
"text/plain": [
"p(('not loaded',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'id': '_azwI1xARSnCZmBU4kc0x7w'})"
"p(('not loaded',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'id': '_WUPc1vlnQUStT30OWNXAHQ'})"
]
},
"execution_count": null,
Expand Down Expand Up @@ -409,12 +557,12 @@
{
"data": {
"text/markdown": [
"<div id=\"_-amTP082QSuMN6YQHH_5ig\">\n",
" <div id=\"_Iek2wGqDSjOUBwo8WziCYQ\"></div>\n",
"<script id=\"_RA5pk9ueSAabBzrwAynasA\">if (window.htmx) htmx.process(document.body)</script></div>\n"
"<div id=\"_C4mtvDiLQPyjFBL_N9guOQ\">\n",
" <div id=\"_dazDkLiNRi_5IRjKxnDApQ\"></div>\n",
"<script id=\"_wBaWxkrPTeyFjAjoTOAjaw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
],
"text/plain": [
"div(('',),{'id': '_Iek2wGqDSjOUBwo8WziCYQ'})"
"div(('',),{'id': '_dazDkLiNRi_5IRjKxnDApQ'})"
]
},
"execution_count": null,
Expand All @@ -435,12 +583,12 @@
{
"data": {
"text/markdown": [
"<div id=\"_yusRcNEMSk2pHsWRapZy0A\">\n",
" <p hx-get=\"/foo\" hx-trigger=\"load\" hx-target=\"#_Iek2wGqDSjOUBwo8WziCYQ\" id=\"_eEBYn2yoR5WaLfytPsKIkw\">hi</p>\n",
"<script id=\"_ESleILSBS9WU0JpM8ndYng\">if (window.htmx) htmx.process(document.body)</script></div>\n"
"<div id=\"_mmZ8zN0IQRWcwaaJuPD17g\">\n",
" <p hx-get=\"/foo\" hx-trigger=\"load\" hx-target=\"#_dazDkLiNRi_5IRjKxnDApQ\" id=\"_21sjPwFeSPKp3vncj4YJrQ\">hi</p>\n",
"<script id=\"_iy4Ov4wQRsuuB6ekh8semw\">if (window.htmx) htmx.process(document.body)</script></div>\n"
],
"text/plain": [
"p(('hi',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'hx-target': '#_Iek2wGqDSjOUBwo8WziCYQ', 'id': '_eEBYn2yoR5WaLfytPsKIkw'})"
"p(('hi',),{'hx-get': <fasthtml.core._mk_locfunc.<locals>._lf object>, 'hx-trigger': 'load', 'hx-target': '#_dazDkLiNRi_5IRjKxnDApQ', 'id': '_21sjPwFeSPKp3vncj4YJrQ'})"
]
},
"execution_count": null,
Expand Down

0 comments on commit a2508e7

Please sign in to comment.