python-fasthtml 0.14.13__tar.gz → 0.14.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.14.13 → python_fasthtml-0.14.14}/CONTRIBUTING.md +4 -4
- {python_fasthtml-0.14.13/python_fasthtml.egg-info → python_fasthtml-0.14.14}/PKG-INFO +2 -2
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/__init__.py +1 -1
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/_modidx.py +1 -9
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/core.py +16 -9
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/oauth.py +4 -1
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/pyproject.toml +2 -2
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14/python_fasthtml.egg-info}/PKG-INFO +2 -2
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/LICENSE +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/MANIFEST.in +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/README.md +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/authmw.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/basics.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/cli.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/common.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/components.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/components.pyi +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/core.pyi +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/fastapp.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/ft.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/js.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/jupyter.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/katex.js +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/live_reload.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/pico.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/starlette.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/stripe_otp.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/svg.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/toaster.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/xtend.py +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/xtend.pyi +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/requires.txt +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/setup.cfg +0 -0
- {python_fasthtml-0.14.13 → python_fasthtml-0.14.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 to the codebase, update the notebooks in the `nbs` folder, not the .py files directly. Then, run `
|
|
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
|
|
|
@@ -11,7 +11,7 @@ You may want to set up a `prep` alias in `~/.zshrc` or other shell startup file:
|
|
|
11
11
|
alias prep='nbdev-export && nbdev-clean && nbdev-trust'
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Run `prep` before each commit to ensure your python files are up to date, and
|
|
14
|
+
Run `prep` before each commit to ensure your python files are up to date, and your notebooks cleaned of metadata and notarized.
|
|
15
15
|
|
|
16
16
|
## Updating README.md
|
|
17
17
|
|
|
@@ -26,13 +26,13 @@ pip install -e '.[dev]'
|
|
|
26
26
|
And install quarto too:
|
|
27
27
|
|
|
28
28
|
```
|
|
29
|
-
|
|
29
|
+
nbdev-install-quarto
|
|
30
30
|
```
|
|
31
31
|
|
|
32
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
33
|
|
|
34
34
|
```
|
|
35
|
-
|
|
35
|
+
nbdev-readme
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Did you find a bug?
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fasthtml
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.14
|
|
4
4
|
Summary: The fastest way to create an HTML app
|
|
5
5
|
Author-email: Jeremy Howard and contributors <github@jhoward.fastmail.fm>
|
|
6
|
-
License: Apache-2.0
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Repository, https://github.com/AnswerDotAI/fasthtml
|
|
8
8
|
Project-URL: Documentation, https://www.fastht.ml/docs/
|
|
9
9
|
Keywords: nbdev,jupyter,notebook,python
|
|
@@ -5,12 +5,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
5
5
|
'doc_host': 'https://www.fastht.ml',
|
|
6
6
|
'git_url': 'https://github.com/AnswerDotAI/fasthtml',
|
|
7
7
|
'lib_path': 'fasthtml'},
|
|
8
|
-
'syms': { 'fasthtml.
|
|
9
|
-
'fasthtml.basics': {},
|
|
10
|
-
'fasthtml.cli': { 'fasthtml.cli._run': ('api/cli.html#_run', 'fasthtml/cli.py'),
|
|
8
|
+
'syms': { 'fasthtml.cli': { 'fasthtml.cli._run': ('api/cli.html#_run', 'fasthtml/cli.py'),
|
|
11
9
|
'fasthtml.cli.railway_deploy': ('api/cli.html#railway_deploy', 'fasthtml/cli.py'),
|
|
12
10
|
'fasthtml.cli.railway_link': ('api/cli.html#railway_link', 'fasthtml/cli.py')},
|
|
13
|
-
'fasthtml.common': {},
|
|
14
11
|
'fasthtml.components': { 'fasthtml.components.FT.__add__': ('api/components.html#ft.__add__', 'fasthtml/components.py'),
|
|
15
12
|
'fasthtml.components.FT.__radd__': ('api/components.html#ft.__radd__', 'fasthtml/components.py'),
|
|
16
13
|
'fasthtml.components.FT.__str__': ('api/components.html#ft.__str__', 'fasthtml/components.py'),
|
|
@@ -169,8 +166,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
169
166
|
'fasthtml.core.until_disconnect': ('api/core.html#until_disconnect', 'fasthtml/core.py'),
|
|
170
167
|
'fasthtml.core.uri': ('api/core.html#uri', 'fasthtml/core.py'),
|
|
171
168
|
'fasthtml.core.vurl': ('api/core.html#vurl', 'fasthtml/core.py')},
|
|
172
|
-
'fasthtml.fastapp': {},
|
|
173
|
-
'fasthtml.ft': {},
|
|
174
169
|
'fasthtml.js': { 'fasthtml.js.HighlightJS': ('api/js.html#highlightjs', 'fasthtml/js.py'),
|
|
175
170
|
'fasthtml.js.KatexMarkdownJS': ('api/js.html#katexmarkdownjs', 'fasthtml/js.py'),
|
|
176
171
|
'fasthtml.js.MarkdownJS': ('api/js.html#markdownjs', 'fasthtml/js.py'),
|
|
@@ -196,7 +191,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
196
191
|
'fasthtml.jupyter.render_ft': ('api/jupyter.html#render_ft', 'fasthtml/jupyter.py'),
|
|
197
192
|
'fasthtml.jupyter.show': ('api/jupyter.html#show', 'fasthtml/jupyter.py'),
|
|
198
193
|
'fasthtml.jupyter.ws_client': ('api/jupyter.html#ws_client', 'fasthtml/jupyter.py')},
|
|
199
|
-
'fasthtml.live_reload': {},
|
|
200
194
|
'fasthtml.oauth': { 'fasthtml.oauth.AppleAppClient': ('api/oauth.html#appleappclient', 'fasthtml/oauth.py'),
|
|
201
195
|
'fasthtml.oauth.AppleAppClient.__init__': ('api/oauth.html#appleappclient.__init__', 'fasthtml/oauth.py'),
|
|
202
196
|
'fasthtml.oauth.AppleAppClient.client_secret': ( 'api/oauth.html#appleappclient.client_secret',
|
|
@@ -265,7 +259,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
265
259
|
'fasthtml.pico.PicoBusy': ('api/pico.html#picobusy', 'fasthtml/pico.py'),
|
|
266
260
|
'fasthtml.pico.Search': ('api/pico.html#search', 'fasthtml/pico.py'),
|
|
267
261
|
'fasthtml.pico.set_pico_cls': ('api/pico.html#set_pico_cls', 'fasthtml/pico.py')},
|
|
268
|
-
'fasthtml.starlette': {},
|
|
269
262
|
'fasthtml.stripe_otp': { 'fasthtml.stripe_otp.Payment': ('explains/stripe.html#payment', 'fasthtml/stripe_otp.py'),
|
|
270
263
|
'fasthtml.stripe_otp._search_app': ('explains/stripe.html#_search_app', 'fasthtml/stripe_otp.py'),
|
|
271
264
|
'fasthtml.stripe_otp.account_management': ( 'explains/stripe.html#account_management',
|
|
@@ -305,7 +298,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
305
298
|
'fasthtml.svg.Text': ('api/svg.html#text', 'fasthtml/svg.py'),
|
|
306
299
|
'fasthtml.svg.ft_svg': ('api/svg.html#ft_svg', 'fasthtml/svg.py'),
|
|
307
300
|
'fasthtml.svg.transformd': ('api/svg.html#transformd', 'fasthtml/svg.py')},
|
|
308
|
-
'fasthtml.toaster': {},
|
|
309
301
|
'fasthtml.xtend': { 'fasthtml.xtend.A': ('api/xtend.html#a', 'fasthtml/xtend.py'),
|
|
310
302
|
'fasthtml.xtend.AX': ('api/xtend.html#ax', 'fasthtml/xtend.py'),
|
|
311
303
|
'fasthtml.xtend.AnyNow': ('api/xtend.html#anynow', 'fasthtml/xtend.py'),
|
|
@@ -18,7 +18,7 @@ The decorated function doubles as a URL builder, so links never need hardcoded p
|
|
|
18
18
|
|
|
19
19
|
Handlers return FT trees (from `fastcore.xml`), rendered as HTML automatically: a full page for regular requests, a bare fragment for HTMX requests. In a full page, top-level `Title`, `Meta`, `Link`, and `Style` items go in `<head>`; the rest goes in `<body>`. Strings are sent as HTML, dicts as JSON, and a Starlette `Response` is sent untouched. A return tuple may mix content with special items: any `HttpHeader` (e.g. from `cookie()` or `HtmxResponseHeaders()`) becomes a response header, any `BackgroundTask` runs after the response is sent, and the rest renders as content. `Redirect` picks the right redirect mechanism for HTMX vs regular requests, and `FtResponse` wraps FT content when you need the status code or headers.
|
|
20
20
|
|
|
21
|
-
Handler parameters are filled from the request: each is looked up in path, cookie, header (snake_case names match Hyphen-Case headers), query, then form data, and cast by calling its annotation on the value. `bool`, `int`, and `date` get smart string parsing; `UploadFile` passes through. Repeated params take the last value unless annotated `list[T]`, which collects them all. A param with no annotation and no special name is ignored, with a warning. A dataclass, TypedDict, namedtuple, or any annotated class collects the whole form body; a `__from_request__` classmethod customizes construction. A missing required param is a 400, a failed cast a 404. These special names need no annotation:
|
|
21
|
+
Handler parameters are filled from the request: each is looked up in path, cookie, header (snake_case names match Hyphen-Case headers), query, then form data, and cast by calling its annotation on the value. Hyphens in form/query names match underscores in handler parameters and typed body fields. Exact names take precedence within each source. Dictionary and request access retain the original keys. `bool`, `int`, and `date` get smart string parsing; `UploadFile` passes through. Repeated params take the last value unless annotated `list[T]`, which collects them all. A param with no annotation and no special name is ignored, with a warning. A dataclass, TypedDict, namedtuple, or any annotated class collects the whole form body; a `__from_request__` classmethod customizes construction. A missing required param is a 400, a failed cast a 404. These special names need no annotation:
|
|
22
22
|
|
|
23
23
|
req, ws the Request / WebSocket connection
|
|
24
24
|
sess session dict
|
|
@@ -80,6 +80,7 @@ from base64 import b64encode,b64decode
|
|
|
80
80
|
from email.utils import format_datetime
|
|
81
81
|
|
|
82
82
|
from .starlette import *
|
|
83
|
+
from python_multipart.exceptions import MultipartParseError
|
|
83
84
|
|
|
84
85
|
# %% ../nbs/api/00_core.ipynb #19d3f2a7
|
|
85
86
|
def _params(f): return signature_ex(f, True).parameters
|
|
@@ -189,7 +190,8 @@ def _is_body(anno): return issubclass(anno, (dict,ns)) or hasattr(anno,'__from_r
|
|
|
189
190
|
|
|
190
191
|
# %% ../nbs/api/00_core.ipynb #ffdde66f
|
|
191
192
|
def _formitem(form, k):
|
|
192
|
-
"Return
|
|
193
|
+
"Return item `k`, accepting hyphens for underscores and unwrapping single-item form lists"
|
|
194
|
+
if k not in form: k = {o.replace('-', '_'):o for o in form}.get(k, k)
|
|
193
195
|
if isinstance(form, dict): return form.get(k)
|
|
194
196
|
o = form.getlist(k)
|
|
195
197
|
return o[0] if len(o) == 1 else o if o else None
|
|
@@ -205,16 +207,21 @@ DEF_MAXPART = 100*1024*1024
|
|
|
205
207
|
|
|
206
208
|
# %% ../nbs/api/00_core.ipynb #42c9cea0
|
|
207
209
|
async def parse_form(req: Request) -> FormData:
|
|
208
|
-
"Starlette errors on empty multipart/json forms, so this
|
|
210
|
+
"Starlette errors on empty multipart/json forms and 500s on malformed multipart, so this handles those situations"
|
|
209
211
|
ctype = req.headers.get("Content-Type", "")
|
|
210
212
|
maxpart = getattr(req, "max_part_size", DEF_MAXPART)
|
|
211
213
|
if ctype.startswith("multipart/form-data"):
|
|
212
214
|
try: boundary = ctype.split("boundary=")[1].strip()
|
|
213
215
|
except IndexError: raise HTTPException(400, "Invalid form-data: no boundary")
|
|
214
216
|
if int(req.headers.get("Content-Length", "0")) <= len(boundary) + 6: return FormData()
|
|
215
|
-
return await req.form(max_part_size=maxpart)
|
|
217
|
+
try: return await req.form(max_part_size=maxpart)
|
|
218
|
+
except MultipartParseError as e: raise HTTPException(400, f"Invalid form-data: {e}")
|
|
216
219
|
body = await req.body() # Cache body for non-multipart request types
|
|
217
|
-
if ctype == 'application/json':
|
|
220
|
+
if ctype == 'application/json':
|
|
221
|
+
try: res = await req.json() if body else {}
|
|
222
|
+
except ValueError: raise HTTPException(400, "Invalid JSON body")
|
|
223
|
+
if not isinstance(res, dict): raise HTTPException(400, "JSON body must be an object")
|
|
224
|
+
return res
|
|
218
225
|
return await req.form(max_part_size=maxpart)
|
|
219
226
|
|
|
220
227
|
# %% ../nbs/api/00_core.ipynb #0caedd04
|
|
@@ -229,6 +236,7 @@ async def _from_body(conn, p, data):
|
|
|
229
236
|
kwargs = await _find_ps(conn, data, conn.headers, ps)
|
|
230
237
|
return await maybe_await(ctor(**kwargs))
|
|
231
238
|
d = _annotations(anno)
|
|
239
|
+
if d: data = {k.replace('-', '_'):v for k,v in data.items()} | data
|
|
232
240
|
cargs = {k: _form_arg(k, v, d) for k, v in data.items() if not d or k in d}
|
|
233
241
|
return anno(**cargs)
|
|
234
242
|
|
|
@@ -286,10 +294,9 @@ async def _find_p(conn, data, hdrs, arg:str, p:Parameter):
|
|
|
286
294
|
res = conn.path_params.get(arg, None)
|
|
287
295
|
if res in (empty,None): res = conn.cookies.get(arg, None)
|
|
288
296
|
if res in (empty,None): res = hdrs.get(snake2hyphens(arg), None)
|
|
289
|
-
if res in (empty,None): res = conn.query_params
|
|
290
|
-
if res
|
|
291
|
-
if res in (empty,None)
|
|
292
|
-
if res in (empty,None) and isinstance(vals := data.get('values'), dict): res = vals.get(arg) # htmx v4 nests form data under `values`
|
|
297
|
+
if res in (empty,None): res = _formitem(conn.query_params, arg)
|
|
298
|
+
if res in (empty,None): res = _formitem(data, arg)
|
|
299
|
+
if res in (empty,None) and isinstance(vals := data.get('values'), dict): res = _formitem(vals, arg) # htmx v4 nests form data under `values`
|
|
293
300
|
if res in (empty,None):
|
|
294
301
|
if p.default is empty:
|
|
295
302
|
if isinstance(conn, Request): raise HTTPException(400, f"Missing required field: {arg}")
|
|
@@ -249,7 +249,10 @@ class OAuth:
|
|
|
249
249
|
return RedirectResponse(self.error_path, status_code=303)
|
|
250
250
|
scheme = 'http' if url_match(req,self.http_patterns) or not self.https else 'https'
|
|
251
251
|
base_url = f"{scheme}://{get_host(req)}"
|
|
252
|
-
info = AttrDictDefault(await cli.retr_info_async(code, base_url+redir_path))
|
|
252
|
+
try: info = AttrDictDefault(await cli.retr_info_async(code, base_url+redir_path))
|
|
253
|
+
except httpx2.HTTPStatusError as e:
|
|
254
|
+
session['oauth_error'] = e.response.text
|
|
255
|
+
return RedirectResponse(self.error_path, status_code=303)
|
|
253
256
|
ident = info.get(self.cli.id_key)
|
|
254
257
|
if not ident: return self.redir_login(session)
|
|
255
258
|
res = await _arun(self.get_auth(info, ident, session, state))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -8,7 +8,7 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "The fastest way to create an HTML app"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
-
license =
|
|
11
|
+
license = "Apache-2.0"
|
|
12
12
|
authors = [{name = "Jeremy Howard and contributors", email = "github@jhoward.fastmail.fm"}]
|
|
13
13
|
keywords = ['nbdev', 'jupyter', 'notebook', 'python']
|
|
14
14
|
classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-fasthtml
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.14
|
|
4
4
|
Summary: The fastest way to create an HTML app
|
|
5
5
|
Author-email: Jeremy Howard and contributors <github@jhoward.fastmail.fm>
|
|
6
|
-
License: Apache-2.0
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Repository, https://github.com/AnswerDotAI/fasthtml
|
|
8
8
|
Project-URL: Documentation, https://www.fastht.ml/docs/
|
|
9
9
|
Keywords: nbdev,jupyter,notebook,python
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|