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.
Files changed (39) hide show
  1. {python_fasthtml-0.12.40/python_fasthtml.egg-info → python_fasthtml-0.12.41}/PKG-INFO +1 -1
  2. python_fasthtml-0.12.41/fasthtml/__init__.py +2 -0
  3. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/_modidx.py +7 -0
  4. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/core.py +2 -0
  5. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/oauth.py +43 -19
  6. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/starlette.py +1 -1
  7. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41/python_fasthtml.egg-info}/PKG-INFO +1 -1
  8. python_fasthtml-0.12.40/fasthtml/__init__.py +0 -2
  9. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/CONTRIBUTING.md +0 -0
  10. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/LICENSE +0 -0
  11. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/MANIFEST.in +0 -0
  12. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/README.md +0 -0
  13. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/authmw.py +0 -0
  14. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/basics.py +0 -0
  15. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/cli.py +0 -0
  16. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/common.py +0 -0
  17. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/components.py +0 -0
  18. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/components.pyi +0 -0
  19. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/core.pyi +0 -0
  20. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/fastapp.py +0 -0
  21. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/ft.py +0 -0
  22. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/js.py +0 -0
  23. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/jupyter.py +0 -0
  24. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/katex.js +0 -0
  25. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/live_reload.py +0 -0
  26. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/pico.py +0 -0
  27. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/stripe_otp.py +0 -0
  28. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/svg.py +0 -0
  29. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/toaster.py +0 -0
  30. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/xtend.py +0 -0
  31. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/fasthtml/xtend.pyi +0 -0
  32. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/pyproject.toml +0 -0
  33. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  34. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  35. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/entry_points.txt +0 -0
  36. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/requires.txt +0 -0
  37. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/python_fasthtml.egg-info/top_level.txt +0 -0
  38. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/setup.cfg +0 -0
  39. {python_fasthtml-0.12.40 → python_fasthtml-0.12.41}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.12.40
3
+ Version: 0.12.41
4
4
  Summary: The fastest way to create an HTML app
5
5
  Author-email: Jeremy Howard and contributors <github@jhoward.fastmail.fm>
6
6
  License: Apache-2.0
@@ -0,0 +1,2 @@
1
+ __version__ = "0.12.41"
2
+ from .core import *
@@ -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
- r.raise_for_status()
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
- r = httpx.get(f"https://{self.domain}/.well-known/openid-configuration")
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, redir_path='/redirect', error_path='/error', logout_path='/logout', login_path='/login', https=True, http_patterns=http_patterns, redir_method='get'):
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.retr_info(code, base_url+redir_path))
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: return self.redir_login(session)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.12.40
3
+ Version: 0.12.41
4
4
  Summary: The fastest way to create an HTML app
5
5
  Author-email: Jeremy Howard and contributors <github@jhoward.fastmail.fm>
6
6
  License: Apache-2.0
@@ -1,2 +0,0 @@
1
- __version__ = "0.12.40"
2
- from .core import *