Skip to content

Commit 71b3745

Browse files
authored
Merge branch 'main' into fix-streamingresponse-issue
2 parents f0f0593 + c9d0a4b commit 71b3745

File tree

13 files changed

+232
-61
lines changed

13 files changed

+232
-61
lines changed

fasthtml/_modidx.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
'fasthtml.core.FastHTML._endp': ('api/core.html#fasthtml._endp', 'fasthtml/core.py'),
4747
'fasthtml.core.FastHTML.add_route': ('api/core.html#fasthtml.add_route', 'fasthtml/core.py'),
4848
'fasthtml.core.FastHTML.devtools_json': ('api/core.html#fasthtml.devtools_json', 'fasthtml/core.py'),
49+
'fasthtml.core.FastHTML.get_client': ('api/core.html#fasthtml.get_client', 'fasthtml/core.py'),
4950
'fasthtml.core.FastHTML.route': ('api/core.html#fasthtml.route', 'fasthtml/core.py'),
5051
'fasthtml.core.FastHTML.set_lifespan': ('api/core.html#fasthtml.set_lifespan', 'fasthtml/core.py'),
5152
'fasthtml.core.FastHTML.setup_ws': ('api/core.html#fasthtml.setup_ws', 'fasthtml/core.py'),

fasthtml/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def railway_deploy(
3636
):
3737
"""Deploy a FastHTML app to Railway"""
3838
nm,ver = check_output("railway --version".split()).decode().split()
39-
assert nm=='railway', f'Unexpected railway version string: {nm}'
39+
assert nm.startswith('railway'), f'Unexpected railway version string: {nm}'
4040
if ver2tuple(ver)<(3,8): return print("Please update your railway CLI version to 3.8 or higher")
4141
cp = run("railway status --json".split(), capture_output=True)
4242
if not cp.returncode:

