python-fasthtml 0.0.4__tar.gz → 0.0.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.0.4/python_fasthtml.egg-info → python-fasthtml-0.0.6}/PKG-INFO +1 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/__init__.py +2 -2
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/_modidx.py +1 -0
- python-fasthtml-0.0.6/fasthtml/authmw.py +45 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/core.py +34 -17
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/fastapp.py +1 -2
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/starlette.py +3 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/xtend.py +12 -12
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6/python_fasthtml.egg-info}/PKG-INFO +1 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/python_fasthtml.egg-info/SOURCES.txt +1 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/settings.ini +2 -1
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/LICENSE +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/MANIFEST.in +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/README.md +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/all.py +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/components.py +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/fasthtml/oauth.py +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/python_fasthtml.egg-info/not-zip-safe +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/python_fasthtml.egg-info/requires.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/setup.cfg +0 -0
- {python-fasthtml-0.0.4 → python-fasthtml-0.0.6}/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.authmw': {},
|
|
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,42 +137,49 @@ 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
|
|
144
145
|
return cls(resp)
|
|
145
146
|
|
|
146
|
-
def _wrap_ep(f, hdrs, **bodykw):
|
|
147
|
+
def _wrap_ep(f, hdrs, before, **bodykw):
|
|
147
148
|
if not (isfunction(f) or ismethod(f)): return f
|
|
148
149
|
sig = signature(f)
|
|
149
150
|
params = sig.parameters
|
|
150
151
|
cls = sig.return_annotation
|
|
151
152
|
|
|
152
153
|
async def _f(req):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
resp = None
|
|
155
|
+
for b in before:
|
|
156
|
+
if not resp:
|
|
157
|
+
wreq = await _wrap_req(req, signature(b).parameters)
|
|
158
|
+
resp = b(*wreq)
|
|
159
|
+
if is_async_callable(b): resp = await resp
|
|
160
|
+
if not resp:
|
|
161
|
+
wreq = await _wrap_req(req, params)
|
|
162
|
+
resp = f(*wreq)
|
|
163
|
+
if is_async_callable(f): resp = await resp
|
|
156
164
|
return _wrap_resp(req, resp, cls, hdrs, **bodykw)
|
|
157
165
|
return _f
|
|
158
166
|
|
|
159
167
|
class RouteX(Route):
|
|
160
168
|
def __init__(self, path:str, endpoint, *, methods=None, name=None, include_in_schema=True, middleware=None,
|
|
161
|
-
hdrs=None, **bodykw):
|
|
162
|
-
super().__init__(path, _wrap_ep(endpoint, hdrs, **bodykw), methods=methods, name=name,
|
|
169
|
+
hdrs=None, before=None, **bodykw):
|
|
170
|
+
super().__init__(path, _wrap_ep(endpoint, hdrs, before, **bodykw), methods=methods, name=name,
|
|
163
171
|
include_in_schema=include_in_schema, middleware=middleware)
|
|
164
172
|
|
|
165
173
|
class RouterX(Router):
|
|
166
174
|
def __init__(self, routes=None, redirect_slashes=True, default=None, on_startup=None, on_shutdown=None,
|
|
167
|
-
lifespan=None, *, middleware=None, hdrs=None, **bodykw):
|
|
175
|
+
lifespan=None, *, middleware=None, hdrs=None, before=None, **bodykw):
|
|
168
176
|
super().__init__(routes, redirect_slashes, default, on_startup, on_shutdown,
|
|
169
177
|
lifespan=lifespan, middleware=middleware)
|
|
170
|
-
self.hdrs,self.bodykw = hdrs or (),bodykw
|
|
178
|
+
self.hdrs,self.bodykw,self.before = hdrs or (),bodykw,before or ()
|
|
171
179
|
|
|
172
180
|
def add_route( self, path: str, endpoint: callable, methods=None, name=None, include_in_schema=True):
|
|
173
181
|
route = RouteX(path, endpoint=endpoint, methods=methods, name=name, include_in_schema=include_in_schema,
|
|
174
|
-
hdrs=self.hdrs, **self.bodykw)
|
|
182
|
+
hdrs=self.hdrs, before=self.before, **self.bodykw)
|
|
175
183
|
self.routes = [o for o in self.routes if o.methods!=methods or o.path!=path]
|
|
176
184
|
self.routes.append(route)
|
|
177
185
|
|
|
@@ -187,12 +195,14 @@ def get_key(key=None, fname='.sesskey'):
|
|
|
187
195
|
fname.write_text(key)
|
|
188
196
|
return key
|
|
189
197
|
|
|
198
|
+
def _list(o): return [] if not o else o if isinstance(o, (tuple,list)) else [o]
|
|
199
|
+
|
|
190
200
|
class FastHTML(Starlette):
|
|
191
201
|
def __init__(self, debug=False, routes=None, middleware=None, exception_handlers=None,
|
|
192
|
-
on_startup=None, on_shutdown=None, lifespan=None, hdrs=None,
|
|
202
|
+
on_startup=None, on_shutdown=None, lifespan=None, hdrs=None, before=None,
|
|
193
203
|
secret_key=None, session_cookie='session_', max_age=365*24*3600, sess_path='/',
|
|
194
204
|
same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey', **bodykw):
|
|
195
|
-
|
|
205
|
+
middleware,before = _list(middleware),_list(before)
|
|
196
206
|
secret_key = get_key(secret_key, key_fname)
|
|
197
207
|
sess = Middleware(SessionMiddleware, secret_key=secret_key, session_cookie=session_cookie,
|
|
198
208
|
max_age=max_age, path=sess_path, same_site=same_site,
|
|
@@ -200,7 +210,8 @@ class FastHTML(Starlette):
|
|
|
200
210
|
middleware.append(sess)
|
|
201
211
|
super().__init__(debug, routes, middleware, exception_handlers, on_startup, on_shutdown, lifespan=lifespan)
|
|
202
212
|
hdrs = list([] if hdrs is None else hdrs) + [htmxscr]
|
|
203
|
-
self.router = RouterX(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan, hdrs=hdrs,
|
|
213
|
+
self.router = RouterX(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan, hdrs=hdrs,
|
|
214
|
+
before=before, **bodykw)
|
|
204
215
|
|
|
205
216
|
def route(self, path:str, methods=None, name=None, include_in_schema=True):
|
|
206
217
|
def f(func):
|
|
@@ -220,3 +231,9 @@ def reg_re_param(m, s):
|
|
|
220
231
|
reg_re_param("path", ".*?")
|
|
221
232
|
reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|xml")
|
|
222
233
|
|
|
234
|
+
class MiddlewareBase:
|
|
235
|
+
async def __call__(self, scope, receive, send) -> None:
|
|
236
|
+
if scope["type"] not in ["http", "websocket"]:
|
|
237
|
+
await self.app(scope, receive, send)
|
|
238
|
+
return
|
|
239
|
+
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.6
|
|
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.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
|
|
File without changes
|