python-fasthtml 0.7.0__tar.gz → 0.8.0__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.
Files changed (41) hide show
  1. {python-fasthtml-0.7.0/python_fasthtml.egg-info → python-fasthtml-0.8.0}/PKG-INFO +1 -1
  2. python-fasthtml-0.8.0/fasthtml/__init__.py +2 -0
  3. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/_modidx.py +13 -18
  4. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/core.py +108 -82
  5. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/fastapp.py +1 -9
  6. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/jupyter.py +1 -32
  7. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/starlette.py +1 -0
  8. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0/python_fasthtml.egg-info}/PKG-INFO +1 -1
  9. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/settings.ini +1 -1
  10. python-fasthtml-0.7.0/fasthtml/__init__.py +0 -2
  11. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/CONTRIBUTING.md +0 -0
  12. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/LICENSE +0 -0
  13. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/MANIFEST.in +0 -0
  14. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/README.md +0 -0
  15. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/authmw.py +0 -0
  16. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/basics.py +0 -0
  17. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/cli.py +0 -0
  18. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/common.py +0 -0
  19. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/components.py +0 -0
  20. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/components.pyi +0 -0
  21. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/core.pyi +0 -0
  22. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/ft.py +0 -0
  23. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/js.py +0 -0
  24. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/katex.js +0 -0
  25. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/live_reload.py +0 -0
  26. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/oauth.py +0 -0
  27. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/pico.py +0 -0
  28. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/svg.py +0 -0
  29. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/toaster.py +0 -0
  30. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/xtend.py +0 -0
  31. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/fasthtml/xtend.pyi +0 -0
  32. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/pyproject.toml +0 -0
  33. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  34. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  35. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/python_fasthtml.egg-info/entry_points.txt +0 -0
  36. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/python_fasthtml.egg-info/not-zip-safe +0 -0
  37. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/python_fasthtml.egg-info/requires.txt +0 -0
  38. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/python_fasthtml.egg-info/top_level.txt +0 -0
  39. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/setup.cfg +0 -0
  40. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/setup.py +0 -0
  41. {python-fasthtml-0.7.0 → python-fasthtml-0.8.0}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard and contributors
