python-fasthtml 0.12.20__tar.gz → 0.12.22__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 (42) hide show
  1. {python_fasthtml-0.12.20/python_fasthtml.egg-info → python_fasthtml-0.12.22}/PKG-INFO +2 -2
  2. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/README.md +1 -1
  3. python_fasthtml-0.12.22/fasthtml/__init__.py +2 -0
  4. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/_modidx.py +1 -0
  5. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/core.py +3 -1
  6. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/core.pyi +9 -6
  7. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/jupyter.py +5 -3
  8. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/oauth.py +17 -10
  9. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/toaster.py +11 -3
  10. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22/python_fasthtml.egg-info}/PKG-INFO +2 -2
  11. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/settings.ini +1 -1
  12. python_fasthtml-0.12.20/fasthtml/__init__.py +0 -2
  13. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/CONTRIBUTING.md +0 -0
  14. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/LICENSE +0 -0
  15. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/MANIFEST.in +0 -0
  16. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/authmw.py +0 -0
  17. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/basics.py +0 -0
  18. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/cli.py +0 -0
  19. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/common.py +0 -0
  20. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/components.py +0 -0
  21. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/components.pyi +0 -0
  22. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/fastapp.py +0 -0
  23. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/ft.py +0 -0
  24. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/js.py +0 -0
  25. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/katex.js +0 -0
  26. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/live_reload.py +0 -0
  27. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/pico.py +0 -0
  28. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/starlette.py +0 -0
  29. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/stripe_otp.py +0 -0
  30. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/svg.py +0 -0
  31. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/xtend.py +0 -0
  32. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/fasthtml/xtend.pyi +0 -0
  33. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/pyproject.toml +0 -0
  34. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  35. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  36. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/python_fasthtml.egg-info/entry_points.txt +0 -0
  37. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/python_fasthtml.egg-info/not-zip-safe +0 -0
  38. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/python_fasthtml.egg-info/requires.txt +0 -0
  39. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/python_fasthtml.egg-info/top_level.txt +0 -0
  40. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/setup.cfg +0 -0
  41. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/setup.py +0 -0
  42. {python_fasthtml-0.12.20 → python_fasthtml-0.12.22}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.12.20
3
+ Version: 0.12.22
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
@@ -187,7 +187,7 @@ repo’s notebooks and the official FastHTML examples repo:
187
187
  Then explore the small but growing third-party ecosystem of FastHTML
188
188
  tutorials, notebooks, libraries, and components:
189
189
 
