python-fasthtml 0.5.1__tar.gz → 0.5.2__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 (40) hide show
  1. {python-fasthtml-0.5.1/python_fasthtml.egg-info → python-fasthtml-0.5.2}/PKG-INFO +27 -2
  2. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/README.md +25 -0
  3. python-fasthtml-0.5.2/fasthtml/__init__.py +2 -0
  4. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/_modidx.py +35 -1
  5. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/components.py +2 -2
  6. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/components.pyi +5 -1
  7. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/core.py +61 -6
  8. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/core.pyi +36 -4
  9. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/fastapp.py +1 -3
  10. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/oauth.py +6 -6
  11. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/starlette.py +1 -1
  12. python-fasthtml-0.5.2/fasthtml/svg.py +173 -0
  13. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/toaster.py +4 -3
  14. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2/python_fasthtml.egg-info}/PKG-INFO +27 -2
  15. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/python_fasthtml.egg-info/requires.txt +1 -1
  16. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/settings.ini +2 -2
  17. python-fasthtml-0.5.1/fasthtml/__init__.py +0 -2
  18. python-fasthtml-0.5.1/fasthtml/svg.py +0 -4
  19. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/CONTRIBUTING.md +0 -0
  20. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/LICENSE +0 -0
  21. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/MANIFEST.in +0 -0
  22. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/authmw.py +0 -0
  23. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/basics.py +0 -0
  24. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/cli.py +0 -0
  25. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/common.py +0 -0
  26. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/ft.py +0 -0
  27. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/js.py +0 -0
  28. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/katex.js +0 -0
  29. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/live_reload.py +0 -0
  30. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/pico.py +0 -0
  31. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/xtend.py +0 -0
  32. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/fasthtml/xtend.pyi +0 -0
  33. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  34. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  35. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/python_fasthtml.egg-info/entry_points.txt +0 -0
  36. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/python_fasthtml.egg-info/not-zip-safe +0 -0
  37. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/python_fasthtml.egg-info/top_level.txt +0 -0
  38. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/setup.cfg +0 -0
  39. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/setup.py +0 -0
  40. {python-fasthtml-0.5.1 → python-fasthtml-0.5.2}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: fastcore>=1.7.2
18
+ Requires-Dist: fastcore>=1.7.5
19
19
  Requires-Dist: python-dateutil
20
20
  Requires-Dist: starlette>0.33
21
21
  Requires-Dist: oauthlib
@@ -114,6 +114,31 @@ the new version returned by the second route.
114
114
  This “hypermedia-based” approach to web development is a powerful way to
115
115
  build web applications.
116
116
 
