python-fasthtml 0.1.5__tar.gz → 0.1.7__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 (32) hide show
  1. {python_fasthtml-0.1.5/python_fasthtml.egg-info → python-fasthtml-0.1.7}/PKG-INFO +1 -1
  2. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/__init__.py +1 -1
  3. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/_modidx.py +3 -1
  4. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/components.py +18 -13
  5. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/components.pyi +5 -1
  6. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/core.py +10 -8
  7. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/fastapp.py +4 -3
  8. python-fasthtml-0.1.7/fasthtml/xt.py +4 -0
  9. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/xtend.py +3 -1
  10. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/xtend.pyi +3 -1
  11. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7/python_fasthtml.egg-info}/PKG-INFO +1 -1
  12. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/python_fasthtml.egg-info/SOURCES.txt +1 -0
  13. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/settings.ini +1 -1
  14. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/LICENSE +0 -0
  15. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/MANIFEST.in +0 -0
  16. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/README.md +0 -0
  17. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/authmw.py +0 -0
  18. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/cli.py +0 -0
  19. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/common.py +0 -0
  20. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/js.py +0 -0
  21. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/live_reload.py +0 -0
  22. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/oauth.py +0 -0
  23. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/starlette.py +0 -0
  24. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/svg.py +0 -0
  25. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/fasthtml/toaster.py +0 -0
  26. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  27. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/python_fasthtml.egg-info/entry_points.txt +0 -0
  28. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/python_fasthtml.egg-info/not-zip-safe +0 -0
  29. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/python_fasthtml.egg-info/requires.txt +0 -0
  30. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/python_fasthtml.egg-info/top_level.txt +0 -0
  31. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/setup.cfg +0 -0
  32. {python_fasthtml-0.1.5 → python-fasthtml-0.1.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.1.5
3
+ Version: 0.1.7
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
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.5"
1
+ __version__ = "0.1.7"
2
2
  from .core import *
3
3
  from .authmw import *
4
4
  from .components import *
@@ -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'),
@@ -84,6 +85,7 @@ d = { 'settings': { 'branch': 'main',
84
85
  'fasthtml.starlette': {},
85
86
  'fasthtml.svg': {},
86
87
  'fasthtml.toaster': {},
88
+ 'fasthtml.xt': {},
87
89
  'fasthtml.xtend': { 'fasthtml.xtend.A': ('xtend.html#a', 'fasthtml/xtend.py'),
88
90
  'fasthtml.xtend.AX': ('xtend.html#ax', 'fasthtml/xtend.py'),
89
91
  'fasthtml.xtend.Card': ('xtend.html#card', '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 13
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 14
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 16
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 18
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,18 +112,18 @@ 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 22
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 23
122
+ # %% ../nbs/01_components.ipynb 24
118
123
  def html2xt(html):
119
124
  rev_map = {'class': 'cls', 'for': 'fr'}
120
125
 
121
- def _parse(elm, lvl=0):
126
+ def _parse(elm, lvl=0, indent=4):
122
127
  if isinstance(elm, str): return repr(elm.strip()) if elm.strip() else ''
123
128
  if isinstance(elm, list): return '\n'.join(_parse(o, lvl) for o in elm)
124
129
  tag_name = elm.name.capitalize()
@@ -127,14 +132,14 @@ def html2xt(html):
127
132
  cs = [repr(c.strip()) if isinstance(c, str) else _parse(c, lvl+1)
128
133
  for c in cts if str(c).strip()]
129
134
  attrs = []
130
- for key, value in elm.attrs.items():
135
+ for key, value in sorted(elm.attrs.items(), key=lambda x: x[0]=='class'):
131
136
  if isinstance(value,(tuple,list)): value = " ".join(value)
132
137
  attrs.append(f'{rev_map.get(key, key).replace("-", "_")}={value!r}')
133
- spc = " "*lvl*2
138
+ spc = " "*lvl*indent
134
139
  onlychild = not cts or (len(cts)==1 and isinstance(cts[0],str))
135
140
  j = ', ' if onlychild else f',\n{spc}'
136
141
  inner = j.join(filter(None, cs+attrs))
137
142
  if onlychild: return f'{tag_name}({inner})'
138
- return f'{tag_name}(\n{spc}{inner}\n{" "*(lvl-1)*2})'
143
+ return f'{tag_name}(\n{spc}{inner}\n{" "*(lvl-1)*indent})'
139
144
 
140
145
  return _parse(BeautifulSoup(html.strip(), 'html.parser'), 1)
@@ -1,4 +1,4 @@
1
- __all__ = ['voids', 'named', 'html_attrs', 'hx_attrs', 'show', 'xt_html', 'xt_hx', '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']
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
2
  from dataclasses import dataclass, asdict, is_dataclass, make_dataclass, replace, astuple, MISSING
3
3
  from bs4 import BeautifulSoup
4
4
  from fastcore.utils import *
@@ -29,6 +29,10 @@ _all_ = ['A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base
29
29
  for o in _all_:
30
30
  _g[o] = partial(xt_hx, o.lower())
31
31
 
32
+ def File(fname):
33
+ """Use the unescaped text in file `fname` directly"""
34
+ ...
35
+
32
36
  def _fill_item(item, obj):
33
37
  ...
34
38
 
@@ -127,7 +127,7 @@ async def _from_body(req, p):
127
127
  anno = p.annotation
128
128
  # Get the fields and types of type `anno`, if available
129
129
  d = _annotations(anno)
130
- cargs = {k:_form_arg(k, v, d) for k,v in form2dict(form).items()}
130
+ cargs = {k:_form_arg(k, v, d) for k,v in form2dict(form).items() if not d or k in d}
131
131
  return anno(**cargs)
132
132
 
133
133
  # %% ../nbs/00_core.ipynb 37
@@ -150,15 +150,17 @@ async def _find_p(req, arg:str, p:Parameter):
150
150
  return None
151
151
  # Look through path, cookies, headers, session, query, and body in that order
152
152
  res = req.path_params.get(arg, None)
153
- if res is empty or res is None: res = req.cookies.get(arg, None)
154
- if res is empty or res is None: res = req.headers.get(snake2hyphens(arg), None)
155
- if res is empty or res is None: res = nested_idx(req.scope, 'session', arg) or None
156
- if res is empty or res is None: res = req.query_params.get(arg, None)
157
- if res is empty or res is None:
153
+ if res in (empty,None): res = req.cookies.get(arg, None)
154
+ if res in (empty,None): res = req.headers.get(snake2hyphens(arg), None)
155
+ if res in (empty,None): res = nested_idx(req.scope, 'session', arg) or None
156
+ if res in (empty,None): res = req.query_params.get(arg, None)
157
+ if res in (empty,None):
158
158
  frm = await req.form()
159
159
  res = _formitem(frm, arg)
160
- # Use default param if needed
161
- if res is empty or res is None: res = p.default
160
+ # Raise 400 error if the param does not include a default
161
+ if (res in (empty,None)) and p.default is empty: raise HTTPException(400, f"Missing required field: {arg}")
162
+ # If we have a default, return that if we have no value
163
+ if res in (empty,None): res = p.default
162
164
  # We can cast str and list[str] to types; otherwise just return what we have
163
165
  if not isinstance(res, (list,str)) or anno is empty: return res
164
166
  anno = _fix_anno(anno)
@@ -14,14 +14,15 @@ def get_tbl(dt, nm, schema):
14
14
 
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
- sess_path='/', same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', **kwargs):
18
- h = (picolink,)
17
+ pico=None, sess_path='/', same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', bodykw=None, **kwargs):
18
+
19
+ h = (picolink,) if pico or (pico is None and default_hdrs) else ()
19
20
  if hdrs: h += tuple(hdrs)
20
21
  app_cls = FastHTMLWithLiveReload if live else FastHTML
21
22
  app = app_cls(hdrs=h, before=before, middleware=middleware, debug=debug, routes=routes, exception_handlers=exception_handlers,
22
23
  on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan, default_hdrs=default_hdrs, secret_key=secret_key,
23
24
  session_cookie=session_cookie, max_age=max_age, sess_path=sess_path, same_site=same_site, sess_https_only=sess_https_only,
24
- sess_domain=sess_domain, key_fname=key_fname)
25
+ sess_domain=sess_domain, key_fname=key_fname, **(bodykw or {}))
25
26
  @app.route("/{fname:path}.{ext:static}")
26
27
  async def get(fname:str, ext:str): return FileResponse(f'{fname}.{ext}')
27
28
  if not db: return app,app.route
@@ -0,0 +1,4 @@
1
+ from fastcore.xml import *
2
+ from .components import *
3
+ from .xtend import *
4
+
@@ -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}
@@ -82,13 +82,15 @@ def loose_format(s, **kw):
82
82
  ...
83
83
 
84
84
  def ScriptX(fname, type=None, _async=None, defer=None, charset=None, crossorigin=None, integrity=None, **kw):
85
- """Create a Script from the text of a file"""
85
+ """A `script` element with contents read from `fname`"""
86
86
  ...
87
87
 
88
88
  def replace_css_vars(css, pre='tpl', **kwargs):
89
+ """Replace `var(--)` CSS variables with `kwargs` if name prefix matches `pre`"""
89
90
  ...
90
91
 
91
92
  def StyleX(fname, **kw):
93
+ """A `style` element with contents read from `fname` and variables replaced from `kw`"""
92
94
  ...
93
95
 
94
96
  def run_js(js, id=None, **kw):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fasthtml
3
- Version: 0.1.5
3
+ Version: 0.1.7
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
@@ -18,6 +18,7 @@ fasthtml/oauth.py
18
18
  fasthtml/starlette.py
19
19
  fasthtml/svg.py
20
20
  fasthtml/toaster.py
21
+ fasthtml/xt.py
21
22
  fasthtml/xtend.py
22
23
  fasthtml/xtend.pyi
23
24
  python_fasthtml.egg-info/PKG-INFO
@@ -1,7 +1,7 @@
1
1
  [DEFAULT]
2
2
  repo = fasthtml
3
3
  lib_name = fasthtml
4
- version = 0.1.5
4
+ version = 0.1.7
5
5
  min_python = 3.10
6
6
  license = apache2
7
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
File without changes