190
- - [FastHTML Gallery](https://fastht.ml/gallery): Learn from minimal
190
+ - [FastHTML Gallery](https://gallery.fastht.ml): Learn from minimal
191
191
  examples of components (ie chat bubbles, click-to-edit, infinite
192
192
  scroll, etc)
193
193
  - [Creating Custom FastHTML Tags for Markdown
@@ -138,7 +138,7 @@ repo’s notebooks and the official FastHTML examples repo:
138
138
  Then explore the small but growing third-party ecosystem of FastHTML
139
139
  tutorials, notebooks, libraries, and components:
140
140
 
141
- - [FastHTML Gallery](https://fastht.ml/gallery): Learn from minimal
141
+ - [FastHTML Gallery](https://gallery.fastht.ml): Learn from minimal
142
142
  examples of components (ie chat bubbles, click-to-edit, infinite
143
143
  scroll, etc)
144
144
  - [Creating Custom FastHTML Tags for Markdown
@@ -0,0 +1,2 @@
1
+ __version__ = "0.12.22"
2
+ from .core import *
@@ -197,6 +197,7 @@ d = { 'settings': { 'branch': 'main',
197
197
  'fasthtml/oauth.py'),
198
198
  'fasthtml.oauth._AppClient.retr_id': ('api/oauth.html#_appclient.retr_id', 'fasthtml/oauth.py'),
199
199
  'fasthtml.oauth._AppClient.retr_info': ('api/oauth.html#_appclient.retr_info', 'fasthtml/oauth.py'),
200
+ 'fasthtml.oauth.get_host': ('api/oauth.html#get_host', 'fasthtml/oauth.py'),
200
201
  'fasthtml.oauth.load_creds': ('api/oauth.html#load_creds', 'fasthtml/oauth.py'),
201
202
  'fasthtml.oauth.redir_url': ('api/oauth.html#redir_url', 'fasthtml/oauth.py'),
202
203
  'fasthtml.oauth.url_match': ('api/oauth.html#url_match', 'fasthtml/oauth.py')},
@@ -240,6 +240,7 @@ def _find_wsp(ws, data, hdrs, arg:str, p:Parameter):
240
240
  if issubclass(anno, HtmxHeaders): return _get_htmx(hdrs)
241
241
  if issubclass(anno, Starlette): return ws.scope['app']
242
242
  if issubclass(anno, WebSocket): return ws
243
+ if issubclass(anno, dict): return data
243
244
  if anno is empty:
244
245
  if arg.lower()=='ws': return ws
245
246
  if arg.lower()=='scope': return dict2obj(ws.scope)
@@ -263,7 +264,8 @@ def _wrap_ws(ws, data, params):
263
264
  # %% ../nbs/api/00_core.ipynb
264
265
  async def _send_ws(ws, resp):
265
266
  if not resp: return
266
- res = to_xml(resp, indent=fh_cfg.indent) if isinstance(resp, (list,tuple,FT)) or hasattr(resp, '__ft__') else resp
267
+ # res = to_xml(resp, indent=fh_cfg.indent) if isinstance(resp, (list,tuple,FT)) or hasattr(resp, '__ft__') else resp
268
+ res = to_xml(resp, indent=fh_cfg.indent)
267
269
  await ws.send_text(res)
268
270
 
269
271
  def _ws_endp(recv, conn=None, disconn=None):
@@ -1,6 +1,6 @@
1
1
  """The `FastHTML` subclass of `Starlette`, along with the `RouterX` and `RouteX` classes it automatically uses."""
2
- __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'devtools_loc', 'parsed_date', 'snake2hyphens', 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'JSONResponse', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple', 'noop_body', 'respond', 'is_full_page', 'Redirect', 'get_key', 'qp', 'def_hdrs', 'FastHTML', 'nested_name', 'serve', 'Client', 'RouteFuncs', 'APIRouter', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'setup_ws']
3
- import json, uuid, inspect, types, signal, asyncio, threading, inspect, random
2
+ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'devtools_loc', 'parsed_date', 'snake2hyphens', 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'JSONResponse', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple', 'noop_body', 'respond', 'is_full_page', 'Redirect', 'get_key', 'qp', 'def_hdrs', 'FastHTML', 'nested_name', 'serve', 'Client', 'RouteFuncs', 'APIRouter', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid']
3
+ import json, uuid, inspect, types, signal, asyncio, threading, inspect, random, contextlib
4
4
  from fastcore.utils import *
5
5
  from fastcore.xml import *
6
6
  from fastcore.meta import use_kwargs_dict
@@ -213,7 +213,7 @@ class Redirect:
213
213
 
214
214
  async def _wrap_call(f, req, params):
215
215
  ...
216
- htmx_exts = {'morph': 'https://cdn.jsdelivr.net/npm/idiomorph@0.7.3/dist/idiomorph-ext.min.js', 'head-support': 'https://cdn.jsdelivr.net/npm/htmx-ext-head-support@2.0.3/head-support.js', 'preload': 'https://cdn.jsdelivr.net/npm/htmx-ext-preload@2.1.0/preload.js', 'class-tools': 'https://cdn.jsdelivr.net/npm/htmx-ext-class-tools@2.0.1/class-tools.js', 'loading-states': 'https://cdn.jsdelivr.net/npm/htmx-ext-loading-states@2.0.0/loading-states.js', 'multi-swap': 'https://cdn.jsdelivr.net/npm/htmx-ext-multi-swap@2.0.0/multi-swap.js', 'path-deps': 'https://cdn.jsdelivr.net/npm/htmx-ext-path-deps@2.0.0/path-deps.js', 'remove-me': 'https://cdn.jsdelivr.net/npm/htmx-ext-remove-me@2.0.0/remove-me.js', 'ws': 'https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2/ws.js', 'chunked-transfer': 'https://cdn.jsdelivr.net/npm/htmx-ext-transfer-encoding-chunked@0.4.0/transfer-encoding-chunked.js'}
216
+ htmx_exts = {'morph': 'https://cdn.jsdelivr.net/npm/idiomorph@0.7.3/dist/idiomorph-ext.min.js', 'head-support': 'https://cdn.jsdelivr.net/npm/htmx-ext-head-support@2.0.3/head-support.js', 'preload': 'https://cdn.jsdelivr.net/npm/htmx-ext-preload@2.1.0/preload.js', 'class-tools': 'https://cdn.jsdelivr.net/npm/htmx-ext-class-tools@2.0.1/class-tools.js', 'loading-states': 'https://cdn.jsdelivr.net/npm/htmx-ext-loading-states@2.0.0/loading-states.js', 'multi-swap': 'https://cdn.jsdelivr.net/npm/htmx-ext-multi-swap@2.0.0/multi-swap.js', 'path-deps': 'https://cdn.jsdelivr.net/npm/htmx-ext-path-deps@2.0.0/path-deps.js', 'remove-me': 'https://cdn.jsdelivr.net/npm/htmx-ext-remove-me@2.0.0/remove-me.js', 'ws': 'https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.3/ws.js', 'chunked-transfer': 'https://cdn.jsdelivr.net/npm/htmx-ext-transfer-encoding-chunked@0.4.0/transfer-encoding-chunked.js'}
217
217
  htmxsrc = Script(src='https://cdn.jsdelivr.net/npm/htmx.org@2.0.4/dist/htmx.min.js')
218
218
  fhjsscr = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@1.0.12/fasthtml.js')
219
219
  surrsrc = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js')
@@ -265,6 +265,9 @@ class FastHTML(Starlette):
265
265
  """Add a route at `path`"""
266
266
  ...
267
267
 
268
+ def set_lifespan(self, value):
269
+ ...
270
+
268
271
  def static_route_exts(self, prefix='/', static_path='.', exts='static'):
269
272
  """Add a static route at URL path `prefix` with files from `static_path` and `exts` defined by `reg_re_param()`"""
270
273
  ...
@@ -273,6 +276,9 @@ class FastHTML(Starlette):
273
276
  """Add a static route at URL path `prefix` with files from `static_path` and single `ext` (including the '.')"""
274
277
  ...
275
278
 
279
+ def setup_ws(app, f=noop):
280
+ ...
281
+
276
282
  def devtools_json(self, path=None, uuid=None):
277
283
  ...
278
284
  all_meths = 'get post put delete patch head trace options'.split()
@@ -370,7 +376,4 @@ def unqid(seeded=False):
370
376
 
371
377
  def _add_ids(s):
372
378
  ...
373
-
374
- def setup_ws(app, f=noop):
375
- ...
376
379
  devtools_loc = '/.well-known/appspecific/com.chrome.devtools.json'
@@ -111,7 +111,7 @@ class JupyUviAsync(JupyUvi):
111
111
  wait_port_free(self.port)
112
112
 
113
113
  # %% ../nbs/api/06_jupyter.ipynb
114
- def HTMX(path="", app=None, host='localhost', port=8000, height="auto", link=False, iframe=True):
114
+ def HTMX(path="/", host='localhost', app=None, port=8000, height="auto", link=False, iframe=True):
115
115
  "An iframe which displays the HTMX application in a notebook."
116
116
  if isinstance(path, (FT,tuple,Safe)):
117
117
  assert app, 'Need an app to render a component'
@@ -127,9 +127,11 @@ def HTMX(path="", app=None, host='localhost', port=8000, height="auto", link=Fal
127
127
  if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
128
128
  }, false);
129
129
  }""" if height == "auto" else ""
130
- if link: display(HTML(f'<a href="http://{host}:{port}{path}" target="_blank">Open in new tab</a>'))
130
+ proto = 'http' if host=='localhost' else 'https'
131
+ fullpath = f"{proto}://{host}:{port}{path}" if host else path
132
+ if link: display(HTML(f'<a href="{fullpath}" target="_blank">Open in new tab</a>'))
131
133
  if iframe:
132
- return HTML(f'<iframe src="http://{host}:{port}{path}" style="width: 100%; height: {height}; border: none;" onload="{scr}" ' + """allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> """)
134
+ return HTML(f'<iframe src="{fullpath}" style="width: 100%; height: {height}; border: none;" onload="{scr}" ' + """allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> """)
133
135
 
134
136
  # %% ../nbs/api/06_jupyter.ipynb
135
137
  def ws_client(app, nm='', host='localhost', port=8000, ws_connect='/ws', frame=True, link=True, **kwargs):
@@ -4,7 +4,7 @@
4
4
 
5
5
  # %% auto 0
6
6
  __all__ = ['http_patterns', 'GoogleAppClient', 'GitHubAppClient', 'HuggingFaceClient', 'DiscordAppClient', 'Auth0AppClient',
7
- 'redir_url', 'url_match', 'OAuth', 'load_creds']
7
+ 'get_host', 'redir_url', 'url_match', 'OAuth', 'load_creds']
8
8
 
9
9
  # %% ../nbs/api/08_oauth.ipynb
10
10
  from .common import *
@@ -121,10 +121,17 @@ def login_link(self:WebApplicationClient, redirect_uri, scope=None, state=None,
121
121
  return self.prepare_request_uri(self.base_url, redirect_uri, scope, state=state, **kwargs)
122
122
 
123
123
  # %% ../nbs/api/08_oauth.ipynb
124
- def redir_url(request, redir_path, scheme=None):
124
+ def get_host(request):
125
+ """Get the host, preferring X-Forwarded-Host if available"""
126
+ forwarded_host = request.headers.get('x-forwarded-host')
127
+ return forwarded_host if forwarded_host else request.url.netloc
128
+
129
+ # %% ../nbs/api/08_oauth.ipynb
130
+ def redir_url(req, redir_path, scheme=None):
125
131
  "Get the redir url for the host in `request`"
126
- scheme = 'http' if request.url.hostname in ("localhost", "127.0.0.1") else 'https'
127
- return f"{scheme}://{request.url.netloc}{redir_path}"
132
+ host = get_host(req)
133
+ scheme = 'http' if host.split(':')[0] in ("localhost", "127.0.0.1") else 'https'
134
+ return f"{scheme}://{host}{redir_path}"
128
135
 
129
136
  # %% ../nbs/api/08_oauth.ipynb
130
137
  @patch
@@ -132,7 +139,7 @@ def parse_response(self:_AppClient, code, redirect_uri):
132
139
  "Get the token from the oauth2 server response"
133
140
  payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
134
141
  client_secret=self.client_secret, grant_type='authorization_code')
135
- r = httpx.post(self.token_url, json=payload)
142
+ r = httpx.post(self.token_url, data=payload)
136
143
  r.raise_for_status()
137
144
  self.parse_request_body_response(r.text)
138
145
 
@@ -159,8 +166,8 @@ def retr_id(self:_AppClient, code, redirect_uri):
159
166
 
160
167
  # %% ../nbs/api/08_oauth.ipynb
161
168
  http_patterns = (r'^(localhost|127\.0\.0\.1)(:\d+)?$',)
162
- def url_match(url, patterns=http_patterns):
163
- return any(re.match(pattern, url.netloc.split(':')[0]) for pattern in patterns)
169
+ def url_match(request, patterns=http_patterns):
170
+ return any(re.match(pattern, get_host(request).split(':')[0]) for pattern in patterns)
164
171
 
165
172
  # %% ../nbs/api/08_oauth.ipynb
166
173
  class OAuth:
@@ -178,8 +185,8 @@ class OAuth:
178
185
  @app.get(redir_path)
179
186
  def redirect(req, session, code:str=None, error:str=None, state:str=None):
180
187
  if not code: session['oauth_error']=error; return RedirectResponse(self.error_path, status_code=303)
181
- scheme = 'http' if url_match(req.url,self.http_patterns) or not self.https else 'https'
182
- base_url = f"{scheme}://{req.url.netloc}"
188
+ scheme = 'http' if url_match(req,self.http_patterns) or not self.https else 'https'
189
+ base_url = f"{scheme}://{get_host(req)}"
183
190
  info = AttrDictDefault(cli.retr_info(code, base_url+redir_path))
184
191
  ident = info.get(self.cli.id_key)
185
192
  if not ident: return self.redir_login(session)
@@ -195,7 +202,7 @@ class OAuth:
195
202
 
196
203
  def redir_login(self, session): return RedirectResponse(self.login_path, status_code=303)
197
204
  def redir_url(self, req):
198
- scheme = 'http' if url_match(req.url,self.http_patterns) or not self.https else 'https'
205
+ scheme = 'http' if url_match(req,self.http_patterns) or not self.https else 'https'
199
206
  return redir_url(req, self.redir_path, scheme)
200
207
 
201
208
  def login_link(self, req, scope=None, state=None): return self.cli.login_link(self.redir_url(req), scope=scope, state=state)
@@ -33,9 +33,17 @@ toast_css = """
33
33
  .fh-toast-error { background-color: #F44336; }
34
34
  """
35
35
 
36
+ js = '''htmx.onLoad(() => {
37
+ if (!htmx.find('#fh-toast-container')) {
38
+ const ctn = document.createElement('div');
39
+ ctn.id = 'fh-toast-container';
40
+ document.body.appendChild(ctn);
41
+ }
42
+ });'''
43
+
36
44
  def Toast(message: str, typ: str = "info", dismiss: bool = False, duration:int=5000):
37
45
  x_btn = Button('x', cls="fh-toast-dismiss", onclick="htmx.remove(this?.parentElement);") if dismiss else None
38
- return Div(Span(message), x_btn, cls=f"fh-toast fh-toast-{typ}", hx_on_transitionend="setTimeout(() => this?.remove(), %d);" % duration)
46
+ return Div(Span(message), x_btn, cls=f"fh-toast fh-toast-{typ}", hx_on_transitionend=f"setTimeout(() => this?.remove(), {duration});")
39
47
 
40
48
  def add_toast(sess, message: str, typ: str = "info", dismiss: bool = False):
41
49
  assert typ in ("info", "success", "warning", "error"), '`typ` not in ("info", "success", "warning", "error")'
@@ -43,7 +51,7 @@ def add_toast(sess, message: str, typ: str = "info", dismiss: bool = False):
43
51
 
44
52
  def render_toasts(sess):
45
53
  toasts = [Toast(msg, typ, dismiss, sess['toast_duration']) for msg, typ, dismiss in sess.pop(sk, [])]
46
- return Div(*toasts, id=tcid, hx_swap_oob='afterbegin')
54
+ return Div(*toasts, hx_swap_oob=f'beforeend:#{tcid}')
47
55
 
48
56
  def toast_after(resp, req, sess):
49
57
  if sk in sess and (not resp or isinstance(resp, (tuple,FT,FtResponse))):
@@ -52,5 +60,5 @@ def toast_after(resp, req, sess):
52
60
 
53
61
  def setup_toasts(app, duration=5000):
54
62
  app.state.toast_duration = duration
55
- app.hdrs += [Style(toast_css)]
63
+ app.hdrs += [Style(toast_css), Script(js)]
56
64
  app.after.append(toast_after)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.12.20
3
+ Version: 0.12.22
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
@@ -187,7 +187,7 @@ repo’s notebooks and the official FastHTML examples repo:
187
187
  Then explore the small but growing third-party ecosystem of FastHTML
188
188
  tutorials, notebooks, libraries, and components:
189
189
 
190
- - [FastHTML Gallery](https://fastht.ml/gallery): Learn from minimal
190
+ - [FastHTML Gallery](https://gallery.fastht.ml): Learn from minimal
191
191
  examples of components (ie chat bubbles, click-to-edit, infinite
192
192
  scroll, etc)
193
193
  - [Creating Custom FastHTML Tags for Markdown
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = python-fasthtml
4
- version = 0.12.20
4
+ version = 0.12.22
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
@@ -1,2 +0,0 @@
1
- __version__ = "0.12.20"
2
- from .core import *