117
+ ### Getting help from AI
118
+
119
+ Because FastHTML is newer than most LLMs, AI systems like Cursor,
120
+ ChatGPT, Claude, and Copilot won’t give useful answers about it. To fix
121
+ that problem, we’ve provided an LLM-friendly guide that teaches them how
122
+ to use FastHTML. To use it, add this link for your AI helper to use:
123
+
124
+ - [/llms-ctx.txt](https://docs.fastht.ml/llms-ctx.txt)
125
+
126
+ This example is in a format based on recommendations from Anthropic for
127
+ use with [Claude
128
+ Projects](https://support.anthropic.com/en/articles/9517075-what-are-projects).
129
+ This works so well that we’ve actually found that Claude can provide
130
+ even better information than our own documentation! For instance, read
131
+ through [this annotated Claude
132
+ chat](https://gist.github.com/jph00/9559b0a563f6a370029bec1d1cc97b74)
133
+ for some great getting-started information, entirely generated from a
134
+ project using the above text file as context.
135
+
136
+ If you use Cursor, type `@doc` then choose “*Add new doc*”, and use the
137
+ /llms-ctx.txt link above. The context file is auto-generated from our
138
+ [`llms.txt`](https://llmstxt.org/) (our proposed standard for providing
139
+ AI-friendly information)—you can generate alternative versions suitable
140
+ for other models as needed.
141
+
117
142
  ## Next Steps
118
143
 
119
144
  Start with the official sources to learn more about FastHTML:
@@ -83,6 +83,31 @@ the new version returned by the second route.
83
83
  This “hypermedia-based” approach to web development is a powerful way to
84
84
  build web applications.
85
85
 
86
+ ### Getting help from AI
87
+
88
+ Because FastHTML is newer than most LLMs, AI systems like Cursor,
89
+ ChatGPT, Claude, and Copilot won’t give useful answers about it. To fix
90
+ that problem, we’ve provided an LLM-friendly guide that teaches them how
91
+ to use FastHTML. To use it, add this link for your AI helper to use:
92
+
93
+ - [/llms-ctx.txt](https://docs.fastht.ml/llms-ctx.txt)
94
+
95
+ This example is in a format based on recommendations from Anthropic for
96
+ use with [Claude
97
+ Projects](https://support.anthropic.com/en/articles/9517075-what-are-projects).
98
+ This works so well that we’ve actually found that Claude can provide
99
+ even better information than our own documentation! For instance, read
100
+ through [this annotated Claude
101
+ chat](https://gist.github.com/jph00/9559b0a563f6a370029bec1d1cc97b74)
102
+ for some great getting-started information, entirely generated from a
103
+ project using the above text file as context.
104
+
105
+ If you use Cursor, type `@doc` then choose “*Add new doc*”, and use the
106
+ /llms-ctx.txt link above. The context file is auto-generated from our
107
+ [`llms.txt`](https://llmstxt.org/) (our proposed standard for providing
108
+ AI-friendly information)—you can generate alternative versions suitable
109
+ for other models as needed.
110
+
86
111
  ## Next Steps
87
112
 
88
113
  Start with the official sources to learn more about FastHTML:
@@ -0,0 +1,2 @@
1
+ __version__ = "0.5.2"
2
+ from .core import *
@@ -25,18 +25,27 @@ d = { 'settings': { 'branch': 'main',
25
25
  'fasthtml.components.sse_message': ('api/components.html#sse_message', 'fasthtml/components.py')},
26
26
  'fasthtml.core': { 'fasthtml.core.Beforeware': ('api/core.html#beforeware', 'fasthtml/core.py'),
27
27
  'fasthtml.core.Beforeware.__init__': ('api/core.html#beforeware.__init__', 'fasthtml/core.py'),
28
+ 'fasthtml.core.Client': ('api/core.html#client', 'fasthtml/core.py'),
29
+ 'fasthtml.core.Client.__init__': ('api/core.html#client.__init__', 'fasthtml/core.py'),
30
+ 'fasthtml.core.Client._sync': ('api/core.html#client._sync', 'fasthtml/core.py'),
28
31
  'fasthtml.core.EventStream': ('api/core.html#eventstream', 'fasthtml/core.py'),
29
32
  'fasthtml.core.FastHTML': ('api/core.html#fasthtml', 'fasthtml/core.py'),
30
33
  'fasthtml.core.FastHTML.__init__': ('api/core.html#fasthtml.__init__', 'fasthtml/core.py'),
31
34
  'fasthtml.core.FastHTML.route': ('api/core.html#fasthtml.route', 'fasthtml/core.py'),
35
+ 'fasthtml.core.FastHTML.static_route': ('api/core.html#fasthtml.static_route', 'fasthtml/core.py'),
36
+ 'fasthtml.core.FastHTML.static_route_exts': ('api/core.html#fasthtml.static_route_exts', 'fasthtml/core.py'),
32
37
  'fasthtml.core.FastHTML.ws': ('api/core.html#fasthtml.ws', 'fasthtml/core.py'),
33
38
  'fasthtml.core.HTTPConnection.url_path_for': ( 'api/core.html#httpconnection.url_path_for',
34
39
  'fasthtml/core.py'),
35
40
  'fasthtml.core.HtmxHeaders': ('api/core.html#htmxheaders', 'fasthtml/core.py'),
36
41
  'fasthtml.core.HtmxHeaders.__bool__': ('api/core.html#htmxheaders.__bool__', 'fasthtml/core.py'),
42
+ 'fasthtml.core.HtmxResponseHeaders': ('api/core.html#htmxresponseheaders', 'fasthtml/core.py'),
37
43
  'fasthtml.core.HttpHeader': ('api/core.html#httpheader', 'fasthtml/core.py'),
38
44
  'fasthtml.core.MiddlewareBase': ('api/core.html#middlewarebase', 'fasthtml/core.py'),
39
45
  'fasthtml.core.MiddlewareBase.__call__': ('api/core.html#middlewarebase.__call__', 'fasthtml/core.py'),
46
+ 'fasthtml.core.Redirect': ('api/core.html#redirect', 'fasthtml/core.py'),
47
+ 'fasthtml.core.Redirect.__init__': ('api/core.html#redirect.__init__', 'fasthtml/core.py'),
48
+ 'fasthtml.core.Redirect.__response__': ('api/core.html#redirect.__response__', 'fasthtml/core.py'),
40
49
  'fasthtml.core.RouteX': ('api/core.html#routex', 'fasthtml/core.py'),
41
50
  'fasthtml.core.RouteX.__init__': ('api/core.html#routex.__init__', 'fasthtml/core.py'),
42
51
  'fasthtml.core.RouteX._endp': ('api/core.html#routex._endp', 'fasthtml/core.py'),
@@ -65,6 +74,7 @@ d = { 'settings': { 'branch': 'main',
65
74
  'fasthtml.core._resp': ('api/core.html#_resp', 'fasthtml/core.py'),
66
75
  'fasthtml.core._send_ws': ('api/core.html#_send_ws', 'fasthtml/core.py'),
67
76
  'fasthtml.core._sig': ('api/core.html#_sig', 'fasthtml/core.py'),
77
+ 'fasthtml.core._to_htmx_header': ('api/core.html#_to_htmx_header', 'fasthtml/core.py'),
68
78
  'fasthtml.core._to_xml': ('api/core.html#_to_xml', 'fasthtml/core.py'),
69
79
  'fasthtml.core._url_for': ('api/core.html#_url_for', 'fasthtml/core.py'),
70
80
  'fasthtml.core._wrap_call': ('api/core.html#_wrap_call', 'fasthtml/core.py'),
@@ -142,7 +152,31 @@ d = { 'settings': { 'branch': 'main',
142
152
  'fasthtml.pico.Search': ('api/pico.html#search', 'fasthtml/pico.py'),
143
153
  'fasthtml.pico.set_pico_cls': ('api/pico.html#set_pico_cls', 'fasthtml/pico.py')},
144
154
  'fasthtml.starlette': {},
145
- 'fasthtml.svg': {},
155
+ 'fasthtml.svg': { 'fasthtml.svg.Circle': ('api/svg.html#circle', 'fasthtml/svg.py'),
156
+ 'fasthtml.svg.Ellipse': ('api/svg.html#ellipse', 'fasthtml/svg.py'),
157
+ 'fasthtml.svg.Line': ('api/svg.html#line', 'fasthtml/svg.py'),
158
+ 'fasthtml.svg.Path': ('api/svg.html#path', 'fasthtml/svg.py'),
159
+ 'fasthtml.svg.PathFT': ('api/svg.html#pathft', 'fasthtml/svg.py'),
160
+ 'fasthtml.svg.PathFT.A': ('api/svg.html#pathft.a', 'fasthtml/svg.py'),
161
+ 'fasthtml.svg.PathFT.C': ('api/svg.html#pathft.c', 'fasthtml/svg.py'),
162
+ 'fasthtml.svg.PathFT.H': ('api/svg.html#pathft.h', 'fasthtml/svg.py'),
163
+ 'fasthtml.svg.PathFT.L': ('api/svg.html#pathft.l', 'fasthtml/svg.py'),
164
+ 'fasthtml.svg.PathFT.M': ('api/svg.html#pathft.m', 'fasthtml/svg.py'),
165
+ 'fasthtml.svg.PathFT.Q': ('api/svg.html#pathft.q', 'fasthtml/svg.py'),
166
+ 'fasthtml.svg.PathFT.S': ('api/svg.html#pathft.s', 'fasthtml/svg.py'),
167
+ 'fasthtml.svg.PathFT.T': ('api/svg.html#pathft.t', 'fasthtml/svg.py'),
168
+ 'fasthtml.svg.PathFT.V': ('api/svg.html#pathft.v', 'fasthtml/svg.py'),
169
+ 'fasthtml.svg.PathFT.Z': ('api/svg.html#pathft.z', 'fasthtml/svg.py'),
170
+ 'fasthtml.svg.PathFT._append_cmd': ('api/svg.html#pathft._append_cmd', 'fasthtml/svg.py'),
171
+ 'fasthtml.svg.Polygon': ('api/svg.html#polygon', 'fasthtml/svg.py'),
172
+ 'fasthtml.svg.Polyline': ('api/svg.html#polyline', 'fasthtml/svg.py'),
173
+ 'fasthtml.svg.Rect': ('api/svg.html#rect', 'fasthtml/svg.py'),
174
+ 'fasthtml.svg.Svg': ('api/svg.html#svg', 'fasthtml/svg.py'),
175
+ 'fasthtml.svg.SvgInb': ('api/svg.html#svginb', 'fasthtml/svg.py'),
176
+ 'fasthtml.svg.SvgOob': ('api/svg.html#svgoob', 'fasthtml/svg.py'),
177
+ 'fasthtml.svg.Text': ('api/svg.html#text', 'fasthtml/svg.py'),
178
+ 'fasthtml.svg.ft_svg': ('api/svg.html#ft_svg', 'fasthtml/svg.py'),
179
+ 'fasthtml.svg.transformd': ('api/svg.html#transformd', 'fasthtml/svg.py')},
146
180
  'fasthtml.toaster': {},
147
181
  'fasthtml.xtend': { 'fasthtml.xtend.A': ('api/xtend.html#a', 'fasthtml/xtend.py'),
148
182
  'fasthtml.xtend.AX': ('api/xtend.html#ax', 'fasthtml/xtend.py'),
@@ -52,13 +52,13 @@ fh_cfg['attrmap']=attrmap_x
52
52
  fh_cfg['valmap' ]=valmap
53
53
 
54
54
  # %% ../nbs/api/01_components.ipynb
55
- def ft_html(tag: str, *c, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, **kwargs):
55
+ def ft_html(tag: str, *c, id=None, cls=None, title=None, style=None, attrmap=None, valmap=None, ft_cls=FT, **kwargs):
56
56
  if attrmap is None: attrmap=fh_cfg.attrmap
57
57
  if valmap is None: valmap =fh_cfg.valmap
58
58
  kwargs['id'],kwargs['cls'],kwargs['title'],kwargs['style'] = id,cls,title,style
59
59
  tag,c,kw = ft(tag, *c, attrmap=attrmap, valmap=valmap, **kwargs).list
60
60
  if tag in named and 'id' in kw and 'name' not in kw: kw['name'] = kw['id']
61
- return FT(tag,c,kw, void_=tag in voids)
61
+ return ft_cls(tag,c,kw, void_=tag in voids)
62
62
 
63
63
  # %% ../nbs/api/01_components.ipynb
64
64
  @use_kwargs(hx_attrs, keep=True)
@@ -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', 'show', 'attrmap_x', 'ft_html', 'ft_hx', 'File', 'fill_form', 'fill_dataclass', 'find_inputs', 'html2ft', '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', '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']
3
3
  from dataclasses import dataclass, asdict, is_dataclass, make_dataclass, replace, astuple, MISSING
4
4
  from bs4 import BeautifulSoup, Comment
5
5
  from fastcore.utils import *
@@ -63,6 +63,10 @@ _re_h2x_attr_key = re.compile('^[A-Za-z_-][\\w-]*$')
63
63
  def html2ft(html, attr1st=False):
64
64
  """Convert HTML to an `ft` expression"""
65
65
  ...
66
+
67
+ def sse_message(elm, event='message'):
68
+ """Convert element `elm` into a format suitable for SSE streaming"""
69
+ ...
66
70
  def ft_html(tag: str, *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:Any=None, hx_post:Any=None, hx_put:Any=None, hx_delete:Any=None, hx_patch:Any=None, hx_trigger:Any=None, hx_target:Any=None, hx_swap:Any=None, hx_swap_oob:Any=None, hx_include:Any=None, hx_select:Any=None, hx_select_oob:Any=None, hx_indicator:Any=None, hx_push_url:Any=None, hx_confirm:Any=None, hx_disable:Any=None, hx_replace_url:Any=None, hx_vals:Any=None, hx_disabled_elt:Any=None, hx_ext:Any=None, hx_headers:Any=None, hx_history:Any=None, hx_history_elt:Any=None, hx_indicator:Any=None, hx_inherit:Any=None, hx_params:Any=None, hx_preserve:Any=None, hx_prompt:Any=None, hx_replace_url:Any=None, hx_request:Any=None, hx_sync:Any=None, hx_validate:Any=None, **kwargs): ...
67
71
  def ft_hx(tag: str, *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:Any=None, hx_post:Any=None, hx_put:Any=None, hx_delete:Any=None, hx_patch:Any=None, hx_trigger:Any=None, hx_target:Any=None, hx_swap:Any=None, hx_swap_oob:Any=None, hx_include:Any=None, hx_select:Any=None, hx_select_oob:Any=None, hx_indicator:Any=None, hx_push_url:Any=None, hx_confirm:Any=None, hx_disable:Any=None, hx_replace_url:Any=None, hx_vals:Any=None, hx_disabled_elt:Any=None, hx_ext:Any=None, hx_headers:Any=None, hx_history:Any=None, hx_history_elt:Any=None, hx_indicator:Any=None, hx_inherit:Any=None, hx_params:Any=None, hx_preserve:Any=None, hx_prompt:Any=None, hx_replace_url:Any=None, hx_request:Any=None, hx_sync:Any=None, hx_validate:Any=None, **kwargs): ...
68
72
  def A(*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:Any=None, hx_post:Any=None, hx_put:Any=None, hx_delete:Any=None, hx_patch:Any=None, hx_trigger:Any=None, hx_target:Any=None, hx_swap:Any=None, hx_swap_oob:Any=None, hx_include:Any=None, hx_select:Any=None, hx_select_oob:Any=None, hx_indicator:Any=None, hx_push_url:Any=None, hx_confirm:Any=None, hx_disable:Any=None, hx_replace_url:Any=None, hx_vals:Any=None, hx_disabled_elt:Any=None, hx_ext:Any=None, hx_headers:Any=None, hx_history:Any=None, hx_history_elt:Any=None, hx_indicator:Any=None, hx_inherit:Any=None, hx_params:Any=None, hx_preserve:Any=None, hx_prompt:Any=None, hx_replace_url:Any=None, hx_request:Any=None, hx_sync:Any=None, hx_validate:Any=None, **kwargs): ...
@@ -3,17 +3,18 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/00_core.ipynb.
4
4
 
5
5
  # %% auto 0
6
- __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmxsrc', 'htmxwssrc', 'fhjsscr', 'htmxctsrc', 'surrsrc', 'scopesrc', 'viewport',
7
- 'charset', 'all_meths', 'date', 'snake2hyphens', 'HtmxHeaders', 'str2int', 'HttpHeader', 'form2dict',
8
- 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'WS_RouteX', 'uri', 'decode_uri', 'flat_tuple',
9
- 'RouteX', 'RouterX', 'get_key', 'FastHTML', 'serve', 'cookie', 'reg_re_param', 'MiddlewareBase']
6
+ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmxsrc', 'htmxwssrc', 'fhjsscr', 'htmxctsrc', 'surrsrc', 'scopesrc',
7
+ 'viewport', 'charset', 'all_meths', 'date', 'snake2hyphens', 'HtmxHeaders', 'str2int', 'HttpHeader',
8
+ 'HtmxResponseHeaders', 'form2dict', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'WS_RouteX',
9
+ 'uri', 'decode_uri', 'flat_tuple', 'Redirect', 'RouteX', 'RouterX', 'get_key', 'FastHTML', 'serve', 'cookie',
10
+ 'reg_re_param', 'MiddlewareBase', 'Client']
10
11
 
11
12
  # %% ../nbs/api/00_core.ipynb
12
- import json,uuid,inspect,types,uvicorn,signal,asyncio
13
- from starlette.datastructures import URLPath
13
+ import json,uuid,inspect,types,uvicorn,signal,asyncio,threading
14
14
 
15
15
  from fastcore.utils import *
16
16
  from fastcore.xml import *
17
+ from fastcore.meta import use_kwargs_dict
17
18
 
18
19
  from types import UnionType, SimpleNamespace as ns, GenericAlias
19
20
  from typing import Optional, get_type_hints, get_args, get_origin, Union, Mapping, TypedDict, List, Any
@@ -27,6 +28,7 @@ from urllib.parse import urlencode, parse_qs, quote, unquote
27
28
  from copy import copy,deepcopy
28
29
  from warnings import warn
29
30
  from dateutil import parser as dtparse
31
+ from httpx import ASGITransport, AsyncClient
30
32
 
31
33
  from .starlette import *
32
34
 
@@ -106,6 +108,20 @@ def _form_arg(k, v, d):
106
108
  @dataclass
107
109
  class HttpHeader: k:str;v:str
108
110
 
111
+ # %% ../nbs/api/00_core.ipynb
112
+ def _to_htmx_header(s):
113
+ return 'HX-' + s.replace('_', '-').title()
114
+
115
+ htmx_resps = dict(location=None, push_url=None, redirect=None, refresh=None, replace_url=None,
116
+ reswap=None, retarget=None, reselect=None, trigger=None, trigger_after_settle=None, trigger_after_swap=None)
117
+
118
+ # %% ../nbs/api/00_core.ipynb
119
+ @use_kwargs_dict(**htmx_resps)
120
+ def HtmxResponseHeaders(**kwargs):
121
+ "HTMX response headers"
122
+ res = tuple(HttpHeader(_to_htmx_header(k), v) for k,v in kwargs.items())
123
+ return res[0] if len(res)==1 else res
124
+
109
125
  # %% ../nbs/api/00_core.ipynb
110
126
  def _annotations(anno):
111
127
  "Same as `get_annotations`, but also works on namedtuples"
@@ -356,6 +372,7 @@ def _xt_resp(req, resp):
356
372
  # %% ../nbs/api/00_core.ipynb
357
373
  def _resp(req, resp, cls=empty):
358
374
  if not resp: resp=()
375
+ if hasattr(resp, '__response__'): resp = resp.__response__(req)
359
376
  if cls in (Any,FT): cls=empty
360
377
  if isinstance(resp, FileResponse) and not os.path.exists(resp.path): raise HTTPException(404, resp.path)
361
378
  if isinstance(resp, Response): return resp
@@ -368,6 +385,14 @@ def _resp(req, resp, cls=empty):
368
385
  cls = HTMLResponse
369
386
  return cls(resp)
370
387
 
388
+ # %% ../nbs/api/00_core.ipynb
389
+ class Redirect:
390
+ "Use HTMX or Starlette RedirectResponse as required to redirect to `loc`"
391
+ def __init__(self, loc): self.loc = loc
392
+ def __response__(self, req):
393
+ if 'hx-request' in req.headers: return HtmxResponseHeaders(redirect=self.loc)
394
+ return RedirectResponse(self.loc, status_code=303)
395
+
371
396
  # %% ../nbs/api/00_core.ipynb
372
397
  async def _wrap_call(f, req, params):
373
398
  wreq = await _wrap_req(req, params)
@@ -565,6 +590,19 @@ def reg_re_param(m, s):
565
590
  reg_re_param("path", ".*?")
566
591
  reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|html|map")
567
592
 
593
+ @patch
594
+ def static_route_exts(self:FastHTML, prefix='/', static_path='.', exts='static'):
595
+ "Add a static route at URL path `prefix` with files from `static_path` and `exts` defined by `reg_re_param()`"
596
+ @self.route(f"{prefix}{{fname:path}}.{{ext:{exts}}}")
597
+ async def get(fname:str, ext:str): return FileResponse(f'{static_path}/{fname}.{ext}')
598
+
599
+ # %% ../nbs/api/00_core.ipynb
600
+ @patch
601
+ def static_route(self:FastHTML, ext='', prefix='/', static_path='.'):
602
+ "Add a static route at URL path `prefix` with files from `static_path` and single `ext` (including the '.')"
603
+ @self.route(f"{prefix}{{fname:path}}{ext}")
604
+ async def get(fname:str): return FileResponse(f'{static_path}/{fname}{ext}')
605
+
568
606
  # %% ../nbs/api/00_core.ipynb
569
607
  class MiddlewareBase:
570
608
  async def __call__(self, scope, receive, send) -> None:
@@ -572,3 +610,20 @@ class MiddlewareBase:
572
610
  await self._app(scope, receive, send)
573
611
  return
574
612
  return HTTPConnection(scope)
613
+
614
+ # %% ../nbs/api/00_core.ipynb
615
+ class Client:
616
+ "An httpx ASGI client that doesn't require `async`"
617
+ def __init__(self, app, url="http://testserver"):
618
+ self.cli = AsyncClient(transport=ASGITransport(app), base_url=url)
619
+
620
+ def _sync(self, method, url, **kwargs):
621
+ @threaded
622
+ def f():
623
+ async def _request(): return await self.cli.request(method, url, **kwargs)
624
+ return asyncio.run(_request())
625
+ r = f()
626
+ r.join()
627
+ return r.result
628
+
629
+ for o in ('get', 'post', 'delete', 'put', 'patch', 'options'): setattr(Client, o, partialmethod(Client._sync, o))
@@ -1,9 +1,9 @@
1
1
  """The `FastHTML` subclass of `Starlette`, along with the `RouterX` and `RouteX` classes it automatically uses."""
2
- __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmxsrc', 'htmxwssrc', 'fhjsscr', 'htmxctsrc', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'all_meths', 'date', 'snake2hyphens', 'HtmxHeaders', 'str2int', 'HttpHeader', 'form2dict', 'flat_xt', 'Beforeware', 'WS_RouteX', 'uri', 'decode_uri', 'flat_tuple', 'RouteX', 'RouterX', 'get_key', 'FastHTML', 'serve', 'cookie', 'reg_re_param', 'MiddlewareBase']
3
- import json, uuid, inspect, types, uvicorn
4
- from starlette.datastructures import URLPath
2
+ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmxsrc', 'htmxwssrc', 'fhjsscr', 'htmxctsrc', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'all_meths', 'date', 'snake2hyphens', 'HtmxHeaders', 'str2int', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'WS_RouteX', 'uri', 'decode_uri', 'flat_tuple', 'Redirect', 'RouteX', 'RouterX', 'get_key', 'FastHTML', 'serve', 'cookie', 'reg_re_param', 'MiddlewareBase']
3
+ import json, uuid, inspect, types, uvicorn, signal, asyncio
5
4
  from fastcore.utils import *
6
5
  from fastcore.xml import *
6
+ from fastcore.meta import use_kwargs_dict
7
7
  from types import UnionType, SimpleNamespace as ns, GenericAlias
8
8
  from typing import Optional, get_type_hints, get_args, get_origin, Union, Mapping, TypedDict, List, Any
9
9
  from datetime import datetime
@@ -16,7 +16,6 @@ from urllib.parse import urlencode, parse_qs, quote, unquote
16
16
  from copy import copy, deepcopy
17
17
  from warnings import warn
18
18
  from dateutil import parser as dtparse
19
- from starlette.requests import HTTPConnection
20
19
  from .starlette import *
21
20
  empty = Parameter.empty
22
21
 
@@ -70,6 +69,15 @@ class HttpHeader:
70
69
  k: str
71
70
  v: str
72
71
 
72
+ def _to_htmx_header(s):
73
+ ...
74
+ htmx_resps = dict(location=None, push_url=None, redirect=None, refresh=None, replace_url=None, reswap=None, retarget=None, reselect=None, trigger=None, trigger_after_settle=None, trigger_after_swap=None)
75
+
76
+ @use_kwargs_dict(**htmx_resps)
77
+ def HtmxResponseHeaders(**kwargs):
78
+ """HTMX response headers"""
79
+ ...
80
+
73
81
  def _annotations(anno):
74
82
  """Same as `get_annotations`, but also works on namedtuples"""
75
83
  ...
@@ -120,6 +128,13 @@ async def _send_ws(ws, resp):
120
128
  def _ws_endp(recv, conn=None, disconn=None):
121
129
  ...
122
130
 
131
+ def EventStream(s):
132
+ """Create a text/event-stream response from `s`"""
133
+ ...
134
+
135
+ def signal_shutdown():
136
+ ...
137
+
123
138
  class WS_RouteX(WebSocketRoute):
124
139
 
125
140
  def __init__(self, app, path: str, recv, conn: callable=None, disconn: callable=None, *, name=None, middleware=None):
@@ -164,6 +179,15 @@ def _xt_resp(req, resp):
164
179
  def _resp(req, resp, cls=empty):
165
180
  ...
166
181
 
182
+ class Redirect:
183
+ """Use HTMX or Starlette RedirectResponse as required to redirect to `loc`"""
184
+
185
+ def __init__(self, loc):
186
+ ...
187
+
188
+ def __response__(self, req):
189
+ ...
190
+
167
191
  async def _wrap_call(f, req, params):
168
192
  ...
169
193
 
@@ -218,6 +242,14 @@ class FastHTML(Starlette):
218
242
  def route(self, path: str=None, methods=None, name=None, include_in_schema=True):
219
243
  """Add a route at `path`"""
220
244
  ...
245
+
246
+ def static_route_exts(self, prefix='/', static_path='.', exts='static'):
247
+ """Add a static route at URL path `prefix` with files from `static_path` and `exts` defined by `reg_re_param()`"""
248
+ ...
249
+
250
+ def static_route(self, ext='', prefix='/', static_path='.'):
251
+ """Add a static route at URL path `prefix` with files from `static_path` and single `ext` (including the '.')"""
252
+ ...
221
253
  all_meths = 'get post put delete patch head trace options'.split()
222
254
  for o in all_meths:
223
255
  setattr(FastHTML, o, partialmethod(FastHTML.route, methods=o))
@@ -78,9 +78,7 @@ def fast_app(
78
78
  session_cookie=session_cookie, max_age=max_age, sess_path=sess_path, same_site=same_site, sess_https_only=sess_https_only,
79
79
  sess_domain=sess_domain, key_fname=key_fname, ws_hdr=ws_hdr, surreal=surreal, htmx=htmx, htmlkw=htmlkw,
80
80
  reload_attempts=reload_attempts, reload_interval=reload_interval, **(bodykw or {}))
81
-
82
- @app.route("/{fname:path}.{ext:static}")
83
- async def get(fname:str, ext:str): return FileResponse(f'{static_path or "."}/{fname}.{ext}')
81
+ app.static_route_exts()
84
82
  if not db_file: return app,app.route
85
83
 
86
84
  db = database(db_file)
@@ -143,7 +143,7 @@ class OAuth:
143
143
  auth = req.scope['auth'] = session.get('auth')
144
144
  if not auth: return RedirectResponse(self.login_path, status_code=303)
145
145
  info = AttrDictDefault(cli.get_info(auth))
146
- if not self._chk_auth(info): return RedirectResponse(self.login_path, status_code=303)
146
+ if not self._chk_auth(info, session): return RedirectResponse(self.login_path, status_code=303)
147
147
  app.before.append(Beforeware(before, skip=skip))
148
148
 
149
149
  @app.get(redir_path)
@@ -152,7 +152,7 @@ class OAuth:
152
152
  base_url = f"{req.url.scheme}://{req.url.netloc}"
153
153
  print(base_url)
154
154
  info = AttrDictDefault(cli.retr_info(code, base_url+redir_path))
155
- if not self._chk_auth(info): return RedirectResponse(self.login_path, status_code=303)
155
+ if not self._chk_auth(info, session): return RedirectResponse(self.login_path, status_code=303)
156
156
  session['auth'] = cli.token['access_token']
157
157
  return self.login(info, state)
158
158
 
@@ -162,11 +162,11 @@ class OAuth:
162
162
  return self.logout(session)
163
163
 
164
164
  def redir_url(self, req): return f"{req.url.scheme}://{req.url.netloc}{self.redir_path}"
165
- def login_link(self, req): return self.cli.login_link(self.redir_url(req))
165
+ def login_link(self, req, scope=None, state=None): return self.cli.login_link(self.redir_url(req), scope=scope, state=state)
166
166
 
167
167
  def login(self, info, state): raise NotImplementedError()
168
168
  def logout(self, session): return RedirectResponse(self.login_path, status_code=303)
169
- def chk_auth(self, info, ident): raise NotImplementedError()
170
- def _chk_auth(self, info):
169
+ def chk_auth(self, info, ident, session): raise NotImplementedError()
170
+ def _chk_auth(self, info, session):
171
171
  ident = info.get(self.cli.id_key)
172
- return ident and self.chk_auth(info, ident)
172
+ return ident and self.chk_auth(info, ident, session)
@@ -15,6 +15,6 @@ from starlette.routing import Route, Router, Mount, WebSocketRoute
15
15
  from starlette.exceptions import HTTPException,WebSocketException
16
16
  from starlette.endpoints import HTTPEndpoint,WebSocketEndpoint
17
17
  from starlette.config import Config
18
- from starlette.datastructures import CommaSeparatedStrings, Secret, UploadFile
18
+ from starlette.datastructures import CommaSeparatedStrings, Secret, UploadFile, URLPath
19
19
  from starlette.types import ASGIApp, Receive, Scope, Send
20
20
  from starlette.concurrency import run_in_threadpool
@@ -0,0 +1,173 @@
1
+ """Simple SVG FT elements"""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/05_svg.ipynb.
4
+
5
+ # %% auto 0
6
+ __all__ = ['g', 'svg_inb', 'Svg', 'ft_svg', 'Rect', 'Circle', 'Ellipse', 'transformd', 'Line', 'Polyline', 'Polygon', 'Text',
7
+ 'PathFT', 'Path', 'SvgOob', 'SvgInb', 'AltGlyph', 'AltGlyphDef', 'AltGlyphItem', 'Animate', 'AnimateColor',
8
+ 'AnimateMotion', 'AnimateTransform', 'ClipPath', 'Color_profile', 'Cursor', 'Defs', 'Desc', 'FeBlend',
9
+ 'FeColorMatrix', 'FeComponentTransfer', 'FeComposite', 'FeConvolveMatrix', 'FeDiffuseLighting',
10
+ 'FeDisplacementMap', 'FeDistantLight', 'FeFlood', 'FeFuncA', 'FeFuncB', 'FeFuncG', 'FeFuncR',
11
+ 'FeGaussianBlur', 'FeImage', 'FeMerge', 'FeMergeNode', 'FeMorphology', 'FeOffset', 'FePointLight',
12
+ 'FeSpecularLighting', 'FeSpotLight', 'FeTile', 'FeTurbulence', 'Filter', 'Font', 'Font_face',
13
+ 'Font_face_format', 'Font_face_name', 'Font_face_src', 'Font_face_uri', 'ForeignObject', 'G', 'Glyph',
14
+ 'GlyphRef', 'Hkern', 'Image', 'LinearGradient', 'Marker', 'Mask', 'Metadata', 'Missing_glyph', 'Mpath',
15
+ 'Pattern', 'RadialGradient', 'Set', 'Stop', 'Switch', 'Symbol', 'TextPath', 'Tref', 'Tspan', 'Use', 'View',
16
+ 'Vkern', 'Template']
17
+
18
+ # %% ../nbs/api/05_svg.ipynb
19
+ from fastcore.utils import *
20
+ from fastcore.meta import delegates
21
+ from fastcore.xml import FT
22
+ from .components import *
23
+ from .xtend import *
24
+
25
+ # %% ../nbs/api/05_svg.ipynb
26
+ _all_ = ['AltGlyph', 'AltGlyphDef', 'AltGlyphItem', 'Animate', 'AnimateColor', 'AnimateMotion', 'AnimateTransform', 'ClipPath', 'Color_profile', 'Cursor', 'Defs', 'Desc', 'FeBlend', 'FeColorMatrix', 'FeComponentTransfer', 'FeComposite', 'FeConvolveMatrix', 'FeDiffuseLighting', 'FeDisplacementMap', 'FeDistantLight', 'FeFlood', 'FeFuncA', 'FeFuncB', 'FeFuncG', 'FeFuncR', 'FeGaussianBlur', 'FeImage', 'FeMerge', 'FeMergeNode', 'FeMorphology', 'FeOffset', 'FePointLight', 'FeSpecularLighting', 'FeSpotLight', 'FeTile', 'FeTurbulence', 'Filter', 'Font', 'Font_face', 'Font_face_format', 'Font_face_name', 'Font_face_src', 'Font_face_uri', 'ForeignObject', 'G', 'Glyph', 'GlyphRef', 'Hkern', 'Image', 'LinearGradient', 'Marker', 'Mask', 'Metadata', 'Missing_glyph', 'Mpath', 'Pattern', 'RadialGradient', 'Set', 'Stop', 'Switch', 'Symbol', 'TextPath', 'Tref', 'Tspan', 'Use', 'View', 'Vkern', 'Template']
27
+
28
+ # %% ../nbs/api/05_svg.ipynb
29
+ g = globals()
30
+ for o in _all_: g[o] = partial(ft_hx, o[0].lower() + o[1:])
31
+
32
+ # %% ../nbs/api/05_svg.ipynb
33
+ def Svg(*args, viewBox=None, h=None, w=None, height=None, width=None, **kwargs):
34
+ "An SVG tag; xmlns is added automatically, and viewBox defaults to height and width if not provided"
35
+ if h: height=h
36
+ if w: width=w
37
+ if not viewBox and height and width: viewBox=f'0 0 {width} {height}'
38
+ return ft_svg('svg', *args, xmlns="http://www.w3.org/2000/svg", viewBox=viewBox, height=height, width=width, **kwargs)
39
+
40
+ # %% ../nbs/api/05_svg.ipynb
41
+ @delegates(ft_hx)
42
+ def ft_svg(tag: str, *c, transform=None, opacity=None, clip=None, mask=None, filter=None,
43
+ vector_effect=None, pointer_events=None, **kwargs):
44
+ "Create a standard `FT` element with some SVG-specific attrs"
45
+ return ft_hx(tag, *c, transform=transform, opacity=opacity, clip=clip, mask=mask, filter=filter,
46
+ vector_effect=vector_effect, pointer_events=pointer_events, **kwargs)
47
+
48
+ # %% ../nbs/api/05_svg.ipynb
49
+ @delegates(ft_svg)
50
+ def Rect(width, height, x=0, y=0, fill=None, stroke=None, stroke_width=None, rx=None, ry=None, **kwargs):
51
+ "A standard SVG `rect` element"
52
+ return ft_svg('rect', width=width, height=height, x=x, y=y, fill=fill,
53
+ stroke=stroke, stroke_width=stroke_width, rx=rx, ry=ry, **kwargs)
54
+
55
+ # %% ../nbs/api/05_svg.ipynb
56
+ @delegates(ft_svg)
57
+ def Circle(r, cx=0, cy=0, fill=None, stroke=None, stroke_width=None, **kwargs):
58
+ "A standard SVG `circle` element"
59
+ return ft_svg('circle', r=r, cx=cx, cy=cy, fill=fill, stroke=stroke, stroke_width=stroke_width, **kwargs)
60
+
61
+ # %% ../nbs/api/05_svg.ipynb
62
+ @delegates(ft_svg)
63
+ def Ellipse(rx, ry, cx=0, cy=0, fill=None, stroke=None, stroke_width=None, **kwargs):
64
+ "A standard SVG `ellipse` element"
65
+ return ft_svg('ellipse', rx=rx, ry=ry, cx=cx, cy=cy, fill=fill, stroke=stroke, stroke_width=stroke_width, **kwargs)
66
+
67
+ # %% ../nbs/api/05_svg.ipynb
68
+ def transformd(translate=None, scale=None, rotate=None, skewX=None, skewY=None, matrix=None):
69
+ "Create an SVG `transform` kwarg dict"
70
+ funcs = []
71
+ if translate is not None: funcs.append(f"translate{translate}")
72
+ if scale is not None: funcs.append(f"scale{scale}")
73
+ if rotate is not None: funcs.append(f"rotate({','.join(map(str,rotate))})")
74
+ if skewX is not None: funcs.append(f"skewX({skewX})")
75
+ if skewY is not None: funcs.append(f"skewY({skewY})")
76
+ if matrix is not None: funcs.append(f"matrix{matrix}")
77
+ return dict(transform=' '.join(funcs)) if funcs else {}
78
+
79
+ # %% ../nbs/api/05_svg.ipynb
80
+ @delegates(ft_svg)
81
+ def Line(x1, y1, x2=0, y2=0, stroke='black', w=None, stroke_width=1, **kwargs):
82
+ "A standard SVG `line` element"
83
+ if w: stroke_width=w
84
+ return ft_svg('line', x1=x1, y1=y1, x2=x2, y2=y2, stroke=stroke, stroke_width=stroke_width, **kwargs)
85
+
86
+ # %% ../nbs/api/05_svg.ipynb
87
+ @delegates(ft_svg)
88
+ def Polyline(*args, points=None, fill=None, stroke=None, stroke_width=None, **kwargs):
89
+ "A standard SVG `polyline` element"
90
+ if points is None: points = ' '.join(f"{x},{y}" for x, y in args)
91
+ return ft_svg('polyline', points=points, fill=fill, stroke=stroke, stroke_width=stroke_width, **kwargs)
92
+
93
+ # %% ../nbs/api/05_svg.ipynb
94
+ @delegates(ft_svg)
95
+ def Polygon(*args, points=None, fill=None, stroke=None, stroke_width=None, **kwargs):
96
+ "A standard SVG `polygon` element"
97
+ if points is None: points = ' '.join(f"{x},{y}" for x, y in args)
98
+ return ft_svg('polygon', points=points, fill=fill, stroke=stroke, stroke_width=stroke_width, **kwargs)
99
+
100
+ # %% ../nbs/api/05_svg.ipynb
101
+ @delegates(ft_svg)
102
+ def Text(*args, x=0, y=0, font_family=None, font_size=None, fill=None, text_anchor=None,
103
+ dominant_baseline=None, font_weight=None, font_style=None, text_decoration=None, **kwargs):
104
+ "A standard SVG `text` element"
105
+ return ft_svg('text', *args, x=x, y=y, font_family=font_family, font_size=font_size, fill=fill,
106
+ text_anchor=text_anchor, dominant_baseline=dominant_baseline, font_weight=font_weight,
107
+ font_style=font_style, text_decoration=text_decoration, **kwargs)
108
+
109
+ # %% ../nbs/api/05_svg.ipynb
110
+ class PathFT(FT):
111
+ def _append_cmd(self, cmd):
112
+ if not isinstance(getattr(self, 'd'), str): self.d = cmd
113
+ else: self.d += f' {cmd}'
114
+ return self
115
+
116
+ def M(self, x, y):
117
+ "Move to."
118
+ return self._append_cmd(f'M{x} {y}')
119
+
120
+ def L(self, x, y):
121
+ "Line to."
122
+ return self._append_cmd(f'L{x} {y}')
123
+
124
+ def H(self, x):
125
+ "Horizontal line to."
126
+ return self._append_cmd(f'H{x}')
127
+
128
+ def V(self, y):
129
+ "Vertical line to."
130
+ return self._append_cmd(f'V{y}')
131
+
132
+ def Z(self):
133
+ "Close path."
134
+ return self._append_cmd('Z')
135
+
136
+ def C(self, x1, y1, x2, y2, x, y):
137
+ "Cubic Bézier curve."
138
+ return self._append_cmd(f'C{x1} {y1} {x2} {y2} {x} {y}')
139
+
140
+ def S(self, x2, y2, x, y):
141
+ "Smooth cubic Bézier curve."
142
+ return self._append_cmd(f'S{x2} {y2} {x} {y}')
143
+
144
+ def Q(self, x1, y1, x, y):
145
+ "Quadratic Bézier curve."
146
+ return self._append_cmd(f'Q{x1} {y1} {x} {y}')
147
+
148
+ def T(self, x, y):
149
+ "Smooth quadratic Bézier curve."
150
+ return self._append_cmd(f'T{x} {y}')
151
+
152
+ def A(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y):
153
+ "Elliptical Arc."
154
+ return self._append_cmd(f'A{rx} {ry} {x_axis_rotation} {large_arc_flag} {sweep_flag} {x} {y}')
155
+
156
+ # %% ../nbs/api/05_svg.ipynb
157
+ @delegates(ft_svg)
158
+ def Path(d='', fill=None, stroke=None, stroke_width=None, **kwargs):
159
+ "Create a standard `path` SVG element. This is a special object"
160
+ return ft_svg('path', fill=fill, stroke=stroke, stroke_width=stroke_width, ft_cls=PathFT, **kwargs)
161
+
162
+ # %% ../nbs/api/05_svg.ipynb
163
+ svg_inb = dict(hx_select="svg>*")
164
+
165
+ # %% ../nbs/api/05_svg.ipynb
166
+ def SvgOob(*args, **kwargs):
167
+ "Wraps an SVG shape as required for an HTMX OOB swap"
168
+ return Template(Svg(*args, **kwargs))
169
+
170
+ # %% ../nbs/api/05_svg.ipynb
171
+ def SvgInb(*args, **kwargs):
172
+ "Wraps an SVG shape as required for an HTMX inband swap"
173
+ return Svg(*args, **kwargs), HtmxResponseHeaders(hx_reselect='svg>*')
@@ -51,8 +51,9 @@ def render_toasts(sess):
51
51
  hx_swap_oob="afterbegin:body")
52
52
 
53
53
  def toast_after(resp, req, sess):
54
- if sk in sess and isinstance(resp, FT): req.injects.append(render_toasts(sess))
54
+ if sk in sess and (not resp or isinstance(resp, (tuple,FT))): req.injects.append(render_toasts(sess))
55
55
 
56
56
  def setup_toasts(app):
57
- app.router.hdrs += (Style(toast_css), Script(toast_js, type="module"))
58
- app.router.after.append(toast_after)
57
+ app.hdrs += (Style(toast_css), Script(toast_js, type="module"))
58
+ app.after.append(toast_after)
59
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: The fastest way to create an HTML app
5
5
  Home-page: https://github.com/AnswerDotAI/fasthtml
6
6
  Author: Jeremy Howard
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: fastcore>=1.7.2
18
+ Requires-Dist: fastcore>=1.7.5
19
19
  Requires-Dist: python-dateutil
20
20
  Requires-Dist: starlette>0.33
21
21
  Requires-Dist: oauthlib
@@ -114,6 +114,31 @@ the new version returned by the second route.
114
114
  This “hypermedia-based” approach to web development is a powerful way to
115
115
  build web applications.
116
116
 
117
+ ### Getting help from AI
118
+
119
+ Because FastHTML is newer than most LLMs, AI systems like Cursor,
120
+ ChatGPT, Claude, and Copilot won’t give useful answers about it. To fix
121
+ that problem, we’ve provided an LLM-friendly guide that teaches them how
122
+ to use FastHTML. To use it, add this link for your AI helper to use:
123
+
124
+ - [/llms-ctx.txt](https://docs.fastht.ml/llms-ctx.txt)
125
+
126
+ This example is in a format based on recommendations from Anthropic for
127
+ use with [Claude
128
+ Projects](https://support.anthropic.com/en/articles/9517075-what-are-projects).
129
+ This works so well that we’ve actually found that Claude can provide
130
+ even better information than our own documentation! For instance, read
131
+ through [this annotated Claude
132
+ chat](https://gist.github.com/jph00/9559b0a563f6a370029bec1d1cc97b74)
133
+ for some great getting-started information, entirely generated from a
134
+ project using the above text file as context.
135
+
136
+ If you use Cursor, type `@doc` then choose “*Add new doc*”, and use the
137
+ /llms-ctx.txt link above. The context file is auto-generated from our
138
+ [`llms.txt`](https://llmstxt.org/) (our proposed standard for providing
139
+ AI-friendly information)—you can generate alternative versions suitable
140
+ for other models as needed.
141
+
117
142
  ## Next Steps
118
143
 
119
144
  Start with the official sources to learn more about FastHTML:
@@ -1,4 +1,4 @@
1
- fastcore>=1.7.2
1
+ fastcore>=1.7.5
2
2
  python-dateutil
3
3
  starlette>0.33
4
4
  oauthlib
@@ -1,10 +1,10 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.5.1
4
+ version = 0.5.2
5
5
  min_python = 3.10
6
6
  license = apache2
7
- requirements = fastcore>=1.7.2 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.0.9 python-multipart beautifulsoup4
7
+ requirements = fastcore>=1.7.5 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.0.9 python-multipart beautifulsoup4
8
8
  dev_requirements = ipython lxml
9
9
  black_formatting = False
10
10
  conda_user = fastai
@@ -1,2 +0,0 @@
1
- __version__ = "0.5.1"
2
- from .core import *
@@ -1,4 +0,0 @@
1
- from fasthtml.components import ft_hx, Svg, AltGlyph, AltGlyphDef, AltGlyphItem, Animate, AnimateColor, AnimateMotion, AnimateTransform, Circle, ClipPath, Color_profile, Cursor, Defs, Desc, Ellipse, FeBlend, FeColorMatrix, FeComponentTransfer, FeComposite, FeConvolveMatrix, FeDiffuseLighting, FeDisplacementMap, FeDistantLight, FeFlood, FeFuncA, FeFuncB, FeFuncG, FeFuncR, FeGaussianBlur, FeImage, FeMerge, FeMergeNode, FeMorphology, FeOffset, FePointLight, FeSpecularLighting, FeSpotLight, FeTile, FeTurbulence, Filter, Font, Font_face, Font_face_format, Font_face_name, Font_face_src, Font_face_uri, ForeignObject, G, Glyph, GlyphRef, Hkern, Image, Line, LinearGradient, Marker, Mask, Metadata, Missing_glyph, Mpath, Pattern, Polygon, Polyline, RadialGradient, Rect, Set, Stop, Switch, Symbol, Text, TextPath, Tref, Tspan, Use, View, Vkern
2
-
3
- def Path(*args, **kwargs): return ft_hx('path', *args, **kwargs)
4
-
File without changes