python-fasthtml 0.0.4__tar.gz → 0.0.5__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.0.4/python_fasthtml.egg-info → python-fasthtml-0.0.5}/PKG-INFO +1 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/__init__.py +2 -2
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/_modidx.py +1 -0
- python-fasthtml-0.0.5/fasthtml/auth.py +45 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/core.py +12 -5
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/fastapp.py +1 -2
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/starlette.py +3 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/xtend.py +12 -12
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5/python_fasthtml.egg-info}/PKG-INFO +1 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/python_fasthtml.egg-info/SOURCES.txt +1 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/settings.ini +2 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/LICENSE +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/MANIFEST.in +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/README.md +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/all.py +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/components.py +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/fasthtml/oauth.py +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/python_fasthtml.egg-info/not-zip-safe +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/python_fasthtml.egg-info/requires.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/setup.cfg +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/setup.py +0 -0
|
@@ -6,6 +6,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
6
6
|
'git_url': 'https://github.com/AnswerDotAI/fasthtml',
|
|
7
7
|
'lib_path': 'fasthtml'},
|
|
8
8
|
'syms': { 'fasthtml.all': {},
|
|
9
|
+
'fasthtml.auth': {},
|
|
9
10
|
'fasthtml.components': { 'fasthtml.components._FindElems': ('components.html#_findelems', 'fasthtml/components.py'),
|
|
10
11
|
'fasthtml.components._FindElems.__init__': ( 'components.html#_findelems.__init__',
|
|
11
12
|
'fasthtml/components.py'),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import base64, binascii
|
|
2
|
+
from fasthtml.core import *
|
|
3
|
+
from fasthtml.starlette import *
|
|
4
|
+
from typing import Mapping
|
|
5
|
+
|
|
6
|
+
auth_hdrs = {'WWW-Authenticate': 'Basic realm="login"'}
|
|
7
|
+
|
|
8
|
+
class BasicAuthMiddleware(MiddlewareBase):
|
|
9
|
+
def __init__(self, app, cb, skip=None): self.app,self.cb,self.skip = app,cb,skip or []
|
|
10
|
+
|
|
11
|
+
async def _resp(self, scope, receive, send, resp):
|
|
12
|
+
await (send({"type": "websocket.close", "code": 1000}) if scope["type"]=="websocket" else resp(scope, receive, send))
|
|
13
|
+
|
|
14
|
+
async def __call__(self, scope, receive, send) -> None:
|
|
15
|
+
conn = await super().__call__(scope, receive, send)
|
|
16
|
+
if not conn: return
|
|
17
|
+
request = Request(scope, receive)
|
|
18
|
+
if not any(re.match(o+'$', request.url.path) for o in self.skip):
|
|
19
|
+
res = await self.authenticate(conn)
|
|
20
|
+
if not res: res = Response('not authenticated', status_code=401, headers=auth_hdrs)
|
|
21
|
+
if isinstance(res, Response): return await self._resp(scope, receive, send, res)
|
|
22
|
+
scope["auth"] = res
|
|
23
|
+
await self.app(scope, receive, send)
|
|
24
|
+
|
|
25
|
+
async def authenticate(self, conn):
|
|
26
|
+
if "Authorization" not in conn.headers: return
|
|
27
|
+
auth = conn.headers["Authorization"]
|
|
28
|
+
try:
|
|
29
|
+
scheme, credentials = auth.split()
|
|
30
|
+
if scheme.lower() != 'basic': return
|
|
31
|
+
decoded = base64.b64decode(credentials).decode("ascii")
|
|
32
|
+
except (ValueError, UnicodeDecodeError, binascii.Error) as exc: raise AuthenticationError('Invalid credentials')
|
|
33
|
+
user, _, pwd = decoded.partition(":")
|
|
34
|
+
if self.cb(user,pwd): return user
|
|
35
|
+
|
|
36
|
+
def user_pwd_auth(lookup=None, skip=None, **kwargs):
|
|
37
|
+
if isinstance(lookup,Mapping): kwargs = lookup | kwargs
|
|
38
|
+
def cb(u,p):
|
|
39
|
+
if u=='logout' or not u or not p: return
|
|
40
|
+
if callable(lookup): return lookup(u,p)
|
|
41
|
+
return kwargs.get(u,None) == p
|
|
42
|
+
return Middleware(BasicAuthMiddleware, cb=cb, skip=skip)
|
|
43
|
+
|
|
44
|
+
def basic_logout(request):
|
|
45
|
+
return f'{request.url.scheme}://logout:logout@{request.headers["host"]}/'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import json,
|
|
1
|
+
import json,dateutil,uuid,inspect
|
|
2
2
|
|
|
3
3
|
from fastcore.utils import *
|
|
4
4
|
from fastcore.xml import *
|
|
@@ -95,6 +95,7 @@ async def _from_body(req, arg, p):
|
|
|
95
95
|
|
|
96
96
|
async def _find_p(req, arg:str, p):
|
|
97
97
|
anno = p.annotation
|
|
98
|
+
if arg.lower()=='auth': return req.scope['auth']
|
|
98
99
|
if isinstance(anno, type):
|
|
99
100
|
if issubclass(anno, Request): return req
|
|
100
101
|
if issubclass(anno, HtmxHeaders): return _get_htmx(req)
|
|
@@ -110,11 +111,11 @@ async def _find_p(req, arg:str, p):
|
|
|
110
111
|
if not res: res = req.query_params.get(arg, None)
|
|
111
112
|
if not res: res = req.cookies.get(arg, None)
|
|
112
113
|
if not res: res = req.headers.get(snake2hyphens(arg), None)
|
|
113
|
-
if not res: res = nested_idx(req.scope, 'session', arg)
|
|
114
|
-
if not res: res = p.default
|
|
114
|
+
if not res: res = nested_idx(req.scope, 'session', arg) or None
|
|
115
115
|
if res is empty or res is None:
|
|
116
116
|
body = await req.form()
|
|
117
117
|
res = body.get(arg, None)
|
|
118
|
+
if not res: res = p.default
|
|
118
119
|
if not isinstance(res, str) or anno is empty: return res
|
|
119
120
|
return _fix_anno(anno)(res)
|
|
120
121
|
|
|
@@ -136,8 +137,8 @@ def _wrap_resp(req, resp, cls, hdrs, **bodykw):
|
|
|
136
137
|
if isinstance(resp, Response): return resp
|
|
137
138
|
if cls is not empty: return cls(resp)
|
|
138
139
|
if isinstance(resp, (list,tuple)): return _xt_resp(req, resp, hdrs, **bodykw)
|
|
139
|
-
if isinstance(resp, str): cls = HTMLResponse
|
|
140
|
-
elif isinstance(resp, Mapping): cls = JSONResponse
|
|
140
|
+
if isinstance(resp, str): cls = HTMLResponse
|
|
141
|
+
elif isinstance(resp, Mapping): cls = JSONResponse
|
|
141
142
|
else:
|
|
142
143
|
resp = str(resp)
|
|
143
144
|
cls = HTMLResponse
|
|
@@ -220,3 +221,9 @@ def reg_re_param(m, s):
|
|
|
220
221
|
reg_re_param("path", ".*?")
|
|
221
222
|
reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|xml")
|
|
222
223
|
|
|
224
|
+
class MiddlewareBase:
|
|
225
|
+
async def __call__(self, scope, receive, send) -> None:
|
|
226
|
+
if scope["type"] not in ["http", "websocket"]:
|
|
227
|
+
await self.app(scope, receive, send)
|
|
228
|
+
return
|
|
229
|
+
return HTTPConnection(scope)
|
|
@@ -16,12 +16,11 @@ def fast_app(db=None, render=None, hdrs=None, tbls=None, **kwargs):
|
|
|
16
16
|
async def get(fname:str, ext:str): return FileResponse(f'{fname}.{ext}')
|
|
17
17
|
if not db: return app
|
|
18
18
|
|
|
19
|
+
db = database(db)
|
|
19
20
|
if not tbls: tbls={}
|
|
20
21
|
if kwargs:
|
|
21
22
|
kwargs['render'] = render
|
|
22
23
|
tbls['items'] = kwargs
|
|
23
|
-
db = Database(db)
|
|
24
|
-
db.enable_wal()
|
|
25
24
|
dbtbls = [get_tbl(db.t, k, v) for k,v in tbls.items()]
|
|
26
25
|
if len(dbtbls)==1: dbtbls=dbtbls[0]
|
|
27
26
|
return app,*dbtbls
|
|
@@ -2,10 +2,11 @@ from starlette.applications import Starlette
|
|
|
2
2
|
from starlette.middleware import Middleware
|
|
3
3
|
from starlette.middleware.sessions import SessionMiddleware
|
|
4
4
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
5
|
+
from starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser, requires
|
|
5
6
|
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
|
|
6
7
|
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
|
7
8
|
from starlette.responses import Response, HTMLResponse, FileResponse, JSONResponse
|
|
8
|
-
from starlette.requests import Request
|
|
9
|
+
from starlette.requests import Request, HTTPConnection
|
|
9
10
|
from starlette.staticfiles import StaticFiles
|
|
10
11
|
from starlette.exceptions import HTTPException
|
|
11
12
|
from starlette._utils import is_async_callable
|
|
@@ -15,4 +16,5 @@ from starlette.exceptions import HTTPException,WebSocketException
|
|
|
15
16
|
from starlette.endpoints import HTTPEndpoint,WebSocketEndpoint
|
|
16
17
|
from starlette.config import Config
|
|
17
18
|
from starlette.datastructures import CommaSeparatedStrings, Secret
|
|
19
|
+
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
18
20
|
|
|
@@ -16,13 +16,13 @@ from .components import *
|
|
|
16
16
|
try: from IPython import display
|
|
17
17
|
except ImportError: display=None
|
|
18
18
|
|
|
19
|
-
# %% ../nbs/02_xtend.ipynb
|
|
19
|
+
# %% ../nbs/02_xtend.ipynb 4
|
|
20
20
|
picocss = "https://cdn.jsdelivr.net/npm/@picocss/pico@latest/css/pico.min.css"
|
|
21
21
|
picolink = Link(rel="stylesheet", href=picocss)
|
|
22
22
|
picocondcss = "https://cdn.jsdelivr.net/npm/@picocss/pico@latest/css/pico.conditional.min.css"
|
|
23
23
|
picocondlink = Link(rel="stylesheet", href=picocondcss)
|
|
24
24
|
|
|
25
|
-
# %% ../nbs/02_xtend.ipynb
|
|
25
|
+
# %% ../nbs/02_xtend.ipynb 7
|
|
26
26
|
def set_pico_cls():
|
|
27
27
|
js = """var sel = '.cell-output, .output_area';
|
|
28
28
|
document.querySelectorAll(sel).forEach(e => e.classList.add('pico'));
|
|
@@ -40,23 +40,23 @@ new MutationObserver(ms => {
|
|
|
40
40
|
}).observe(document.body, { childList: true, subtree: true });"""
|
|
41
41
|
return display.Javascript(js)
|
|
42
42
|
|
|
43
|
-
# %% ../nbs/02_xtend.ipynb
|
|
43
|
+
# %% ../nbs/02_xtend.ipynb 10
|
|
44
44
|
def Html(*c, doctype=True, **kwargs):
|
|
45
45
|
res = xt('html', *c, **kwargs)
|
|
46
46
|
if not doctype: return res
|
|
47
47
|
return (xt('!DOCTYPE', html=True), res)
|
|
48
48
|
|
|
49
|
-
# %% ../nbs/02_xtend.ipynb
|
|
49
|
+
# %% ../nbs/02_xtend.ipynb 11
|
|
50
50
|
@delegates(xt_hx, keep=True)
|
|
51
51
|
def A(*c, hx_get=None, target_id=None, hx_swap=None, href='#', **kwargs):
|
|
52
52
|
return xt_hx('a', *c, href=href, hx_get=hx_get, target_id=target_id, hx_swap=hx_swap, **kwargs)
|
|
53
53
|
|
|
54
|
-
# %% ../nbs/02_xtend.ipynb
|
|
54
|
+
# %% ../nbs/02_xtend.ipynb 13
|
|
55
55
|
@delegates(xt_hx, keep=True)
|
|
56
56
|
def AX(txt, hx_get=None, target_id=None, hx_swap=None, href='#', **kwargs):
|
|
57
57
|
return xt_hx('a', txt, href=href, hx_get=hx_get, target_id=target_id, hx_swap=hx_swap, **kwargs)
|
|
58
58
|
|
|
59
|
-
# %% ../nbs/02_xtend.ipynb
|
|
59
|
+
# %% ../nbs/02_xtend.ipynb 15
|
|
60
60
|
@delegates(xt_hx, keep=True)
|
|
61
61
|
def Checkbox(checked:bool=False, label=None, value="1", **kwargs):
|
|
62
62
|
if not checked: checked=None
|
|
@@ -64,36 +64,36 @@ def Checkbox(checked:bool=False, label=None, value="1", **kwargs):
|
|
|
64
64
|
if label: res = Label(res, label)
|
|
65
65
|
return res
|
|
66
66
|
|
|
67
|
-
# %% ../nbs/02_xtend.ipynb
|
|
67
|
+
# %% ../nbs/02_xtend.ipynb 17
|
|
68
68
|
@delegates(xt_hx, keep=True)
|
|
69
69
|
def Card(*c, header=None, footer=None, **kwargs):
|
|
70
70
|
if header: c = (Header(header),) + c
|
|
71
71
|
if footer: c += (Footer(footer),)
|
|
72
72
|
return Article(*c, **kwargs)
|
|
73
73
|
|
|
74
|
-
# %% ../nbs/02_xtend.ipynb
|
|
74
|
+
# %% ../nbs/02_xtend.ipynb 19
|
|
75
75
|
@delegates(xt_hx, keep=True)
|
|
76
76
|
def Group(*c, **kwargs):
|
|
77
77
|
return Fieldset(*c, role="group", **kwargs)
|
|
78
78
|
|
|
79
|
-
# %% ../nbs/02_xtend.ipynb
|
|
79
|
+
# %% ../nbs/02_xtend.ipynb 21
|
|
80
80
|
@delegates(xt_hx, keep=True)
|
|
81
81
|
def Search(*c, **kwargs):
|
|
82
82
|
return Form(*c, role="search", **kwargs)
|
|
83
83
|
|
|
84
|
-
# %% ../nbs/02_xtend.ipynb
|
|
84
|
+
# %% ../nbs/02_xtend.ipynb 23
|
|
85
85
|
@delegates(xt_hx, keep=True)
|
|
86
86
|
def Grid(*c, cls='grid', **kwargs):
|
|
87
87
|
c = tuple(o if isinstance(o,list) else Div(o) for o in c)
|
|
88
88
|
return xt_hx('div', *c, cls=cls, **kwargs)
|
|
89
89
|
|
|
90
|
-
# %% ../nbs/02_xtend.ipynb
|
|
90
|
+
# %% ../nbs/02_xtend.ipynb 25
|
|
91
91
|
@delegates(xt_hx, keep=True)
|
|
92
92
|
def DialogX(*c, open=None, header=None, footer=None, id=None, **kwargs):
|
|
93
93
|
card = Card(*c, header=header, footer=footer, **kwargs)
|
|
94
94
|
return Dialog(card, open=open, id=id)
|
|
95
95
|
|
|
96
|
-
# %% ../nbs/02_xtend.ipynb
|
|
96
|
+
# %% ../nbs/02_xtend.ipynb 27
|
|
97
97
|
@delegates(xt_hx, keep=True)
|
|
98
98
|
def Hidden(value:str="", **kwargs):
|
|
99
99
|
return Input(type="hidden", value=value, **kwargs)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[DEFAULT]
|
|
2
2
|
repo = fasthtml
|
|
3
3
|
lib_name = fasthtml
|
|
4
|
-
version = 0.0.
|
|
4
|
+
version = 0.0.5
|
|
5
5
|
min_python = 3.10
|
|
6
6
|
license = apache2
|
|
7
7
|
requirements = fastcore python-dateutil starlette oauthlib itsdangerous uvicorn httpx fastlite
|
|
8
8
|
dev_requirements = ipython lxml
|
|
9
9
|
black_formatting = False
|
|
10
|
+
conda_user = fastai
|
|
10
11
|
doc_path = _docs
|
|
11
12
|
lib_path = fasthtml
|
|
12
13
|
nbs_path = nbs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python-fasthtml-0.0.4 → python-fasthtml-0.0.5}/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
|
|
File without changes
|