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.
Files changed (38) hide show
  1. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/CONTRIBUTING.md +1 -1
  2. {python_fasthtml-0.14.12/python_fasthtml.egg-info → python_fasthtml-0.14.13}/PKG-INFO +1 -1
  3. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/__init__.py +1 -1
  4. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/_modidx.py +1 -0
  5. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/core.py +42 -39
  6. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13/python_fasthtml.egg-info}/PKG-INFO +1 -1
  7. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/LICENSE +0 -0
  8. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/MANIFEST.in +0 -0
  9. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/README.md +0 -0
  10. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/authmw.py +0 -0
  11. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/basics.py +0 -0
  12. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/cli.py +0 -0
  13. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/common.py +0 -0
  14. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/components.py +0 -0
  15. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/components.pyi +0 -0
  16. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/core.pyi +0 -0
  17. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/fastapp.py +0 -0
  18. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/ft.py +0 -0
  19. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/js.py +0 -0
  20. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/jupyter.py +0 -0
  21. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/katex.js +0 -0
  22. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/live_reload.py +0 -0
  23. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/oauth.py +0 -0
  24. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/pico.py +0 -0
  25. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/starlette.py +0 -0
  26. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/stripe_otp.py +0 -0
  27. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/svg.py +0 -0
  28. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/toaster.py +0 -0
  29. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/xtend.py +0 -0
  30. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/fasthtml/xtend.pyi +0 -0
  31. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/pyproject.toml +0 -0
  32. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  33. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  34. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/entry_points.txt +0 -0
  35. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/requires.txt +0 -0
  36. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/python_fasthtml.egg-info/top_level.txt +0 -0
  37. {python_fasthtml-0.14.12 → python_fasthtml-0.14.13}/setup.cfg +0 -0
  38. {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='nbdev_export && nbdev_clean && nbdev_trust'
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.14.12
3
+ Version: 0.14.13
4
4
  Summary: The fastest way to create an HTML app
5
5
  Author-email: Jeremy Howard and contributors <github@jhoward.fastmail.fm>
6
6
  License: Apache-2.0
@@ -4,5 +4,5 @@ Modules:
4
4
 
5
5
  - `fasthtml.core`: The `FastHTML` subclass of `Starlette`."""
6
6
 
7
- __version__ = "0.14.12"
7
+ __version__ = "0.14.13"
8
8
  from .core import *
@@ -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
- reswap=None, retarget=None, reselect=None, trigger=None, trigger_after_settle=None, trigger_after_swap=None)
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://unpkg.com/htmx.org@4.0.0-beta6/dist/ext/hx-ws.js",
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://unpkg.com/htmx.org@4.0.0-beta6/dist/ext/hx-sse.js",
561
- "live": "https://unpkg.com/htmx.org@4.0.0-beta6/dist/ext/hx-live.js",
562
- "multipart": "https://cdn.jsdelivr.net/gh/bigskysoftware/htmx@four/src/ext/hx-multipart.min.js"
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://unpkg.com/htmx.org@4.0.0-beta6/dist/htmx.js")
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
- on_startup=None, on_shutdown=None, lifespan=None, hdrs=None, ftrs=None, exts=None,
685
- before=None, after=None, surreal=True, htmx=True, htmx4=False, default_hdrs=True, sess_cls=SessionMiddleware,
686
- secret_key=None, session_cookie='session_', max_age=365*24*3600, sess_path='/',
687
- same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',
688
- body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, max_part_size=DEF_MAXPART, **bodykw):
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
- max_age=max_age, path=sess_path, same_site=same_site,
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
- (r.path==route.path and r.name == route.name and
746
- ((route.methods is None) or (set(r.methods) == set(route.methods))))]
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
- appname=None, # Name of the module
883
- app='app', # App instance to be served
884
- host='0.0.0.0', # If host is 0.0.0.0 will convert to localhost
885
- port=None, # If port is None it will default to 5001 or the PORT environment variable
886
- reload=True, # Default is to reload the app upon code changes
887
- **kwargs
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.14.12
3
+ Version: 0.14.13
4
4
  Summary: The fastest way to create an HTML app
5
5
  Author-email: Jeremy Howard and contributors <github@jhoward.fastmail.fm>
6
6
  License: Apache-2.0