python-fasthtml 0.12.40__tar.gz → 0.12.42__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.12.40/python_fasthtml.egg-info → python_fasthtml-0.12.42}/PKG-INFO +1 -1
- python_fasthtml-0.12.42/fasthtml/__init__.py +2 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/_modidx.py +7 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/core.py +11 -8
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/oauth.py +51 -21
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/starlette.py +1 -1
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42/python_fasthtml.egg-info}/PKG-INFO +1 -1
- python_fasthtml-0.12.40/fasthtml/__init__.py +0 -2
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/CONTRIBUTING.md +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/LICENSE +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/MANIFEST.in +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/README.md +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/authmw.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/basics.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/cli.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/common.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/components.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/components.pyi +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/core.pyi +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/fastapp.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/ft.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/js.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/jupyter.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/katex.js +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/live_reload.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/pico.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/stripe_otp.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/svg.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/toaster.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/xtend.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/fasthtml/xtend.pyi +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/pyproject.toml +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/requires.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/setup.cfg +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/tests/test_toaster.py +0 -0
|
@@ -203,10 +203,17 @@ d = { 'settings': { 'branch': 'main',
|
|
|
203
203
|
'fasthtml.oauth._AppClient': ('api/oauth.html#_appclient', 'fasthtml/oauth.py'),
|
|
204
204
|
'fasthtml.oauth._AppClient.__init__': ('api/oauth.html#_appclient.__init__', 'fasthtml/oauth.py'),
|
|
205
205
|
'fasthtml.oauth._AppClient.get_info': ('api/oauth.html#_appclient.get_info', 'fasthtml/oauth.py'),
|
|
206
|
+
'fasthtml.oauth._AppClient.get_info_async': ( 'api/oauth.html#_appclient.get_info_async',
|
|
207
|
+
'fasthtml/oauth.py'),
|
|
206
208
|
'fasthtml.oauth._AppClient.parse_response': ( 'api/oauth.html#_appclient.parse_response',
|
|
207
209
|
'fasthtml/oauth.py'),
|
|
210
|
+
'fasthtml.oauth._AppClient.parse_response_async': ( 'api/oauth.html#_appclient.parse_response_async',
|
|
211
|
+
'fasthtml/oauth.py'),
|
|
208
212
|
'fasthtml.oauth._AppClient.retr_id': ('api/oauth.html#_appclient.retr_id', 'fasthtml/oauth.py'),
|
|
209
213
|
'fasthtml.oauth._AppClient.retr_info': ('api/oauth.html#_appclient.retr_info', 'fasthtml/oauth.py'),
|
|
214
|
+
'fasthtml.oauth._AppClient.retr_info_async': ( 'api/oauth.html#_appclient.retr_info_async',
|
|
215
|
+
'fasthtml/oauth.py'),
|
|
216
|
+
'fasthtml.oauth._arun': ('api/oauth.html#_arun', 'fasthtml/oauth.py'),
|
|
210
217
|
'fasthtml.oauth.get_host': ('api/oauth.html#get_host', 'fasthtml/oauth.py'),
|
|
211
218
|
'fasthtml.oauth.load_creds': ('api/oauth.html#load_creds', 'fasthtml/oauth.py'),
|
|
212
219
|
'fasthtml.oauth.redir_url': ('api/oauth.html#redir_url', 'fasthtml/oauth.py'),
|
|
@@ -12,11 +12,12 @@ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc',
|
|
|
12
12
|
'unqid']
|
|
13
13
|
|
|
14
14
|
# %% ../nbs/api/00_core.ipynb #23503b9e
|
|
15
|
-
import json,uuid,inspect,types,signal,asyncio,threading,inspect,random,contextlib,httpx,itsdangerous
|
|
15
|
+
import json,uuid,inspect,types,signal,asyncio,threading,inspect,random,contextlib,httpx,itsdangerous,uvicorn
|
|
16
16
|
|
|
17
17
|
from fastcore.utils import *
|
|
18
18
|
from fastcore.xml import *
|
|
19
|
-
from fastcore.meta import use_kwargs_dict
|
|
19
|
+
from fastcore.meta import use_kwargs_dict,delegates
|
|
20
|
+
from fastcore.style import S
|
|
20
21
|
|
|
21
22
|
from types import UnionType, SimpleNamespace as ns, GenericAlias
|
|
22
23
|
from typing import Optional, get_type_hints, get_args, get_origin, Union, Mapping, TypedDict, List, Any
|
|
@@ -183,6 +184,7 @@ async def _find_p(req, arg:str, p:Parameter):
|
|
|
183
184
|
if issubclass(anno, HtmxHeaders): return _get_htmx(req.headers)
|
|
184
185
|
if issubclass(anno, Starlette): return req.scope['app']
|
|
185
186
|
if _is_body(anno) and 'session'.startswith(arg.lower()): return req.scope.get('session', {})
|
|
187
|
+
if issubclass(anno, State): return req.scope['app'].state
|
|
186
188
|
if _is_body(anno): return await _from_body(req, p)
|
|
187
189
|
# If there's no annotation, check for special names
|
|
188
190
|
if anno is empty:
|
|
@@ -193,6 +195,7 @@ async def _find_p(req, arg:str, p:Parameter):
|
|
|
193
195
|
if arg.lower()=='htmx': return _get_htmx(req.headers)
|
|
194
196
|
if arg.lower()=='app': return req.scope['app']
|
|
195
197
|
if arg.lower()=='body': return (await req.body()).decode()
|
|
198
|
+
if arg.lower()=='state': return req.scope['app'].state
|
|
196
199
|
if arg.lower() in ('hdrs','ftrs','bodykw','htmlkw'): return getattr(req, arg.lower())
|
|
197
200
|
if arg!='resp': warn(f"`{arg} has no type annotation and is not a recognised special name, so is ignored.")
|
|
198
201
|
return None
|
|
@@ -700,15 +703,15 @@ def set_lifespan(self:FastHTML, value):
|
|
|
700
703
|
self.router.lifespan_context = value
|
|
701
704
|
|
|
702
705
|
# %% ../nbs/api/00_core.ipynb #3a348474
|
|
706
|
+
@delegates(uvicorn.run)
|
|
703
707
|
def serve(
|
|
704
708
|
appname=None, # Name of the module
|
|
705
709
|
app='app', # App instance to be served
|
|
706
710
|
host='0.0.0.0', # If host is 0.0.0.0 will convert to localhost
|
|
707
711
|
port=None, # If port is None it will default to 5001 or the PORT environment variable
|
|
708
712
|
reload=True, # Default is to reload the app upon code changes
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
):
|
|
713
|
+
**kwargs
|
|
714
|
+
):
|
|
712
715
|
"Run the app in an async server, with live reload set as the default."
|
|
713
716
|
bk = inspect.currentframe().f_back
|
|
714
717
|
glb = bk.f_globals
|
|
@@ -716,11 +719,11 @@ def serve(
|
|
|
716
719
|
if not appname:
|
|
717
720
|
if glb.get('__name__')=='__main__': appname = Path(glb.get('__file__', '')).stem
|
|
718
721
|
elif code.co_name=='main' and bk.f_back.f_globals.get('__name__')=='__main__': appname = inspect.getmodule(bk).__name__
|
|
719
|
-
import uvicorn
|
|
720
722
|
if appname:
|
|
721
723
|
if not port: port=int(os.getenv("PORT", default=5001))
|
|
722
|
-
|
|
723
|
-
|
|
724
|
+
link = f'http://{"localhost" if host=="0.0.0.0" else host}:{port}'
|
|
725
|
+
print('Link: '+ S.light_red.bold(link))
|
|
726
|
+
uvicorn.run(f'{appname}:{app}', host=host, port=port, reload=reload, **kwargs)
|
|
724
727
|
|
|
725
728
|
# %% ../nbs/api/00_core.ipynb #8121968a
|
|
726
729
|
class Client:
|
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/08_oauth.ipynb.
|
|
4
4
|
|
|
5
5
|
# %% auto #0
|
|
6
|
-
__all__ = ['http_patterns', 'GoogleAppClient', 'GitHubAppClient', 'HuggingFaceClient', 'DiscordAppClient',
|
|
7
|
-
'AppleAppClient', 'get_host', 'redir_url', 'url_match', 'OAuth', 'load_creds']
|
|
6
|
+
__all__ = ['log', 'http_patterns', 'GoogleAppClient', 'GitHubAppClient', 'HuggingFaceClient', 'DiscordAppClient',
|
|
7
|
+
'Auth0AppClient', 'AppleAppClient', 'get_host', 'redir_url', 'url_match', 'OAuth', 'load_creds']
|
|
8
8
|
|
|
9
9
|
# %% ../nbs/api/08_oauth.ipynb #793722f2
|
|
10
10
|
from .common import *
|
|
11
11
|
from oauthlib.oauth2 import WebApplicationClient
|
|
12
12
|
from urllib.parse import urlparse, urlencode, parse_qs, quote, unquote
|
|
13
|
-
import secrets, httpx, time
|
|
13
|
+
import secrets, httpx, time, asyncio, logging
|
|
14
|
+
|
|
15
|
+
# %% ../nbs/api/08_oauth.ipynb #44aa4a88
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
14
17
|
|
|
15
18
|
# %% ../nbs/api/08_oauth.ipynb #0a078133
|
|
16
19
|
class _AppClient(WebApplicationClient):
|
|
@@ -90,8 +93,8 @@ class DiscordAppClient(_AppClient):
|
|
|
90
93
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
|
91
94
|
data = dict(grant_type='authorization_code', code=code)
|
|
92
95
|
if redirect_uri: data['redirect_uri'] = redirect_uri
|
|
93
|
-
r = httpx.post(self.token_url, data=data, headers=headers, auth=(self.client_id, self.client_secret)
|
|
94
|
-
|
|
96
|
+
r = httpx.post(self.token_url, data=data, headers=headers, auth=(self.client_id, self.client_secret)
|
|
97
|
+
).raise_for_status()
|
|
95
98
|
self.parse_request_body_response(r.text)
|
|
96
99
|
|
|
97
100
|
# %% ../nbs/api/08_oauth.ipynb #276520b0
|
|
@@ -104,9 +107,7 @@ class Auth0AppClient(_AppClient):
|
|
|
104
107
|
super().__init__(client_id, client_secret, code=code, scope=scope, redirect_uri=redirect_uri, **kwargs)
|
|
105
108
|
|
|
106
109
|
def _fetch_openid_config(self):
|
|
107
|
-
|
|
108
|
-
r.raise_for_status()
|
|
109
|
-
return r.json()
|
|
110
|
+
return httpx.get(f"https://{self.domain}/.well-known/openid-configuration").raise_for_status().json()
|
|
110
111
|
|
|
111
112
|
def login_link(self, req):
|
|
112
113
|
d = dict(response_type="code", client_id=self.client_id, scope=self.scope, redirect_uri=redir_url(req, self.redirect_uri))
|
|
@@ -166,11 +167,9 @@ def parse_response(self:_AppClient, code, redirect_uri):
|
|
|
166
167
|
"Get the token from the oauth2 server response"
|
|
167
168
|
payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
|
|
168
169
|
client_secret=self.client_secret, grant_type='authorization_code')
|
|
169
|
-
r = httpx.post(self.token_url, data=payload)
|
|
170
|
-
r.raise_for_status()
|
|
170
|
+
r = httpx.post(self.token_url, data=payload).raise_for_status()
|
|
171
171
|
self.parse_request_body_response(r.text)
|
|
172
172
|
|
|
173
|
-
# %% ../nbs/api/08_oauth.ipynb #6967dbb0
|
|
174
173
|
@patch
|
|
175
174
|
def get_info(self:_AppClient, token=None):
|
|
176
175
|
"Get the info for authenticated user"
|
|
@@ -178,13 +177,39 @@ def get_info(self:_AppClient, token=None):
|
|
|
178
177
|
headers = {'Authorization': f'Bearer {token}'}
|
|
179
178
|
return httpx.get(self.info_url, headers=headers).json()
|
|
180
179
|
|
|
181
|
-
# %% ../nbs/api/08_oauth.ipynb #03702349
|
|
182
180
|
@patch
|
|
183
181
|
def retr_info(self:_AppClient, code, redirect_uri):
|
|
184
182
|
"Combines `parse_response` and `get_info`"
|
|
185
183
|
self.parse_response(code, redirect_uri)
|
|
186
184
|
return self.get_info()
|
|
187
185
|
|
|
186
|
+
# %% ../nbs/api/08_oauth.ipynb #0ff353ae
|
|
187
|
+
@patch
|
|
188
|
+
async def parse_response_async(self:_AppClient, code, redirect_uri):
|
|
189
|
+
"Get the token from the oauth2 server response"
|
|
190
|
+
payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
|
|
191
|
+
client_secret=self.client_secret, grant_type='authorization_code')
|
|
192
|
+
log.debug(f"OAuth token request: redirect_uri={redirect_uri}, code={code[:20]}...")
|
|
193
|
+
async with httpx.AsyncClient() as c:
|
|
194
|
+
r = (await c.post(self.token_url, data=payload))
|
|
195
|
+
log.debug(f"OAuth response: {r.status_code} - {r.text}")
|
|
196
|
+
r.raise_for_status()
|
|
197
|
+
self.parse_request_body_response(r.text)
|
|
198
|
+
|
|
199
|
+
@patch
|
|
200
|
+
async def get_info_async(self:_AppClient, token=None):
|
|
201
|
+
"Get the info for authenticated user"
|
|
202
|
+
if not token: token = self.token["access_token"]
|
|
203
|
+
headers = {'Authorization': f'Bearer {token}'}
|
|
204
|
+
async with httpx.AsyncClient() as c:
|
|
205
|
+
return (await c.get(self.info_url, headers=headers)).raise_for_status().json()
|
|
206
|
+
|
|
207
|
+
@patch
|
|
208
|
+
async def retr_info_async(self:_AppClient, code, redirect_uri):
|
|
209
|
+
"Combines `parse_response` and `get_info`"
|
|
210
|
+
await self.parse_response_async(code, redirect_uri)
|
|
211
|
+
return await self.get_info_async()
|
|
212
|
+
|
|
188
213
|
# %% ../nbs/api/08_oauth.ipynb #29f52061
|
|
189
214
|
@patch
|
|
190
215
|
def retr_id(self:_AppClient, code, redirect_uri):
|
|
@@ -197,38 +222,43 @@ def url_match(request, patterns=http_patterns):
|
|
|
197
222
|
return any(re.match(pattern, get_host(request).split(':')[0]) for pattern in patterns)
|
|
198
223
|
|
|
199
224
|
# %% ../nbs/api/08_oauth.ipynb #dda68390
|
|
225
|
+
async def _arun(res): return await res if asyncio.iscoroutine(res) else res
|
|
226
|
+
|
|
227
|
+
# %% ../nbs/api/08_oauth.ipynb #c09a79ff
|
|
200
228
|
class OAuth:
|
|
201
|
-
def __init__(self, app, cli, skip=None,
|
|
229
|
+
def __init__(self, app, cli, skip=None,
|
|
230
|
+
redir_path='/redirect', error_path='/error', logout_path='/logout', login_path='/login',
|
|
231
|
+
https=True, http_patterns=http_patterns, redir_method='get'):
|
|
202
232
|
if not skip: skip = [redir_path,error_path,login_path]
|
|
203
233
|
redir_handler = app.post if redir_method == 'post' else app.get
|
|
204
234
|
store_attr()
|
|
205
|
-
def before(req, session):
|
|
235
|
+
async def before(req, session):
|
|
206
236
|
if 'auth' not in req.scope: req.scope['auth'] = session.get('auth')
|
|
207
237
|
auth = req.scope['auth']
|
|
208
238
|
if not auth: return self.redir_login(session)
|
|
209
|
-
res = self.check_invalid(req, session, auth)
|
|
239
|
+
res = await _arun(self.check_invalid(req, session, auth))
|
|
210
240
|
if res: return res
|
|
211
241
|
app.before.append(Beforeware(before, skip=skip))
|
|
212
242
|
|
|
213
243
|
@redir_handler(redir_path)
|
|
214
|
-
def redirect(req, session, code:str=None, error:str=None, state:str=None):
|
|
244
|
+
async def redirect(req, session, code:str=None, error:str=None, state:str=None):
|
|
215
245
|
if not code:
|
|
216
246
|
session['oauth_error']=error
|
|
217
247
|
return RedirectResponse(self.error_path, status_code=303)
|
|
218
248
|
scheme = 'http' if url_match(req,self.http_patterns) or not self.https else 'https'
|
|
219
249
|
base_url = f"{scheme}://{get_host(req)}"
|
|
220
|
-
info = AttrDictDefault(cli.
|
|
250
|
+
info = AttrDictDefault(await cli.retr_info_async(code, base_url+redir_path))
|
|
221
251
|
ident = info.get(self.cli.id_key)
|
|
222
252
|
if not ident: return self.redir_login(session)
|
|
223
|
-
res = self.get_auth(info, ident, session, state)
|
|
224
|
-
if not res:
|
|
253
|
+
res = await _arun(self.get_auth(info, ident, session, state))
|
|
254
|
+
if not res: return self.redir_login(session)
|
|
225
255
|
req.scope['auth'] = session['auth'] = ident
|
|
226
256
|
return res
|
|
227
257
|
|
|
228
258
|
@app.get(logout_path)
|
|
229
|
-
def logout(session):
|
|
259
|
+
async def logout(session):
|
|
230
260
|
session.pop('auth', None)
|
|
231
|
-
return self.logout(session)
|
|
261
|
+
return await _arun(self.logout(session))
|
|
232
262
|
|
|
233
263
|
def redir_login(self, session): return RedirectResponse(self.login_path, status_code=303)
|
|
234
264
|
def redir_url(self, req):
|
|
@@ -16,7 +16,7 @@ from starlette.routing import Route, Router, Mount, WebSocketRoute
|
|
|
16
16
|
from starlette.exceptions import HTTPException,WebSocketException
|
|
17
17
|
from starlette.endpoints import HTTPEndpoint,WebSocketEndpoint
|
|
18
18
|
from starlette.config import Config
|
|
19
|
-
from starlette.datastructures import CommaSeparatedStrings, Secret, UploadFile, URLPath
|
|
19
|
+
from starlette.datastructures import CommaSeparatedStrings, Secret, UploadFile, URLPath, State
|
|
20
20
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
21
21
|
from starlette.concurrency import run_in_threadpool
|
|
22
22
|
from starlette.background import BackgroundTask, BackgroundTasks
|
|
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
|
|
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.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python_fasthtml-0.12.40 → python_fasthtml-0.12.42}/python_fasthtml.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|