python-fasthtml 0.11.0__tar.gz → 0.12.1__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.11.0/python_fasthtml.egg-info → python_fasthtml-0.12.1}/PKG-INFO +50 -2
  2. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/README.md +48 -0
  3. python_fasthtml-0.12.1/fasthtml/__init__.py +2 -0
  4. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/_modidx.py +7 -2
  5. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/components.py +5 -5
  6. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/components.pyi +17 -5
  7. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/core.py +41 -33
  8. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/core.pyi +44 -15
  9. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/jupyter.py +14 -1
  10. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/xtend.pyi +2 -2
  11. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1/python_fasthtml.egg-info}/PKG-INFO +50 -2
  12. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/python_fasthtml.egg-info/requires.txt +1 -1
  13. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/settings.ini +4 -4
  14. python_fasthtml-0.11.0/fasthtml/__init__.py +0 -2
  15. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/CONTRIBUTING.md +0 -0
  16. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/LICENSE +0 -0
  17. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/MANIFEST.in +0 -0
  18. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/authmw.py +0 -0
  19. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/basics.py +0 -0
  20. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/cli.py +0 -0
  21. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/common.py +0 -0
  22. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/fastapp.py +0 -0
  23. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/ft.py +0 -0
  24. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/js.py +0 -0
  25. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/katex.js +0 -0
  26. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/live_reload.py +0 -0
  27. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/oauth.py +0 -0
  28. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/pico.py +0 -0
  29. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/starlette.py +0 -0
  30. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/svg.py +0 -0
  31. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/toaster.py +0 -0
  32. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/fasthtml/xtend.py +0 -0
  33. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/pyproject.toml +0 -0
  34. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  35. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  36. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/python_fasthtml.egg-info/entry_points.txt +0 -0
  37. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/python_fasthtml.egg-info/not-zip-safe +0 -0
  38. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/python_fasthtml.egg-info/top_level.txt +0 -0
  39. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/setup.cfg +0 -0
  40. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/setup.py +0 -0
  41. {python_fasthtml-0.11.0 → python_fasthtml-0.12.1}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.11.0
3
+ Version: 0.12.1
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard and contributors
@@ -22,7 +22,7 @@ Requires-Dist: oauthlib
22
22
  Requires-Dist: itsdangerous
23
23
  Requires-Dist: uvicorn[standard]>=0.30
24
24
  Requires-Dist: httpx
25
- Requires-Dist: fastlite>=0.0.9
25
+ Requires-Dist: fastlite>=0.1.1
26
26
  Requires-Dist: python-multipart
27
27
  Requires-Dist: beautifulsoup4
28
28
  Provides-Extra: dev
@@ -185,3 +185,51 @@ Finally, join the FastHTML community to ask questions, share your work,
185
185
  and learn from others:
186
186
 
