python-fasthtml 0.12.40__tar.gz → 0.12.41__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.41}/PKG-INFO +1 -1
- python_fasthtml-0.12.41/fasthtml/__init__.py +2 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/_modidx.py +7 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/core.py +2 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/oauth.py +43 -19
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/starlette.py +1 -1
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41/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.41}/CONTRIBUTING.md +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/LICENSE +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/MANIFEST.in +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/README.md +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/authmw.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/basics.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/cli.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/common.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/components.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/components.pyi +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/core.pyi +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/fastapp.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/ft.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/js.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/jupyter.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/katex.js +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/live_reload.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/pico.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/stripe_otp.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/svg.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/toaster.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/xtend.py +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/xtend.pyi +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/pyproject.toml +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/SOURCES.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/dependency_links.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/entry_points.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/requires.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/top_level.txt +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/setup.cfg +0 -0
- {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/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'),
|
|
@@ -183,6 +183,7 @@ async def _find_p(req, arg:str, p:Parameter):
|
|
|
183
183
|
if issubclass(anno, HtmxHeaders): return _get_htmx(req.headers)
|
|
184
184
|
if issubclass(anno, Starlette): return req.scope['app']
|
|
185
185
|
if _is_body(anno) and 'session'.startswith(arg.lower()): return req.scope.get('session', {})
|
|
186
|
+
if issubclass(anno, State): return req.scope['app'].state
|
|
186
187
|
if _is_body(anno): return await _from_body(req, p)
|
|
187
188
|
# If there's no annotation, check for special names
|
|
188
189
|
if anno is empty:
|
|
@@ -193,6 +194,7 @@ async def _find_p(req, arg:str, p:Parameter):
|
|
|
193
194
|
if arg.lower()=='htmx': return _get_htmx(req.headers)
|
|
194
195
|
if arg.lower()=='app': return req.scope['app']
|
|
195
196
|
if arg.lower()=='body': return (await req.body()).decode()
|
|
197
|
+
if arg.lower()=='state': return req.scope['app'].state
|
|
196
198
|
if arg.lower() in ('hdrs','ftrs','bodykw','htmlkw'): return getattr(req, arg.lower())
|
|
197
199
|
if arg!='resp': warn(f"`{arg} has no type annotation and is not a recognised special name, so is ignored.")
|
|
198
200
|
return None
|
|
@@ -10,7 +10,7 @@ __all__ = ['http_patterns', 'GoogleAppClient', 'GitHubAppClient', 'HuggingFaceCl
|
|
|
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
|
|
14
14
|
|
|
15
15
|
# %% ../nbs/api/08_oauth.ipynb #0a078133
|
|
16
16
|
class _AppClient(WebApplicationClient):
|
|
@@ -90,8 +90,8 @@ class DiscordAppClient(_AppClient):
|
|
|
90
90
|
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
|
91
91
|
data = dict(grant_type='authorization_code', code=code)
|
|
92
92
|
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
|
-
|
|
93
|
+
r = httpx.post(self.token_url, data=data, headers=headers, auth=(self.client_id, self.client_secret)
|
|
94
|
+
).raise_for_status()
|
|
95
95
|
self.parse_request_body_response(r.text)
|
|
96
96
|
|
|
97
97
|
# %% ../nbs/api/08_oauth.ipynb #276520b0
|
|
@@ -104,9 +104,7 @@ class Auth0AppClient(_AppClient):
|
|
|
104
104
|
super().__init__(client_id, client_secret, code=code, scope=scope, redirect_uri=redirect_uri, **kwargs)
|
|
105
105
|
|
|
106
106
|
def _fetch_openid_config(self):
|
|
107
|
-
|
|
108
|
-
r.raise_for_status()
|
|
109
|
-
return r.json()
|
|
107
|
+
return httpx.get(f"https://{self.domain}/.well-known/openid-configuration").raise_for_status().json()
|
|
110
108
|
|
|
111
109
|
def login_link(self, req):
|
|
112
110
|
d = dict(response_type="code", client_id=self.client_id, scope=self.scope, redirect_uri=redir_url(req, self.redirect_uri))
|
|
@@ -166,11 +164,9 @@ def parse_response(self:_AppClient, code, redirect_uri):
|
|
|
166
164
|
"Get the token from the oauth2 server response"
|
|
167
165
|
payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
|
|
168
166
|
client_secret=self.client_secret, grant_type='authorization_code')
|
|
169
|
-
r = httpx.post(self.token_url, data=payload)
|
|
170
|
-
r.raise_for_status()
|
|
167
|
+
r = httpx.post(self.token_url, data=payload).raise_for_status()
|
|
171
168
|
self.parse_request_body_response(r.text)
|
|
172
169
|
|
|
173
|
-
# %% ../nbs/api/08_oauth.ipynb #6967dbb0
|
|
174
170
|
@patch
|
|
175
171
|
def get_info(self:_AppClient, token=None):
|
|
176
172
|
"Get the info for authenticated user"
|
|
@@ -178,13 +174,36 @@ def get_info(self:_AppClient, token=None):
|
|
|
178
174
|
headers = {'Authorization': f'Bearer {token}'}
|
|
179
175
|
return httpx.get(self.info_url, headers=headers).json()
|
|
180
176
|
|
|
181
|
-
# %% ../nbs/api/08_oauth.ipynb #03702349
|
|
182
177
|
@patch
|
|
183
178
|
def retr_info(self:_AppClient, code, redirect_uri):
|
|
184
179
|
"Combines `parse_response` and `get_info`"
|
|
185
180
|
self.parse_response(code, redirect_uri)
|
|
186
181
|
return self.get_info()
|
|
187
182
|
|
|
183
|
+
# %% ../nbs/api/08_oauth.ipynb #0ff353ae
|
|
184
|
+
@patch
|
|
185
|
+
async def parse_response_async(self:_AppClient, code, redirect_uri):
|
|
186
|
+
"Get the token from the oauth2 server response"
|
|
187
|
+
payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
|
|
188
|
+
client_secret=self.client_secret, grant_type='authorization_code')
|
|
189
|
+
async with httpx.AsyncClient() as c:
|
|
190
|
+
r = (await c.post(self.token_url, data=payload)).raise_for_status()
|
|
191
|
+
self.parse_request_body_response(r.text)
|
|
192
|
+
|
|
193
|
+
@patch
|
|
194
|
+
async def get_info_async(self:_AppClient, token=None):
|
|
195
|
+
"Get the info for authenticated user"
|
|
196
|
+
if not token: token = self.token["access_token"]
|
|
197
|
+
headers = {'Authorization': f'Bearer {token}'}
|
|
198
|
+
async with httpx.AsyncClient() as c:
|
|
199
|
+
return (await c.get(self.info_url, headers=headers)).raise_for_status().json()
|
|
200
|
+
|
|
201
|
+
@patch
|
|
202
|
+
async def retr_info_async(self:_AppClient, code, redirect_uri):
|
|
203
|
+
"Combines `parse_response` and `get_info`"
|
|
204
|
+
await self.parse_response_async(code, redirect_uri)
|
|
205
|
+
return await self.get_info_async()
|
|
206
|
+
|
|
188
207
|
# %% ../nbs/api/08_oauth.ipynb #29f52061
|
|
189
208
|
@patch
|
|
190
209
|
def retr_id(self:_AppClient, code, redirect_uri):
|
|
@@ -197,38 +216,43 @@ def url_match(request, patterns=http_patterns):
|
|
|
197
216
|
return any(re.match(pattern, get_host(request).split(':')[0]) for pattern in patterns)
|
|
198
217
|
|
|
199
218
|
# %% ../nbs/api/08_oauth.ipynb #dda68390
|
|
219
|
+
async def _arun(res): return await res if asyncio.iscoroutine(res) else res
|
|
220
|
+
|
|
221
|
+
# %% ../nbs/api/08_oauth.ipynb #c09a79ff
|
|
200
222
|
class OAuth:
|
|
201
|
-
def __init__(self, app, cli, skip=None,
|
|
223
|
+
def __init__(self, app, cli, skip=None,
|
|
224
|
+
redir_path='/redirect', error_path='/error', logout_path='/logout', login_path='/login',
|
|
225
|
+
https=True, http_patterns=http_patterns, redir_method='get'):
|
|
202
226
|
if not skip: skip = [redir_path,error_path,login_path]
|
|
203
227
|
redir_handler = app.post if redir_method == 'post' else app.get
|
|
204
228
|
store_attr()
|
|
205
|
-
def before(req, session):
|
|
229
|
+
async def before(req, session):
|
|
206
230
|
if 'auth' not in req.scope: req.scope['auth'] = session.get('auth')
|
|
207
231
|
auth = req.scope['auth']
|
|
208
232
|
if not auth: return self.redir_login(session)
|
|
209
|
-
res = self.check_invalid(req, session, auth)
|
|
233
|
+
res = await _arun(self.check_invalid(req, session, auth))
|
|
210
234
|
if res: return res
|
|
211
235
|
app.before.append(Beforeware(before, skip=skip))
|
|
212
236
|
|
|
213
237
|
@redir_handler(redir_path)
|
|
214
|
-
def redirect(req, session, code:str=None, error:str=None, state:str=None):
|
|
238
|
+
async def redirect(req, session, code:str=None, error:str=None, state:str=None):
|
|
215
239
|
if not code:
|
|
216
240
|
session['oauth_error']=error
|
|
217
241
|
return RedirectResponse(self.error_path, status_code=303)
|
|
218
242
|
scheme = 'http' if url_match(req,self.http_patterns) or not self.https else 'https'
|
|
219
243
|
base_url = f"{scheme}://{get_host(req)}"
|
|
220
|
-
info = AttrDictDefault(cli.
|
|
244
|
+
info = AttrDictDefault(await cli.retr_info_async(code, base_url+redir_path))
|
|
221
245
|
ident = info.get(self.cli.id_key)
|
|
222
246
|
if not ident: return self.redir_login(session)
|
|
223
|
-
res = self.get_auth(info, ident, session, state)
|
|
224
|
-
if not res:
|
|
247
|
+
res = await _arun(self.get_auth(info, ident, session, state))
|
|
248
|
+
if not res: return self.redir_login(session)
|
|
225
249
|
req.scope['auth'] = session['auth'] = ident
|
|
226
250
|
return res
|
|
227
251
|
|
|
228
252
|
@app.get(logout_path)
|
|
229
|
-
def logout(session):
|
|
253
|
+
async def logout(session):
|
|
230
254
|
session.pop('auth', None)
|
|
231
|
-
return self.logout(session)
|
|
255
|
+
return await _arun(self.logout(session))
|
|
232
256
|
|
|
233
257
|
def redir_login(self, session): return RedirectResponse(self.login_path, status_code=303)
|
|
234
258
|
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.41}/python_fasthtml.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|