python-fasthtml 0.14.12__tar.gz → 0.14.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/CONTRIBUTING.md +1 -1
- {python_fasthtml-0.14.12/python_fasthtml.egg-info → python_fasthtml-0.14.13}/PKG-INFO +1 -1
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/__init__.py +1 -1
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/_modidx.py +1 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/core.py +42 -39
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13/python_fasthtml.egg-info}/PKG-INFO +1 -1
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/LICENSE +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/MANIFEST.in +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/README.md +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/authmw.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/basics.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/cli.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/common.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/components.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/components.pyi +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/core.pyi +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/fastapp.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/ft.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/js.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/jupyter.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/katex.js +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/live_reload.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/oauth.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/pico.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/starlette.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/stripe_otp.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/svg.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/toaster.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/xtend.py +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/xtend.pyi +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/pyproject.toml +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/requires.txt +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/setup.cfg +0 -0
- {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/tests/test_toaster.py +0 -0
|
@@ -8,7 +8,7 @@ This project uses [nbdev](https://nbdev.fast.ai/getting_started.html) for develo
|
|
|
8
8
|
You may want to set up a `prep` alias in `~/.zshrc` or other shell startup file:
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
alias prep='
|
|
11
|
+
alias prep='nbdev-export && nbdev-clean && nbdev-trust'
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Run `prep` before each commit to ensure your python files are up to date, and you notebooks cleaned of metadata and notarized.
|
|
@@ -130,6 +130,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
130
130
|
'fasthtml.core._resp': ('api/core.html#_resp', 'fasthtml/core.py'),
|
|
131
131
|
'fasthtml.core._route_pn': ('api/core.html#_route_pn', 'fasthtml/core.py'),
|
|
132
132
|
'fasthtml.core._send_ws': ('api/core.html#_send_ws', 'fasthtml/core.py'),
|
|
133
|
+
'fasthtml.core._spawn': ('api/core.html#_spawn', 'fasthtml/core.py'),
|
|
133
134
|
'fasthtml.core._to_htmx_header': ('api/core.html#_to_htmx_header', 'fasthtml/core.py'),
|
|
134
135
|
'fasthtml.core._to_xml': ('api/core.html#_to_xml', 'fasthtml/core.py'),
|
|
135
136
|
'fasthtml.core._url_for': ('api/core.html#_url_for', 'fasthtml/core.py'),
|
|
@@ -55,7 +55,7 @@ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'DEF_MAXPART', 'htmx_ex
|
|
|
55
55
|
'MiddlewareBase', 'FtResponse', 'unqid']
|
|
56
56
|
|
|
57
57
|
# %% ../nbs/api/00_core.ipynb #23503b9e
|
|
58
|
-
import json,uuid,inspect,types,asyncio,inspect,random,contextlib,itsdangerous,hashlib
|
|
58
|
+
import json,uuid,inspect,types,asyncio,inspect,random,contextlib,itsdangerous,hashlib,signal,subprocess
|
|
59
59
|
from uuid import uuid5, NAMESPACE_URL
|
|
60
60
|
|
|
61
61
|
from fastcore.utils import *
|
|
@@ -169,7 +169,7 @@ class HttpHeader:
|
|
|
169
169
|
def _to_htmx_header(s): return 'HX-' + s.replace('_', '-').title()
|
|
170
170
|
|
|
171
171
|
htmx_resps = dict(location=None, push_url=None, redirect=None, refresh=None, replace_url=None,
|
|
172
|
-
|
|
172
|
+
reswap=None, retarget=None, reselect=None, trigger=None, trigger_after_settle=None, trigger_after_swap=None)
|
|
173
173
|
|
|
174
174
|
# %% ../nbs/api/00_core.ipynb #f6a2e62e
|
|
175
175
|
@use_kwargs_dict(**htmx_resps)
|
|
@@ -544,6 +544,7 @@ async def _wrap_call(f, req, params):
|
|
|
544
544
|
return await _handle(f, **wreq)
|
|
545
545
|
|
|
546
546
|
# %% ../nbs/api/00_core.ipynb #b0d1cbbf
|
|
547
|
+
# chkstyle: skip
|
|
547
548
|
htmx_exts = {
|
|
548
549
|
"morph": "https://cdn.jsdelivr.net/npm/idiomorph@0.7.3/dist/idiomorph-ext.min.js",
|
|
549
550
|
"head-support": "https://cdn.jsdelivr.net/npm/htmx-ext-head-support@2.0.4/head-support.js",
|
|
@@ -555,16 +556,16 @@ htmx_exts = {
|
|
|
555
556
|
"remove-me": "https://cdn.jsdelivr.net/npm/htmx-ext-remove-me@2.0.0/remove-me.js",
|
|
556
557
|
"debug": "https://unpkg.com/htmx.org@1.9.12/dist/ext/debug.js",
|
|
557
558
|
"ws": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.3/ws.js",
|
|
558
|
-
"ws4": "https://
|
|
559
|
+
"ws4": "https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-ws.min.js",
|
|
559
560
|
"chunked-transfer": "https://cdn.jsdelivr.net/npm/htmx-ext-transfer-encoding-chunked@0.4.0/transfer-encoding-chunked.js",
|
|
560
|
-
"sse4": "https://
|
|
561
|
-
"live": "https://
|
|
562
|
-
"multipart": "https://cdn.jsdelivr.net/
|
|
561
|
+
"sse4": "https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-sse.min.js",
|
|
562
|
+
"live": "https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-live.min.js",
|
|
563
|
+
"multipart": "https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/ext/hx-multipart.min.js"
|
|
563
564
|
}
|
|
564
565
|
|
|
565
566
|
# %% ../nbs/api/00_core.ipynb #60cb52ea
|
|
566
567
|
htmxsrc = Script(src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.js")
|
|
567
|
-
htmx4src = Script(src="https://
|
|
568
|
+
htmx4src = Script(src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0/dist/htmx.min.js")
|
|
568
569
|
fhjsscr = Script(src="https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@1.0.12/fasthtml.js")
|
|
569
570
|
surrsrc = Script(src="https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js")
|
|
570
571
|
scopesrc = Script(src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js")
|
|
@@ -618,13 +619,11 @@ def def_hdrs(htmx=True, htmx4=False, surreal=True):
|
|
|
618
619
|
meta_cfg = Meta(name="htmx-config", content=json.dumps({"metaCharacter": "-"}))
|
|
619
620
|
hdrs = [meta_cfg, htmx4src,fhjsscr] + hdrs
|
|
620
621
|
# TODO: Check if fhjsscr works with htmx4
|
|
621
|
-
elif htmx:
|
|
622
|
-
hdrs = [htmxsrc,fhjsscr] + hdrs
|
|
622
|
+
elif htmx: hdrs = [htmxsrc,fhjsscr] + hdrs
|
|
623
623
|
return [charset, viewport] + hdrs
|
|
624
624
|
|
|
625
625
|
# %% ../nbs/api/00_core.ipynb #2c5285ae
|
|
626
|
-
cors_allow = Middleware(CORSMiddleware, allow_credentials=True,
|
|
627
|
-
allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
|
626
|
+
cors_allow = Middleware(CORSMiddleware, allow_credentials=True, allow_origins=['*'], allow_methods=['*'], allow_headers=['*'])
|
|
628
627
|
|
|
629
628
|
iframe_scr = Script(NotStr("""
|
|
630
629
|
function sendmsg() {
|
|
@@ -674,23 +673,21 @@ class Lifespan:
|
|
|
674
673
|
else: yield
|
|
675
674
|
for f in self.shutdown: await _handle(f)
|
|
676
675
|
|
|
677
|
-
def on_event(self, event_type):
|
|
678
|
-
return lambda f: (getattr(self, event_type)).append(f)
|
|
676
|
+
def on_event(self, event_type): return lambda f: (getattr(self, event_type)).append(f)
|
|
679
677
|
|
|
680
678
|
# %% ../nbs/api/00_core.ipynb #3327a1e9
|
|
681
679
|
class FastHTML(Starlette):
|
|
682
680
|
"An HTML-first Starlette app: handler params filled from the request, FT returns rendered as pages or HTMX fragments"
|
|
683
681
|
def __init__(self, debug=False, routes=None, middleware=None, title: str = "FastHTML page", exception_handlers=None,
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
682
|
+
on_startup=None, on_shutdown=None, lifespan=None, hdrs=None, ftrs=None, exts=None,
|
|
683
|
+
before=None, after=None, surreal=True, htmx=True, htmx4=False, default_hdrs=True, sess_cls=SessionMiddleware,
|
|
684
|
+
secret_key=None, session_cookie='session_', max_age=365*24*3600, sess_path='/',
|
|
685
|
+
same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',
|
|
686
|
+
body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, max_part_size=DEF_MAXPART, **bodykw):
|
|
689
687
|
middleware,before,after = map(_list, (middleware,before,after))
|
|
690
688
|
self.title,self.canonical,self.session_cookie,self.key_fname = title,canonical,session_cookie,key_fname
|
|
691
689
|
hdrs,ftrs,exts = map(listify, (hdrs,ftrs,exts))
|
|
692
|
-
if htmx4 and exts:
|
|
693
|
-
exts = ['ws4' if e in ('ws', 'ws4') else e for e in exts]
|
|
690
|
+
if htmx4 and exts: exts = ['ws4' if e in ('ws', 'ws4') else e for e in exts]
|
|
694
691
|
exts = {k:htmx_exts[k] for k in exts}
|
|
695
692
|
htmlkw = htmlkw or {}
|
|
696
693
|
if default_hdrs: hdrs = def_hdrs(htmx, htmx4, surreal=surreal) + hdrs
|
|
@@ -705,9 +702,8 @@ class FastHTML(Starlette):
|
|
|
705
702
|
self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw,self.max_part_size = body_wrap,before,after,htmlkw,bodykw,max_part_size
|
|
706
703
|
self.secret_key = get_key(secret_key, key_fname)
|
|
707
704
|
if sess_cls:
|
|
708
|
-
sess = Middleware(sess_cls, secret_key=self.secret_key,session_cookie=session_cookie,
|
|
709
|
-
|
|
710
|
-
https_only=sess_https_only, domain=sess_domain)
|
|
705
|
+
sess = Middleware(sess_cls, secret_key=self.secret_key, session_cookie=session_cookie, max_age=max_age, path=sess_path,
|
|
706
|
+
same_site=same_site, https_only=sess_https_only, domain=sess_domain)
|
|
711
707
|
middleware.append(sess)
|
|
712
708
|
exception_handlers = ifnone(exception_handlers, {})
|
|
713
709
|
if 404 not in exception_handlers:
|
|
@@ -742,8 +738,8 @@ def add_route(self:FastHTML, route):
|
|
|
742
738
|
"Add or replace a route in the FastHTML app"
|
|
743
739
|
route.methods = [m.upper() for m in listify(route.methods)]
|
|
744
740
|
self.router.routes = [r for r in self.router.routes if not
|
|
745
|
-
|
|
746
|
-
|
|
741
|
+
(r.path==route.path and r.name == route.name and
|
|
742
|
+
((route.methods is None) or (set(r.methods) == set(route.methods))))]
|
|
747
743
|
self.router.routes.append(route)
|
|
748
744
|
self.router.routes.sort(key=lambda r: not getattr(r, 'host', None))
|
|
749
745
|
|
|
@@ -767,8 +763,7 @@ def _endp(self:FastHTML, f, body_wrap, before:Optional[Callable|tuple]=None):
|
|
|
767
763
|
if not resp:
|
|
768
764
|
if isinstance(b, Beforeware): bf,skip = b.f,b.skip
|
|
769
765
|
else: bf,skip = b,[]
|
|
770
|
-
if not any(re.fullmatch(r, req.url.path) for r in skip):
|
|
771
|
-
resp = await _wrap_call(bf, req, _params(bf))
|
|
766
|
+
if not any(re.fullmatch(r, req.url.path) for r in skip): resp = await _wrap_call(bf, req, _params(bf))
|
|
772
767
|
for b in listify(before):
|
|
773
768
|
if not resp: resp = await _wrap_call(b, req, _params(b))
|
|
774
769
|
req.body_wrap = body_wrap
|
|
@@ -879,12 +874,13 @@ def set_lifespan(self:FastHTML, value):
|
|
|
879
874
|
|
|
880
875
|
# %% ../nbs/api/00_core.ipynb #3a348474
|
|
881
876
|
def serve(
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
877
|
+
appname=None, # Name of the module
|
|
878
|
+
app='app', # App instance to be served
|
|
879
|
+
host='0.0.0.0', # If host is 0.0.0.0 will convert to localhost
|
|
880
|
+
port=None, # If port is None it will default to 5001 or the PORT environment variable
|
|
881
|
+
reload=True, # Default is to reload the app upon code changes
|
|
882
|
+
procs=None, # Processes to run on consecutive ports from `port`; default 1 or the PROCS environment variable
|
|
883
|
+
**kwargs
|
|
888
884
|
):
|
|
889
885
|
"Run the app in an async server, with live reload set as the default."
|
|
890
886
|
from uvicorn import run
|
|
@@ -895,10 +891,20 @@ def serve(
|
|
|
895
891
|
elif back and back.f_globals.get('__name__')=='__main__': appname = inspect.getmodule(bk).__name__
|
|
896
892
|
if appname:
|
|
897
893
|
if not port: port=int(os.getenv("PORT", default=5001))
|
|
894
|
+
if not procs: procs=int(os.getenv("PROCS", default=1))
|
|
895
|
+
if procs>1 and 'FH_PROCNUM' not in os.environ: return _spawn(procs)
|
|
896
|
+
port += int(os.getenv('FH_PROCNUM', 0))
|
|
898
897
|
link = f'http://{"localhost" if host=="0.0.0.0" else host}:{port}'
|
|
899
898
|
print('Link: '+ S.light_red.bold(link))
|
|
900
899
|
run(f'{appname}:{app}', host=host, port=port, reload=reload, **kwargs)
|
|
901
900
|
|
|
901
|
+
# %% ../nbs/api/00_core.ipynb #91b08e9a
|
|
902
|
+
def _spawn(n):
|
|
903
|
+
"Run this script `n` times over, numbering each copy in `FH_PROCNUM`, and wait for them all"
|
|
904
|
+
ps = [subprocess.Popen([sys.executable, *sys.argv], env={**os.environ, 'FH_PROCNUM':str(i)}) for i in range(n)]
|
|
905
|
+
signal.signal(signal.SIGTERM, lambda *a: [p.terminate() for p in ps])
|
|
906
|
+
for p in ps: p.wait()
|
|
907
|
+
|
|
902
908
|
# %% ../nbs/api/00_core.ipynb #c5220a0e
|
|
903
909
|
async def _wait_disconnect(req):
|
|
904
910
|
while (await req.receive())["type"] != "http.disconnect": pass
|
|
@@ -1001,8 +1007,7 @@ def cookie(key: str, value="", max_age=None, expires=None, path="/", domain=None
|
|
|
1001
1007
|
cookie = cookies.SimpleCookie()
|
|
1002
1008
|
cookie[key] = value
|
|
1003
1009
|
if max_age is not None: cookie[key]["max-age"] = max_age
|
|
1004
|
-
if expires is not None:
|
|
1005
|
-
cookie[key]["expires"] = format_datetime(expires, usegmt=True) if isinstance(expires, datetime) else expires
|
|
1010
|
+
if expires is not None: cookie[key]["expires"] = format_datetime(expires, usegmt=True) if isinstance(expires, datetime) else expires
|
|
1006
1011
|
if path is not None: cookie[key]["path"] = path
|
|
1007
1012
|
if domain is not None: cookie[key]["domain"] = domain
|
|
1008
1013
|
if secure: cookie[key]["secure"] = True
|
|
@@ -1159,8 +1164,7 @@ def devtools_json(self:FastHTML, path=None, uuid=None):
|
|
|
1159
1164
|
if not path: path = Path().absolute()
|
|
1160
1165
|
if not uuid: uuid = str(uuid5(NAMESPACE_URL, str(path)))
|
|
1161
1166
|
@self.route(devtools_loc)
|
|
1162
|
-
def devtools():
|
|
1163
|
-
return dict(workspace=dict(root=str(path), uuid=uuid))
|
|
1167
|
+
def devtools(): return dict(workspace=dict(root=str(path), uuid=uuid))
|
|
1164
1168
|
|
|
1165
1169
|
# %% ../nbs/api/00_core.ipynb #e27908c0
|
|
1166
1170
|
@patch
|
|
@@ -1192,8 +1196,7 @@ def get_testclient(self:FastHTML, **kw):
|
|
|
1192
1196
|
"A Starlette TestClient with a `session` property"
|
|
1193
1197
|
@property
|
|
1194
1198
|
def session(self):
|
|
1195
|
-
cookie = next((c.value for c in reversed(list(self.cookies.jar))
|
|
1196
|
-
if c.name == self.app.session_cookie), None)
|
|
1199
|
+
cookie = next((c.value for c in reversed(list(self.cookies.jar)) if c.name == self.app.session_cookie), None)
|
|
1197
1200
|
return self.app.decode_session(cookie)
|
|
1198
1201
|
|
|
1199
1202
|
return FastHTMLTestClient(self, cookies=self.get_client(**kw).cookies)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|