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.
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/CONTRIBUTING.md +23 -1
- {python-fasthtml-0.6.12/python_fasthtml.egg-info → python-fasthtml-0.6.14}/PKG-INFO +5 -2
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/README.md +3 -0
- python-fasthtml-0.6.14/fasthtml/__init__.py +2 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/_modidx.py +6 -7
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/components.py +1 -1
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/core.py +25 -44
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/js.py +42 -1
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/jupyter.py +22 -4
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/svg.py +2 -2
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/xtend.py +7 -1
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14/python_fasthtml.egg-info}/PKG-INFO +5 -2
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/requires.txt +1 -1
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/settings.ini +2 -2
- python-fasthtml-0.6.12/fasthtml/__init__.py +0 -2
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/LICENSE +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/MANIFEST.in +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/authmw.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/basics.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/cli.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/common.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/components.pyi +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/core.pyi +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/fastapp.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/ft.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/katex.js +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/live_reload.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/oauth.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/pico.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/starlette.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/toaster.py +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/fasthtml/xtend.pyi +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/pyproject.toml +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/not-zip-safe +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/setup.cfg +0 -0
- {python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/setup.py +0 -0
- {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.
|
|
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.
|
|
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,
|
|
@@ -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')}}}
|
|
@@ -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', '
|
|
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
|
|
90
|
-
|
|
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
|
|
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
|
|
198
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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() {
|
|
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',
|
|
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=
|
|
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.
|
|
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.
|
|
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,10 +1,10 @@
|
|
|
1
1
|
[DEFAULT]
|
|
2
2
|
repo = fasthtml
|
|
3
3
|
lib_name = fasthtml
|
|
4
|
-
version = 0.6.
|
|
4
|
+
version = 0.6.14
|
|
5
5
|
min_python = 3.10
|
|
6
6
|
license = apache2
|
|
7
|
-
requirements = fastcore>=1.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
|
|
8
8
|
dev_requirements = ipython lxml pysymbol_llm
|
|
9
9
|
black_formatting = False
|
|
10
10
|
conda_user = fastai
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python-fasthtml-0.6.12 → python-fasthtml-0.6.14}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|