python-fasthtml 0.14.1__tar.gz → 0.14.3__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.14.1/python_fasthtml.egg-info → python_fasthtml-0.14.3}/PKG-INFO +3 -3
  2. python_fasthtml-0.14.3/fasthtml/__init__.py +2 -0
  3. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/_modidx.py +3 -1
  4. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/components.py +3 -1
  5. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/core.py +23 -17
  6. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/js.py +3 -1
  7. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/jupyter.py +46 -21
  8. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/oauth.py +10 -8
  9. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/pico.py +3 -1
  10. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/stripe_otp.py +2 -0
  11. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/svg.py +3 -1
  12. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/xtend.py +3 -1
  13. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/pyproject.toml +1 -1
  14. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3/python_fasthtml.egg-info}/PKG-INFO +3 -3
  15. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/python_fasthtml.egg-info/requires.txt +2 -2
  16. python_fasthtml-0.14.1/fasthtml/__init__.py +0 -2
  17. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/CONTRIBUTING.md +0 -0
  18. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/LICENSE +0 -0
  19. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/MANIFEST.in +0 -0
  20. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/README.md +0 -0
  21. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/authmw.py +0 -0
  22. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/basics.py +0 -0
  23. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/cli.py +0 -0
  24. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/common.py +0 -0
  25. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/components.pyi +0 -0
  26. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/core.pyi +0 -0
  27. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/fastapp.py +0 -0
  28. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/ft.py +0 -0
  29. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/katex.js +0 -0
  30. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/live_reload.py +0 -0
  31. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/starlette.py +0 -0
  32. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/toaster.py +0 -0
  33. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/fasthtml/xtend.pyi +0 -0
  34. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/python_fasthtml.egg-info/SOURCES.txt +0 -0
  35. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/python_fasthtml.egg-info/dependency_links.txt +0 -0
  36. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/python_fasthtml.egg-info/entry_points.txt +0 -0
  37. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/python_fasthtml.egg-info/top_level.txt +0 -0
  38. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/setup.cfg +0 -0
  39. {python_fasthtml-0.14.1 → python_fasthtml-0.14.3}/tests/test_toaster.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.14.1
3
+ Version: 0.14.3
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
@@ -17,11 +17,11 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: fastcore>=1.12.45
19
19
  Requires-Dist: python-dateutil
20
- Requires-Dist: starlette~=1.0
20
+ Requires-Dist: starlette>=1.0.1
21
21
  Requires-Dist: oauthlib
22
22
  Requires-Dist: itsdangerous
23
23
  Requires-Dist: uvicorn[standard]>=0.30
24
- Requires-Dist: httpx
24
+ Requires-Dist: httpx2
25
25
  Requires-Dist: fastlite>=0.1.1
26
26
  Requires-Dist: python-multipart
27
27
  Requires-Dist: beautifulsoup4
