python-fasthtml 0.6.12__tar.gz → 0.6.14__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.6.12 → python-fasthtml-0.6.14}/CONTRIBUTING.md +23 -1
  2. {python-fasthtml-0.6.12/python_fasthtml.egg-info → python-fasthtml-0.6.14}/PKG-INFO +5 -2
  3. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/README.md +3 -0
  4. python-fasthtml-0.6.14/fasthtml/__init__.py +2 -0
  5. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/_modidx.py +6 -7
  6. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/components.py +1 -1
  7. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/core.py +25 -44
  8. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/js.py +42 -1
  9. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/jupyter.py +22 -4
  10. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/svg.py +2 -2
  11. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/xtend.py +7 -1
  12. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14/python_fasthtml.egg-info}/PKG-INFO +5 -2
  13. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/requires.txt +1 -1
  14. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/settings.ini +2 -2
  15. python-fasthtml-0.6.12/fasthtml/__init__.py +0 -2
  16. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/LICENSE +0 -0
  17. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/MANIFEST.in +0 -0
  18. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/authmw.py +0 -0
  19. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/basics.py +0 -0
  20. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/cli.py +0 -0
  21. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/common.py +0 -0
  22. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/components.pyi +0 -0
  23. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/core.pyi +0 -0
  24. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/fastapp.py +0 -0
  25. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/ft.py +0 -0
  26. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/katex.js +0 -0
  27. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/live_reload.py +0 -0
  28. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/oauth.py +0 -0
  29. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/pico.py +0 -0
  30. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/starlette.py +0 -0
  31. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/toaster.py +0 -0
  32. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/xtend.pyi +0 -0
  33. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/pyproject.toml +0 -0
  34. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  35. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  36. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/entry_points.txt +0 -0
  37. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/not-zip-safe +0 -0
  38. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/top_level.txt +0 -0
  39. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/setup.cfg +0 -0
  40. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/setup.py +0 -0
  41. {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/tests/test_toaster.py +0 -0
@@ -3,7 +3,7 @@
3
3
  Make sure you have read the [doc on code style](
4
4
  https://docs.fast.ai/dev/style.html) first. (Note that we don't follow PEP8, but instead follow a coding style designed specifically for numerical and interactive programming.)
5
5
 
6
- This project uses [nbdev](https://nbdev.fast.ai/getting_started.html) for development. Before beginning, make sure that nbdev and a jupyter-compatible client such as jupyterlab or nbclassic are installed. To make changes, update the notebooks in the `nbs` folder, not the .py files directly. Then, run `nbdev_export`. For more details, have a look at the [nbdev tutorial](https://nbdev.fast.ai/tutorials/tutorial.html). Depending on the code changes, you might also need to run `tools/update.sh` to update python interface modules and [LLM reference material](https://github.com/AnswerDotAI/llms-txt).
6
+ This project uses [nbdev](https://nbdev.fast.ai/getting_started.html) for development. Before beginning, make sure that nbdev and a jupyter-compatible client such as jupyterlab or nbclassic are installed. To make changes to the codebase, update the notebooks in the `nbs` folder, not the .py files directly. Then, run `nbdev_export`. For more details, have a look at the [nbdev tutorial](https://nbdev.fast.ai/tutorials/tutorial.html). Depending on the code changes, you might also need to run `tools/update.sh` to update python interface modules and [LLM reference material](https://github.com/AnswerDotAI/llms-txt).
7
7
 
8
8
  You may want to set up a `prep` alias in `~/.zshrc` or other shell startup file:
9
9
 
@@ -13,6 +13,28 @@ alias prep='nbdev_export && nbdev_clean && nbdev_trust'
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.
15
15
 
16
+ ## Updating README.md
17
+
18
+ Similar to updating Python source code files, to update the `README.md` file you will need to edit a notebook file, specifically `nbs/index.ipynb`.
19
+
20
+ However, there are a couple of extra dependencies that you need to install first in order to make this work properly. Go to the directory you cloned the repo to, and type:
21
+
22
+ ```
23
+ pip install -e '.[dev]'
24
+ ```
25
+
26
+ And install quarto too:
27
+
28
+ ```
29
+ nbdev_install_quarto
30
+ ```
31
+
32
+ Then, after you make subsequent changes to `nbs/index.ipynb`, run the following from the repo's root directory to (re)build `README.md`:
33
+
34
+ ```
35
+ nbdev_readme
36
+ ```
37
+
16
38
  ## Did you find a bug?
17
39
 
18
40
  * Ensure the bug was not already reported by searching on GitHub under Issues.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.6.12
3
+ Version: 0.6.14
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
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: fastcore>=1.7.15
18
+ Requires-Dist: fastcore>=1.7.18
19
19
  Requires-Dist: python-dateutil
20
20
  Requires-Dist: starlette>0.33
21
21
  Requires-Dist: oauthlib
@@ -176,6 +176,9 @@ tutorials, notebooks, libraries, and components:
176
176
  - [Creating Custom FastHTML Tags for Markdown
177
177
  Rendering](https://isaac-flath.github.io/website/posts/boots/FasthtmlTutorial.html)
178
178
  by Isaac Flath
179
+ - [How to Build a Simple Login System in
180
+ FastHTML](https://blog.mariusvach.com/posts/login-fasthtml) by Marius
181
+ Vach
179
182
  - Your tutorial here!
180
183
 
181
184
  Finally, join the FastHTML community to ask questions, share your work,
@@ -144,6 +144,9 @@ tutorials, notebooks, libraries, and components:
144
144
  - [Creating Custom FastHTML Tags for Markdown
145
145
  Rendering](https://isaac-flath.github.io/website/posts/boots/FasthtmlTutorial.html)
146
146
  by Isaac Flath
147
+ - [How to Build a Simple Login System in
148
+ FastHTML](https://blog.mariusvach.com/posts/login-fasthtml) by Marius
149
+ Vach
147
150
  - Your tutorial here!
148
151
 
149
152
  Finally, join the FastHTML community to ask questions, share your work,
@@ -0,0 +1,2 @@
1
+ __version__ = "0.6.14"
2
+ from .core import *
@@ -46,11 +46,6 @@ d = { 'settings': { 'branch': 'main',
46
46
  'fasthtml.core.HttpHeader': ('api/core.html#httpheader', 'fasthtml/core.py'),
47
47
  'fasthtml.core.MiddlewareBase': ('api/core.html#middlewarebase', 'fasthtml/core.py'),
48
48
  'fasthtml.core.MiddlewareBase.__call__': ('api/core.html#middlewarebase.__call__', 'fasthtml/core.py'),
49
- 'fasthtml.core.Pusher': ('api/core.html#pusher', 'fasthtml/core.py'),
50
- 'fasthtml.core.Pusher.__call__': ('api/core.html#pusher.__call__', 'fasthtml/core.py'),
51
- 'fasthtml.core.Pusher.__init__': ('api/core.html#pusher.__init__', 'fasthtml/core.py'),
52
- 'fasthtml.core.Pusher.queue': ('api/core.html#pusher.queue', 'fasthtml/core.py'),
53
- 'fasthtml.core.Pusher.set_q': ('api/core.html#pusher.set_q', 'fasthtml/core.py'),
54
49
  'fasthtml.core.Redirect': ('api/core.html#redirect', 'fasthtml/core.py'),
55
50
  'fasthtml.core.Redirect.__init__': ('api/core.html#redirect.__init__', 'fasthtml/core.py'),
56
51
  'fasthtml.core.Redirect.__response__': ('api/core.html#redirect.__response__', 'fasthtml/core.py'),
@@ -106,6 +101,7 @@ d = { 'settings': { 'branch': 'main',
106
101
  'fasthtml.core.parsed_date': ('api/core.html#parsed_date', 'fasthtml/core.py'),
107
102
  'fasthtml.core.reg_re_param': ('api/core.html#reg_re_param', 'fasthtml/core.py'),
108
103
  'fasthtml.core.serve': ('api/core.html#serve', 'fasthtml/core.py'),
104
+ 'fasthtml.core.setup_ws': ('api/core.html#setup_ws', 'fasthtml/core.py'),
109
105
  'fasthtml.core.signal_shutdown': ('api/core.html#signal_shutdown', 'fasthtml/core.py'),
110
106
  'fasthtml.core.snake2hyphens': ('api/core.html#snake2hyphens', 'fasthtml/core.py'),
111
107
  'fasthtml.core.unqid': ('api/core.html#unqid', 'fasthtml/core.py'),
@@ -117,6 +113,7 @@ d = { 'settings': { 'branch': 'main',
117
113
  'fasthtml.js': { 'fasthtml.js.HighlightJS': ('api/js.html#highlightjs', 'fasthtml/js.py'),
118
114
  'fasthtml.js.KatexMarkdownJS': ('api/js.html#katexmarkdownjs', 'fasthtml/js.py'),
119
115
  'fasthtml.js.MarkdownJS': ('api/js.html#markdownjs', 'fasthtml/js.py'),
116
+ 'fasthtml.js.MermaidJS': ('api/js.html#mermaidjs', 'fasthtml/js.py'),
120
117
  'fasthtml.js.SortableJS': ('api/js.html#sortablejs', 'fasthtml/js.py'),
121
118
  'fasthtml.js.dark_media': ('api/js.html#dark_media', 'fasthtml/js.py'),
122
119
  'fasthtml.js.light_media': ('api/js.html#light_media', 'fasthtml/js.py')},
@@ -130,7 +127,8 @@ d = { 'settings': { 'branch': 'main',
130
127
  'fasthtml.jupyter.jupy_app': ('api/jupyter.html#jupy_app', 'fasthtml/jupyter.py'),
131
128
  'fasthtml.jupyter.nb_serve': ('api/jupyter.html#nb_serve', 'fasthtml/jupyter.py'),
132
129
  'fasthtml.jupyter.nb_serve_async': ('api/jupyter.html#nb_serve_async', 'fasthtml/jupyter.py'),
133
- 'fasthtml.jupyter.wait_port_free': ('api/jupyter.html#wait_port_free', 'fasthtml/jupyter.py')},
130
+ 'fasthtml.jupyter.wait_port_free': ('api/jupyter.html#wait_port_free', 'fasthtml/jupyter.py'),
131
+ 'fasthtml.jupyter.ws_client': ('api/jupyter.html#ws_client', 'fasthtml/jupyter.py')},
134
132
  'fasthtml.live_reload': {},
135
133
  'fasthtml.oauth': { 'fasthtml.oauth.DiscordAppClient': ('api/oauth.html#discordappclient', 'fasthtml/oauth.py'),
136
134
  'fasthtml.oauth.DiscordAppClient.__init__': ( 'api/oauth.html#discordappclient.__init__',
@@ -227,4 +225,5 @@ d = { 'settings': { 'branch': 'main',
227
225
  'fasthtml.xtend.loose_format': ('api/xtend.html#loose_format', 'fasthtml/xtend.py'),
228
226
  'fasthtml.xtend.replace_css_vars': ('api/xtend.html#replace_css_vars', 'fasthtml/xtend.py'),
229
227
  'fasthtml.xtend.run_js': ('api/xtend.html#run_js', 'fasthtml/xtend.py'),
230
- 'fasthtml.xtend.undouble_braces': ('api/xtend.html#undouble_braces', 'fasthtml/xtend.py')}}}
228
+ 'fasthtml.xtend.undouble_braces': ('api/xtend.html#undouble_braces', 'fasthtml/xtend.py'),
229
+ 'fasthtml.xtend.with_sid': ('api/xtend.html#with_sid', 'fasthtml/xtend.py')}}}
@@ -24,7 +24,7 @@ from fastcore.utils import *
24
24
  from fastcore.xml import *
25
25
  from fastcore.meta import use_kwargs, delegates
26
26
  from fastcore.test import *
27
- from .core import fh_cfg
27
+ from .core import fh_cfg, unqid
28
28
 
29
29
  import types, json
30
30
 
@@ -7,7 +7,7 @@ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc',
7
7
  'scopesrc', 'viewport', 'charset', 'all_meths', 'parsed_date', 'snake2hyphens', 'HtmxHeaders', 'HttpHeader',
8
8
  'HtmxResponseHeaders', 'form2dict', 'parse_form', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown',
9
9
  'WS_RouteX', 'uri', 'decode_uri', 'flat_tuple', 'Redirect', 'RouteX', 'RouterX', 'get_key', 'def_hdrs',
10
- 'FastHTML', 'serve', 'Client', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'Pusher']
10
+ 'FastHTML', 'serve', 'Client', '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
@@ -73,21 +73,22 @@ def _get_htmx(h):
73
73
  return HtmxHeaders(**res)
74
74
 
75
75
  # %% ../nbs/api/00_core.ipynb
76
- def _mk_list(t, v): return [t(o) for o in v]
76
+ def _mk_list(t, v): return [t(o) for o in listify(v)]
77
77
 
78
78
  # %% ../nbs/api/00_core.ipynb
79
79
  fh_cfg = AttrDict(indent=True)
80
80
 
81
81
  # %% ../nbs/api/00_core.ipynb
82
- def _fix_anno(t):
82
+ def _fix_anno(t, o):
83
83
  "Create appropriate callable type for casting a `str` to type `t` (or first type in `t` if union)"
84
84
  origin = get_origin(t)
85
85
  if origin is Union or origin is UnionType or origin in (list,List):
86
86
  t = first(o for o in get_args(t) if o!=type(None))
87
87
  d = {bool: str2bool, int: str2int, date: str2date, UploadFile: noop}
88
88
  res = d.get(t, t)
89
- if origin in (list,List): return partial(_mk_list, res)
90
- return lambda o: res(o[-1]) if isinstance(o,(list,tuple)) else res(o)
89
+ if origin in (list,List): return _mk_list(res, o)
90
+ if not isinstance(o, (str,list,tuple)): return o
91
+ return res(o[-1]) if isinstance(o,(list,tuple)) else res(o)
91
92
 
92
93
  # %% ../nbs/api/00_core.ipynb
93
94
  def _form_arg(k, v, d):
@@ -97,7 +98,7 @@ def _form_arg(k, v, d):
97
98
  # This is the type we want to cast `v` to
98
99
  anno = d.get(k, None)
99
100
  if not anno: return v
100
- return _fix_anno(anno)(v)
101
+ return _fix_anno(anno, v)
101
102
 
102
103
  # %% ../nbs/api/00_core.ipynb
103
104
  @dataclass
@@ -175,6 +176,7 @@ async def _find_p(req, arg:str, p:Parameter):
175
176
  if anno is empty:
176
177
  if 'request'.startswith(arg.lower()): return req
177
178
  if 'session'.startswith(arg.lower()): return req.scope.get('session', {})
179
+ if arg.lower()=='scope': return dict2obj(req.scope)
178
180
  if arg.lower()=='auth': return req.scope.get('auth', None)
179
181
  if arg.lower()=='htmx': return _get_htmx(req.headers)
180
182
  if arg.lower()=='app': return req.scope['app']
@@ -194,9 +196,8 @@ async def _find_p(req, arg:str, p:Parameter):
194
196
  # If we have a default, return that if we have no value
195
197
  if res in (empty,None): res = p.default
196
198
  # We can cast str and list[str] to types; otherwise just return what we have
197
- if not isinstance(res, (list,str)) or anno is empty: return res
198
- anno = _fix_anno(anno)
199
- try: return anno(res)
199
+ if anno is empty: return res
200
+ try: return _fix_anno(anno, res)
200
201
  except ValueError: raise HTTPException(404, req.url.path) from None
201
202
 
202
203
  async def _wrap_req(req, params):
@@ -210,7 +211,7 @@ def flat_xt(lst):
210
211
  for item in lst:
211
212
  if isinstance(item, (list,tuple)): result.extend(item)
212
213
  else: result.append(item)
213
- return result
214
+ return tuple(result)
214
215
 
215
216
  # %% ../nbs/api/00_core.ipynb
216
217
  class Beforeware:
@@ -229,6 +230,7 @@ def _find_wsp(ws, data, hdrs, arg:str, p:Parameter):
229
230
  if issubclass(anno, Starlette): return ws.scope['app']
230
231
  if anno is empty:
231
232
  if arg.lower()=='ws': return ws
233
+ if arg.lower()=='scope': return dict2obj(ws.scope)
232
234
  if arg.lower()=='data': return data
233
235
  if arg.lower()=='htmx': return _get_htmx(hdrs)
234
236
  if arg.lower()=='app': return ws.scope['app']
@@ -239,8 +241,7 @@ def _find_wsp(ws, data, hdrs, arg:str, p:Parameter):
239
241
  if res is empty or res is None: res = p.default
240
242
  # We can cast str and list[str] to types; otherwise just return what we have
241
243
  if not isinstance(res, (list,str)) or anno is empty: return res
242
- anno = _fix_anno(anno)
243
- return [anno(o) for o in res] if isinstance(res,list) else anno(res)
244
+ return [_fix_anno(anno, o) for o in res] if isinstance(res,list) else _fix_anno(anno, res)
244
245
 
245
246
  def _wrap_ws(ws, data, params):
246
247
  hdrs = {k.lower().replace('-','_'):v for k,v in data.pop('HEADERS', {}).items()}
@@ -345,7 +346,7 @@ def _find_targets(req, resp):
345
346
  def _apply_ft(o):
346
347
  if isinstance(o, tuple): o = tuple(_apply_ft(c) for c in o)
347
348
  if hasattr(o, '__ft__'): o = o.__ft__()
348
- if isinstance(o, FT): o.children = [_apply_ft(c) for c in o.children]
349
+ if isinstance(o, FT): o.children = tuple(_apply_ft(c) for c in o.children)
349
350
  return o
350
351
 
351
352
  def _to_xml(req, resp, indent):
@@ -554,8 +555,8 @@ class FastHTML(Starlette):
554
555
 
555
556
  def ws(self, path:str, conn=None, disconn=None, name=None):
556
557
  "Add a websocket route at `path`"
557
- def f(func):
558
- self.router.add_ws(path, func, conn=conn, disconn=disconn, name=name)
558
+ def f(func=None):
559
+ self.router.add_ws(path, func or noop, conn=conn, disconn=disconn, name=name)
559
560
  return func
560
561
  return f
561
562
 
@@ -689,32 +690,12 @@ def _add_ids(s):
689
690
  for c in s.children: _add_ids(c)
690
691
 
691
692
  # %% ../nbs/api/00_core.ipynb
692
- class Pusher:
693
- def __init__(self, app, dest_id='_dest', auto_id=True):
694
- store_attr()
695
- self._queue = None
696
- self('')
697
- @app.route
698
- def index():
699
- return Div(id=self.dest_id, hx_trigger='load', hx_ext="ws",
700
- ws_send=True, ws_connect="/ws")
701
-
702
- @property
703
- def queue(self):
704
- self.set_q()
705
- return self._queue
706
-
707
- def set_q(self):
708
- if self._queue: return
709
- self._queue = asyncio.Queue()
710
- @self.app.ws("/ws")
711
- async def ws(ws, send):
712
- try:
713
- while True: await send(await self.queue.get())
714
- except WebSocketDisconnect: self._queue=None
715
-
716
- def __call__(self, *s):
717
- id = getattr(s[0], 'id', None)
718
- if not id: s = Div(*s, hx_swap_oob='innerHTML', id=self.dest_id)
719
- if self.auto_id: _add_ids(s)
720
- self.queue.put_nowait(s)
693
+ def setup_ws(app):
694
+ conns = {}
695
+ async def on_connect(scope, send): conns[scope.client] = send
696
+ async def on_disconnect(scope): conns.pop(scope.client)
697
+ app.ws('/ws', conn=on_connect, disconn=on_disconnect)()
698
+ async def send(s):
699
+ for o in conns.values(): await o(s)
700
+ app._send = send
701
+ return send
@@ -3,7 +3,8 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_js.ipynb.
4
4
 
5
5
  # %% auto 0
6
- __all__ = ['marked_imp', 'npmcdn', 'light_media', 'dark_media', 'MarkdownJS', 'KatexMarkdownJS', 'HighlightJS', 'SortableJS']
6
+ __all__ = ['marked_imp', 'npmcdn', 'light_media', 'dark_media', 'MarkdownJS', 'KatexMarkdownJS', 'HighlightJS', 'SortableJS',
7
+ 'MermaidJS']
7
8
 
8
9
  # %% ../nbs/api/03_js.ipynb
9
10
  import re
@@ -87,3 +88,43 @@ import {Sortable} from 'https://cdn.jsdelivr.net/npm/sortablejs/+esm';
87
88
  proc_htmx('%s', Sortable.create);
88
89
  """ % sel
89
90
  return Script(src, type='module')
91
+
92
+ # %% ../nbs/api/03_js.ipynb
93
+ def MermaidJS(
94
+ sel='.language-mermaid', # CSS selector for mermaid elements
95
+ theme='base', # Mermaid theme to use
96
+ ):
97
+ "Implements browser-based Mermaid diagram rendering."
98
+ src = """
99
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
100
+
101
+ mermaid.initialize({
102
+ startOnLoad: false,
103
+ theme: '%s',
104
+ securityLevel: 'loose',
105
+ flowchart: { useMaxWidth: false, useMaxHeight: false }
106
+ });
107
+
108
+ function renderMermaidDiagrams(element, index) {
109
+ try {
110
+ const graphDefinition = element.textContent;
111
+ const graphId = `mermaid-diagram-${index}`;
112
+ mermaid.render(graphId, graphDefinition)
113
+ .then(({svg, bindFunctions}) => {
114
+ element.innerHTML = svg;
115
+ bindFunctions?.(element);
116
+ })
117
+ .catch(error => {
118
+ console.error(`Error rendering Mermaid diagram ${index}:`, error);
119
+ element.innerHTML = `<p>Error rendering diagram: ${error.message}</p>`;
120
+ });
121
+ } catch (error) {
122
+ console.error(`Error processing Mermaid diagram ${index}:`, error);
123
+ }
124
+ }
125
+
126
+ // Assuming proc_htmx is a function that triggers rendering
127
+ proc_htmx('%s', renderMermaidDiagrams);
128
+ """ % (theme, sel)
129
+ return Script(src, type='module')
130
+
@@ -2,14 +2,14 @@
2
2
 
3
3
  # %% auto 0
4
4
  __all__ = ['cors_allow', 'nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'JupyUvi', 'FastJupy', 'HTMX',
5
- 'jupy_app']
5
+ 'ws_client', 'jupy_app']
6
6
 
7
7
  # %% ../nbs/api/06_jupyter.ipynb
8
8
  import asyncio, socket, time, uvicorn
9
9
  from threading import Thread
10
10
  from fastcore.utils import *
11
11
  from .common import *
12
- from IPython.display import HTML,Markdown,IFrame
12
+ from IPython.display import HTML,Markdown,IFrame,display
13
13
  from starlette.middleware.cors import CORSMiddleware
14
14
  from starlette.middleware import Middleware
15
15
  from fastcore.parallel import startthread
@@ -74,10 +74,13 @@ class JupyUvi:
74
74
  # %% ../nbs/api/06_jupyter.ipynb
75
75
  # The script lets an iframe parent know of changes so that it can resize automatically.
76
76
  _iframe_scr = Script("""
77
- function sendmsg() {window.parent.postMessage({height: document.documentElement.offsetHeight}, '*')}
77
+ function sendmsg() {
78
+ window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');
79
+ }
78
80
  window.onload = function() {
79
81
  sendmsg();
80
- document.body.addEventListener('htmx:afterSettle', sendmsg);
82
+ document.body.addEventListener('htmx:afterSettle', sendmsg);
83
+ document.body.addEventListener('htmx:wsAfterMessage', sendmsg);
81
84
  };""")
82
85
 
83
86
  # %% ../nbs/api/06_jupyter.ipynb
@@ -93,10 +96,25 @@ def HTMX(path="", host='localhost', port=8000, iframe_height="auto"):
93
96
  return HTML(f'<iframe src="http://{host}:{port}{str(path)}" style="width: 100%; height: {iframe_height}; border: none;" ' + """onload="{
94
97
  let frame = this;
95
98
  window.addEventListener('message', function(e) {
99
+ if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe
96
100
  if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
97
101
  }, false);
98
102
  }" 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> """)
99
103
 
104
+ # %% ../nbs/api/06_jupyter.ipynb
105
+ def ws_client(app, nm='', host='localhost', port=8000, ws_connect='/ws', frame=True, link=True, **kwargs):
106
+ path = f'/{nm}'
107
+ c = Main('', cls="container", id=unqid())
108
+ @app.get(path)
109
+ def f():
110
+ return Div(c, id=nm or '_dest', hx_trigger='load',
111
+ hx_ext="ws", ws_connect=ws_connect, **kwargs)
112
+ if link: display(HTML(f'<a href="http://{host}:{port}{path}" target="_blank">open in browser</a>'))
113
+ if frame: display(HTMX(path, host=host, port=port))
114
+ def send(o): asyncio.create_task(app._send(o))
115
+ c.on(send)
116
+ return c
117
+
100
118
  # %% ../nbs/api/06_jupyter.ipynb
101
119
  def jupy_app(pico=False, hdrs=None, middleware=None, **kwargs):
102
120
  "Same as `fast_app` but for Jupyter notebooks"
@@ -31,12 +31,12 @@ g = globals()
31
31
  for o in _all_: g[o] = partial(ft_hx, o[0].lower() + o[1:])
32
32
 
33
33
  # %% ../nbs/api/05_svg.ipynb
34
- def Svg(*args, viewBox=None, h=None, w=None, height=None, width=None, **kwargs):
34
+ def Svg(*args, viewBox=None, h=None, w=None, height=None, width=None, xmlns="http://www.w3.org/2000/svg", **kwargs):
35
35
  "An SVG tag; xmlns is added automatically, and viewBox defaults to height and width if not provided"
36
36
  if h: height=h
37
37
  if w: width=w
38
38
  if not viewBox and height and width: viewBox=f'0 0 {width} {height}'
39
- return ft_svg('svg', *args, xmlns="http://www.w3.org/2000/svg", viewBox=viewBox, height=height, width=width, **kwargs)
39
+ return ft_svg('svg', *args, xmlns=xmlns, viewBox=viewBox, height=height, width=width, **kwargs)
40
40
 
41
41
  # %% ../nbs/api/05_svg.ipynb
42
42
  @delegates(ft_hx)
@@ -5,7 +5,7 @@
5
5
  # %% auto 0
6
6
  __all__ = ['sid_scr', 'A', 'AX', 'Form', 'Hidden', 'CheckboxX', 'Script', 'Style', 'double_braces', 'undouble_braces',
7
7
  'loose_format', 'ScriptX', 'replace_css_vars', 'StyleX', 'Nbsp', 'Surreal', 'On', 'Prev', 'Now', 'AnyNow',
8
- 'run_js', 'HtmxOn', 'jsd', 'Titled', 'Socials', 'Favicon', 'clear']
8
+ 'run_js', 'HtmxOn', 'jsd', 'Titled', 'Socials', 'Favicon', 'clear', 'with_sid']
9
9
 
10
10
  # %% ../nbs/api/02_xtend.ipynb
11
11
  from dataclasses import dataclass, asdict
@@ -15,6 +15,7 @@ from fastcore.utils import *
15
15
  from fastcore.xtras import partial_format
16
16
  from fastcore.xml import *
17
17
  from fastcore.meta import use_kwargs, delegates
18
+ from .core import *
18
19
  from .components import *
19
20
 
20
21
  try: from IPython import display
@@ -232,3 +233,8 @@ htmx.on("htmx:configRequest", (e) => {
232
233
  }
233
234
  });
234
235
  ''')
236
+
237
+ # %% ../nbs/api/02_xtend.ipynb
238
+ def with_sid(app, dest, path='/'):
239
+ @app.route(path)
240
+ def get(): return Div(hx_get=dest, hx_trigger=f'load delay:0.001s', hx_swap='outerHTML')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.6.12
3
+ Version: 0.6.14
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
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: fastcore>=1.7.15
18
+ Requires-Dist: fastcore>=1.7.18
19
19
  Requires-Dist: python-dateutil
20
20
  Requires-Dist: starlette>0.33
21
21
  Requires-Dist: oauthlib
@@ -176,6 +176,9 @@ tutorials, notebooks, libraries, and components:
176
176
  - [Creating Custom FastHTML Tags for Markdown
177
177
  Rendering](https://isaac-flath.github.io/website/posts/boots/FasthtmlTutorial.html)
178
178
  by Isaac Flath
179
+ - [How to Build a Simple Login System in
180
+ FastHTML](https://blog.mariusvach.com/posts/login-fasthtml) by Marius
181
+ Vach
179
182
  - Your tutorial here!
180
183
 
181
184
  Finally, join the FastHTML community to ask questions, share your work,
@@ -1,4 +1,4 @@
1
- fastcore>=1.7.15
1
+ fastcore>=1.7.18
2
2
  python-dateutil
3
3
  starlette>0.33
4
4
  oauthlib
@@ -1,10 +1,10 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.6.12
4
+ version = 0.6.14
5
5
  min_python = 3.10
6
6
  license = apache2
7
- requirements = fastcore>=1.7.15 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.0.9 python-multipart beautifulsoup4
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
8
8
  dev_requirements = ipython lxml pysymbol_llm
9
9
  black_formatting = False
10
10
  conda_user = fastai
@@ -1,2 +0,0 @@
1
- __version__ = "0.6.12"
2
- from .core import *