@@ -0,0 +1,2 @@
1
+ __version__ = "0.8.0"
2
+ from .core import *
@@ -23,7 +23,12 @@ d = { 'settings': { 'branch': 'main',
23
23
  'fasthtml.components.html2ft': ('api/components.html#html2ft', 'fasthtml/components.py'),
24
24
  'fasthtml.components.show': ('api/components.html#show', 'fasthtml/components.py'),
25
25
  'fasthtml.components.sse_message': ('api/components.html#sse_message', 'fasthtml/components.py')},
26
- 'fasthtml.core': { 'fasthtml.core.Beforeware': ('api/core.html#beforeware', 'fasthtml/core.py'),
26
+ 'fasthtml.core': { 'fasthtml.core.APIRouter': ('api/core.html#apirouter', 'fasthtml/core.py'),
27
+ 'fasthtml.core.APIRouter.__call__': ('api/core.html#apirouter.__call__', 'fasthtml/core.py'),
28
+ 'fasthtml.core.APIRouter.__init__': ('api/core.html#apirouter.__init__', 'fasthtml/core.py'),
29
+ 'fasthtml.core.APIRouter.to_app': ('api/core.html#apirouter.to_app', 'fasthtml/core.py'),
30
+ 'fasthtml.core.APIRouter.ws': ('api/core.html#apirouter.ws', 'fasthtml/core.py'),
31
+ 'fasthtml.core.Beforeware': ('api/core.html#beforeware', 'fasthtml/core.py'),
27
32
  'fasthtml.core.Beforeware.__init__': ('api/core.html#beforeware.__init__', 'fasthtml/core.py'),
28
33
  'fasthtml.core.Client': ('api/core.html#client', 'fasthtml/core.py'),
29
34
  'fasthtml.core.Client.__init__': ('api/core.html#client.__init__', 'fasthtml/core.py'),
@@ -31,6 +36,10 @@ d = { 'settings': { 'branch': 'main',
31
36
  'fasthtml.core.EventStream': ('api/core.html#eventstream', 'fasthtml/core.py'),
32
37
  'fasthtml.core.FastHTML': ('api/core.html#fasthtml', 'fasthtml/core.py'),
33
38
  'fasthtml.core.FastHTML.__init__': ('api/core.html#fasthtml.__init__', 'fasthtml/core.py'),
39
+ 'fasthtml.core.FastHTML._add_route': ('api/core.html#fasthtml._add_route', 'fasthtml/core.py'),
40
+ 'fasthtml.core.FastHTML._add_ws': ('api/core.html#fasthtml._add_ws', 'fasthtml/core.py'),
41
+ 'fasthtml.core.FastHTML._endp': ('api/core.html#fasthtml._endp', 'fasthtml/core.py'),
42
+ 'fasthtml.core.FastHTML.add_route': ('api/core.html#fasthtml.add_route', 'fasthtml/core.py'),
34
43
  'fasthtml.core.FastHTML.route': ('api/core.html#fasthtml.route', 'fasthtml/core.py'),
35
44
  'fasthtml.core.FastHTML.static_route': ('api/core.html#fasthtml.static_route', 'fasthtml/core.py'),
36
45
  'fasthtml.core.FastHTML.static_route_exts': ('api/core.html#fasthtml.static_route_exts', 'fasthtml/core.py'),
@@ -49,17 +58,7 @@ d = { 'settings': { 'branch': 'main',
49
58
  'fasthtml.core.Redirect': ('api/core.html#redirect', 'fasthtml/core.py'),
50
59
  'fasthtml.core.Redirect.__init__': ('api/core.html#redirect.__init__', 'fasthtml/core.py'),
51
60
  'fasthtml.core.Redirect.__response__': ('api/core.html#redirect.__response__', 'fasthtml/core.py'),
52
- 'fasthtml.core.RouteX': ('api/core.html#routex', 'fasthtml/core.py'),
53
- 'fasthtml.core.RouteX.__init__': ('api/core.html#routex.__init__', 'fasthtml/core.py'),
54
- 'fasthtml.core.RouteX._endp': ('api/core.html#routex._endp', 'fasthtml/core.py'),
55
- 'fasthtml.core.RouterX': ('api/core.html#routerx', 'fasthtml/core.py'),
56
- 'fasthtml.core.RouterX.__init__': ('api/core.html#routerx.__init__', 'fasthtml/core.py'),
57
- 'fasthtml.core.RouterX._add_route': ('api/core.html#routerx._add_route', 'fasthtml/core.py'),
58
- 'fasthtml.core.RouterX.add_route': ('api/core.html#routerx.add_route', 'fasthtml/core.py'),
59
- 'fasthtml.core.RouterX.add_ws': ('api/core.html#routerx.add_ws', 'fasthtml/core.py'),
60
61
  'fasthtml.core.StringConvertor.to_string': ('api/core.html#stringconvertor.to_string', 'fasthtml/core.py'),
61
- 'fasthtml.core.WS_RouteX': ('api/core.html#ws_routex', 'fasthtml/core.py'),
62
- 'fasthtml.core.WS_RouteX.__init__': ('api/core.html#ws_routex.__init__', 'fasthtml/core.py'),
63
62
  'fasthtml.core._add_ids': ('api/core.html#_add_ids', 'fasthtml/core.py'),
64
63
  'fasthtml.core._annotations': ('api/core.html#_annotations', 'fasthtml/core.py'),
65
64
  'fasthtml.core._apply_ft': ('api/core.html#_apply_ft', 'fasthtml/core.py'),
@@ -77,9 +76,9 @@ d = { 'settings': { 'branch': 'main',
77
76
  'fasthtml.core._list': ('api/core.html#_list', 'fasthtml/core.py'),
78
77
  'fasthtml.core._mk_list': ('api/core.html#_mk_list', 'fasthtml/core.py'),
79
78
  'fasthtml.core._mk_locfunc': ('api/core.html#_mk_locfunc', 'fasthtml/core.py'),
79
+ 'fasthtml.core._params': ('api/core.html#_params', 'fasthtml/core.py'),
80
80
  'fasthtml.core._resp': ('api/core.html#_resp', 'fasthtml/core.py'),
81
81
  'fasthtml.core._send_ws': ('api/core.html#_send_ws', 'fasthtml/core.py'),
82
- 'fasthtml.core._sig': ('api/core.html#_sig', 'fasthtml/core.py'),
83
82
  'fasthtml.core._to_htmx_header': ('api/core.html#_to_htmx_header', 'fasthtml/core.py'),
84
83
  'fasthtml.core._to_xml': ('api/core.html#_to_xml', 'fasthtml/core.py'),
85
84
  'fasthtml.core._url_for': ('api/core.html#_url_for', 'fasthtml/core.py'),
@@ -106,9 +105,7 @@ d = { 'settings': { 'branch': 'main',
106
105
  'fasthtml.core.snake2hyphens': ('api/core.html#snake2hyphens', 'fasthtml/core.py'),
107
106
  'fasthtml.core.unqid': ('api/core.html#unqid', 'fasthtml/core.py'),
108
107
  'fasthtml.core.uri': ('api/core.html#uri', 'fasthtml/core.py')},
109
- 'fasthtml.fastapp': { 'fasthtml.fastapp._app_factory': ('api/fastapp.html#_app_factory', 'fasthtml/fastapp.py'),
110
- 'fasthtml.fastapp._get_tbl': ('api/fastapp.html#_get_tbl', 'fasthtml/fastapp.py'),
111
- 'fasthtml.fastapp.fast_app': ('api/fastapp.html#fast_app', 'fasthtml/fastapp.py')},
108
+ 'fasthtml.fastapp': {},
112
109
  'fasthtml.ft': {},
113
110
  'fasthtml.js': { 'fasthtml.js.HighlightJS': ('api/js.html#highlightjs', 'fasthtml/js.py'),
114
111
  'fasthtml.js.KatexMarkdownJS': ('api/js.html#katexmarkdownjs', 'fasthtml/js.py'),
@@ -117,14 +114,12 @@ d = { 'settings': { 'branch': 'main',
117
114
  'fasthtml.js.SortableJS': ('api/js.html#sortablejs', 'fasthtml/js.py'),
118
115
  'fasthtml.js.dark_media': ('api/js.html#dark_media', 'fasthtml/js.py'),
119
116
  'fasthtml.js.light_media': ('api/js.html#light_media', 'fasthtml/js.py')},
120
- 'fasthtml.jupyter': { 'fasthtml.jupyter.FastJupy': ('api/jupyter.html#fastjupy', 'fasthtml/jupyter.py'),
121
- 'fasthtml.jupyter.HTMX': ('api/jupyter.html#htmx', 'fasthtml/jupyter.py'),
117
+ 'fasthtml.jupyter': { 'fasthtml.jupyter.HTMX': ('api/jupyter.html#htmx', 'fasthtml/jupyter.py'),
122
118
  'fasthtml.jupyter.JupyUvi': ('api/jupyter.html#jupyuvi', 'fasthtml/jupyter.py'),
123
119
  'fasthtml.jupyter.JupyUvi.__init__': ('api/jupyter.html#jupyuvi.__init__', 'fasthtml/jupyter.py'),
124
120
  'fasthtml.jupyter.JupyUvi.start': ('api/jupyter.html#jupyuvi.start', 'fasthtml/jupyter.py'),
125
121
  'fasthtml.jupyter.JupyUvi.stop': ('api/jupyter.html#jupyuvi.stop', 'fasthtml/jupyter.py'),
126
122
  'fasthtml.jupyter.is_port_free': ('api/jupyter.html#is_port_free', 'fasthtml/jupyter.py'),
127
- 'fasthtml.jupyter.jupy_app': ('api/jupyter.html#jupy_app', 'fasthtml/jupyter.py'),
128
123
  'fasthtml.jupyter.nb_serve': ('api/jupyter.html#nb_serve', 'fasthtml/jupyter.py'),
129
124
  'fasthtml.jupyter.nb_serve_async': ('api/jupyter.html#nb_serve_async', 'fasthtml/jupyter.py'),
130
125
  'fasthtml.jupyter.wait_port_free': ('api/jupyter.html#wait_port_free', 'fasthtml/jupyter.py'),
@@ -4,10 +4,10 @@
4
4
 
5
5
  # %% auto 0
6
6
  __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc', 'viewport',
7
- 'charset', 'all_meths', 'parsed_date', 'snake2hyphens', 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders',
8
- 'form2dict', 'parse_form', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'WS_RouteX', 'uri',
9
- 'decode_uri', 'flat_tuple', 'Redirect', 'RouteX', 'RouterX', 'get_key', 'def_hdrs', 'FastHTML', 'serve',
10
- 'Client', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'setup_ws']
7
+ 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'parsed_date', 'snake2hyphens', 'HtmxHeaders',
8
+ 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'flat_xt', 'Beforeware', 'EventStream',
9
+ 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple', 'Redirect', 'get_key', 'def_hdrs', 'FastHTML', 'serve',
10
+ 'Client', 'APIRouter', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'setup_ws']
11
11
 
12
12
  # %% ../nbs/api/00_core.ipynb
13
13
  import json,uuid,inspect,types,uvicorn,signal,asyncio,threading
@@ -35,10 +35,10 @@ from base64 import b85encode,b64encode
35
35
 
36
36
  from .starlette import *
37
37
 
38
- empty = Parameter.empty
39
-
40
38
  # %% ../nbs/api/00_core.ipynb
41
- def _sig(f): return signature_ex(f, True)
39
+ def _params(f): return signature_ex(f, True).parameters
40
+
41
+ empty = Parameter.empty
42
42
 
43
43
  # %% ../nbs/api/00_core.ipynb
44
44
  def parsed_date(s:str):
@@ -257,7 +257,7 @@ def _ws_endp(recv, conn=None, disconn=None):
257
257
  cls = type('WS_Endp', (WebSocketEndpoint,), {"encoding":"text"})
258
258
 
259
259
  async def _generic_handler(handler, ws, data=None):
260
- wd = _wrap_ws(ws, loads(data) if data else {}, _sig(handler).parameters)
260
+ wd = _wrap_ws(ws, loads(data) if data else {}, _params(handler))
261
261
  resp = await _handle(handler, wd)
262
262
  if resp: await _send_ws(ws, resp)
263
263
 
@@ -289,13 +289,6 @@ def signal_shutdown():
289
289
  for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, signal_handler)
290
290
  return event
291
291
 
292
- # %% ../nbs/api/00_core.ipynb
293
- class WS_RouteX(WebSocketRoute):
294
- def __init__(self, app, path:str, recv, conn:callable=None, disconn:callable=None, *,
295
- name=None, middleware=None):
296
- super().__init__(path, _ws_endp(recv, conn, disconn), name=name, middleware=middleware)
297
- self.methods = ['WS']
298
-
299
292
  # %% ../nbs/api/00_core.ipynb
300
293
  def uri(_arg, **kwargs):
301
294
  return f"{quote(_arg)}/{urlencode(kwargs, doseq=True)}"
@@ -418,52 +411,6 @@ async def _wrap_call(f, req, params):
418
411
  wreq = await _wrap_req(req, params)
419
412
  return await _handle(f, wreq)
420
413
 
421
- # %% ../nbs/api/00_core.ipynb
422
- class RouteX(Route):
423
- def __init__(self, app, path:str, endpoint, *, methods=None, name=None, include_in_schema=True, middleware=None):
424
- self.f,self.sig,self._app = endpoint,_sig(endpoint),app
425
- super().__init__(path, self._endp, methods=methods, name=name, include_in_schema=include_in_schema, middleware=middleware)
426
-
427
- async def _endp(self, req):
428
- resp = None
429
- req.injects = []
430
- req.hdrs,req.ftrs,req.htmlkw,req.bodykw = map(deepcopy, (self._app.hdrs,self._app.ftrs,self._app.htmlkw,self._app.bodykw))
431
- req.hdrs,req.ftrs = listify(req.hdrs),listify(req.ftrs)
432
- for b in self._app.before:
433
- if not resp:
434
- if isinstance(b, Beforeware): bf,skip = b.f,b.skip
435
- else: bf,skip = b,[]
436
- if not any(re.fullmatch(r, req.url.path) for r in skip):
437
- resp = await _wrap_call(bf, req, _sig(bf).parameters)
438
- if not resp: resp = await _wrap_call(self.f, req, self.sig.parameters)
439
- for a in self._app.after:
440
- _,*wreq = await _wrap_req(req, _sig(a).parameters)
441
- nr = a(resp, *wreq)
442
- if nr: resp = nr
443
- return _resp(req, resp, self.sig.return_annotation)
444
-
445
- # %% ../nbs/api/00_core.ipynb
446
- class RouterX(Router):
447
- def __init__(self, app, routes=None, redirect_slashes=True, default=None, *, middleware=None):
448
- self._app = app
449
- super().__init__(routes, redirect_slashes, default, app.on_startup, app.on_shutdown,
450
- lifespan=app.lifespan, middleware=middleware)
451
-
452
- def _add_route(self, route):
453
- route.methods = [m.upper() for m in listify(route.methods)]
454
- self.routes = [r for r in self.routes if not
455
- (r.path==route.path and r.name == route.name and
456
- ((route.methods is None) or (set(r.methods) == set(route.methods))))]
457
- self.routes.append(route)
458
-
459
- def add_route(self, path: str, endpoint: callable, methods=None, name=None, include_in_schema=True):
460
- route = RouteX(self._app, path, endpoint=endpoint, methods=methods, name=name, include_in_schema=include_in_schema)
461
- self._add_route(route)
462
-
463
- def add_ws(self, path: str, recv: callable, conn:callable=None, disconn:callable=None, name=None):
464
- route = WS_RouteX(self._app, path, recv=recv, conn=conn, disconn=disconn, name=name)
465
- self._add_route(route)
466
-
467
414
  # %% ../nbs/api/00_core.ipynb
468
415
  htmx_exts = {
469
416
  "head-support": "https://unpkg.com/htmx-ext-head-support@2.0.1/head-support.js",
@@ -522,6 +469,20 @@ def def_hdrs(htmx=True, surreal=True):
522
469
  if htmx: hdrs = [htmxsrc,fhjsscr] + hdrs
523
470
  return [charset, viewport] + hdrs
524
471
 
472
+ # %% ../nbs/api/00_core.ipynb
473
+ cors_allow = Middleware(CORSMiddleware, allow_credentials=True,
474
+ allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
475
+
476
+ iframe_scr = Script("""
477
+ function sendmsg() {
478
+ window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');
479
+ }
480
+ window.onload = function() {
481
+ sendmsg();
482
+ document.body.addEventListener('htmx:afterSettle', sendmsg);
483
+ document.body.addEventListener('htmx:wsAfterMessage', sendmsg);
484
+ };""")
485
+
525
486
  # %% ../nbs/api/00_core.ipynb
526
487
  class FastHTML(Starlette):
527
488
  def __init__(self, debug=False, routes=None, middleware=None, exception_handlers=None,
@@ -536,6 +497,9 @@ class FastHTML(Starlette):
536
497
  htmlkw = htmlkw or {}
537
498
  if default_hdrs: hdrs = def_hdrs(htmx, surreal=surreal) + hdrs
538
499
  hdrs += [Script(src=ext) for ext in exts.values()]
500
+ if IN_NOTEBOOK:
501
+ hdrs.append(iframe_scr)
502
+ middleware.append(cors_allow)
539
503
  self.on_startup,self.on_shutdown,self.lifespan,self.hdrs,self.ftrs = on_startup,on_shutdown,lifespan,hdrs,ftrs
540
504
  self.before,self.after,self.htmlkw,self.bodykw = before,after,htmlkw,bodykw
541
505
  secret_key = get_key(secret_key, key_fname)
@@ -550,36 +514,78 @@ class FastHTML(Starlette):
550
514
  exception_handlers[404] = _not_found
551
515
  excs = {k:_wrap_ex(v, hdrs, ftrs, htmlkw, bodykw) for k,v in exception_handlers.items()}
552
516
  super().__init__(debug, routes, middleware=middleware, exception_handlers=excs, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan)
553
- self.router = RouterX(self, routes)
554
-
555
- def ws(self, path:str, conn=None, disconn=None, name=None):
556
- "Add a websocket route at `path`"
557
- def f(func=None):
558
- self.router.add_ws(path, func or noop, conn=conn, disconn=disconn, name=name)
559
- return func
560
- return f
517
+
518
+ def add_route(self, route):
519
+ route.methods = [m.upper() for m in listify(route.methods)]
520
+ self.router.routes = [r for r in self.router.routes if not
521
+ (r.path==route.path and r.name == route.name and
522
+ ((route.methods is None) or (set(r.methods) == set(route.methods))))]
523
+ self.router.routes.append(route)
561
524
 
562
525
  # %% ../nbs/api/00_core.ipynb
563
526
  all_meths = 'get post put delete patch head trace options'.split()
564
527
 
528
+ # %% ../nbs/api/00_core.ipynb
529
+ @patch
530
+ def _endp(self:FastHTML, f):
531
+ sig = signature_ex(f, True)
532
+ async def _f(req):
533
+ resp = None
534
+ req.injects = []
535
+ req.hdrs,req.ftrs,req.htmlkw,req.bodykw = map(deepcopy, (self.hdrs,self.ftrs,self.htmlkw,self.bodykw))
536
+ req.hdrs,req.ftrs = listify(req.hdrs),listify(req.ftrs)
537
+ for b in self.before:
538
+ if not resp:
539
+ if isinstance(b, Beforeware): bf,skip = b.f,b.skip
540
+ else: bf,skip = b,[]
541
+ if not any(re.fullmatch(r, req.url.path) for r in skip):
542
+ resp = await _wrap_call(bf, req, _params(bf))
543
+ if not resp: resp = await _wrap_call(f, req, sig.parameters)
544
+ for a in self.after:
545
+ _,*wreq = await _wrap_req(req, _params(a))
546
+ nr = a(resp, *wreq)
547
+ if nr: resp = nr
548
+ return _resp(req, resp, sig.return_annotation)
549
+ return _f
550
+
551
+ # %% ../nbs/api/00_core.ipynb
552
+ @patch
553
+ def _add_ws(self:FastHTML, func, path, conn, disconn, name, middleware):
554
+ endp = _ws_endp(func, conn, disconn)
555
+ route = WebSocketRoute(path, endpoint=endp, name=name, middleware=middleware)
556
+ route.methods = ['ws']
557
+ self.add_route(route)
558
+ return func
559
+
560
+ # %% ../nbs/api/00_core.ipynb
561
+ @patch
562
+ def ws(self:FastHTML, path:str, conn=None, disconn=None, name=None, middleware=None):
563
+ "Add a websocket route at `path`"
564
+ def f(func=noop): return self._add_ws(func, path, conn, disconn, name=name, middleware=middleware)
565
+ return f
566
+
567
+ # %% ../nbs/api/00_core.ipynb
568
+ @patch
569
+ def _add_route(self:FastHTML, func, path, methods, name, include_in_schema):
570
+ n,fn,p = name,func.__name__,None if callable(path) else path
571
+ if methods: m = [methods] if isinstance(methods,str) else methods
572
+ elif fn in all_meths and p is not None: m = [fn]
573
+ else: m = ['get','post']
574
+ if not n: n = fn
575
+ if not p: p = '/'+('' if fn=='index' else fn)
576
+ route = Route(p, endpoint=self._endp(func), methods=m, name=n, include_in_schema=include_in_schema)
577
+ self.add_route(route)
578
+ lf = _mk_locfunc(func, p)
579
+ lf.__routename__ = n
580
+ return lf
581
+
582
+ # %% ../nbs/api/00_core.ipynb
565
583
  @patch
566
584
  def route(self:FastHTML, path:str=None, methods=None, name=None, include_in_schema=True):
567
585
  "Add a route at `path`"
568
- pathstr = None if callable(path) else path
569
- def f(func):
570
- n,fn,p = name,func.__name__,pathstr
571
- if methods: m = [methods] if isinstance(methods,str) else methods
572
- elif fn in all_meths and p is not None: m = [fn]
573
- else: m = ['get','post']
574
- if not n: n = fn
575
- if not p: p = '/'+('' if fn=='index' else fn)
576
- self.router.add_route(p, func, methods=m, name=n, include_in_schema=include_in_schema)
577
- lf = _mk_locfunc(func, p)
578
- lf.__routename__ = n
579
- return lf
586
+ def f(func): return self._add_route(func, path, methods, name=name, include_in_schema=include_in_schema)
580
587
  return f(path) if callable(path) else f
581
588
 
582
- # %% ../nbs/api/00_core.ipynb
583
589
  for o in all_meths: setattr(FastHTML, o, partialmethod(FastHTML.route, methods=o))
584
590
 
585
591
  # %% ../nbs/api/00_core.ipynb
@@ -616,6 +622,26 @@ class Client:
616
622
 
617
623
  for o in ('get', 'post', 'delete', 'put', 'patch', 'options'): setattr(Client, o, partialmethod(Client._sync, o))
618
624
 
625
+ # %% ../nbs/api/00_core.ipynb
626
+ class APIRouter:
627
+ "Add routes to an app"
628
+ def __init__(self): self.routes,self.wss = [],[]
629
+
630
+ def __call__(self:FastHTML, path:str=None, methods=None, name=None, include_in_schema=True):
631
+ "Add a route at `path`"
632
+ def f(func): return self.routes.append((func, path,methods,name,include_in_schema))
633
+ return f(path) if callable(path) else f
634
+
635
+ def to_app(self, app):
636
+ "Add routes to `app`"
637
+ for args in self.routes: app._add_route(*args)
638
+ for args in self.wss : app._add_ws (*args)
639
+
640
+ def ws(self:FastHTML, path:str, conn=None, disconn=None, name=None, middleware=None):
641
+ "Add a websocket route at `path`"
642
+ def f(func=noop): return self.wss.append((func, path, conn, disconn, name, middleware))
643
+ return f
644
+
619
645
  # %% ../nbs/api/00_core.ipynb
620
646
  def cookie(key: str, value="", max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, samesite="lax",):
621
647
  "Create a 'set-cookie' `HttpHeader`"
@@ -1,10 +1,5 @@
1
1
  """The `fast_app` convenience wrapper"""
2
2
 
3
- # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/10_fastapp.ipynb.
4
-
5
- # %% ../nbs/api/10_fastapp.ipynb 3
6
- from __future__ import annotations
7
-
8
3
  import inspect,uvicorn
9
4
  from fastcore.utils import *
10
5
  from fastlite import *
@@ -13,10 +8,8 @@ from .pico import *
13
8
  from .starlette import *
14
9
  from .live_reload import FastHTMLWithLiveReload
15
10
 
16
- # %% auto 0
17
11
  __all__ = ['fast_app']
18
12
 
19
- # %% ../nbs/api/10_fastapp.ipynb
20
13
  def _get_tbl(dt, nm, schema):
21
14
  render = schema.pop('render', None)
22
15
  tbl = dt[nm]
@@ -26,7 +19,6 @@ def _get_tbl(dt, nm, schema):
26
19
  if render: dc.__ft__ = render
27
20
  return tbl,dc
28
21
 
29
- # %% ../nbs/api/10_fastapp.ipynb
30
22
  def _app_factory(*args, **kwargs) -> FastHTML | FastHTMLWithLiveReload:
31
23
  "Creates a FastHTML or FastHTMLWithLiveReload app instance"
32
24
  if kwargs.pop('live', False): return FastHTMLWithLiveReload(*args, **kwargs)
@@ -34,7 +26,6 @@ def _app_factory(*args, **kwargs) -> FastHTML | FastHTMLWithLiveReload:
34
26
  kwargs.pop('reload_interval', None)
35
27
  return FastHTML(*args, **kwargs)
36
28
 
37
- # %% ../nbs/api/10_fastapp.ipynb
38
29
  def fast_app(
39
30
  db_file:Optional[str]=None, # Database file name, if needed
40
31
  render:Optional[callable]=None, # Function used to render default database class
@@ -91,3 +82,4 @@ def fast_app(
91
82
  dbtbls = [_get_tbl(db.t, k, v) for k,v in tbls.items()]
92
83
  if len(dbtbls)==1: dbtbls=dbtbls[0]
93
84
  return app,app.route,*dbtbls
85
+
@@ -1,8 +1,7 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/06_jupyter.ipynb.
2
2
 
3
3
  # %% auto 0
4
- __all__ = ['cors_allow', 'nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'JupyUvi', 'FastJupy', 'HTMX',
5
- 'ws_client', 'jupy_app']
4
+ __all__ = ['nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'JupyUvi', 'HTMX', 'ws_client']
6
5
 
7
6
  # %% ../nbs/api/06_jupyter.ipynb
8
7
  import asyncio, socket, time, uvicorn
@@ -51,10 +50,6 @@ def wait_port_free(port, host='localhost', max_wait=3):
51
50
  if time.time() - start_time>max_wait: return print(f"Timeout")
52
51
  time.sleep(0.1)
53
52
 
54
- # %% ../nbs/api/06_jupyter.ipynb
55
- cors_allow = Middleware(CORSMiddleware, allow_credentials=True,
56
- allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
57
-
58
53
  # %% ../nbs/api/06_jupyter.ipynb
59
54
  class JupyUvi:
60
55
  "Start and stop a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
@@ -71,25 +66,6 @@ class JupyUvi:
71
66
  self.server.should_exit = True
72
67
  wait_port_free(self.port)
73
68
 
74
- # %% ../nbs/api/06_jupyter.ipynb
75
- # The script lets an iframe parent know of changes so that it can resize automatically.
76
- _iframe_scr = Script("""
77
- function sendmsg() {
78
- window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');
79
- }
80
- window.onload = function() {
81
- sendmsg();
82
- document.body.addEventListener('htmx:afterSettle', sendmsg);
83
- document.body.addEventListener('htmx:wsAfterMessage', sendmsg);
84
- };""")
85
-
86
- # %% ../nbs/api/06_jupyter.ipynb
87
- def FastJupy(hdrs=None, middleware=None, **kwargs):
88
- "Same as FastHTML, but with Jupyter compatible middleware and headers added"
89
- hdrs = listify(hdrs)+[_iframe_scr]
90
- middleware = listify(middleware)+[cors_allow]
91
- return FastHTML(hdrs=hdrs, middleware=middleware, **kwargs)
92
-
93
69
  # %% ../nbs/api/06_jupyter.ipynb
94
70
  def HTMX(path="", host='localhost', port=8000, iframe_height="auto"):
95
71
  "An iframe which displays the HTMX application in a notebook."
@@ -114,10 +90,3 @@ def ws_client(app, nm='', host='localhost', port=8000, ws_connect='/ws', frame=T
114
90
  def send(o): asyncio.create_task(app._send(o))
115
91
  c.on(send)
116
92
  return c
117
-
118
- # %% ../nbs/api/06_jupyter.ipynb
119
- def jupy_app(pico=False, hdrs=None, middleware=None, **kwargs):
120
- "Same as `fast_app` but for Jupyter notebooks"
121
- hdrs = listify(hdrs)+[_iframe_scr]
122
- middleware = listify(middleware)+[cors_allow]
123
- return fast_app(pico=pico, hdrs=hdrs, middleware=middleware, **kwargs)
@@ -1,6 +1,7 @@
1
1
  from starlette.applications import Starlette
2
2
  from starlette.middleware import Middleware
3
3
  from starlette.middleware.sessions import SessionMiddleware
4
+ from starlette.middleware.cors import CORSMiddleware
4
5
  from starlette.middleware.authentication import AuthenticationMiddleware
5
6
  from starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser, requires
6
7
  from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard and contributors
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.7.0
4
+ version = 0.8.0
5
5
  min_python = 3.10
6
6
  license = apache2
7
7
  requirements = fastcore>=1.7.18 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.0.9 python-multipart beautifulsoup4
@@ -1,2 +0,0 @@
1
- __version__ = "0.6.15"
2
- from .core import *
File without changes