@@ -0,0 +1,2 @@
1
+ __version__ = "0.14.3"
2
+ from .core import *
@@ -173,8 +173,9 @@ d = { 'settings': { 'branch': 'main',
173
173
  'fasthtml.jupyter': { 'fasthtml.jupyter.HTMX': ('api/jupyter.html#htmx', 'fasthtml/jupyter.py'),
174
174
  'fasthtml.jupyter.JupyUvi': ('api/jupyter.html#jupyuvi', 'fasthtml/jupyter.py'),
175
175
  'fasthtml.jupyter.JupyUvi.__init__': ('api/jupyter.html#jupyuvi.__init__', 'fasthtml/jupyter.py'),
176
+ 'fasthtml.jupyter.JupyUvi._live_sse': ('api/jupyter.html#jupyuvi._live_sse', 'fasthtml/jupyter.py'),
177
+ 'fasthtml.jupyter.JupyUvi._setup_live': ('api/jupyter.html#jupyuvi._setup_live', 'fasthtml/jupyter.py'),
176
178
  'fasthtml.jupyter.JupyUvi.start': ('api/jupyter.html#jupyuvi.start', 'fasthtml/jupyter.py'),
177
- 'fasthtml.jupyter.JupyUvi.start_async': ('api/jupyter.html#jupyuvi.start_async', 'fasthtml/jupyter.py'),
178
179
  'fasthtml.jupyter.JupyUvi.stop': ('api/jupyter.html#jupyuvi.stop', 'fasthtml/jupyter.py'),
179
180
  'fasthtml.jupyter.JupyUviAsync': ('api/jupyter.html#jupyuviasync', 'fasthtml/jupyter.py'),
180
181
  'fasthtml.jupyter.JupyUviAsync.__init__': ( 'api/jupyter.html#jupyuviasync.__init__',
@@ -188,6 +189,7 @@ d = { 'settings': { 'branch': 'main',
188
189
  'fasthtml.jupyter.render_ft': ('api/jupyter.html#render_ft', 'fasthtml/jupyter.py'),
189
190
  'fasthtml.jupyter.show': ('api/jupyter.html#show', 'fasthtml/jupyter.py'),
190
191
  'fasthtml.jupyter.wait_port_free': ('api/jupyter.html#wait_port_free', 'fasthtml/jupyter.py'),
192
+ 'fasthtml.jupyter.wait_port_free_async': ('api/jupyter.html#wait_port_free_async', 'fasthtml/jupyter.py'),
191
193
  'fasthtml.jupyter.ws_client': ('api/jupyter.html#ws_client', 'fasthtml/jupyter.py')},
192
194
  'fasthtml.live_reload': {},
193
195
  'fasthtml.oauth': { 'fasthtml.oauth.AppleAppClient': ('api/oauth.html#appleappclient', 'fasthtml/oauth.py'),
@@ -1,4 +1,6 @@
1
- """`ft_html` and `ft_hx` functions to add some conveniences to `ft`, along with a full set of basic HTML components, and functions to work with forms and `FT` conversion"""
1
+ """`ft_html` and `ft_hx` functions to add some conveniences to `ft`, along with a full set of basic HTML components, and functions to work with forms and `FT` conversion
2
+
3
+ Docs: https://www.fastht.ml/docsapi/components.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/01_components.ipynb.
4
6
 
@@ -1,15 +1,17 @@
1
- """The `FastHTML` subclass of `Starlette`."""
1
+ """The `FastHTML` subclass of `Starlette`.
2
+
3
+ Docs: https://www.fastht.ml/docsapi/core.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/00_core.ipynb.
4
6
 
5
7
  # %% auto #0
6
- __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc', 'viewport',
7
- 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'devtools_loc', 'parsed_date', 'snake2hyphens',
8
- 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'ApiReturn', 'JSONResponse',
9
- 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple', 'noop_body',
10
- 'respond', 'is_full_page', 'Redirect', 'get_key', 'qp', 'def_hdrs', 'Lifespan', 'FastHTML', 'HostRoute',
11
- 'nested_name', 'serve', 'Client', 'RouteFuncs', 'APIRouter', 'cookie', 'reg_re_param', 'StaticNoCache',
12
- 'add_sig_param', 'into', 'MiddlewareBase', 'FtResponse', 'unqid']
8
+ __all__ = ['empty', 'htmx_hdrs', 'fh_cfg', 'htmx_resps', 'DEF_MAXPART', 'htmx_exts', 'htmxsrc', 'fhjsscr', 'surrsrc', 'scopesrc',
9
+ 'viewport', 'charset', 'cors_allow', 'iframe_scr', 'all_meths', 'devtools_loc', 'parsed_date',
10
+ 'snake2hyphens', 'HtmxHeaders', 'HttpHeader', 'HtmxResponseHeaders', 'form2dict', 'parse_form', 'ApiReturn',
11
+ 'JSONResponse', 'flat_xt', 'Beforeware', 'EventStream', 'signal_shutdown', 'uri', 'decode_uri', 'flat_tuple',
12
+ 'noop_body', 'respond', 'is_full_page', 'Redirect', 'get_key', 'qp', 'def_hdrs', 'Lifespan', 'FastHTML',
13
+ 'HostRoute', 'nested_name', 'serve', 'Client', 'RouteFuncs', 'APIRouter', 'cookie', 'reg_re_param',
14
+ 'StaticNoCache', 'add_sig_param', 'into', 'MiddlewareBase', 'FtResponse', 'unqid']
13
15
 
14
16
  # %% ../nbs/api/00_core.ipynb #23503b9e
15
17
  import json,uuid,inspect,types,asyncio,inspect,random,contextlib,itsdangerous
@@ -150,18 +152,21 @@ def form2dict(form: FormData) -> dict:
150
152
  if isinstance(form, dict): return form
151
153
  return {k: _formitem(form, k) for k in form}
152
154
 
155
+ # %% ../nbs/api/00_core.ipynb #8899bc0a
156
+ DEF_MAXPART = 100*1024*1024
157
+
153
158
  # %% ../nbs/api/00_core.ipynb #42c9cea0
154
159
  async def parse_form(req: Request) -> FormData:
155
160
  "Starlette errors on empty multipart forms, so this checks for that situation"
156
161
  ctype = req.headers.get("Content-Type", "")
162
+ maxpart = getattr(req, "max_part_size", DEF_MAXPART)
157
163
  if ctype.startswith("multipart/form-data"):
158
164
  try: boundary = ctype.split("boundary=")[1].strip()
159
165
  except IndexError: raise HTTPException(400, "Invalid form-data: no boundary")
160
166
  if int(req.headers.get("Content-Length", "0")) <= len(boundary) + 6: return FormData()
161
- return await req.form()
167
+ return await req.form(max_part_size=maxpart)
162
168
  await req.body() # Cache body for non-multipart request types
163
- return await req.json() if ctype == 'application/json' else await req.form()
164
-
169
+ return await req.json() if ctype == 'application/json' else await req.form(max_part_size=maxpart)
165
170
 
166
171
  # %% ../nbs/api/00_core.ipynb #0caedd04
167
172
  async def _from_body(conn, p, data):
@@ -600,7 +605,7 @@ class FastHTML(Starlette):
600
605
  before=None, after=None, surreal=True, htmx=True, default_hdrs=True, sess_cls=SessionMiddleware,
601
606
  secret_key=None, session_cookie='session_', max_age=365*24*3600, sess_path='/',
602
607
  same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',
603
- body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, **bodykw):
608
+ body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, max_part_size=DEF_MAXPART, **bodykw):
604
609
  middleware,before,after = map(_list, (middleware,before,after))
605
610
  self.title,self.canonical,self.session_cookie,self.key_fname = title,canonical,session_cookie,key_fname
606
611
  hdrs,ftrs,exts = map(listify, (hdrs,ftrs,exts))
@@ -615,7 +620,7 @@ class FastHTML(Starlette):
615
620
  middleware.append(cors_allow)
616
621
  self.lifespan = Lifespan(on_startup, on_shutdown, lifespan)
617
622
  self.hdrs,self.ftrs = hdrs,ftrs
618
- self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw = body_wrap,before,after,htmlkw,bodykw
623
+ self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw,self.max_part_size = body_wrap,before,after,htmlkw,bodykw,max_part_size
619
624
  self.secret_key = get_key(secret_key, key_fname)
620
625
  if sess_cls:
621
626
  sess = Middleware(sess_cls, secret_key=self.secret_key,session_cookie=session_cookie,
@@ -673,6 +678,7 @@ def _endp(self:FastHTML, f, body_wrap, before:Optional[Callable|tuple]=None):
673
678
  async def _f(req):
674
679
  resp = None
675
680
  req.injects = []
681
+ req.max_part_size = self.max_part_size
676
682
  req.hdrs,req.ftrs,req.htmlkw,req.bodykw = map(deepcopy, (self.hdrs,self.ftrs,self.htmlkw,self.bodykw))
677
683
  req.hdrs,req.ftrs = listify(req.hdrs),listify(req.ftrs)
678
684
  for b in self.before:
@@ -816,8 +822,8 @@ def serve(
816
822
  class Client:
817
823
  "A simple httpx ASGI client that doesn't require `async`"
818
824
  def __init__(self, app, url="http://testserver"):
819
- import httpx
820
- self.cli = httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url=url)
825
+ import httpx2
826
+ self.cli = httpx2.AsyncClient(transport=httpx2.ASGITransport(app), base_url=url)
821
827
 
822
828
  def _sync(self, method, url, **kwargs):
823
829
  async def _request(): return await self.cli.request(method, url, **kwargs)
@@ -1026,11 +1032,11 @@ def devtools_json(self:FastHTML, path=None, uuid=None):
1026
1032
  @patch
1027
1033
  def get_client(self:FastHTML, asink=False, **kw):
1028
1034
  "Get an httpx client with session cookes set from `**kw`"
1029
- import httpx
1035
+ import httpx2
1030
1036
  signer = itsdangerous.TimestampSigner(self.secret_key)
1031
1037
  data = b64encode(dumps(kw).encode())
1032
1038
  data = signer.sign(data)
1033
- client = httpx.AsyncClient() if asink else httpx.Client()
1039
+ client = httpx2.AsyncClient() if asink else httpx2.Client()
1034
1040
  client.cookies.update({self.session_cookie: data.decode()})
1035
1041
  return client
1036
1042
 
@@ -1,4 +1,6 @@
1
- """Basic external Javascript lib wrappers"""
1
+ """Basic external Javascript lib wrappers
2
+
3
+ Docs: https://www.fastht.ml/docsapi/js.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_js.ipynb.
4
6
 
@@ -1,10 +1,12 @@
1
- """Use FastHTML in Jupyter notebooks"""
1
+ """Use FastHTML in Jupyter notebooks
2
+
3
+ Docs: https://www.fastht.ml/docsapi/jupyter.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/06_jupyter.ipynb.
4
6
 
5
7
  # %% auto #0
6
- __all__ = ['nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'show', 'render_ft', 'htmx_config_port', 'JupyUvi',
7
- 'JupyUviAsync', 'HTMX', 'ws_client']
8
+ __all__ = ['nb_serve', 'nb_serve_async', 'is_port_free', 'wait_port_free', 'wait_port_free_async', 'show', 'render_ft',
9
+ 'htmx_config_port', 'JupyUvi', 'JupyUviAsync', 'HTMX', 'ws_client']
8
10
 
9
11
  # %% ../nbs/api/06_jupyter.ipynb #2c69d9d0
10
12
  import asyncio, socket, time, uvicorn
@@ -36,23 +38,31 @@ async def nb_serve_async(app, log_level="error", port=8000, host='0.0.0.0', **kw
36
38
 
37
39
  # %% ../nbs/api/06_jupyter.ipynb #508917bc
38
40
  def is_port_free(port, host='localhost'):
39
- "Check if `port` is free on `host`"
40
41
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
42
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
41
43
  try:
42
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
43
44
  sock.bind((host, port))
45
+ sock.listen(1)
44
46
  return True
45
47
  except OSError: return False
46
48
  finally: sock.close()
47
49
 
48
50
  # %% ../nbs/api/06_jupyter.ipynb #1779cb76
49
- def wait_port_free(port, host='localhost', max_wait=3):
51
+ def wait_port_free(port, host='localhost', max_wait=20):
50
52
  "Wait for `port` to be free on `host`"
51
53
  start_time = time.time()
52
- while not is_port_free(port):
53
- if time.time() - start_time>max_wait: return print(f"Timeout")
54
+ while not is_port_free(port, host):
55
+ if time.time() - start_time > max_wait: raise TimeoutError(f"Port {host}:{port} not free after {max_wait}s")
54
56
  time.sleep(0.1)
55
57
 
58
+ async def wait_port_free_async(port, host='localhost', max_wait=20):
59
+ "Async wait for `port` to be free on `host`"
60
+ start_time = time.time()
61
+ while not is_port_free(port, host):
62
+ if time.time() - start_time > max_wait: raise TimeoutError(f"Port {host}:{port} not free after {max_wait}s")
63
+ await asyncio.sleep(0.1)
64
+
65
+
56
66
  # %% ../nbs/api/06_jupyter.ipynb #654b36bb
57
67
  @delegates(_show)
58
68
  def show(*s, **kwargs):
@@ -79,28 +89,42 @@ document.body.addEventListener('htmx:configRequest', (event) => {
79
89
  });
80
90
  </script>''' % port))
81
91
 
82
- # %% ../nbs/api/06_jupyter.ipynb #29a834a5
92
+ # %% ../nbs/api/06_jupyter.ipynb #79406618
83
93
  class JupyUvi:
84
94
  "Start and stop a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
85
- def __init__(self, app, log_level="error", host='0.0.0.0', port=8000, start=True, daemon=False, **kwargs):
95
+ def __init__(self, app, log_level="error", host='0.0.0.0', port=8000, start=True, live=False, live_rt='/_lr', daemon=False, **kwargs):
86
96
  self.kwargs = kwargs
87
- store_attr(but='start')
97
+ store_attr(but='start,live')
88
98
  self.server = None
99
+ self._live_ver = 0
100
+ if live: self._setup_live(app)
89
101
  if start: self.start()
90
102
  if not os.environ.get('IN_SOLVEIT'): htmx_config_port(port)
91
-
103
+
92
104
  def start(self):
93
105
  self.server = nb_serve(self.app, log_level=self.log_level, host=self.host, port=self.port,daemon=self.daemon, **self.kwargs)
94
106
 
95
- async def start_async(self):
96
- self.server = await nb_serve_async(self.app, log_level=self.log_level, host=self.host, port=self.port, **self.kwargs)
97
-
98
107
  def stop(self):
99
108
  self.server.should_exit = True
100
- wait_port_free(self.port)
101
-
102
-
103
- # %% ../nbs/api/06_jupyter.ipynb #9134035e
109
+ wait_port_free(self.port, self.host)
110
+
111
+ def _setup_live(self, app):
112
+ rt = self.live_rt or '/_lr'
113
+ if not rt.startswith('/'): rt = f'/{rt}'
114
+ app.hdrs.append(Script(f"new EventSource({rt!r}).onmessage=e=>{{if(e.data==='reload')navigation.reload()}}"))
115
+ @app.get(rt)
116
+ async def _sse(): return EventStream(self._live_sse())
117
+ get_ipython().events.register('post_run_cell', lambda _: setattr(self, '_live_ver', self._live_ver+1))
118
+
119
+ async def _live_sse(self):
120
+ ver = self._live_ver
121
+ while not self.server.should_exit:
122
+ await asyncio.sleep(0.1)
123
+ if ver != self._live_ver:
124
+ ver = self._live_ver
125
+ yield 'data: reload\n\n'
126
+
127
+ # %% ../nbs/api/06_jupyter.ipynb #f6316c73
104
128
  class JupyUviAsync(JupyUvi):
105
129
  "Start and stop an async Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level`"
106
130
  def __init__(self, app, log_level="error", host='0.0.0.0', port=8000, **kwargs):
@@ -109,9 +133,10 @@ class JupyUviAsync(JupyUvi):
109
133
  async def start(self):
110
134
  self.server = await nb_serve_async(self.app, log_level=self.log_level, host=self.host, port=self.port, **self.kwargs)
111
135
 
112
- def stop(self):
136
+ async def stop(self):
113
137
  self.server.should_exit = True
114
- wait_port_free(self.port)
138
+ await wait_port_free_async(self.port, self.host)
139
+
115
140
 
116
141
  # %% ../nbs/api/06_jupyter.ipynb #a448e420
117
142
  from starlette.testclient import TestClient
@@ -1,4 +1,6 @@
1
- """Basic scaffolding for handling OAuth"""
1
+ """Basic scaffolding for handling OAuth
2
+
3
+ Docs: https://www.fastht.ml/docsapi/oauth.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/08_oauth.ipynb.
4
6
 
@@ -10,7 +12,7 @@ __all__ = ['log', 'http_patterns', 'GoogleAppClient', 'GitHubAppClient', 'Huggin
10
12
  from .common import *
11
13
  from oauthlib.oauth2 import WebApplicationClient
12
14
  from urllib.parse import urlparse, urlencode, parse_qs, quote, unquote
13
- import secrets, httpx, time, asyncio, logging
15
+ import secrets, httpx2, time, asyncio, logging
14
16
 
15
17
  # %% ../nbs/api/08_oauth.ipynb #44aa4a88
16
18
  log = logging.getLogger(__name__)
@@ -93,7 +95,7 @@ class DiscordAppClient(_AppClient):
93
95
  headers = {'Content-Type': 'application/x-www-form-urlencoded'}
94
96
  data = dict(grant_type='authorization_code', code=code)
95
97
  if redirect_uri: data['redirect_uri'] = redirect_uri
96
- r = httpx.post(self.token_url, data=data, headers=headers, auth=(self.client_id, self.client_secret)
98
+ r = httpx2.post(self.token_url, data=data, headers=headers, auth=(self.client_id, self.client_secret)
97
99
  ).raise_for_status()
98
100
  self.parse_request_body_response(r.text)
99
101
 
@@ -107,7 +109,7 @@ class Auth0AppClient(_AppClient):
107
109
  super().__init__(client_id, client_secret, code=code, scope=scope, redirect_uri=redirect_uri, **kwargs)
108
110
 
109
111
  def _fetch_openid_config(self):
110
- return httpx.get(f"https://{self.domain}/.well-known/openid-configuration").raise_for_status().json()
112
+ return httpx2.get(f"https://{self.domain}/.well-known/openid-configuration").raise_for_status().json()
111
113
 
112
114
  def login_link(self, req):
113
115
  d = dict(response_type="code", client_id=self.client_id, scope=self.scope, redirect_uri=redir_url(req, self.redirect_uri))
@@ -167,7 +169,7 @@ def parse_response(self:_AppClient, code, redirect_uri):
167
169
  "Get the token from the oauth2 server response"
168
170
  payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
169
171
  client_secret=self.client_secret, grant_type='authorization_code')
170
- r = httpx.post(self.token_url, data=payload).raise_for_status()
172
+ r = httpx2.post(self.token_url, data=payload).raise_for_status()
171
173
  self.parse_request_body_response(r.text)
172
174
 
173
175
  @patch
@@ -175,7 +177,7 @@ def get_info(self:_AppClient, token=None):
175
177
  "Get the info for authenticated user"
176
178
  if not token: token = self.token["access_token"]
177
179
  headers = {'Authorization': f'Bearer {token}'}
178
- return httpx.get(self.info_url, headers=headers).json()
180
+ return httpx2.get(self.info_url, headers=headers).json()
179
181
 
180
182
  @patch
181
183
  def retr_info(self:_AppClient, code, redirect_uri):
@@ -190,7 +192,7 @@ async def parse_response_async(self:_AppClient, code, redirect_uri):
190
192
  payload = dict(code=code, redirect_uri=redirect_uri, client_id=self.client_id,
191
193
  client_secret=self.client_secret, grant_type='authorization_code')
192
194
  log.debug(f"OAuth token request: redirect_uri={redirect_uri}, code={code[:20]}...")
193
- async with httpx.AsyncClient() as c:
195
+ async with httpx2.AsyncClient() as c:
194
196
  r = (await c.post(self.token_url, data=payload))
195
197
  log.debug(f"OAuth response: {r.status_code} - {r.text}")
196
198
  r.raise_for_status()
@@ -201,7 +203,7 @@ async def get_info_async(self:_AppClient, token=None):
201
203
  "Get the info for authenticated user"
202
204
  if not token: token = self.token["access_token"]
203
205
  headers = {'Authorization': f'Bearer {token}'}
204
- async with httpx.AsyncClient() as c:
206
+ async with httpx2.AsyncClient() as c:
205
207
  return (await c.get(self.info_url, headers=headers)).raise_for_status().json()
206
208
 
207
209
  @patch
@@ -1,4 +1,6 @@
1
- """Basic components for generating Pico CSS tags"""
1
+ """Basic components for generating Pico CSS tags
2
+
3
+ Docs: https://www.fastht.ml/docsapi/pico.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_pico.ipynb.
4
6
 
@@ -1,3 +1,5 @@
1
+ """Docs: https://www.fastht.ml/docsexplains/stripe.html.md"""
2
+
1
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/explains/Stripe.ipynb.
2
4
 
3
5
  # %% auto #0
@@ -1,4 +1,6 @@
1
- """Simple SVG FT elements"""
1
+ """Simple SVG FT elements
2
+
3
+ Docs: https://www.fastht.ml/docsapi/svg.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/05_svg.ipynb.
4
6
 
@@ -1,4 +1,6 @@
1
- """Simple extensions to standard HTML components, such as adding sensible defaults"""
1
+ """Simple extensions to standard HTML components, such as adding sensible defaults
2
+
3
+ Docs: https://www.fastht.ml/docsapi/xtend.html.md"""
2
4
 
3
5
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/02_xtend.ipynb.
4
6
 
@@ -12,7 +12,7 @@ license = {text = "Apache-2.0"}
12
12
  authors = [{name = "Jeremy Howard and contributors", email = "github@jhoward.fastmail.fm"}]
13
13
  keywords = ['nbdev', 'jupyter', 'notebook', 'python']
14
14
  classifiers = ["Natural Language :: English", "Intended Audience :: Developers", "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only"]
15
- dependencies = ['fastcore>=1.12.45', 'python-dateutil', 'starlette~=1.0', 'oauthlib', 'itsdangerous', 'uvicorn[standard]>=0.30', 'httpx', 'fastlite>=0.1.1', 'python-multipart', 'beautifulsoup4']
15
+ dependencies = ['fastcore>=1.12.45', 'python-dateutil', 'starlette>=1.0.1', 'oauthlib', 'itsdangerous', 'uvicorn[standard]>=0.30', 'httpx2', 'fastlite>=0.1.1', 'python-multipart', 'beautifulsoup4']
16
16
 
17
17
  [project.urls]
18
18
  Repository = "https://github.com/AnswerDotAI/fasthtml"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fasthtml
3
- Version: 0.14.1
3
+ Version: 0.14.3
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
@@ -17,11 +17,11 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: fastcore>=1.12.45
19
19
  Requires-Dist: python-dateutil
20
- Requires-Dist: starlette~=1.0
20
+ Requires-Dist: starlette>=1.0.1
21
21
  Requires-Dist: oauthlib
22
22
  Requires-Dist: itsdangerous
23
23
  Requires-Dist: uvicorn[standard]>=0.30
24
- Requires-Dist: httpx
24
+ Requires-Dist: httpx2
25
25
  Requires-Dist: fastlite>=0.1.1
26
26
  Requires-Dist: python-multipart
27
27
  Requires-Dist: beautifulsoup4
@@ -1,10 +1,10 @@
1
1
  fastcore>=1.12.45
2
2
  python-dateutil
3
- starlette~=1.0
3
+ starlette>=1.0.1
4
4
  oauthlib
5
5
  itsdangerous
6
6
  uvicorn[standard]>=0.30
7
- httpx
7
+ httpx2
8
8
  fastlite>=0.1.1
9
9
  python-multipart
10
10
  beautifulsoup4
@@ -1,2 +0,0 @@
1
- __version__ = "0.14.1"
2
- from .core import *