python-fasthtml 0.12.22__tar.gz → 0.12.23__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.12.22/python_fasthtml.egg-info → python_fasthtml-0.12.23}/PKG-INFO +1 -1
- python_fasthtml-0.12.23/fasthtml/__init__.py +2 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/core.py +32 -8
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23/python_fasthtml.egg-info}/PKG-INFO +1 -1
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/settings.ini +1 -1
- python_fasthtml-0.12.22/fasthtml/__init__.py +0 -2
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/CONTRIBUTING.md +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/LICENSE +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/MANIFEST.in +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/README.md +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/_modidx.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/authmw.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/basics.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/cli.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/common.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/components.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/components.pyi +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/core.pyi +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/fastapp.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/ft.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/js.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/jupyter.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/katex.js +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/live_reload.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/oauth.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/pico.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/starlette.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/stripe_otp.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/svg.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/toaster.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/xtend.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/fasthtml/xtend.pyi +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/pyproject.toml +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/not-zip-safe +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/requires.txt +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/setup.cfg +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/setup.py +0 -0
- {python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/tests/test_toaster.py +0 -0
|
@@ -106,8 +106,7 @@ def _form_arg(k, v, d):
|
|
|
106
106
|
class HttpHeader: k:str;v:str
|
|
107
107
|
|
|
108
108
|
# %% ../nbs/api/00_core.ipynb
|
|
109
|
-
def _to_htmx_header(s):
|
|
110
|
-
return 'HX-' + s.replace('_', '-').title()
|
|
109
|
+
def _to_htmx_header(s): return 'HX-' + s.replace('_', '-').title()
|
|
111
110
|
|
|
112
111
|
htmx_resps = dict(location=None, push_url=None, redirect=None, refresh=None, replace_url=None,
|
|
113
112
|
reswap=None, retarget=None, reselect=None, trigger=None, trigger_after_settle=None, trigger_after_swap=None)
|
|
@@ -126,7 +125,7 @@ def _annotations(anno):
|
|
|
126
125
|
return get_annotations(anno)
|
|
127
126
|
|
|
128
127
|
# %% ../nbs/api/00_core.ipynb
|
|
129
|
-
def _is_body(anno): return issubclass(anno, (dict,ns)) or _annotations(anno)
|
|
128
|
+
def _is_body(anno): return issubclass(anno, (dict,ns)) or hasattr(anno,'__from_request__') or _annotations(anno)
|
|
130
129
|
|
|
131
130
|
# %% ../nbs/api/00_core.ipynb
|
|
132
131
|
def _formitem(form, k):
|
|
@@ -156,11 +155,13 @@ async def parse_form(req: Request) -> FormData:
|
|
|
156
155
|
|
|
157
156
|
# %% ../nbs/api/00_core.ipynb
|
|
158
157
|
async def _from_body(req, p):
|
|
158
|
+
"Parse request form/query data and create an instance of the annotated parameter type"
|
|
159
159
|
anno = p.annotation
|
|
160
|
-
# Get the fields and types of type `anno`, if available
|
|
161
|
-
d = _annotations(anno)
|
|
162
160
|
data = form2dict(await parse_form(req))
|
|
163
161
|
if req.query_params: data = {**data, **dict(req.query_params)}
|
|
162
|
+
ctor = getattr(anno, '__from_request__', None)
|
|
163
|
+
if ctor: return await ctor(data, req) if asyncio.iscoroutinefunction(ctor) else ctor(data, req)
|
|
164
|
+
d = _annotations(anno)
|
|
164
165
|
cargs = {k: _form_arg(k, v, d) for k, v in data.items() if not d or k in d}
|
|
165
166
|
return anno(**cargs)
|
|
166
167
|
|
|
@@ -306,10 +307,12 @@ def signal_shutdown():
|
|
|
306
307
|
|
|
307
308
|
# %% ../nbs/api/00_core.ipynb
|
|
308
309
|
def uri(_arg, **kwargs):
|
|
310
|
+
"Create a URI by URL-encoding `_arg` and appending query parameters from `kwargs`"
|
|
309
311
|
return f"{quote(_arg)}/{urlencode(kwargs, doseq=True)}"
|
|
310
312
|
|
|
311
313
|
# %% ../nbs/api/00_core.ipynb
|
|
312
314
|
def decode_uri(s):
|
|
315
|
+
"Decode a URI created by `uri()` back into argument and keyword dict"
|
|
313
316
|
arg,_,kw = s.partition('/')
|
|
314
317
|
return unquote(arg), {k:v[0] for k,v in parse_qs(kw).items()}
|
|
315
318
|
|
|
@@ -336,6 +339,7 @@ def url_path_for(self:HTTPConnection, name: str, **path_params):
|
|
|
336
339
|
_verbs = dict(get='hx-get', post='hx-post', put='hx-post', delete='hx-delete', patch='hx-patch', link='href')
|
|
337
340
|
|
|
338
341
|
def _url_for(req, t):
|
|
342
|
+
"Generate URL for route `t` using request `req`"
|
|
339
343
|
if callable(t): t = t.__routename__
|
|
340
344
|
kw = {}
|
|
341
345
|
if t.find('/')>-1 and (t.find('?')<0 or t.find('/')<t.find('?')): t,kw = decode_uri(t)
|
|
@@ -343,6 +347,7 @@ def _url_for(req, t):
|
|
|
343
347
|
return f"{req.url_path_for(t, **kw)}{m}{q}"
|
|
344
348
|
|
|
345
349
|
def _find_targets(req, resp):
|
|
350
|
+
"Find and convert route targets in response attributes to URLs"
|
|
346
351
|
if isinstance(resp, tuple):
|
|
347
352
|
for o in resp: _find_targets(req, o)
|
|
348
353
|
if isinstance(resp, FT):
|
|
@@ -352,12 +357,14 @@ def _find_targets(req, resp):
|
|
|
352
357
|
if t: resp.attrs[v] = _url_for(req, t)
|
|
353
358
|
|
|
354
359
|
def _apply_ft(o):
|
|
360
|
+
"Apply FastTag transformation recursively to object `o`"
|
|
355
361
|
if isinstance(o, tuple): o = tuple(_apply_ft(c) for c in o)
|
|
356
362
|
if hasattr(o, '__ft__'): o = o.__ft__()
|
|
357
363
|
if isinstance(o, FT): o.children = tuple(_apply_ft(c) for c in o.children)
|
|
358
364
|
return o
|
|
359
365
|
|
|
360
366
|
def _to_xml(req, resp, indent):
|
|
367
|
+
"Convert response to XML string with target URL resolution"
|
|
361
368
|
resp = _apply_ft(resp)
|
|
362
369
|
_find_targets(req, resp)
|
|
363
370
|
return to_xml(resp, indent)
|
|
@@ -367,7 +374,7 @@ _iter_typs = (tuple,list,map,filter,range,types.GeneratorType)
|
|
|
367
374
|
|
|
368
375
|
# %% ../nbs/api/00_core.ipynb
|
|
369
376
|
def flat_tuple(o):
|
|
370
|
-
"Flatten
|
|
377
|
+
"Flatten nested iterables into a single tuple"
|
|
371
378
|
result = []
|
|
372
379
|
if not isinstance(o,_iter_typs): o=[o]
|
|
373
380
|
o = list(o)
|
|
@@ -392,11 +399,13 @@ def respond(req, heads, bdy):
|
|
|
392
399
|
|
|
393
400
|
# %% ../nbs/api/00_core.ipynb
|
|
394
401
|
def is_full_page(req, resp):
|
|
402
|
+
"Check if response should be rendered as full page or fragment"
|
|
395
403
|
if resp and any(getattr(o, 'tag', '')=='html' for o in resp): return True
|
|
396
404
|
return 'hx-request' in req.headers and 'hx-history-restore-request' not in req.headers
|
|
397
405
|
|
|
398
406
|
# %% ../nbs/api/00_core.ipynb
|
|
399
407
|
def _part_resp(req, resp):
|
|
408
|
+
"Partition response into HTTP headers, background tasks, and content"
|
|
400
409
|
resp = flat_tuple(resp)
|
|
401
410
|
resp = resp + tuple(getattr(req, 'injects', ()))
|
|
402
411
|
http_hdrs,resp = partition(resp, risinstance(HttpHeader))
|
|
@@ -413,6 +422,7 @@ def _part_resp(req, resp):
|
|
|
413
422
|
|
|
414
423
|
# %% ../nbs/api/00_core.ipynb
|
|
415
424
|
def _xt_cts(req, resp):
|
|
425
|
+
"Extract content and headers, render as full page or fragment"
|
|
416
426
|
hdr_tags = 'title','meta','link','style','base'
|
|
417
427
|
resp = tuplify(resp)
|
|
418
428
|
heads,bdy = partition(resp, lambda o: getattr(o, 'tag', '') in hdr_tags)
|
|
@@ -423,10 +433,13 @@ def _xt_cts(req, resp):
|
|
|
423
433
|
return _to_xml(req, resp, indent=fh_cfg.indent)
|
|
424
434
|
|
|
425
435
|
# %% ../nbs/api/00_core.ipynb
|
|
426
|
-
def _is_ft_resp(resp):
|
|
436
|
+
def _is_ft_resp(resp):
|
|
437
|
+
"Check if response is a FastTag-compatible type"
|
|
438
|
+
return isinstance(resp, _iter_typs+(HttpHeader,FT)) or hasattr(resp, '__ft__')
|
|
427
439
|
|
|
428
440
|
# %% ../nbs/api/00_core.ipynb
|
|
429
441
|
def _resp(req, resp, cls=empty, status_code=200):
|
|
442
|
+
"Create appropriate HTTP response from request and response data"
|
|
430
443
|
if not resp: resp=''
|
|
431
444
|
if hasattr(resp, '__response__'): resp = resp.__response__(req)
|
|
432
445
|
if cls in (Any,FT): cls=empty
|
|
@@ -454,6 +467,7 @@ class Redirect:
|
|
|
454
467
|
|
|
455
468
|
# %% ../nbs/api/00_core.ipynb
|
|
456
469
|
async def _wrap_call(f, req, params):
|
|
470
|
+
"Wrap function call with request parameter injection"
|
|
457
471
|
wreq = await _wrap_req(req, params)
|
|
458
472
|
return await _handle(f, wreq)
|
|
459
473
|
|
|
@@ -481,6 +495,7 @@ charset = Meta(charset="utf-8")
|
|
|
481
495
|
|
|
482
496
|
# %% ../nbs/api/00_core.ipynb
|
|
483
497
|
def get_key(key=None, fname='.sesskey'):
|
|
498
|
+
"Get session key from `key` param or read/create from file `fname`"
|
|
484
499
|
if key: return key
|
|
485
500
|
fname = Path(fname)
|
|
486
501
|
if fname.exists(): return fname.read_text()
|
|
@@ -489,10 +504,13 @@ def get_key(key=None, fname='.sesskey'):
|
|
|
489
504
|
return key
|
|
490
505
|
|
|
491
506
|
# %% ../nbs/api/00_core.ipynb
|
|
492
|
-
def _list(o):
|
|
507
|
+
def _list(o):
|
|
508
|
+
"Wrap non-list item in a list, returning empty list if None"
|
|
509
|
+
return [] if not o else list(o) if isinstance(o, (tuple,list)) else [o]
|
|
493
510
|
|
|
494
511
|
# %% ../nbs/api/00_core.ipynb
|
|
495
512
|
def _wrap_ex(f, status_code, hdrs, ftrs, htmlkw, bodykw, body_wrap):
|
|
513
|
+
"Wrap exception handler with FastHTML request processing"
|
|
496
514
|
async def _f(req, exc):
|
|
497
515
|
req.hdrs,req.ftrs,req.htmlkw,req.bodykw = map(deepcopy, (hdrs, ftrs, htmlkw, bodykw))
|
|
498
516
|
req.body_wrap = body_wrap
|
|
@@ -573,6 +591,7 @@ class FastHTML(Starlette):
|
|
|
573
591
|
# %% ../nbs/api/00_core.ipynb
|
|
574
592
|
@patch
|
|
575
593
|
def add_route(self:FastHTML, route):
|
|
594
|
+
"Add or replace a route in the FastHTML app"
|
|
576
595
|
route.methods = [m.upper() for m in listify(route.methods)]
|
|
577
596
|
self.router.routes = [r for r in self.router.routes if not
|
|
578
597
|
(r.path==route.path and r.name == route.name and
|
|
@@ -585,6 +604,7 @@ all_meths = 'get post put delete patch head trace options'.split()
|
|
|
585
604
|
# %% ../nbs/api/00_core.ipynb
|
|
586
605
|
@patch
|
|
587
606
|
def _endp(self:FastHTML, f, body_wrap):
|
|
607
|
+
"Create endpoint wrapper with before/after middleware processing"
|
|
588
608
|
sig = signature_ex(f, True)
|
|
589
609
|
async def _f(req):
|
|
590
610
|
resp = None
|
|
@@ -609,6 +629,7 @@ def _endp(self:FastHTML, f, body_wrap):
|
|
|
609
629
|
# %% ../nbs/api/00_core.ipynb
|
|
610
630
|
@patch
|
|
611
631
|
def _add_ws(self:FastHTML, func, path, conn, disconn, name, middleware):
|
|
632
|
+
"Add websocket route to FastHTML app"
|
|
612
633
|
endp = _ws_endp(func, conn, disconn)
|
|
613
634
|
route = WebSocketRoute(path, endpoint=endp, name=name, middleware=middleware)
|
|
614
635
|
route.methods = ['ws']
|
|
@@ -624,6 +645,7 @@ def ws(self:FastHTML, path:str, conn=None, disconn=None, name=None, middleware=N
|
|
|
624
645
|
|
|
625
646
|
# %% ../nbs/api/00_core.ipynb
|
|
626
647
|
def _mk_locfunc(f,p):
|
|
648
|
+
"Create a location function wrapper with route path and to() method"
|
|
627
649
|
class _lf:
|
|
628
650
|
def __init__(self): update_wrapper(self, f)
|
|
629
651
|
def __call__(self, *args, **kw): return f(*args, **kw)
|
|
@@ -639,6 +661,7 @@ def nested_name(f):
|
|
|
639
661
|
# %% ../nbs/api/00_core.ipynb
|
|
640
662
|
@patch
|
|
641
663
|
def _add_route(self:FastHTML, func, path, methods, name, include_in_schema, body_wrap):
|
|
664
|
+
"Add HTTP route to FastHTML app with automatic method detection"
|
|
642
665
|
n,fn,p = name,nested_name(func),None if callable(path) else path
|
|
643
666
|
if methods: m = [methods] if isinstance(methods,str) else methods
|
|
644
667
|
elif fn in all_meths and p is not None: m = [fn]
|
|
@@ -663,6 +686,7 @@ for o in all_meths: setattr(FastHTML, o, partialmethod(FastHTML.route, methods=o
|
|
|
663
686
|
# %% ../nbs/api/00_core.ipynb
|
|
664
687
|
@patch
|
|
665
688
|
def set_lifespan(self:FastHTML, value):
|
|
689
|
+
"Set the lifespan context manager for the FastHTML app"
|
|
666
690
|
if inspect.isasyncgenfunction(value): value = contextlib.asynccontextmanager(value)
|
|
667
691
|
self.router.lifespan_context = value
|
|
668
692
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[DEFAULT]
|
|
2
2
|
repo = fasthtml
|
|
3
3
|
lib_name = python-fasthtml
|
|
4
|
-
version = 0.12.
|
|
4
|
+
version = 0.12.23
|
|
5
5
|
min_python = 3.10
|
|
6
6
|
license = apache2
|
|
7
7
|
requirements = fastcore>=1.8.1 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.1.1 python-multipart beautifulsoup4
|
|
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
|
|
File without changes
|
|
File without changes
|
{python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python_fasthtml-0.12.22 → python_fasthtml-0.12.23}/python_fasthtml.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|