fasthtml/components.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def File(fname):
127127
# %% ../nbs/api/01_components.ipynb
128128
def show(ft, *rest, iframe=False, height='auto', style=None):
129129
"Renders FT Components into HTML within a Jupyter notebook."
130+
if isinstance(ft, str): ft = Safe(ft)
130131
if rest: ft = (ft,)+rest
131132
res = to_xml(ft)
132133
if iframe:
@@ -206,14 +207,14 @@ def _f(*c, target_id=None, **kwargs): return ft_hx(tag, *c, target_id=target_id,
206207
def html2ft(html, attr1st=False):
207208
"""Convert HTML to an `ft` expression"""
208209
rev_map = {'class': 'cls', 'for': 'fr'}
209-
210+
210211
def _parse(elm, lvl=0, indent=4):
211-
if isinstance(elm, str): return repr(elm.strip()) if elm.strip() else ''
212+
if isinstance(elm, str): return repr(elm.strip("\n")) if elm.strip() else ''
212213
if isinstance(elm, list): return '\n'.join(_parse(o, lvl) for o in elm)
213214
tag_name = elm.name.capitalize().replace("-", "_")
214215
if tag_name=='[document]': return _parse(list(elm.children), lvl)
215216
cts = elm.contents
216-
cs = [repr(c.strip()) if isinstance(c, str) else _parse(c, lvl+1)
217+
cs = [repr(c.strip("\n")) if isinstance(c, str) else _parse(c, lvl+1)
217218
for c in cts if str(c).strip()]
218219
attrs, exotic_attrs = [], {}
219220
for key, value in sorted(elm.attrs.items(), key=lambda x: x[0]=='class'):

fasthtml/core.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'unqid']
1313

1414
# %% ../nbs/api/00_core.ipynb
15-
import json,uuid,inspect,types,signal,asyncio,threading,inspect,random,contextlib
15+
import json,uuid,inspect,types,signal,asyncio,threading,inspect,random,contextlib,httpx,itsdangerous
1616

1717
from fastcore.utils import *
1818
from fastcore.xml import *
@@ -30,7 +30,6 @@
3030
from copy import copy,deepcopy
3131
from warnings import warn
3232
from dateutil import parser as dtparse
33-
from httpx import ASGITransport, AsyncClient
3433
from anyio import from_thread
3534
from uuid import uuid4, UUID
3635
from base64 import b85encode,b64encode
@@ -566,7 +565,7 @@ def __init__(self, debug=False, routes=None, middleware=None, title: str = "Fast
566565
same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',
567566
body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, **bodykw):
568567
middleware,before,after = map(_list, (middleware,before,after))
569-
self.title,self.canonical = title,canonical
568+
self.title,self.canonical,self.session_cookie,self.key_fname = title,canonical,session_cookie,key_fname
570569
hdrs,ftrs,exts = map(listify, (hdrs,ftrs,exts))
571570
exts = {k:htmx_exts[k] for k in exts}
572571
htmlkw = htmlkw or {}
@@ -580,9 +579,9 @@ def __init__(self, debug=False, routes=None, middleware=None, title: str = "Fast
580579
on_startup,on_shutdown = listify(on_startup) or None,listify(on_shutdown) or None
581580
self.lifespan,self.hdrs,self.ftrs = lifespan,hdrs,ftrs
582581
self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw = body_wrap,before,after,htmlkw,bodykw
583-
secret_key = get_key(secret_key, key_fname)
582+
self.secret_key = get_key(secret_key, key_fname)
584583
if sess_cls:
585-
sess = Middleware(sess_cls, secret_key=secret_key,session_cookie=session_cookie,
584+
sess = Middleware(sess_cls, secret_key=self.secret_key,session_cookie=session_cookie,
586585
max_age=max_age, path=sess_path, same_site=same_site,
587586
https_only=sess_https_only, domain=sess_domain)
588587
middleware.append(sess)
@@ -727,7 +726,7 @@ def serve(
727726
class Client:
728727
"A simple httpx ASGI client that doesn't require `async`"
729728
def __init__(self, app, url="http://testserver"):
730-
self.cli = AsyncClient(transport=ASGITransport(app), base_url=url)
729+
self.cli = httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url=url)
731730

732731
def _sync(self, method, url, **kwargs):
733732
async def _request(): return await self.cli.request(method, url, **kwargs)
@@ -894,3 +893,14 @@ def devtools_json(self:FastHTML, path=None, uuid=None):
894893
@self.route(devtools_loc)
895894
def devtools():
896895
return dict(workspace=dict(root=path, uuid=uuid))
896+
897+
# %% ../nbs/api/00_core.ipynb
898+
@patch
899+
def get_client(self:FastHTML, asink=False, **kw):
900+
"Get an httpx client with session cookes set from `**kw`"
901+
signer = itsdangerous.TimestampSigner(self.secret_key)
902+
data = b64encode(dumps(kw).encode())
903+
data = signer.sign(data)
904+
client = httpx.AsyncClient() if asink else httpx.Client()
905+
client.cookies.update({self.session_cookie: data.decode()})
906+
return client

nbs/api/00_core.ipynb

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"outputs": [],
4444
"source": [
4545
"#| export\n",
46-
"import json,uuid,inspect,types,signal,asyncio,threading,inspect,random,contextlib\n",
46+
"import json,uuid,inspect,types,signal,asyncio,threading,inspect,random,contextlib,httpx,itsdangerous\n",
4747
"\n",
4848
"from fastcore.utils import *\n",
4949
"from fastcore.xml import *\n",
@@ -61,7 +61,6 @@
6161
"from copy import copy,deepcopy\n",
6262
"from warnings import warn\n",
6363
"from dateutil import parser as dtparse\n",
64-
"from httpx import ASGITransport, AsyncClient\n",
6564
"from anyio import from_thread\n",
6665
"from uuid import uuid4, UUID\n",
6766
"from base64 import b85encode,b64encode\n",
@@ -1692,7 +1691,7 @@
16921691
" same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',\n",
16931692
" body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, **bodykw):\n",
16941693
" middleware,before,after = map(_list, (middleware,before,after))\n",
1695-
" self.title,self.canonical = title,canonical\n",
1694+
" self.title,self.canonical,self.session_cookie,self.key_fname = title,canonical,session_cookie,key_fname\n",
16961695
" hdrs,ftrs,exts = map(listify, (hdrs,ftrs,exts))\n",
16971696
" exts = {k:htmx_exts[k] for k in exts}\n",
16981697
" htmlkw = htmlkw or {}\n",
@@ -1706,9 +1705,9 @@
17061705
" on_startup,on_shutdown = listify(on_startup) or None,listify(on_shutdown) or None\n",
17071706
" self.lifespan,self.hdrs,self.ftrs = lifespan,hdrs,ftrs\n",
17081707
" self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw = body_wrap,before,after,htmlkw,bodykw\n",
1709-
" secret_key = get_key(secret_key, key_fname)\n",
1708+
" self.secret_key = get_key(secret_key, key_fname)\n",
17101709
" if sess_cls:\n",
1711-
" sess = Middleware(sess_cls, secret_key=secret_key,session_cookie=session_cookie,\n",
1710+
" sess = Middleware(sess_cls, secret_key=self.secret_key,session_cookie=session_cookie,\n",
17121711
" max_age=max_age, path=sess_path, same_site=same_site,\n",
17131712
" https_only=sess_https_only, domain=sess_domain)\n",
17141713
" middleware.append(sess)\n",
@@ -2032,7 +2031,7 @@
20322031
"class Client:\n",
20332032
" \"A simple httpx ASGI client that doesn't require `async`\"\n",
20342033
" def __init__(self, app, url=\"http://testserver\"):\n",
2035-
" self.cli = AsyncClient(transport=ASGITransport(app), base_url=url)\n",
2034+
" self.cli = httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url=url)\n",
20362035
"\n",
20372036
" def _sync(self, method, url, **kwargs):\n",
20382037
" async def _request(): return await self.cli.request(method, url, **kwargs)\n",
@@ -3963,6 +3962,25 @@
39633962
" return dict(workspace=dict(root=path, uuid=uuid))"
39643963
]
39653964
},
3965+
{
3966+
"cell_type": "code",
3967+
"execution_count": null,
3968+
"id": "e27908c0",
3969+
"metadata": {},
3970+
"outputs": [],
3971+
"source": [
3972+
"#| export\n",
3973+
"@patch\n",
3974+
"def get_client(self:FastHTML, asink=False, **kw):\n",
3975+
" \"Get an httpx client with session cookes set from `**kw`\"\n",
3976+
" signer = itsdangerous.TimestampSigner(self.secret_key)\n",
3977+
" data = b64encode(dumps(kw).encode())\n",
3978+
" data = signer.sign(data)\n",
3979+
" client = httpx.AsyncClient() if asink else httpx.Client()\n",
3980+
" client.cookies.update({self.session_cookie: data.decode()})\n",
3981+
" return client"
3982+
]
3983+
},
39663984
{
39673985
"cell_type": "markdown",
39683986
"id": "474e14b4",
@@ -3978,7 +3996,7 @@
39783996
"metadata": {},
39793997
"outputs": [],
39803998
"source": [
3981-
"#|hide\n",
3999+
"#| hide\n",
39824000
"import nbdev; nbdev.nbdev_export()"
39834001
]
39844002
},

0 commit comments

Comments
 (0)