python-fasthtml 0.1.4__tar.gz → 0.1.6__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 (31) hide show
  1. {python_fasthtml-0.1.4/python_fasthtml.egg-info → python_fasthtml-0.1.6}/PKG-INFO +2 -2
  2. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/__init__.py +1 -2
  3. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/_modidx.py +5 -1
  4. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/components.py +14 -9
  5. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/components.pyi +54 -0
  6. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/core.py +25 -6
  7. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/fastapp.py +4 -2
  8. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/xtend.py +30 -2
  9. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/xtend.pyi +39 -2
  10. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6/python_fasthtml.egg-info}/PKG-INFO +2 -2
  11. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/requires.txt +1 -1
  12. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/settings.ini +2 -2
  13. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/LICENSE +0 -0
  14. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/MANIFEST.in +0 -0
  15. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/README.md +0 -0
  16. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/authmw.py +0 -0
  17. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/cli.py +0 -0
  18. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/common.py +0 -0
  19. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/js.py +0 -0
  20. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/live_reload.py +0 -0
  21. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/oauth.py +0 -0
  22. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/starlette.py +0 -0
  23. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/svg.py +0 -0
  24. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/toaster.py +0 -0
  25. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  26. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  27. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/entry_points.txt +0 -0
  28. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/not-zip-safe +0 -0
  29. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/top_level.txt +0 -0
  30. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/setup.cfg +0 -0
  31. {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.1.4
3
+ Version: 0.1.6
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
@@ -20,7 +20,7 @@ Requires-Dist: python-dateutil
20
20
  Requires-Dist: starlette>0.33
21
21
  Requires-Dist: oauthlib
22
22
  Requires-Dist: itsdangerous
23
- Requires-Dist: uvicorn[standard]
23
+ Requires-Dist: uvicorn[standard]>=0.30
24
24
  Requires-Dist: httpx
25
25
  Requires-Dist: fastlite>=0.0.6
26
26
  Requires-Dist: python-multipart
@@ -1,8 +1,7 @@
1
- __version__ = "0.1.4"
1
+ __version__ = "0.1.6"
2
2
  from .core import *
3
3
  from .authmw import *
4
4
  from .components import *
5
5
  from .xtend import *
6
6
  from .live_reload import *
7
7
  from .toaster import *
8
-
@@ -10,7 +10,8 @@ d = { 'settings': { 'branch': 'main',
10
10
  'fasthtml.cli.railway_deploy': ('cli.html#railway_deploy', 'fasthtml/cli.py'),
11
11
  'fasthtml.cli.railway_link': ('cli.html#railway_link', 'fasthtml/cli.py')},
12
12
  'fasthtml.common': {},
13
- 'fasthtml.components': { 'fasthtml.components.__getattr__': ('components.html#__getattr__', 'fasthtml/components.py'),
13
+ 'fasthtml.components': { 'fasthtml.components.File': ('components.html#file', 'fasthtml/components.py'),
14
+ 'fasthtml.components.__getattr__': ('components.html#__getattr__', 'fasthtml/components.py'),
14
15
  'fasthtml.components._fill_item': ('components.html#_fill_item', 'fasthtml/components.py'),
15
16
  'fasthtml.components.fill_dataclass': ('components.html#fill_dataclass', 'fasthtml/components.py'),
16
17
  'fasthtml.components.fill_form': ('components.html#fill_form', 'fasthtml/components.py'),
@@ -55,6 +56,7 @@ d = { 'settings': { 'branch': 'main',
55
56
  'fasthtml.core._wrap_ws': ('core.html#_wrap_ws', 'fasthtml/core.py'),
56
57
  'fasthtml.core._ws_endp': ('core.html#_ws_endp', 'fasthtml/core.py'),
57
58
  'fasthtml.core._xt_resp': ('core.html#_xt_resp', 'fasthtml/core.py'),
59
+ 'fasthtml.core.cookie': ('core.html#cookie', 'fasthtml/core.py'),
58
60
  'fasthtml.core.date': ('core.html#date', 'fasthtml/core.py'),
59
61
  'fasthtml.core.flat_xt': ('core.html#flat_xt', 'fasthtml/core.py'),
60
62
  'fasthtml.core.form2dict': ('core.html#form2dict', 'fasthtml/core.py'),
@@ -89,6 +91,7 @@ d = { 'settings': { 'branch': 'main',
89
91
  'fasthtml.xtend.Checkbox': ('xtend.html#checkbox', 'fasthtml/xtend.py'),
90
92
  'fasthtml.xtend.Container': ('xtend.html#container', 'fasthtml/xtend.py'),
91
93
  'fasthtml.xtend.DialogX': ('xtend.html#dialogx', 'fasthtml/xtend.py'),
94
+ 'fasthtml.xtend.Favicon': ('xtend.html#favicon', 'fasthtml/xtend.py'),
92
95
  'fasthtml.xtend.Grid': ('xtend.html#grid', 'fasthtml/xtend.py'),
93
96
  'fasthtml.xtend.Group': ('xtend.html#group', 'fasthtml/xtend.py'),
94
97
  'fasthtml.xtend.Hidden': ('xtend.html#hidden', 'fasthtml/xtend.py'),
@@ -96,6 +99,7 @@ d = { 'settings': { 'branch': 'main',
96
99
  'fasthtml.xtend.Script': ('xtend.html#script', 'fasthtml/xtend.py'),
97
100
  'fasthtml.xtend.ScriptX': ('xtend.html#scriptx', 'fasthtml/xtend.py'),
98
101
  'fasthtml.xtend.Search': ('xtend.html#search', 'fasthtml/xtend.py'),
102
+ 'fasthtml.xtend.Socials': ('xtend.html#socials', 'fasthtml/xtend.py'),
99
103
  'fasthtml.xtend.Style': ('xtend.html#style', 'fasthtml/xtend.py'),
100
104
  'fasthtml.xtend.StyleX': ('xtend.html#stylex', 'fasthtml/xtend.py'),
101
105
  'fasthtml.xtend.Titled': ('xtend.html#titled', 'fasthtml/xtend.py'),
@@ -1,9 +1,9 @@
1
1
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_components.ipynb.
2
2
 
3
3
  # %% auto 0
4
- __all__ = ['voids', 'named', 'html_attrs', 'hx_attrs', 'show', 'xt_html', 'xt_hx', 'fill_form', 'fill_dataclass', 'find_inputs',
5
- 'html2xt', 'A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi', 'Bdo',
6
- 'Blockquote', 'Body', 'Br', 'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data',
4
+ __all__ = ['voids', 'named', 'html_attrs', 'hx_attrs', 'show', 'xt_html', 'xt_hx', 'File', 'fill_form', 'fill_dataclass',
5
+ 'find_inputs', 'html2xt', 'A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi',
6
+ 'Bdo', 'Blockquote', 'Body', 'Br', 'Button', 'Canvas', 'Caption', 'Cite', 'Code', 'Col', 'Colgroup', 'Data',
7
7
  'Datalist', 'Dd', 'Del', 'Details', 'Dfn', 'Dialog', 'Div', 'Dl', 'Dt', 'Em', 'Embed', 'Fencedframe',
8
8
  'Fieldset', 'Figcaption', 'Figure', 'Footer', 'Form', 'H1', 'Head', 'Header', 'Hgroup', 'Hr', 'Html', 'I',
9
9
  'Iframe', 'Img', 'Input', 'Ins', 'Kbd', 'Label', 'Legend', 'Li', 'Link', 'Main', 'Map', 'Mark', 'Menu',
@@ -65,7 +65,12 @@ _all_ = [
65
65
  'Td', 'Template', 'Textarea', 'Tfoot', 'Th', 'Thead', 'Time', 'Title', 'Tr', 'Track', 'U', 'Ul', 'Var', 'Video', 'Wbr']
66
66
  for o in _all_: _g[o] = partial(xt_hx, o.lower())
67
67
 
68
- # %% ../nbs/01_components.ipynb 12
68
+ # %% ../nbs/01_components.ipynb 9
69
+ def File(fname):
70
+ "Use the unescaped text in file `fname` directly"
71
+ return NotStr(Path(fname).read_text())
72
+
73
+ # %% ../nbs/01_components.ipynb 14
69
74
  def _fill_item(item, obj):
70
75
  if not isinstance(item,list): return item
71
76
  tag,cs,attr = item
@@ -81,19 +86,19 @@ def _fill_item(item, obj):
81
86
  if tag=='textarea': cs=(val,)
82
87
  return XT(tag,cs,attr)
83
88
 
84
- # %% ../nbs/01_components.ipynb 13
89
+ # %% ../nbs/01_components.ipynb 15
85
90
  def fill_form(form:XT, obj)->XT:
86
91
  "Fills named items in `form` using attributes in `obj`"
87
92
  if not isinstance(obj,dict): obj = asdict(obj)
88
93
  return _fill_item(form, obj)
89
94
 
90
- # %% ../nbs/01_components.ipynb 15
95
+ # %% ../nbs/01_components.ipynb 17
91
96
  def fill_dataclass(src, dest):
92
97
  "Modifies dataclass in-place and returns it"
93
98
  for nm,val in asdict(src).items(): setattr(dest, nm, val)
94
99
  return dest
95
100
 
96
- # %% ../nbs/01_components.ipynb 17
101
+ # %% ../nbs/01_components.ipynb 19
97
102
  def find_inputs(e, tags='input', **kw):
98
103
  # Recursively find all elements in `e` with `tags` and attrs matching `kw`
99
104
  if not isinstance(e, (list,tuple)): return []
@@ -107,14 +112,14 @@ def find_inputs(e, tags='input', **kw):
107
112
  for o in cs: inputs += find_inputs(o, tags, **kw)
108
113
  return inputs
109
114
 
110
- # %% ../nbs/01_components.ipynb 21
115
+ # %% ../nbs/01_components.ipynb 23
111
116
  def __getattr__(tag):
112
117
  if tag.startswith('_') or tag[0].islower(): raise AttributeError
113
118
  tag = tag.replace("_", "-")
114
119
  def _f(*c, target_id=None, **kwargs): return xt_hx(tag, *c, target_id=target_id, **kwargs)
115
120
  return _f
116
121
 
117
- # %% ../nbs/01_components.ipynb 22
122
+ # %% ../nbs/01_components.ipynb 24
118
123
  def html2xt(html):
119
124
  rev_map = {'class': 'cls', 'for': 'fr'}
120
125
 
@@ -1,3 +1,57 @@
1
+ __all__ = ['voids', 'named', 'html_attrs', 'hx_attrs', 'show', 'xt_html', 'xt_hx', 'File', 'fill_form', 'fill_dataclass', 'find_inputs', 'html2xt', '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', '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
+ from dataclasses import dataclass, asdict, is_dataclass, make_dataclass, replace, astuple, MISSING
3
+ from bs4 import BeautifulSoup
4
+ from fastcore.utils import *
5
+ from fastcore.xml import *
6
+ from fastcore.meta import use_kwargs, delegates
7
+ import types
8
+ try:
9
+ from IPython import display
10
+ except ImportError:
11
+ display = None
12
+
13
+ def show(xt, *rest):
14
+ ...
15
+ voids = set('area base br col command embed hr img input keygen link meta param source track wbr !doctype'.split())
16
+ named = set('a button form frame iframe img input map meta object param select textarea'.split())
17
+ html_attrs = 'id cls title style accesskey contenteditable dir draggable enterkeyhint hidden inert inputmode lang popover spellcheck tabindex translate'.split()
18
+ hx_attrs = 'get post put delete patch trigger target swap include select indicator push_url confirm disable replace_url on'
19
+ hx_attrs = html_attrs + [f'hx_{o}' for o in hx_attrs.split()]
20
+
21
+ def xt_html(tag: str, *c, id=None, cls=None, title=None, style=None, **kwargs):
22
+ ...
23
+
24
+ @use_kwargs(hx_attrs, keep=True)
25
+ def xt_hx(tag: str, *c, target_id=None, **kwargs):
26
+ ...
27
+ _g = globals()
28
+ _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', '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']
29
+ for o in _all_:
30
+ _g[o] = partial(xt_hx, o.lower())
31
+
32
+ def File(fname):
33
+ """Use the unescaped text in file `fname` directly"""
34
+ ...
35
+
36
+ def _fill_item(item, obj):
37
+ ...
38
+
39
+ def fill_form(form: XT, obj) -> XT:
40
+ """Fills named items in `form` using attributes in `obj`"""
41
+ ...
42
+
43
+ def fill_dataclass(src, dest):
44
+ """Modifies dataclass in-place and returns it"""
45
+ ...
46
+
47
+ def find_inputs(e, tags='input', **kw):
48
+ ...
49
+
50
+ def __getattr__(tag):
51
+ ...
52
+
53
+ def html2xt(html):
54
+ ...
1
55
  def xt_html(tag: str, *c, id:str|None=None, cls:str|None=None, title:str|None=None, style:str|None=None, accesskey:str|None=None, contenteditable:str|None=None, dir:str|None=None, draggable:str|None=None, enterkeyhint:str|None=None, hidden:str|None=None, inert:str|None=None, inputmode:str|None=None, lang:str|None=None, popover:str|None=None, spellcheck:str|None=None, tabindex:str|None=None, translate:str|None=None, id:str|None=None, cls:str|None=None, title:str|None=None, style:str|None=None, accesskey:str|None=None, contenteditable:str|None=None, dir:str|None=None, draggable:str|None=None, enterkeyhint:str|None=None, hidden:str|None=None, inert:str|None=None, inputmode:str|None=None, lang:str|None=None, popover:str|None=None, spellcheck:str|None=None, tabindex:str|None=None, translate:str|None=None, hx_get:str|None=None, hx_post:str|None=None, hx_put:str|None=None, hx_delete:str|None=None, hx_patch:str|None=None, hx_trigger:str|None=None, hx_target:str|None=None, hx_swap:str|None=None, hx_include:str|None=None, hx_select:str|None=None, hx_indicator:str|None=None, hx_push_url:str|None=None, hx_confirm:str|None=None, hx_disable:str|None=None, hx_replace_url:str|None=None, hx_on:str|None=None, **kwargs): ...
2
56
  def xt_hx(tag: str, *c, id:str|None=None, cls:str|None=None, title:str|None=None, style:str|None=None, accesskey:str|None=None, contenteditable:str|None=None, dir:str|None=None, draggable:str|None=None, enterkeyhint:str|None=None, hidden:str|None=None, inert:str|None=None, inputmode:str|None=None, lang:str|None=None, popover:str|None=None, spellcheck:str|None=None, tabindex:str|None=None, translate:str|None=None, id:str|None=None, cls:str|None=None, title:str|None=None, style:str|None=None, accesskey:str|None=None, contenteditable:str|None=None, dir:str|None=None, draggable:str|None=None, enterkeyhint:str|None=None, hidden:str|None=None, inert:str|None=None, inputmode:str|None=None, lang:str|None=None, popover:str|None=None, spellcheck:str|None=None, tabindex:str|None=None, translate:str|None=None, hx_get:str|None=None, hx_post:str|None=None, hx_put:str|None=None, hx_delete:str|None=None, hx_patch:str|None=None, hx_trigger:str|None=None, hx_target:str|None=None, hx_swap:str|None=None, hx_include:str|None=None, hx_select:str|None=None, hx_indicator:str|None=None, hx_push_url:str|None=None, hx_confirm:str|None=None, hx_disable:str|None=None, hx_replace_url:str|None=None, hx_on:str|None=None, **kwargs): ...
3
57
  def A(*c, name:str|None=None, id:str|None=None, cls:str|None=None, title:str|None=None, style:str|None=None, accesskey:str|None=None, contenteditable:str|None=None, dir:str|None=None, draggable:str|None=None, enterkeyhint:str|None=None, hidden:str|None=None, inert:str|None=None, inputmode:str|None=None, lang:str|None=None, popover:str|None=None, spellcheck:str|None=None, tabindex:str|None=None, translate:str|None=None, id:str|None=None, cls:str|None=None, title:str|None=None, style:str|None=None, accesskey:str|None=None, contenteditable:str|None=None, dir:str|None=None, draggable:str|None=None, enterkeyhint:str|None=None, hidden:str|None=None, inert:str|None=None, inputmode:str|None=None, lang:str|None=None, popover:str|None=None, spellcheck:str|None=None, tabindex:str|None=None, translate:str|None=None, hx_get:str|None=None, hx_post:str|None=None, hx_put:str|None=None, hx_delete:str|None=None, hx_patch:str|None=None, hx_trigger:str|None=None, hx_target:str|None=None, hx_swap:str|None=None, hx_include:str|None=None, hx_select:str|None=None, hx_indicator:str|None=None, hx_push_url:str|None=None, hx_confirm:str|None=None, hx_disable:str|None=None, hx_replace_url:str|None=None, hx_on:str|None=None, **kwargs): ...
@@ -3,7 +3,7 @@
3
3
  # %% auto 0
4
4
  __all__ = ['empty', 'htmx_hdrs', 'htmxscr', 'htmxwsscr', 'surrsrc', 'scopesrc', 'viewport', 'charset', 'all_meths',
5
5
  'is_typeddict', 'is_namedtuple', 'date', 'snake2hyphens', 'HtmxHeaders', 'str2int', 'HttpHeader',
6
- 'form2dict', 'flat_xt', 'Beforeware', 'WS_RouteX', 'RouteX', 'RouterX', 'get_key', 'FastHTML',
6
+ 'form2dict', 'flat_xt', 'Beforeware', 'WS_RouteX', 'RouteX', 'RouterX', 'get_key', 'FastHTML', 'cookie',
7
7
  'reg_re_param', 'MiddlewareBase']
8
8
 
9
9
  # %% ../nbs/00_core.ipynb 3
@@ -20,7 +20,7 @@ from dataclasses import dataclass,fields,is_dataclass,MISSING,asdict
20
20
  from collections import namedtuple
21
21
  from inspect import isfunction,ismethod,signature,Parameter,get_annotations
22
22
  from functools import wraps, partialmethod
23
- from copy import deepcopy
23
+ from http import cookies
24
24
 
25
25
  from .starlette import *
26
26
 
@@ -162,7 +162,8 @@ async def _find_p(req, arg:str, p:Parameter):
162
162
  # We can cast str and list[str] to types; otherwise just return what we have
163
163
  if not isinstance(res, (list,str)) or anno is empty: return res
164
164
  anno = _fix_anno(anno)
165
- return [anno(o) for o in res] if isinstance(res,list) else anno(res)
165
+ try: return [anno(o) for o in res] if isinstance(res,list) else anno(res)
166
+ except ValueError: raise HTTPException(404, req.url.path) from None
166
167
 
167
168
  async def _wrap_req(req, params):
168
169
  return [await _find_p(req, arg, p) for arg,p in params.items()]
@@ -194,7 +195,7 @@ def _wrap_resp(req, resp, cls, hdrs, **bodykw):
194
195
  if isinstance(resp, FileResponse) and not os.path.exists(resp.path): raise HTTPException(404, resp.path)
195
196
  if isinstance(resp, Response): return resp
196
197
  if cls is not empty: return cls(resp)
197
- if isinstance(resp, (list,tuple)) or hasattr(resp, '__xt__'): return _xt_resp(req, resp, hdrs, **bodykw)
198
+ if isinstance(resp, (list,tuple,HttpHeader)) or hasattr(resp, '__xt__'): return _xt_resp(req, resp, hdrs, **bodykw)
198
199
  if isinstance(resp, str): cls = HTMLResponse
199
200
  elif isinstance(resp, Mapping): cls = JSONResponse
200
201
  else:
@@ -374,16 +375,34 @@ all_meths = 'get post put delete patch head trace options'.split()
374
375
  for o in all_meths: setattr(FastHTML, o, partialmethod(FastHTML.route, methods=o))
375
376
 
376
377
  # %% ../nbs/00_core.ipynb 59
378
+ def cookie(key: str, value="", max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, samesite="lax",):
379
+ "Create a 'set-cookie' `HttpHeader`"
380
+ cookie = cookies.SimpleCookie()
381
+ cookie[key] = value
382
+ if max_age is not None: cookie[key]["max-age"] = max_age
383
+ if expires is not None:
384
+ cookie[key]["expires"] = format_datetime(expires, usegmt=True) if isinstance(expires, datetime) else expires
385
+ if path is not None: cookie[key]["path"] = path
386
+ if domain is not None: cookie[key]["domain"] = domain
387
+ if secure: cookie[key]["secure"] = True
388
+ if httponly: cookie[key]["httponly"] = True
389
+ if samesite is not None:
390
+ assert samesite.lower() in [ "strict", "lax", "none", ], "must be 'strict', 'lax' or 'none'"
391
+ cookie[key]["samesite"] = samesite
392
+ cookie_val = cookie.output(header="").strip()
393
+ return HttpHeader("set-cookie", cookie_val)
394
+
395
+ # %% ../nbs/00_core.ipynb 60
377
396
  def reg_re_param(m, s):
378
397
  cls = get_class(f'{m}Conv', sup=StringConvertor, regex=s)
379
398
  register_url_convertor(m, cls())
380
399
 
381
- # %% ../nbs/00_core.ipynb 60
400
+ # %% ../nbs/00_core.ipynb 61
382
401
  # Starlette doesn't have the '?', so it chomps the whole remaining URL
383
402
  reg_re_param("path", ".*?")
384
403
  reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|xml|html")
385
404
 
386
- # %% ../nbs/00_core.ipynb 61
405
+ # %% ../nbs/00_core.ipynb 62
387
406
  class MiddlewareBase:
388
407
  async def __call__(self, scope, receive, send) -> None:
389
408
  if scope["type"] not in ["http", "websocket"]:
@@ -15,7 +15,9 @@ def get_tbl(dt, nm, schema):
15
15
  def fast_app(db=None, render=None, hdrs=None, tbls=None, before=None, middleware=None, live=False, debug=False, routes=None, exception_handlers=None,
16
16
  on_startup=None, on_shutdown=None, lifespan=None, default_hdrs=True, secret_key=None, session_cookie='session_', max_age=365*24*3600,
17
17
  sess_path='/', same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', **kwargs):
18
- h = (picolink,)
18
+
19
+ h = ()
20
+ if default_hdrs: h += (picolink, )
19
21
  if hdrs: h += tuple(hdrs)
20
22
  app_cls = FastHTMLWithLiveReload if live else FastHTML
21
23
  app = app_cls(hdrs=h, before=before, middleware=middleware, debug=debug, routes=routes, exception_handlers=exception_handlers,
@@ -43,7 +45,7 @@ def run_uv(fname=None, app='app', host='0.0.0.0', port=None, reload=True):
43
45
  if not fname: fname = Path(glb.get('__file__', '')).stem
44
46
  if not port: port=int(os.getenv("PORT", default=5001))
45
47
  print(f'Link: http://{"localhost" if host=="0.0.0.0" else host}:{port}')
46
- uvicorn.run(f"{fname}:app", host=host, port=port, reload=reload)
48
+ uvicorn.run(f"{fname}:{app}", host=host, port=port, reload=reload)
47
49
 
48
50
  def clear(id): return Div(hx_swap_oob='innerHTML', id=id)
49
51
  def ContainerX(*cs, **kwargs): return Main(*cs, **kwargs, cls='container', hx_push_url='true', hx_swap_oob='true', id='main')
@@ -3,7 +3,7 @@
3
3
  # %% auto 0
4
4
  __all__ = ['picocss', 'picolink', 'picocondcss', 'picocondlink', 'set_pico_cls', 'Html', 'A', 'AX', 'Checkbox', 'Card', 'Group',
5
5
  'Search', 'Grid', 'DialogX', 'Hidden', 'Container', 'Script', 'Style', 'double_braces', 'undouble_braces',
6
- 'loose_format', 'ScriptX', 'replace_css_vars', 'StyleX', 'run_js', 'Titled', 'jsd']
6
+ 'loose_format', 'ScriptX', 'replace_css_vars', 'StyleX', 'run_js', 'Titled', 'Socials', 'Favicon', 'jsd']
7
7
 
8
8
  # %% ../nbs/02_xtend.ipynb 2
9
9
  from dataclasses import dataclass, asdict
@@ -148,7 +148,7 @@ def loose_format(s, **kw):
148
148
 
149
149
  # %% ../nbs/02_xtend.ipynb 34
150
150
  def ScriptX(fname, type=None, _async=None, defer=None, charset=None, crossorigin=None, integrity=None, **kw):
151
- "Create a Script from the text of a file"
151
+ "A `script` element with contents read from `fname`"
152
152
  attrs = ['src', 'type', 'async', 'defer', 'charset', 'crossorigin', 'integrity', 'nomodule']
153
153
  scr_kw = {k:kw.pop(k) for k in attrs if k in kw}
154
154
  s = loose_format(Path(fname).read_text(), **kw)
@@ -156,6 +156,7 @@ def ScriptX(fname, type=None, _async=None, defer=None, charset=None, crossorigin
156
156
 
157
157
  # %% ../nbs/02_xtend.ipynb 35
158
158
  def replace_css_vars(css, pre='tpl', **kwargs):
159
+ "Replace `var(--)` CSS variables with `kwargs` if name prefix matches `pre`"
159
160
  def replace_var(m):
160
161
  var_name = m.group(1).replace('-', '_')
161
162
  return kwargs.get(var_name, m.group(0))
@@ -163,6 +164,7 @@ def replace_css_vars(css, pre='tpl', **kwargs):
163
164
 
164
165
  # %% ../nbs/02_xtend.ipynb 36
165
166
  def StyleX(fname, **kw):
167
+ "A `style` element with contents read from `fname` and variables replaced from `kw`"
166
168
  s = Path(fname).read_text()
167
169
  attrs = ['type', 'media', 'scoped', 'title', 'nonce', 'integrity', 'crossorigin']
168
170
  sty_kw = {k:kw.pop(k) for k in attrs if k in kw}
@@ -182,6 +184,32 @@ def Titled(title:str="FastHTML app", *args, **kwargs)->XT:
182
184
  return Title(title), Main(H1(title), *args, cls="container", **kwargs)
183
185
 
184
186
  # %% ../nbs/02_xtend.ipynb 39
187
+ def Socials(title, site_name, description, image, url, w=1200, h=630, twitter_site=None, creator=None, card='summary'):
188
+ "OG and Twitter social card headers"
189
+ res = [Meta(property='og:image', content=image),
190
+ Meta(property='og:site_name', content=site_name),
191
+ Meta(property='og:image:type', content='image/png'),
192
+ Meta(property='og:image:width', content=w),
193
+ Meta(property='og:image:height', content=h),
194
+ Meta(property='og:type', content='website'),
195
+ Meta(property='og:url', content=url),
196
+ Meta(property='og:title', content=title),
197
+ Meta(property='og:description', content=description),
198
+ Meta(name='twitter:image', content=image),
199
+ Meta(name='twitter:card', content=card),
200
+ Meta(name='twitter:title', content=title),
201
+ Meta(name='twitter:description', content=description)]
202
+ if twitter_site is not None: res.append(Meta(name='twitter:site', content=twitter_site))
203
+ if creator is not None: res.append(Meta(name='twitter:creator', content=creator))
204
+ return tuple(res)
205
+
206
+ # %% ../nbs/02_xtend.ipynb 40
207
+ def Favicon(light_icon, dark_icon):
208
+ "Light and dark favicon headers"
209
+ return (Link(rel='icon', type='image/x-ico', href=light_icon, media='(prefers-color-scheme: light)'),
210
+ Link(rel='icon', type='image/x-ico', href=dark_icon, media='(prefers-color-scheme: dark)'))
211
+
212
+ # %% ../nbs/02_xtend.ipynb 41
185
213
  def jsd(org, repo, root, path, prov='gh', typ='script', ver=None, esm=False, **kwargs)->XT:
186
214
  "jsdelivr `Script` or CSS `Link` tag, or URL"
187
215
  ver = '@'+ver if ver else ''
@@ -1,6 +1,7 @@
1
- __all__ = ['picocss', 'picolink', 'picocondcss', 'picocondlink', 'set_pico_cls', 'Html', 'A', 'AX', 'Checkbox', 'Card', 'Group', 'Search', 'Grid', 'DialogX', 'Hidden', 'Container', 'Script', 'Style', 'Titled', 'jsd']
1
+ __all__ = ['picocss', 'picolink', 'picocondcss', 'picocondlink', 'set_pico_cls', 'Html', 'A', 'AX', 'Checkbox', 'Card', 'Group', 'Search', 'Grid', 'DialogX', 'Hidden', 'Container', 'Script', 'Style', 'double_braces', 'undouble_braces', 'loose_format', 'ScriptX', 'replace_css_vars', 'StyleX', 'run_js', 'Titled', 'Socials', 'Favicon', 'jsd']
2
2
  from dataclasses import dataclass, asdict
3
3
  from fastcore.utils import *
4
+ from fastcore.xtras import partial_format
4
5
  from fastcore.xml import *
5
6
  from fastcore.meta import use_kwargs, delegates
6
7
  from .components import *
@@ -64,14 +65,50 @@ def Script(code: str='', *, id=None, cls=None, title=None, style=None, **kwargs)
64
65
  """A Script tag that doesn't escape its code"""
65
66
  ...
66
67
 
67
- def Style(css: str='', *, id=None, cls=None, title=None, style=None, **kwargs) -> XT:
68
+ def Style(*c, id=None, cls=None, title=None, style=None, **kwargs) -> XT:
68
69
  """A Style tag that doesn't escape its code"""
69
70
  ...
70
71
 
72
+ def double_braces(s):
73
+ """Convert single braces to double braces if next to special chars or newline"""
74
+ ...
75
+
76
+ def undouble_braces(s):
77
+ """Convert double braces to single braces if next to special chars or newline"""
78
+ ...
79
+
80
+ def loose_format(s, **kw):
81
+ """String format `s` using `kw`, without being strict about braces outside of template params"""
82
+ ...
83
+
84
+ def ScriptX(fname, type=None, _async=None, defer=None, charset=None, crossorigin=None, integrity=None, **kw):
85
+ """A `script` element with contents read from `fname`"""
86
+ ...
87
+
88
+ def replace_css_vars(css, pre='tpl', **kwargs):
89
+ """Replace `var(--)` CSS variables with `kwargs` if name prefix matches `pre`"""
90
+ ...
91
+
92
+ def StyleX(fname, **kw):
93
+ """A `style` element with contents read from `fname` and variables replaced from `kw`"""
94
+ ...
95
+
96
+ def run_js(js, id=None, **kw):
97
+ """Run `js` script, auto-generating `id` based on name of caller if needed, and js-escaping any `kw` params"""
98
+ ...
99
+
71
100
  def Titled(title: str='FastHTML app', *args, target_id=None, id=None, cls=None, style=None, accesskey=None, contenteditable=None, dir=None, draggable=None, enterkeyhint=None, hidden=None, inert=None, inputmode=None, lang=None, popover=None, spellcheck=None, tabindex=None, translate=None, hx_get=None, hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, hx_trigger=None, hx_target=None, hx_swap=None, hx_include=None, hx_select=None, hx_indicator=None, hx_push_url=None, hx_confirm=None, hx_disable=None, hx_replace_url=None, hx_on=None, **kwargs) -> XT:
72
101
  """An HTML partial containing a `Title`, and `H1`, and any provided children"""
73
102
  ...
74
103
 
104
+ def Socials(title, site_name, description, image, url, w=1200, h=630, twitter_site=None, creator=None, card='summary'):
105
+ """OG and Twitter social card headers"""
106
+ ...
107
+
108
+ def Favicon(light_icon, dark_icon):
109
+ """Light and dark favicon headers"""
110
+ ...
111
+
75
112
  def jsd(org, repo, root, path, prov='gh', typ='script', ver=None, esm=False, **kwargs) -> XT:
76
113
  """jsdelivr `Script` or CSS `Link` tag, or URL"""
77
114
  ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.1.4
3
+ Version: 0.1.6
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
@@ -20,7 +20,7 @@ Requires-Dist: python-dateutil
20
20
  Requires-Dist: starlette>0.33
21
21
  Requires-Dist: oauthlib
22
22
  Requires-Dist: itsdangerous
23
- Requires-Dist: uvicorn[standard]
23
+ Requires-Dist: uvicorn[standard]>=0.30
24
24
  Requires-Dist: httpx
25
25
  Requires-Dist: fastlite>=0.0.6
26
26
  Requires-Dist: python-multipart
@@ -3,7 +3,7 @@ python-dateutil
3
3
  starlette>0.33
4
4
  oauthlib
5
5
  itsdangerous
6
- uvicorn[standard]
6
+ uvicorn[standard]>=0.30
7
7
  httpx
8
8
  fastlite>=0.0.6
9
9
  python-multipart
@@ -1,10 +1,10 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.1.4
4
+ version = 0.1.6
5
5
  min_python = 3.10
6
6
  license = apache2
7
- requirements = fastcore>=1.5.46 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard] httpx fastlite>=0.0.6 python-multipart beautifulsoup4
7
+ requirements = fastcore>=1.5.46 python-dateutil starlette>0.33 oauthlib itsdangerous uvicorn[standard]>=0.30 httpx fastlite>=0.0.6 python-multipart beautifulsoup4
8
8
  dev_requirements = ipython lxml
9
9
  black_formatting = False
10
10
  conda_user = fastai
File without changes