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.
Files changed (38) hide show
  1. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/CONTRIBUTING.md +4 -4
  2. {python_fasthtml-0.14.13/python_fasthtml.egg-info → python_fasthtml-0.14.14}/PKG-INFO +2 -2
  3. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/__init__.py +1 -1
  4. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/_modidx.py +1 -9
  5. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/core.py +16 -9
  6. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/oauth.py +4 -1
  7. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/pyproject.toml +2 -2
  8. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14/python_fasthtml.egg-info}/PKG-INFO +2 -2
  9. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/LICENSE +0 -0
  10. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/MANIFEST.in +0 -0
  11. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/README.md +0 -0
  12. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/authmw.py +0 -0
  13. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/basics.py +0 -0
  14. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/cli.py +0 -0
  15. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/common.py +0 -0
  16. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/components.py +0 -0
  17. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/components.pyi +0 -0
  18. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/core.pyi +0 -0
  19. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/fastapp.py +0 -0
  20. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/ft.py +0 -0
  21. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/js.py +0 -0
  22. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/jupyter.py +0 -0
  23. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/katex.js +0 -0
  24. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/live_reload.py +0 -0
  25. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/pico.py +0 -0
  26. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/starlette.py +0 -0
  27. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/stripe_otp.py +0 -0
  28. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/svg.py +0 -0
  29. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/toaster.py +0 -0
  30. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/xtend.py +0 -0
  31. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/fasthtml/xtend.pyi +0 -0
  32. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  33. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  34. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/entry_points.txt +0 -0
  35. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/requires.txt +0 -0
  36. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/python_fasthtml.egg-info/top_level.txt +0 -0
  37. {python_fasthtml-0.14.13 → python_fasthtml-0.14.14}/setup.cfg +0 -0
  38. {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 `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
 
@@ -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 you notebooks cleaned of metadata and notarized.
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
- nbdev_install_quarto
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
- nbdev_readme
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.13
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
@@ -4,5 +4,5 @@ Modules:
4
4
 
5
5
  - `fasthtml.core`: The `FastHTML` subclass of `Starlette`."""
6
6
 
7
- __version__ = "0.14.13"
7
+ __version__ = "0.14.14"
8
8
  from .core import *
@@ -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.authmw': {},
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 single item `k` from `form` if len 1, otherwise return list"
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 checks for that situation"
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': return await req.json() if body else {}
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.getlist(arg)
290
- if res==[]: res = None
291
- if res in (empty,None): res = data.get(arg, 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>=64"]
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 = {text = "Apache-2.0"}
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.13
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