Skip to content

Commit

Permalink
fixes #445
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Sep 22, 2024
1 parent 5ce9057 commit b36531d
Show file tree
Hide file tree
Showing 14 changed files with 643 additions and 58 deletions.
1 change: 0 additions & 1 deletion examples/adv_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ def before(req, sess):

markdown_js = """
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
import { proc_htmx} from "https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js/fasthtml.js";
proc_htmx('.markdown', e => e.innerHTML = marked.parse(e.textContent));
"""

Expand Down
17 changes: 13 additions & 4 deletions fasthtml/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,27 @@
'fasthtml.core.str2date': ('api/core.html#str2date', 'fasthtml/core.py'),
'fasthtml.core.str2int': ('api/core.html#str2int', 'fasthtml/core.py'),
'fasthtml.core.uri': ('api/core.html#uri', 'fasthtml/core.py')},
'fasthtml.fastapp': { 'fasthtml.fastapp.ContainerX': ('api/fastapp.html#containerx', 'fasthtml/fastapp.py'),
'fasthtml.fastapp.PageX': ('api/fastapp.html#pagex', 'fasthtml/fastapp.py'),
'fasthtml.fastapp._app_factory': ('api/fastapp.html#_app_factory', 'fasthtml/fastapp.py'),
'fasthtml.fastapp': { 'fasthtml.fastapp._app_factory': ('api/fastapp.html#_app_factory', 'fasthtml/fastapp.py'),
'fasthtml.fastapp._get_tbl': ('api/fastapp.html#_get_tbl', 'fasthtml/fastapp.py'),
'fasthtml.fastapp.fast_app': ('api/fastapp.html#fast_app', 'fasthtml/fastapp.py')},
'fasthtml.fastapp.fast_app': ('api/fastapp.html#fast_app', 'fasthtml/fastapp.py'),
'fasthtml.fastapp.jupy_app': ('api/fastapp.html#jupy_app', 'fasthtml/fastapp.py')},
'fasthtml.ft': {},
'fasthtml.js': { 'fasthtml.js.HighlightJS': ('api/js.html#highlightjs', 'fasthtml/js.py'),
'fasthtml.js.KatexMarkdownJS': ('api/js.html#katexmarkdownjs', 'fasthtml/js.py'),
'fasthtml.js.MarkdownJS': ('api/js.html#markdownjs', 'fasthtml/js.py'),
'fasthtml.js.SortableJS': ('api/js.html#sortablejs', 'fasthtml/js.py'),
'fasthtml.js.dark_media': ('api/js.html#dark_media', 'fasthtml/js.py'),
'fasthtml.js.light_media': ('api/js.html#light_media', 'fasthtml/js.py')},
'fasthtml.jupyter': { 'fasthtml.jupyter.FastJupy': ('api/jupyter.html#fastjupy', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.HTMX': ('api/jupyter.html#htmx', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.JupyUvi': ('api/jupyter.html#jupyuvi', 'fasthtml/jupyter.py'),
'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.is_port_free': ('api/jupyter.html#is_port_free', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.nb_serve': ('api/jupyter.html#nb_serve', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.nb_serve_async': ('api/jupyter.html#nb_serve_async', 'fasthtml/jupyter.py'),
'fasthtml.jupyter.wait_port_free': ('api/jupyter.html#wait_port_free', 'fasthtml/jupyter.py')},
'fasthtml.live_reload': {},
'fasthtml.oauth': { 'fasthtml.oauth.DiscordAppClient': ('api/oauth.html#discordappclient', 'fasthtml/oauth.py'),
'fasthtml.oauth.DiscordAppClient.__init__': ( 'api/oauth.html#discordappclient.__init__',
Expand Down
5 changes: 4 additions & 1 deletion fasthtml/core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,17 @@ class RouterX(Router):
def __init__(self, app, routes=None, redirect_slashes=True, default=None, *, middleware=None):
...

def _add_route(self, route):
...

def add_route(self, path: str, endpoint: callable, methods=None, name=None, include_in_schema=True):
...

def add_ws(self, path: str, recv: callable, conn: callable=None, disconn: callable=None, name=None):
...
htmxsrc = Script(src='https://unpkg.com/htmx.org@next/dist/htmx.min.js')
htmxwssrc = Script(src='https://unpkg.com/htmx-ext-ws/ws.js')
fhjsscr = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@main/fasthtml.js')
fhjsscr = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@1.0.4/fasthtml.js')
htmxctsrc = Script(src='https://unpkg.com/htmx-ext-transfer-encoding-chunked/transfer-encoding-chunked.js')
surrsrc = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js')
scopesrc = Script(src='https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js')
Expand Down
10 changes: 7 additions & 3 deletions fasthtml/fastapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
from .pico import *
from .starlette import *
from .live_reload import FastHTMLWithLiveReload
from .jupyter import _iframe_scr, cors_allow

# %% auto 0
__all__ = ['fast_app', 'ContainerX', 'PageX']
__all__ = ['fast_app', 'jupy_app']

# %% ../nbs/api/10_fastapp.ipynb
def _get_tbl(dt, nm, schema):
Expand Down Expand Up @@ -93,5 +94,8 @@ def fast_app(
return app,app.route,*dbtbls

# %% ../nbs/api/10_fastapp.ipynb
def ContainerX(*cs, **kwargs): return Main(*cs, **kwargs, cls='container', hx_push_url='true', hx_swap_oob='true', id='main')
def PageX(title, *con): return Title(title), ContainerX(H1(title), *con)
def jupy_app(pico=False, hdrs=None, middleware=None, **kwargs):
"Same as `fast_app` but for Jupyter notebooks"
hdrs = listify(hdrs)+[_iframe_scr]
middleware = listify(middleware)+[cors_allow]
return fast_app(pico=pico, hdrs=hdrs, middleware=middleware, **kwargs)
2 changes: 0 additions & 2 deletions fasthtml/js.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def dark_media(

# %% ../nbs/api/03_js.ipynb
marked_imp = """import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
import { proc_htmx } from "https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js";
"""
npmcdn = 'https://cdn.jsdelivr.net/npm/'

Expand Down Expand Up @@ -85,7 +84,6 @@ def SortableJS(
):
src = """
import {Sortable} from 'https://cdn.jsdelivr.net/npm/sortablejs/+esm';
import {proc_htmx} from "https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js";
proc_htmx('%s', Sortable.create);
""" % sel
return Script(src, type='module')
99 changes: 99 additions & 0 deletions fasthtml/jupyter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/06_jupyter.ipynb.

# %% auto 0
__all__ = ['cors_allow', 'nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'JupyUvi', 'FastJupy', 'HTMX']

# %% ../nbs/api/06_jupyter.ipynb
import asyncio, socket, time, uvicorn
from threading import Thread
from fastcore.utils import *
from .core import *
from .components import *
from .xtend import *
from IPython.display import HTML,Markdown,IFrame
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware import Middleware
from fastcore.parallel import startthread

# %% ../nbs/api/06_jupyter.ipynb
def nb_serve(app, log_level="error", port=8000, **kwargs):
"Start a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
server = uvicorn.Server(uvicorn.Config(app, log_level=log_level, port=port, **kwargs))
async def async_run_server(server): await server.serve()
@startthread
def run_server(): asyncio.run(async_run_server(server))
while not server.started: time.sleep(0.01)
return server

# %% ../nbs/api/06_jupyter.ipynb
async def nb_serve_async(app, log_level="error", port=8000, **kwargs):
"Async version of `nb_serve`"
server = uvicorn.Server(uvicorn.Config(app, log_level=log_level, port=port, **kwargs))
asyncio.get_running_loop().create_task(server.serve())
while not server.started: await asyncio.sleep(0.01)
return server

# %% ../nbs/api/06_jupyter.ipynb
def is_port_free(port, host='localhost'):
"Check if `port` is free on `host`"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
return True
except OSError: return False
finally: sock.close()

# %% ../nbs/api/06_jupyter.ipynb
def wait_port_free(port, host='localhost', max_wait=3):
"Wait for `port` to be free on `host`"
start_time = time.time()
while not is_port_free(port):
if time.time() - start_time>max_wait: return print(f"Timeout")
time.sleep(0.1)

# %% ../nbs/api/06_jupyter.ipynb
cors_allow = Middleware(CORSMiddleware, allow_credentials=True,
allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

# %% ../nbs/api/06_jupyter.ipynb
class JupyUvi:
"Start and stop a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
def __init__(self, app, log_level="error", port=8000, start=True, **kwargs):
self.kwargs = kwargs
store_attr(but='start')
self.server = None
if start: self.start()

def start(self):
self.server = nb_serve(self.app, log_level=self.log_level, port=self.port, **self.kwargs)

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

# %% ../nbs/api/06_jupyter.ipynb
# The script lets an iframe parent know of changes so that it can resize automatically.
_iframe_scr = Script("""
function sendmsg() {window.parent.postMessage({height: document.documentElement.offsetHeight}, '*')}
window.onload = function() {
sendmsg();
document.body.addEventListener('htmx:afterSettle', sendmsg);
};""")

# %% ../nbs/api/06_jupyter.ipynb
def FastJupy(hdrs=None, middleware=None, **kwargs):
"Same as FastHTML, but with Jupyter compatible middleware and headers added"
hdrs = listify(hdrs)+[_iframe_scr]
middleware = listify(middleware)+[cors_allow]
return FastHTML(hdrs=hdrs, middleware=middleware, **kwargs)

# %% ../nbs/api/06_jupyter.ipynb
def HTMX(host='localhost', port=8000):
"An iframe which displays the HTMX application in a notebook."
return HTML(f'<iframe src="http://{host}:{port}" ' + """style="width: 100%; border: none;" onload="{
let frame = this;
window.addEventListener('message', function(e) {
if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
}, false);
}"></iframe> """)
1 change: 0 additions & 1 deletion fasthtml/katex.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
import { proc_htmx } from "https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js";
import katex from "https://cdn.jsdelivr.net/npm/katex/dist/katex.mjs";

const renderMath = (tex, displayMode) => { return katex.renderToString(tex, {
Expand Down
24 changes: 14 additions & 10 deletions fasthtml/xtend.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Simple extensions to standard HTML components, such as adding sensible defaults"""
__all__ = ['sid_scr', 'A', 'Form', 'AX', 'Hidden', 'CheckboxX', 'Script', 'Style', 'double_braces', 'undouble_braces', 'loose_format', 'ScriptX', 'replace_css_vars', 'StyleX', 'On', 'Prev', 'Now', 'AnyNow', 'run_js', 'HtmxOn', 'Titled', 'Socials', 'Favicon', 'jsd', 'clear']
__all__ = ['sid_scr', 'A', 'AX', 'Form', 'Hidden', 'CheckboxX', 'Script', 'Style', 'double_braces', 'undouble_braces', 'loose_format', 'ScriptX', 'replace_css_vars', 'StyleX', 'Surreal', 'On', 'Prev', 'Now', 'AnyNow', 'run_js', 'HtmxOn', 'jsd', 'Titled', 'Socials', 'Favicon', 'clear']
from dataclasses import dataclass, asdict
from typing import Any
from fastcore.utils import *
Expand All @@ -16,14 +16,14 @@ def A(*c, hx_get=None, target_id=None, hx_swap=None, href='#', hx_vals=None, id=
"""An A tag; `href` defaults to '#' for more concise use with HTMX"""
...

def Form(*c, enctype='multipart/form-data', target_id=None, hx_vals=None, id=None, cls=None, title=None, style=None, accesskey=None, contenteditable=None, dir=None, draggable=None, enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None, popover=None, spellcheck=None, tabindex=None, translate=None, hx_get=None, hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, hx_trigger=None, hx_target=None, hx_swap=None, hx_swap_oob=None, hx_include=None, hx_select=None, hx_select_oob=None, hx_indicator=None, hx_push_url=None, hx_confirm=None, hx_disable=None, hx_replace_url=None, hx_disabled_elt=None, hx_ext=None, hx_headers=None, hx_history=None, hx_history_elt=None, hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None, hx_validate=None, **kwargs) -> FT:
"""A Form tag; identical to plain `ft_hx` version except default `enctype='multipart/form-data'`"""
...

def AX(txt, hx_get=None, target_id=None, hx_swap=None, href='#', *, hx_vals=None, id=None, cls=None, title=None, style=None, accesskey=None, contenteditable=None, dir=None, draggable=None, enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None, popover=None, spellcheck=None, tabindex=None, translate=None, hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, hx_trigger=None, hx_target=None, hx_swap_oob=None, hx_include=None, hx_select=None, hx_select_oob=None, hx_indicator=None, hx_push_url=None, hx_confirm=None, hx_disable=None, hx_replace_url=None, hx_disabled_elt=None, hx_ext=None, hx_headers=None, hx_history=None, hx_history_elt=None, hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None, hx_validate=None, **kwargs) -> FT:
"""An A tag with just one text child, allowing hx_get, target_id, and hx_swap to be positional params"""
...

def Form(*c, enctype='multipart/form-data', target_id=None, hx_vals=None, id=None, cls=None, title=None, style=None, accesskey=None, contenteditable=None, dir=None, draggable=None, enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None, popover=None, spellcheck=None, tabindex=None, translate=None, hx_get=None, hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, hx_trigger=None, hx_target=None, hx_swap=None, hx_swap_oob=None, hx_include=None, hx_select=None, hx_select_oob=None, hx_indicator=None, hx_push_url=None, hx_confirm=None, hx_disable=None, hx_replace_url=None, hx_disabled_elt=None, hx_ext=None, hx_headers=None, hx_history=None, hx_history_elt=None, hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None, hx_validate=None, **kwargs) -> FT:
"""A Form tag; identical to plain `ft_hx` version except default `enctype='multipart/form-data'`"""
...

def Hidden(value: Any='', id: Any=None, *, target_id=None, hx_vals=None, cls=None, title=None, style=None, accesskey=None, contenteditable=None, dir=None, draggable=None, enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None, popover=None, spellcheck=None, tabindex=None, translate=None, hx_get=None, hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, hx_trigger=None, hx_target=None, hx_swap=None, hx_swap_oob=None, hx_include=None, hx_select=None, hx_select_oob=None, hx_indicator=None, hx_push_url=None, hx_confirm=None, hx_disable=None, hx_replace_url=None, hx_disabled_elt=None, hx_ext=None, hx_headers=None, hx_history=None, hx_history_elt=None, hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None, hx_validate=None, **kwargs) -> FT:
"""An Input of type 'hidden'"""
...
Expand Down Expand Up @@ -64,8 +64,12 @@ def StyleX(fname, **kw):
"""A `style` element with contents read from `fname` and variables replaced from `kw`"""
...

def Surreal(code: str):
"""Wrap `code` in `domReadyExecute` and set `m=me()` and `p=me('-')`"""
...

def On(code: str, event: str='click', sel: str='', me=True):
"""An async surreal.js script block event handler for `event` on selector `sel`, making available parent `p`, event `ev`, and target `e`"""
"""An async surreal.js script block event handler for `event` on selector `sel,p`, making available parent `p`, event `ev`, and target `e`"""
...

def Prev(code: str, event: str='click'):
Expand All @@ -87,6 +91,10 @@ def run_js(js, id=None, **kw):
def HtmxOn(eventname: str, code: str):
...

def jsd(org, repo, root, path, prov='gh', typ='script', ver=None, esm=False, **kwargs) -> FT:
"""jsdelivr `Script` or CSS `Link` tag, or URL"""
...

def Titled(title: str='FastHTML app', *args, cls='container', target_id=None, hx_vals=None, id=None, style=None, accesskey=None, contenteditable=None, dir=None, draggable=None, enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None, popover=None, spellcheck=None, tabindex=None, translate=None, hx_get=None, hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, hx_trigger=None, hx_target=None, hx_swap=None, hx_swap_oob=None, hx_include=None, hx_select=None, hx_select_oob=None, hx_indicator=None, hx_push_url=None, hx_confirm=None, hx_disable=None, hx_replace_url=None, hx_disabled_elt=None, hx_ext=None, hx_headers=None, hx_history=None, hx_history_elt=None, hx_inherit=None, hx_params=None, hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None, hx_validate=None, **kwargs) -> FT:
"""An HTML partial containing a `Title`, and `H1`, and any provided children"""
...
Expand All @@ -99,10 +107,6 @@ def Favicon(light_icon, dark_icon):
"""Light and dark favicon headers"""
...

def jsd(org, repo, root, path, prov='gh', typ='script', ver=None, esm=False, **kwargs) -> FT:
"""jsdelivr `Script` or CSS `Link` tag, or URL"""
...

def clear(id):
...
sid_scr = Script('\nfunction uuid() {\n return [...crypto.getRandomValues(new Uint8Array(10))].map(b=>b.toString(36)).join(\'\');\n}\n\nsessionStorage.setItem("sid", sessionStorage.getItem("sid") || uuid());\n\nhtmx.on("htmx:configRequest", (e) => {\n const sid = sessionStorage.getItem("sid");\n if (sid) {\n const url = new URL(e.detail.path, window.location.origin);\n url.searchParams.set(\'sid\', sid);\n e.detail.path = url.pathname + url.search;\n }\n});\n')
2 changes: 0 additions & 2 deletions nbs/api/03_js.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@
"source": [
"#| export\n",
"marked_imp = \"\"\"import { marked } from \"https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js\";\n",
" import { proc_htmx } from \"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\";\n",
"\"\"\"\n",
"npmcdn = 'https://cdn.jsdelivr.net/npm/'"
]
Expand Down Expand Up @@ -265,7 +264,6 @@
" ):\n",
" src = \"\"\"\n",
"import {Sortable} from 'https://cdn.jsdelivr.net/npm/sortablejs/+esm';\n",
"import {proc_htmx} from \"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\";\n",
"proc_htmx('%s', Sortable.create);\n",
"\"\"\" % sel\n",
" return Script(src, type='module')"
Expand Down
Loading

0 comments on commit b36531d

Please sign in to comment.