187
187
  - [Discord](https://discord.gg/qcXvcxMhdP)
188
+
189
+ ## Other languages and related projects
190
+
191
+ If you’re not a Python user, or are keen to try out a new language,
192
+ we’ll list here other projects that have a similar approach to FastHTML.
193
+ (Please reach out if you know of any other projects that you’d like to
194
+ see added.)
195
+
196
+ - [htmgo](https://htmgo.dev/) (Go): “*htmgo is a lightweight pure go way
197
+ to build interactive websites / web applications using go & htmx. By
198
+ combining the speed & simplicity of go + hypermedia attributes (htmx)
199
+ to add interactivity to websites, all conveniently wrapped in pure go,
200
+ you can build simple, fast, interactive websites without touching
201
+ javascript. All compiled to a single deployable binary*”
202
+
203
+ If you’re just interested in functional HTML components, rather than a
204
+ full HTMX server solution, consider:
205
+
206
+ - [fastcore.xml.FT](https://fastcore.fast.ai/xml.html): This is actually
207
+ what FastHTML uses behind the scenes
208
+ - [htpy](https://htpy.dev/): Similar to
209
+ [`fastcore.xml.FT`](https://fastcore.fast.ai/xml.html#ft), but with a
210
+ somewhat different syntax
211
+ - [elm-html](https://package.elm-lang.org/packages/elm/html/latest/):
212
+ Elm’s built-in HTML library with a type-safe functional approach
213
+ - [hiccup](https://github.com/weavejester/hiccup): Popular library for
214
+ representing HTML in Clojure using vectors
215
+ - [hiccl](https://github.com/garlic0x1/hiccl): HTML generation library
216
+ for Common Lisp inspired by Clojure’s Hiccup
217
+ - [Falco.Markup](https://github.com/pimbrouwers/Falco): F# HTML DSL and
218
+ web framework with type-safe HTML generation
219
+ - [Lucid](https://github.com/chrisdone/lucid): Type-safe HTML generation
220
+ for Haskell using monad transformers
221
+ - [dream-html](https://github.com/aantron/dream): Part of the Dream web
222
+ framework for OCaml, provides type-safe HTML templating
223
+
224
+ For other hypermedia application platforms, not based on HTMX, take a
225
+ look at:
226
+
227
+ - [Hotwire/Turbo](https://turbo.hotwired.dev/): Rails-oriented framework
228
+ that similarly uses HTML-over-the-wire
229
+ - [LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html):
230
+ Phoenix framework’s solution for building interactive web apps with
231
+ minimal JavaScript
232
+ - [Unpoly](https://unpoly.com/): Another HTML-over-the-wire framework
233
+ with progressive enhancement
234
+ - [Livewire](https://laravel-livewire.com/): Laravel’s take on building
235
+ dynamic interfaces with minimal JavaScript
@@ -153,3 +153,51 @@ Finally, join the FastHTML community to ask questions, share your work,
153
153
  and learn from others:
154
154
 
155
155
  - [Discord](https://discord.gg/qcXvcxMhdP)
156
+
157
+ ## Other languages and related projects
158
+
159
+ If you’re not a Python user, or are keen to try out a new language,
160
+ we’ll list here other projects that have a similar approach to FastHTML.
161
+ (Please reach out if you know of any other projects that you’d like to
162
+ see added.)
163
+
164
+ - [htmgo](https://htmgo.dev/) (Go): “*htmgo is a lightweight pure go way
165
+ to build interactive websites / web applications using go & htmx. By
166
+ combining the speed & simplicity of go + hypermedia attributes (htmx)
167
+ to add interactivity to websites, all conveniently wrapped in pure go,
168
+ you can build simple, fast, interactive websites without touching
169
+ javascript. All compiled to a single deployable binary*”
170
+
171
+ If you’re just interested in functional HTML components, rather than a
172
+ full HTMX server solution, consider:
173
+
174
+ - [fastcore.xml.FT](https://fastcore.fast.ai/xml.html): This is actually
175
+ what FastHTML uses behind the scenes
176
+ - [htpy](https://htpy.dev/): Similar to
177
+ [`fastcore.xml.FT`](https://fastcore.fast.ai/xml.html#ft), but with a
178
+ somewhat different syntax
179
+ - [elm-html](https://package.elm-lang.org/packages/elm/html/latest/):
180
+ Elm’s built-in HTML library with a type-safe functional approach
181
+ - [hiccup](https://github.com/weavejester/hiccup): Popular library for
182
+ representing HTML in Clojure using vectors
183
+ - [hiccl](https://github.com/garlic0x1/hiccl): HTML generation library
184
+ for Common Lisp inspired by Clojure’s Hiccup
185
+ - [Falco.Markup](https://github.com/pimbrouwers/Falco): F# HTML DSL and
186
+ web framework with type-safe HTML generation
187
+ - [Lucid](https://github.com/chrisdone/lucid): Type-safe HTML generation
188
+ for Haskell using monad transformers
189
+ - [dream-html](https://github.com/aantron/dream): Part of the Dream web
190
+ framework for OCaml, provides type-safe HTML templating
191
+
192
+ For other hypermedia application platforms, not based on HTMX, take a
193
+ look at:
194
+
195
+ - [Hotwire/Turbo](https://turbo.hotwired.dev/): Rails-oriented framework
196
+ that similarly uses HTML-over-the-wire
197
+ - [LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html):
198
+ Phoenix framework’s solution for building interactive web apps with
199
+ minimal JavaScript
200
+ - [Unpoly](https://unpoly.com/): Another HTML-over-the-wire framework
201
+ with progressive enhancement
202
+ - [Livewire](https://laravel-livewire.com/): Laravel’s take on building
203
+ dynamic interfaces with minimal JavaScript
@@ -0,0 +1,2 @@
1
+ __version__ = "0.12.1"
2
+ from .core import *
@@ -1,8 +1,8 @@
1
1
  # Autogenerated by nbdev
2
2
 
3
3
  d = { 'settings': { 'branch': 'main',
4
- 'doc_baseurl': '/fasthtml',
5
- 'doc_host': 'https://AnswerDotAI.github.io',
4
+ 'doc_baseurl': '/',
5
+ 'doc_host': 'https://docs.fastht.ml',
6
6
  'git_url': 'https://github.com/AnswerDotAI/fasthtml',
7
7
  'lib_path': 'fasthtml'},
8
8
  'syms': { 'fasthtml.authmw': {},
@@ -133,6 +133,11 @@ d = { 'settings': { 'branch': 'main',
133
133
  'fasthtml.jupyter.JupyUvi.__init__': ('api/jupyter.html#jupyuvi.__init__', 'fasthtml/jupyter.py'),
134
134
  'fasthtml.jupyter.JupyUvi.start': ('api/jupyter.html#jupyuvi.start', 'fasthtml/jupyter.py'),
135
135
  'fasthtml.jupyter.JupyUvi.stop': ('api/jupyter.html#jupyuvi.stop', 'fasthtml/jupyter.py'),
136
+ 'fasthtml.jupyter.JupyUviAsync': ('api/jupyter.html#jupyuviasync', 'fasthtml/jupyter.py'),
137
+ 'fasthtml.jupyter.JupyUviAsync.__init__': ( 'api/jupyter.html#jupyuviasync.__init__',
138
+ 'fasthtml/jupyter.py'),
139
+ 'fasthtml.jupyter.JupyUviAsync.start': ('api/jupyter.html#jupyuviasync.start', 'fasthtml/jupyter.py'),
140
+ 'fasthtml.jupyter.JupyUviAsync.stop': ('api/jupyter.html#jupyuviasync.stop', 'fasthtml/jupyter.py'),
136
141
  'fasthtml.jupyter.htmx_config_port': ('api/jupyter.html#htmx_config_port', 'fasthtml/jupyter.py'),
137
142
  'fasthtml.jupyter.is_port_free': ('api/jupyter.html#is_port_free', 'fasthtml/jupyter.py'),
138
143
  'fasthtml.jupyter.nb_serve': ('api/jupyter.html#nb_serve', 'fasthtml/jupyter.py'),
@@ -8,10 +8,10 @@ __all__ = ['named', 'html_attrs', 'hx_attrs', 'hx_attrs_annotations', 'show', 'a
8
8
  'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi', 'Bdo', 'Blockquote', 'Body', 'Br', 'Button', 'Canvas',
9
9
  'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data', 'Datalist', 'Dd', 'Del', 'Details', 'Dfn', 'Dialog',
10
10
  'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe', 'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form', 'H1',
11
- 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header', 'Hgroup', 'Hr', 'Html', 'I', 'Iframe', 'Img', 'Input', 'Ins',
12
- 'Kbd', 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript',
13
- 'Object', 'Ol', 'Optgroup', 'Option', 'Output', 'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q',
14
- 'Rp', 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search', 'Section', 'Select', 'Slot', 'Small', 'Source', 'Span',
11
+ 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header', 'Hgroup', 'Hr', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd',
12
+ 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript', 'Object',
13
+ 'Ol', 'Optgroup', 'Option', 'Output', 'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q', 'Rp',
14
+ 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search', 'Section', 'Select', 'Slot', 'Small', 'Source', 'Span',
15
15
  'Strong', 'Style', 'Sub', 'Summary', 'Sup', 'Table', 'Tbody', 'Td', 'Template', 'Textarea', 'Tfoot', 'Th',
16
16
  'Thead', 'Time', 'Title', 'Tr', 'Track', 'U', 'Ul', 'Var', 'Video', 'Wbr']
17
17
 
@@ -110,7 +110,7 @@ _all_ = [
110
110
  'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data', 'Datalist', 'Dd', 'Del', 'Details', 'Dfn',
111
111
  'Dialog', 'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe', 'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form',
112
112
  'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header',
113
- 'Hgroup', 'Hr', 'Html', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li',
113
+ 'Hgroup', 'Hr', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li',
114
114
  'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript', 'Object', 'Ol', 'Optgroup', 'Option', 'Output',
115
115
  'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q', 'Rp', 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search',
116
116
  'Section', 'Select', 'Slot', 'Small', 'Source', 'Span', 'Strong', 'Style', 'Sub', 'Summary', 'Sup', 'Table', 'Tbody',
@@ -1,5 +1,5 @@
1
1
  """`ft_html` and `ft_hx` functions to add some conveniences to `ft`, along with a full set of basic HTML components, and functions to work with forms and `FT` conversion"""
2
- __all__ = ['named', 'html_attrs', 'hx_attrs', 'hx_attrs_annotations', 'show', 'attrmap_x', 'ft_html', 'ft_hx', 'File', 'fill_form', 'fill_dataclass', 'find_inputs', 'html2ft', 'sse_message', 'A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi', 'Bdo', 'Blockquote', 'Body', 'Br', 'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data', 'Datalist', 'Dd', 'Del', 'Details', 'Dfn', 'Dialog', 'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe', 'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header', 'Hgroup', 'Hr', 'Html', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript', 'Object', 'Ol', 'Optgroup', 'Option', 'Output', 'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q', 'Rp', 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search', 'Section', 'Select', 'Slot', 'Small', 'Source', 'Span', 'Strong', 'Style', 'Sub', 'Summary', 'Sup', 'Table', 'Tbody', 'Td', 'Template', 'Textarea', 'Tfoot', 'Th', 'Thead', 'Time', 'Title', 'Tr', 'Track', 'U', 'Ul', 'Var', 'Video', 'Wbr']
2
+ __all__ = ['named', 'html_attrs', 'hx_attrs', 'hx_attrs_annotations', 'show', 'attrmap_x', 'ft_html', 'ft_hx', 'File', 'fill_form', 'fill_dataclass', 'find_inputs', 'html2ft', 'sse_message', 'A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi', 'Bdo', 'Blockquote', 'Body', 'Br', 'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data', 'Datalist', 'Dd', 'Del', 'Details', 'Dfn', 'Dialog', 'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe', 'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header', 'Hgroup', 'Hr', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript', 'Object', 'Ol', 'Optgroup', 'Option', 'Output', 'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q', 'Rp', 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search', 'Section', 'Select', 'Slot', 'Small', 'Source', 'Span', 'Strong', 'Style', 'Sub', 'Summary', 'Sup', 'Table', 'Tbody', 'Td', 'Template', 'Textarea', 'Tfoot', 'Th', 'Thead', 'Time', 'Title', 'Tr', 'Track', 'U', 'Ul', 'Var', 'Video', 'Wbr']
3
3
  from dataclasses import dataclass, asdict, is_dataclass, make_dataclass, replace, astuple, MISSING
4
4
  from bs4 import BeautifulSoup, Comment
5
5
  from typing import Literal, Optional
@@ -17,11 +17,23 @@ except ImportError:
17
17
  def show(ft, *rest):
18
18
  """Renders FT Components into HTML within a Jupyter notebook."""
19
19
  ...
20
+
21
+ @patch
22
+ def __str__(self: FT):
23
+ ...
24
+
25
+ @patch
26
+ def __radd__(self: FT, b):
27
+ ...
28
+
29
+ @patch
30
+ def __add__(self: FT, b):
31
+ ...
20
32
  named = set('a button form frame iframe img input map meta object param select textarea'.split())
21
33
  html_attrs = 'id cls title style accesskey contenteditable dir draggable enterkeyhint hidden inert inputmode lang popover spellcheck tabindex translate'.split()
22
34
  hx_attrs = 'get post put delete patch trigger target swap swap_oob include select select_oob indicator push_url confirm disable replace_url vals disabled_elt ext headers history history_elt indicator inherit params preserve prompt replace_url request sync validate'
23
35
  hx_attrs = [f'hx_{o}' for o in hx_attrs.split()]
24
- hx_attrs_annotations = {'hx_swap': Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'] | str, 'hx_swap_oob': Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'] | str, 'hx_push_url': Literal['true', 'false'] | str, 'hx_replace_url': Literal['true', 'false'] | str, 'hx_disabled_elt': Literal['this', 'next', 'previous'] | str, 'hx_history': Literal['false'] | str, 'hx_params': Literal['*', 'none'] | str, 'hx_replace_url': Literal['true', 'false'] | str, 'hx_validate': Literal['true', 'false']}
36
+ hx_attrs_annotations = {'hx_swap': Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'] | str, 'hx_swap_oob': Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'] | str, 'hx_push_url': Literal['true', 'false'] | str, 'hx_replace_url': Literal['true', 'false'] | str, 'hx_disabled_elt': Literal['this', 'next', 'previous'] | str, 'hx_history': Literal['false'] | str, 'hx_params': Literal['*', 'none'] | str, 'hx_validate': Literal['true', 'false']}
25
37
  hx_attrs_annotations |= {o: str for o in set(hx_attrs) - set(hx_attrs_annotations.keys())}
26
38
  hx_attrs_annotations = {k: Optional[v] for k, v in hx_attrs_annotations.items()}
27
39
  hx_attrs = html_attrs + hx_attrs
@@ -32,15 +44,16 @@ fh_cfg['attrmap'] = attrmap_x
32
44
  fh_cfg['valmap'] = valmap
33
45
  fh_cfg['ft_cls'] = FT
34
46
  fh_cfg['auto_id'] = False
47
+ fh_cfg['auto_name'] = True
35
48
 
36
- def ft_html(tag: str, *c, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=None, auto_id=None, **kwargs):
49
+ def ft_html(tag: str, *c, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=None, **kwargs):
37
50
  ...
38
51
 
39
52
  @use_kwargs(hx_attrs, keep=True)
40
53
  def ft_hx(tag: str, *c, target_id=None, hx_vals=None, hx_target=None, **kwargs):
41
54
  ...
42
55
  _g = globals()
43
- _all_ = ['A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi', 'Bdo', 'Blockquote', 'Body', 'Br', 'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data', 'Datalist', 'Dd', 'Del', 'Details', 'Dfn', 'Dialog', 'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe', 'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header', 'Hgroup', 'Hr', 'Html', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript', 'Object', 'Ol', 'Optgroup', 'Option', 'Output', 'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q', 'Rp', 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search', 'Section', 'Select', 'Slot', 'Small', 'Source', 'Span', 'Strong', 'Style', 'Sub', 'Summary', 'Sup', 'Table', 'Tbody', 'Td', 'Template', 'Textarea', 'Tfoot', 'Th', 'Thead', 'Time', 'Title', 'Tr', 'Track', 'U', 'Ul', 'Var', 'Video', 'Wbr']
56
+ _all_ = ['A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi', 'Bdo', 'Blockquote', 'Body', 'Br', 'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data', 'Datalist', 'Dd', 'Del', 'Details', 'Dfn', 'Dialog', 'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe', 'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Head', 'Header', 'Hgroup', 'Hr', 'I', 'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu', 'Meta', 'Meter', 'Nav', 'Noscript', 'Object', 'Ol', 'Optgroup', 'Option', 'Output', 'P', 'Picture', 'PortalExperimental', 'Pre', 'Progress', 'Q', 'Rp', 'Rt', 'Ruby', 'S', 'Samp', 'Script', 'Search', 'Section', 'Select', 'Slot', 'Small', 'Source', 'Span', 'Strong', 'Style', 'Sub', 'Summary', 'Sup', 'Table', 'Tbody', 'Td', 'Template', 'Textarea', 'Tfoot', 'Th', 'Thead', 'Time', 'Title', 'Tr', 'Track', 'U', 'Ul', 'Var', 'Video', 'Wbr']
44
57
  for o in _all_:
45
58
  _g[o] = partial(ft_hx, o.lower())
46
59
 
@@ -125,7 +138,6 @@ def Head(*c, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesske
125
138
  def Header(*c, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
126
139
  def Hgroup(*c, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
127
140
  def Hr(*c, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
128
- def Html(*c, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
129
141
  def I(*c, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
130
142
  def Iframe(*c, name:Any=None, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
131
143
  def Img(*c, name:Any=None, id:Any=None, cls:Any=None, title:Any=None, style:Any=None, accesskey:Any=None, contenteditable:Any=None, dir:Any=None, draggable:Any=None, enterkeyhint:Any=None, hidden:Any=None, inert:Any=None, inputmode:Any=None, lang:Any=None, popover:Any=None, spellcheck:Any=None, tabindex:Any=None, translate:Any=None, hx_get:Optional[str]=None, hx_post:Optional[str]=None, hx_put:Optional[str]=None, hx_delete:Optional[str]=None, hx_patch:Optional[str]=None, hx_trigger:Optional[str]=None, hx_target:Optional[str]=None, hx_swap:Union[Literal['innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_swap_oob:Union[Literal['true', 'innerHTML', 'outerHTML', 'afterbegin', 'beforebegin', 'beforeend', 'afterend', 'delete', 'none'], str, NoneType]=None, hx_include:Optional[str]=None, hx_select:Optional[str]=None, hx_select_oob:Optional[str]=None, hx_indicator:Optional[str]=None, hx_push_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_confirm:Optional[str]=None, hx_disable:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_vals:Optional[str]=None, hx_disabled_elt:Union[Literal['this', 'next', 'previous'], str, NoneType]=None, hx_ext:Optional[str]=None, hx_headers:Optional[str]=None, hx_history:Union[Literal['false'], str, NoneType]=None, hx_history_elt:Optional[str]=None, hx_indicator:Optional[str]=None, hx_inherit:Optional[str]=None, hx_params:Union[Literal['*', 'none'], str, NoneType]=None, hx_preserve:Optional[str]=None, hx_prompt:Optional[str]=None, hx_replace_url:Union[Literal['true', 'false'], str, NoneType]=None, hx_request:Optional[str]=None, hx_sync:Optional[str]=None, hx_validate:Optional[Literal['true', 'false']]=None, **kwargs): ...
@@ -11,7 +11,7 @@ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc',
11
11
  'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'setup_ws']
12
12
 
13
13
  # %% ../nbs/api/00_core.ipynb
14
- import json,uuid,inspect,types,uvicorn,signal,asyncio,threading,inspect
14
+ import json,uuid,inspect,types,signal,asyncio,threading,inspect
15
15
 
16
16
  from fastcore.utils import *
17
17
  from fastcore.xml import *
@@ -260,7 +260,7 @@ async def _send_ws(ws, resp):
260
260
 
261
261
  def _ws_endp(recv, conn=None, disconn=None):
262
262
  cls = type('WS_Endp', (WebSocketEndpoint,), {"encoding":"text"})
263
-
263
+
264
264
  async def _generic_handler(handler, ws, data=None):
265
265
  wd = _wrap_ws(ws, loads(data) if data else {}, _params(handler))
266
266
  resp = await _handle(handler, wd)
@@ -285,13 +285,13 @@ def EventStream(s):
285
285
 
286
286
  # %% ../nbs/api/00_core.ipynb
287
287
  def signal_shutdown():
288
+ from uvicorn.main import Server
288
289
  event = asyncio.Event()
289
- def signal_handler(signum, frame):
290
+ @patch
291
+ def handle_exit(self:Server, *args, **kwargs):
290
292
  event.set()
291
- signal.signal(signum, signal.SIG_DFL)
292
- os.kill(os.getpid(), signum)
293
-
294
- for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, signal_handler)
293
+ self.force_exit = True
294
+ self._orig_handle_exit(*args, **kwargs)
295
295
  return event
296
296
 
297
297
  # %% ../nbs/api/00_core.ipynb
@@ -299,7 +299,7 @@ def uri(_arg, **kwargs):
299
299
  return f"{quote(_arg)}/{urlencode(kwargs, doseq=True)}"
300
300
 
301
301
  # %% ../nbs/api/00_core.ipynb
302
- def decode_uri(s):
302
+ def decode_uri(s):
303
303
  arg,_,kw = s.partition('/')
304
304
  return unquote(arg), {k:v[0] for k,v in parse_qs(kw).items()}
305
305
 
@@ -329,7 +329,7 @@ def _url_for(req, t):
329
329
  if callable(t): t = t.__routename__
330
330
  kw = {}
331
331
  if t.find('/')>-1 and (t.find('?')<0 or t.find('/')<t.find('?')): t,kw = decode_uri(t)
332
- t,m,q = t.partition('?')
332
+ t,m,q = t.partition('?')
333
333
  return f"{req.url_path_for(t, **kw)}{m}{q}"
334
334
 
335
335
  def _find_targets(req, resp):
@@ -397,28 +397,28 @@ def _xt_cts(req, resp):
397
397
  return _to_xml(req, resp, indent=fh_cfg.indent), http_hdrs, ts
398
398
 
399
399
  # %% ../nbs/api/00_core.ipynb
400
- def _xt_resp(req, resp):
400
+ def _xt_resp(req, resp, status_code):
401
401
  cts,http_hdrs,tasks = _xt_cts(req, resp)
402
- return HTMLResponse(cts, headers=http_hdrs, background=tasks)
402
+ return HTMLResponse(cts, status_code=status_code, headers=http_hdrs, background=tasks)
403
403
 
404
404
  # %% ../nbs/api/00_core.ipynb
405
405
  def _is_ft_resp(resp): return isinstance(resp, _iter_typs+(HttpHeader,FT)) or hasattr(resp, '__ft__')
406
406
 
407
407
  # %% ../nbs/api/00_core.ipynb
408
- def _resp(req, resp, cls=empty):
408
+ def _resp(req, resp, cls=empty, status_code=200):
409
409
  if not resp: resp=()
410
410
  if hasattr(resp, '__response__'): resp = resp.__response__(req)
411
411
  if cls in (Any,FT): cls=empty
412
412
  if isinstance(resp, FileResponse) and not os.path.exists(resp.path): raise HTTPException(404, resp.path)
413
- if cls is not empty: return cls(resp)
414
- if isinstance(resp, Response): return resp
415
- if _is_ft_resp(resp): return _xt_resp(req, resp)
413
+ if cls is not empty: return cls(resp, status_code=status_code)
414
+ if isinstance(resp, Response): return resp # respect manually set status_code
415
+ if _is_ft_resp(resp): return _xt_resp(req, resp, status_code)
416
416
  if isinstance(resp, str): cls = HTMLResponse
417
417
  elif isinstance(resp, Mapping): cls = JSONResponse
418
418
  else:
419
419
  resp = str(resp)
420
420
  cls = HTMLResponse
421
- return cls(resp)
421
+ return cls(resp, status_code=status_code)
422
422
 
423
423
  # %% ../nbs/api/00_core.ipynb
424
424
  class Redirect:
@@ -467,19 +467,25 @@ def get_key(key=None, fname='.sesskey'):
467
467
  def _list(o): return [] if not o else list(o) if isinstance(o, (tuple,list)) else [o]
468
468
 
469
469
  # %% ../nbs/api/00_core.ipynb
470
- def _wrap_ex(f, hdrs, ftrs, htmlkw, bodykw, body_wrap):
470
+ def _wrap_ex(f, status_code, hdrs, ftrs, htmlkw, bodykw, body_wrap):
471
471
  async def _f(req, exc):
472
472
  req.hdrs,req.ftrs,req.htmlkw,req.bodykw = map(deepcopy, (hdrs, ftrs, htmlkw, bodykw))
473
473
  req.body_wrap = body_wrap
474
474
  res = await _handle(f, (req, exc))
475
- return _resp(req, res)
475
+ return _resp(req, res, status_code=status_code)
476
476
  return _f
477
477
 
478
478
  # %% ../nbs/api/00_core.ipynb
479
479
  def qp(p:str, **kw) -> str:
480
- "Add query parameters to path p"
481
- kw = {k:('' if v in (False,None) else v) for k,v in kw.items()}
482
- return p + ('?' + urlencode(kw,doseq=True) if kw else '')
480
+ "Add parameters kw to path p"
481
+ def _sub(m):
482
+ pre,post = m.groups()
483
+ if pre not in kw: return f'{{{pre}{post or ""}}}'
484
+ pre = kw.pop(pre)
485
+ return '' if pre in (False,None) else str(pre)
486
+ p = re.sub(r'\{([^:}]+)(:.+?)?}', _sub, p)
487
+ # encode query params
488
+ return p + ('?' + urlencode({k:'' if v in (False,None) else v for k,v in kw.items()},doseq=True) if kw else '')
483
489
 
484
490
  # %% ../nbs/api/00_core.ipynb
485
491
  def def_hdrs(htmx=True, surreal=True):
@@ -523,7 +529,8 @@ class FastHTML(Starlette):
523
529
  from IPython.display import display,HTML
524
530
  if nb_hdrs: display(HTML(to_xml(tuple(hdrs))))
525
531
  middleware.append(cors_allow)
526
- self.on_startup,self.on_shutdown,self.lifespan,self.hdrs,self.ftrs = on_startup,on_shutdown,lifespan,hdrs,ftrs
532
+ on_startup,on_shutdown = listify(on_startup) or None,listify(on_shutdown) or None
533
+ self.lifespan,self.hdrs,self.ftrs = lifespan,hdrs,ftrs
527
534
  self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw = body_wrap,before,after,htmlkw,bodykw
528
535
  secret_key = get_key(secret_key, key_fname)
529
536
  if sess_cls:
@@ -532,12 +539,12 @@ class FastHTML(Starlette):
532
539
  https_only=sess_https_only, domain=sess_domain)
533
540
  middleware.append(sess)
534
541
  exception_handlers = ifnone(exception_handlers, {})
535
- if 404 not in exception_handlers:
536
- def _not_found(req, exc): return Response('404 Not Found', status_code=404)
542
+ if 404 not in exception_handlers:
543
+ def _not_found(req, exc): return Response('404 Not Found', status_code=404)
537
544
  exception_handlers[404] = _not_found
538
- excs = {k:_wrap_ex(v, hdrs, ftrs, htmlkw, bodykw, body_wrap=body_wrap) for k,v in exception_handlers.items()}
545
+ excs = {k:_wrap_ex(v, k, hdrs, ftrs, htmlkw, bodykw, body_wrap=body_wrap) for k,v in exception_handlers.items()}
539
546
  super().__init__(debug, routes, middleware=middleware, exception_handlers=excs, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan)
540
-
547
+
541
548
  def add_route(self, route):
542
549
  route.methods = [m.upper() for m in listify(route.methods)]
543
550
  self.router.routes = [r for r in self.router.routes if not
@@ -635,7 +642,7 @@ def serve(
635
642
  reload=True, # Default is to reload the app upon code changes
636
643
  reload_includes:list[str]|str|None=None, # Additional files to watch for changes
637
644
  reload_excludes:list[str]|str|None=None # Files to ignore for changes
638
- ):
645
+ ):
639
646
  "Run the app in an async server, with live reload set as the default."
640
647
  bk = inspect.currentframe().f_back
641
648
  glb = bk.f_globals
@@ -643,6 +650,7 @@ def serve(
643
650
  if not appname:
644
651
  if glb.get('__name__')=='__main__': appname = Path(glb.get('__file__', '')).stem
645
652
  elif code.co_name=='main' and bk.f_back.f_globals.get('__name__')=='__main__': appname = inspect.getmodule(bk).__name__
653
+ import uvicorn
646
654
  if appname:
647
655
  if not port: port=int(os.getenv("PORT", default=5001))
648
656
  print(f'Link: http://{"localhost" if host=="0.0.0.0" else host}:{port}')
@@ -664,7 +672,7 @@ for o in ('get', 'post', 'delete', 'put', 'patch', 'options'): setattr(Client, o
664
672
  class RouteFuncs:
665
673
  def __init__(self): super().__setattr__('_funcs', {})
666
674
  def __setattr__(self, name, value): self._funcs[name] = value
667
- def __getattr__(self, name):
675
+ def __getattr__(self, name):
668
676
  if name in all_meths: raise AttributeError("Route functions with HTTP Names are not accessible here")
669
677
  try: return self._funcs[name]
670
678
  except KeyError: raise AttributeError(f"No route named {name} found in route functions")
@@ -673,7 +681,7 @@ class RouteFuncs:
673
681
  # %% ../nbs/api/00_core.ipynb
674
682
  class APIRouter:
675
683
  "Add routes to an app"
676
- def __init__(self, prefix:str|None=None, body_wrap=noop_body):
684
+ def __init__(self, prefix:str|None=None, body_wrap=noop_body):
677
685
  self.routes,self.wss = [],[]
678
686
  self.rt_funcs = RouteFuncs() # Store wrapped route function for discoverability
679
687
  self.prefix = prefix if prefix else ""
@@ -695,7 +703,7 @@ class APIRouter:
695
703
  self.routes.append((func, p, methods, name, include_in_schema, body_wrap or self.body_wrap))
696
704
  return wrapped
697
705
  return f(path) if callable(path) else f
698
-
706
+
699
707
  def __getattr__(self, name):
700
708
  try: return getattr(self.rt_funcs, name)
701
709
  except AttributeError: return super().__getattr__(self, name)
@@ -704,7 +712,7 @@ class APIRouter:
704
712
  "Add routes to `app`"
705
713
  for args in self.routes: app._add_route(*args)
706
714
  for args in self.wss: app._add_ws(*args)
707
-
715
+
708
716
  def ws(self, path:str, conn=None, disconn=None, name=None, middleware=None):
709
717
  "Add a websocket route at `path`"
710
718
  def f(func=noop): return self.wss.append((func, f"{self.prefix}{path}", conn, disconn, name, middleware))
@@ -739,7 +747,7 @@ def reg_re_param(m, s):
739
747
  # %% ../nbs/api/00_core.ipynb
740
748
  # Starlette doesn't have the '?', so it chomps the whole remaining URL
741
749
  reg_re_param("path", ".*?")
742
- reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|html|map")
750
+ reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|html|map|pdf")
743
751
 
744
752
  @patch
745
753
  def static_route_exts(self:FastHTML, prefix='/', static_path='.', exts='static'):
@@ -768,7 +776,7 @@ class FtResponse:
768
776
  def __init__(self, content, status_code:int=200, headers=None, cls=HTMLResponse, media_type:str|None=None):
769
777
  self.content,self.status_code,self.headers = content,status_code,headers
770
778
  self.cls,self.media_type = cls,media_type
771
-
779
+
772
780
  def __response__(self, req):
773
781
  cts,httphdrs,tasks = _xt_cts(req, self.content)
774
782
  headers = {**(self.headers or {}), **httphdrs}
@@ -1,6 +1,6 @@
1
1
  """The `FastHTML` subclass of `Starlette`, along with the `RouterX` and `RouteX` classes it automatically uses."""
2
- __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'parsed_date', 'snake2hyphens', 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple', 'noop_body', 'respond', 'Redirect', 'get_key', 'def_hdrs', 'FastHTML', 'serve', 'Client', 'APIRouter', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'setup_ws']
3
- import json, uuid, inspect, types, uvicorn, signal, asyncio, threading
2
+ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'parsed_date', 'snake2hyphens', 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple', 'noop_body', 'respond', 'Redirect', 'get_key', 'qp', 'def_hdrs', 'FastHTML', 'nested_name', 'serve', 'Client', 'RouteFuncs', 'APIRouter', 'cookie', 'reg_re_param', 'MiddlewareBase', 'FtResponse', 'unqid', 'setup_ws']
3
+ import json, uuid, inspect, types, uvicorn, signal, asyncio, threading, inspect
4
4
  from fastcore.utils import *
5
5
  from fastcore.xml import *
6
6
  from fastcore.meta import use_kwargs_dict
@@ -167,6 +167,7 @@ def _apply_ft(o):
167
167
 
168
168
  def _to_xml(req, resp, indent):
169
169
  ...
170
+ _iter_typs = (tuple, list, map, filter, range, types.GeneratorType)
170
171
 
171
172
  def flat_tuple(o):
172
173
  """Flatten lists"""
@@ -183,13 +184,13 @@ def respond(req, heads, bdy):
183
184
  def _xt_cts(req, resp):
184
185
  ...
185
186
 
186
- def _xt_resp(req, resp):
187
+ def _xt_resp(req, resp, status_code):
187
188
  ...
188
189
 
189
190
  def _is_ft_resp(resp):
190
191
  ...
191
192
 
192
- def _resp(req, resp, cls=empty):
193
+ def _resp(req, resp, cls=empty, status_code=200):
193
194
  ...
194
195
 
195
196
  class Redirect:
@@ -203,9 +204,9 @@ class Redirect:
203
204
 
204
205
  async def _wrap_call(f, req, params):
205
206
  ...
206
- htmx_exts = {'head-support': 'https://unpkg.com/htmx-ext-head-support@2.0.1/head-support.js', 'preload': 'https://unpkg.com/htmx-ext-preload@2.0.1/preload.js', 'class-tools': 'https://unpkg.com/htmx-ext-class-tools@2.0.1/class-tools.js', 'loading-states': 'https://unpkg.com/htmx-ext-loading-states@2.0.0/loading-states.js', 'multi-swap': 'https://unpkg.com/htmx-ext-multi-swap@2.0.0/multi-swap.js', 'path-deps': 'https://unpkg.com/htmx-ext-path-deps@2.0.0/path-deps.js', 'remove-me': 'https://unpkg.com/htmx-ext-remove-me@2.0.0/remove-me.js', 'ws': 'https://unpkg.com/htmx-ext-ws/ws.js', 'chunked-transfer': 'https://unpkg.com/htmx-ext-transfer-encoding-chunked/transfer-encoding-chunked.js'}
207
- htmxsrc = Script(src='https://unpkg.com/htmx.org@next/dist/htmx.min.js')
208
- fhjsscr = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@1.0.4/fasthtml.js')
207
+ htmx_exts = {'head-support': 'https://unpkg.com/htmx-ext-head-support@2.0.3/head-support.js', 'preload': 'https://unpkg.com/htmx-ext-preload@2.1.0/preload.js', 'class-tools': 'https://unpkg.com/htmx-ext-class-tools@2.0.1/class-tools.js', 'loading-states': 'https://unpkg.com/htmx-ext-loading-states@2.0.0/loading-states.js', 'multi-swap': 'https://unpkg.com/htmx-ext-multi-swap@2.0.0/multi-swap.js', 'path-deps': 'https://unpkg.com/htmx-ext-path-deps@2.0.0/path-deps.js', 'remove-me': 'https://unpkg.com/htmx-ext-remove-me@2.0.0/remove-me.js', 'ws': 'https://unpkg.com/htmx-ext-ws@2.0.2/ws.js', 'chunked-transfer': 'https://unpkg.com/htmx-ext-transfer-encoding-chunked@0.4.0/transfer-encoding-chunked.js'}
208
+ htmxsrc = Script(src='https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js')
209
+ fhjsscr = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@1.0.12/fasthtml.js')
209
210
  surrsrc = Script(src='https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js')
210
211
  scopesrc = Script(src='https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js')
211
212
  viewport = Meta(name='viewport', content='width=device-width, initial-scale=1, viewport-fit=cover')
@@ -217,10 +218,11 @@ def get_key(key=None, fname='.sesskey'):
217
218
  def _list(o):
218
219
  ...
219
220
 
220
- def _wrap_ex(f, hdrs, ftrs, htmlkw, bodykw, body_wrap):
221
+ def _wrap_ex(f, status_code, hdrs, ftrs, htmlkw, bodykw, body_wrap):
221
222
  ...
222
223
 
223
- def _mk_locfunc(f, p):
224
+ def qp(p: str, **kw) -> str:
225
+ """Add query parameters to path p"""
224
226
  ...
225
227
 
226
228
  def def_hdrs(htmx=True, surreal=True):
@@ -231,7 +233,7 @@ iframe_scr = Script(NotStr("\n function sendmsg() {\n window.parent.po
231
233
 
232
234
  class FastHTML(Starlette):
233
235
 
234
- def __init__(self, debug=False, routes=None, middleware=None, exception_handlers=None, on_startup=None, on_shutdown=None, lifespan=None, hdrs=None, ftrs=None, exts=None, before=None, after=None, surreal=True, htmx=True, default_hdrs=True, sess_cls=SessionMiddleware, secret_key=None, session_cookie='session_', max_age=365 * 24 * 3600, sess_path='/', same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', body_wrap=noop_body, htmlkw=None, nb_hdrs=True, **bodykw):
236
+ def __init__(self, debug=False, routes=None, middleware=None, title: str='FastHTML page', exception_handlers=None, on_startup=None, on_shutdown=None, lifespan=None, hdrs=None, ftrs=None, exts=None, before=None, after=None, surreal=True, htmx=True, default_hdrs=True, sess_cls=SessionMiddleware, secret_key=None, session_cookie='session_', max_age=365 * 24 * 3600, sess_path='/', same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', body_wrap=noop_body, htmlkw=None, nb_hdrs=False, **bodykw):
235
237
  ...
236
238
 
237
239
  def add_route(self, route):
@@ -250,7 +252,7 @@ class FastHTML(Starlette):
250
252
  def _add_route(self, func, path, methods, name, include_in_schema, body_wrap):
251
253
  ...
252
254
 
253
- def route(self, path: str=None, methods=None, name=None, include_in_schema=True, body_wrap=noop_body):
255
+ def route(self, path: str=None, methods=None, name=None, include_in_schema=True, body_wrap=None):
254
256
  """Add a route at `path`"""
255
257
  ...
256
258
 
@@ -262,6 +264,13 @@ class FastHTML(Starlette):
262
264
  """Add a static route at URL path `prefix` with files from `static_path` and single `ext` (including the '.')"""
263
265
  ...
264
266
  all_meths = 'get post put delete patch head trace options'.split()
267
+
268
+ def _mk_locfunc(f, p):
269
+ ...
270
+
271
+ def nested_name(f):
272
+ """Get name of function `f` using '_' to join nested function names"""
273
+ ...
265
274
  for o in all_meths:
266
275
  setattr(FastHTML, o, partialmethod(FastHTML.route, methods=o))
267
276
 
@@ -280,21 +289,41 @@ class Client:
280
289
  for o in ('get', 'post', 'delete', 'put', 'patch', 'options'):
281
290
  setattr(Client, o, partialmethod(Client._sync, o))
282
291
 
292
+ class RouteFuncs:
293
+
294
+ def __init__(self):
295
+ ...
296
+
297
+ def __setattr__(self, name, value):
298
+ ...
299
+
300
+ def __getattr__(self, name):
301
+ ...
302
+
303
+ def __dir__(self):
304
+ ...
305
+
283
306
  class APIRouter:
284
307
  """Add routes to an app"""
285
308
 
286
- def __init__(self):
309
+ def __init__(self, prefix: str | None=None, body_wrap=noop_body):
287
310
  ...
288
311
 
289
- def __call__(self: FastHTML, path: str=None, methods=None, name=None, include_in_schema=True, body_wrap=noop_body):
312
+ def _wrap_func(self, func, path=None):
313
+ ...
314
+
315
+ def __call__(self, path: str=None, methods=None, name=None, include_in_schema=True, body_wrap=None):
290
316
  """Add a route at `path`"""
291
317
  ...
292
318
 
319
+ def __getattr__(self, name):
320
+ ...
321
+
293
322
  def to_app(self, app):
294
323
  """Add routes to `app`"""
295
324
  ...
296
325
 
297
- def ws(self: FastHTML, path: str, conn=None, disconn=None, name=None, middleware=None):
326
+ def ws(self, path: str, conn=None, disconn=None, name=None, middleware=None):
298
327
  """Add a websocket route at `path`"""
299
328
  ...
300
329
  for o in all_meths:
@@ -307,7 +336,7 @@ def cookie(key: str, value='', max_age=None, expires=None, path='/', domain=None
307
336
  def reg_re_param(m, s):
308
337
  ...
309
338
  reg_re_param('path', '.*?')
310
- reg_re_param('static', 'ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|html|map')
339
+ reg_re_param('static', 'ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|html|map|pdf')
311
340
 
312
341
  class MiddlewareBase:
313
342
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  # %% auto 0
6
6
  __all__ = ['nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'show', 'render_ft', 'htmx_config_port', 'JupyUvi',
7
- 'HTMX', 'ws_client']
7
+ 'JupyUviAsync', 'HTMX', 'ws_client']
8
8
 
9
9
  # %% ../nbs/api/06_jupyter.ipynb
10
10
  import asyncio, socket, time, uvicorn
@@ -92,6 +92,19 @@ class JupyUvi:
92
92
  self.server.should_exit = True
93
93
  wait_port_free(self.port)
94
94
 
95
+ # %% ../nbs/api/06_jupyter.ipynb
96
+ class JupyUviAsync(JupyUvi):
97
+ "Start and stop an async Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
98
+ def __init__(self, app, log_level="error", host='0.0.0.0', port=8000, **kwargs):
99
+ super().__init__(app, log_level=log_level, host=host, port=port, start=False, **kwargs)
100
+
101
+ async def start(self):
102
+ self.server = await nb_serve_async(self.app, log_level=self.log_level, host=self.host, port=self.port, **self.kwargs)
103
+
104
+ def stop(self):
105
+ self.server.should_exit = True
106
+ wait_port_free(self.port)
107
+
95
108
  # %% ../nbs/api/06_jupyter.ipynb
96
109
  def HTMX(path="", app=None, host='localhost', port=8000, height="auto", link=False, iframe=True):
97
110
  "An iframe which displays the HTMX application in a notebook."
@@ -33,11 +33,11 @@ def CheckboxX(checked: bool=False, label=None, value='1', id=None, name=None, *,
33
33
  """A Checkbox optionally inside a Label, preceded by a `Hidden` with matching name"""
34
34
  ...
35
35
 
36
- def Script(code: str='', *, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=None, auto_id=None, **kwargs) -> FT:
36
+ def Script(code: str='', *, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=None, **kwargs) -> FT:
37
37
  """A Script tag that doesn't escape its code"""
38
38
  ...
39
39
 
40
- def Style(*c, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=None, auto_id=None, **kwargs) -> FT:
40
+ def Style(*c, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=None, **kwargs) -> FT:
41
41
  """A Style tag that doesn't escape its code"""
42
42
  ...
43
43
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.11.0
3
+ Version: 0.12.1
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard and contributors
@@ -22,7 +22,7 @@ Requires-Dist: oauthlib
22
22
  Requires-Dist: itsdangerous
23
23
  Requires-Dist: uvicorn[standard]>=0.30
24
24
  Requires-Dist: httpx
25
- Requires-Dist: fastlite>=0.0.9
25
+ Requires-Dist: fastlite>=0.1.1
26
26
  Requires-Dist: python-multipart
27
27
  Requires-Dist: beautifulsoup4
28
28
  Provides-Extra: dev
@@ -185,3 +185,51 @@ Finally, join the FastHTML community to ask questions, share your work,
185
185
  and learn from others:
186
186
 
187
187
  - [Discord](https://discord.gg/qcXvcxMhdP)
188
+
189
+ ## Other languages and related projects
190
+
191
+ If you’re not a Python user, or are keen to try out a new language,
192
+ we’ll list here other projects that have a similar approach to FastHTML.
193
+ (Please reach out if you know of any other projects that you’d like to
194
+ see added.)
195
+
196
+ - [htmgo](https://htmgo.dev/) (Go): “*htmgo is a lightweight pure go way
197
+ to build interactive websites / web applications using go & htmx. By
198
+ combining the speed & simplicity of go + hypermedia attributes (htmx)
199
+ to add interactivity to websites, all conveniently wrapped in pure go,
200
+ you can build simple, fast, interactive websites without touching
201
+ javascript. All compiled to a single deployable binary*”
202
+
203
+ If you’re just interested in functional HTML components, rather than a
204
+ full HTMX server solution, consider:
205
+
206
+ - [fastcore.xml.FT](https://fastcore.fast.ai/xml.html): This is actually
207
+ what FastHTML uses behind the scenes
208
+ - [htpy](https://htpy.dev/): Similar to
209
+ [`fastcore.xml.FT`](https://fastcore.fast.ai/xml.html#ft), but with a
210
+ somewhat different syntax
211
+ - [elm-html](https://package.elm-lang.org/packages/elm/html/latest/):
212
+ Elm’s built-in HTML library with a type-safe functional approach
213
+ - [hiccup](https://github.com/weavejester/hiccup): Popular library for
214
+ representing HTML in Clojure using vectors
215
+ - [hiccl](https://github.com/garlic0x1/hiccl): HTML generation library
216
+ for Common Lisp inspired by Clojure’s Hiccup
217
+ - [Falco.Markup](https://github.com/pimbrouwers/Falco): F# HTML DSL and
218
+ web framework with type-safe HTML generation
219
+ - [Lucid](https://github.com/chrisdone/lucid): Type-safe HTML generation
220
+ for Haskell using monad transformers
221
+ - [dream-html](https://github.com/aantron/dream): Part of the Dream web
222
+ framework for OCaml, provides type-safe HTML templating
223
+
224
+ For other hypermedia application platforms, not based on HTMX, take a
225
+ look at:
226
+
227
+ - [Hotwire/Turbo](https://turbo.hotwired.dev/): Rails-oriented framework
228
+ that similarly uses HTML-over-the-wire
229
+ - [LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html):
230
+ Phoenix framework’s solution for building interactive web apps with
231
+ minimal JavaScript
232
+ - [Unpoly](https://unpoly.com/): Another HTML-over-the-wire framework
233
+ with progressive enhancement
234
+ - [Livewire](https://laravel-livewire.com/): Laravel’s take on building
235
+ dynamic interfaces with minimal JavaScript
@@ -5,7 +5,7 @@ oauthlib
5
5
  itsdangerous
6
6
  uvicorn[standard]>=0.30
7
7
  httpx
8
- fastlite>=0.0.9
8
+ fastlite>=0.1.1
9
9
  python-multipart
10
10
  beautifulsoup4
11
11
 
@@ -1,10 +1,10 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.11.0
4
+ version = 0.12.1
5
5
  min_python = 3.10
6
6
  license = apache2
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
7
+ requirements = fastcore>=1.7.18 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.1.1 python-multipart beautifulsoup4
8
8
  dev_requirements = ipython lxml pysymbol_llm
9
9
  black_formatting = False
10
10
  conda_user = fastai
@@ -16,8 +16,8 @@ tst_flags = notest
16
16
  put_version_in_init = True
17
17
  branch = main
18
18
  custom_sidebar = False
19
- doc_host = https://AnswerDotAI.github.io
20
- doc_baseurl = /fasthtml
19
+ doc_host = https://docs.fastht.ml
20
+ doc_baseurl = /
21
21
  git_url = https://github.com/AnswerDotAI/fasthtml
22
22
  title = fasthtml
23
23
  audience = Developers
@@ -1,2 +0,0 @@
1
- __version__ = "0.11.0"
2
- from .core import *