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.
- {python_fasthtml-0.1.4/python_fasthtml.egg-info → python_fasthtml-0.1.6}/PKG-INFO +2 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/__init__.py +1 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/_modidx.py +5 -1
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/components.py +14 -9
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/components.pyi +54 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/core.py +25 -6
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/fastapp.py +4 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/xtend.py +30 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/xtend.pyi +39 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6/python_fasthtml.egg-info}/PKG-INFO +2 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/requires.txt +1 -1
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/settings.ini +2 -2
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/LICENSE +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/MANIFEST.in +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/README.md +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/authmw.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/cli.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/common.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/js.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/live_reload.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/oauth.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/starlette.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/svg.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/fasthtml/toaster.py +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/not-zip-safe +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/setup.cfg +0 -0
- {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.
|
|
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
|
|
@@ -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.
|
|
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', '
|
|
5
|
-
'html2xt', 'A', 'Abbr', 'Address', 'Area', 'Article', 'Aside', 'Audio', 'B', 'Base', 'Bdi',
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
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(
|
|
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.
|
|
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,10 +1,10 @@
|
|
|
1
1
|
[DEFAULT]
|
|
2
2
|
repo = fasthtml
|
|
3
3
|
lib_name = fasthtml
|
|
4
|
-
version = 0.1.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_fasthtml-0.1.4 → python_fasthtml-0.1.6}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|