Skip to content

Commit c9d0a4b

Browse files
committed
fixes #800
1 parent 0d923c2 commit c9d0a4b

File tree

3 files changed

+45
-16
lines changed

3 files changed

+45
-16
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/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: 28 additions & 10 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",
@@ -131,7 +130,7 @@
131130
{
132131
"data": {
133132
"text/plain": [
134-
"datetime.datetime(2025, 10, 27, 14, 0)"
133+
"datetime.datetime(2025, 11, 19, 14, 0)"
135134
]
136135
},
137136
"execution_count": null,
@@ -1646,7 +1645,7 @@
16461645
" same_site='lax', sess_https_only=False, sess_domain=None, key_fname='.sesskey',\n",
16471646
" body_wrap=noop_body, htmlkw=None, nb_hdrs=False, canonical=True, **bodykw):\n",
16481647
" middleware,before,after = map(_list, (middleware,before,after))\n",
1649-
" self.title,self.canonical = title,canonical\n",
1648+
" self.title,self.canonical,self.session_cookie,self.key_fname = title,canonical,session_cookie,key_fname\n",
16501649
" hdrs,ftrs,exts = map(listify, (hdrs,ftrs,exts))\n",
16511650
" exts = {k:htmx_exts[k] for k in exts}\n",
16521651
" htmlkw = htmlkw or {}\n",
@@ -1660,9 +1659,9 @@
16601659
" on_startup,on_shutdown = listify(on_startup) or None,listify(on_shutdown) or None\n",
16611660
" self.lifespan,self.hdrs,self.ftrs = lifespan,hdrs,ftrs\n",
16621661
" self.body_wrap,self.before,self.after,self.htmlkw,self.bodykw = body_wrap,before,after,htmlkw,bodykw\n",
1663-
" secret_key = get_key(secret_key, key_fname)\n",
1662+
" self.secret_key = get_key(secret_key, key_fname)\n",
16641663
" if sess_cls:\n",
1665-
" sess = Middleware(sess_cls, secret_key=secret_key,session_cookie=session_cookie,\n",
1664+
" sess = Middleware(sess_cls, secret_key=self.secret_key,session_cookie=session_cookie,\n",
16661665
" max_age=max_age, path=sess_path, same_site=same_site,\n",
16671666
" https_only=sess_https_only, domain=sess_domain)\n",
16681667
" middleware.append(sess)\n",
@@ -1986,7 +1985,7 @@
19861985
"class Client:\n",
19871986
" \"A simple httpx ASGI client that doesn't require `async`\"\n",
19881987
" def __init__(self, app, url=\"http://testserver\"):\n",
1989-
" self.cli = AsyncClient(transport=ASGITransport(app), base_url=url)\n",
1988+
" self.cli = httpx.AsyncClient(transport=httpx.ASGITransport(app), base_url=url)\n",
19901989
"\n",
19911990
" def _sync(self, method, url, **kwargs):\n",
19921991
" async def _request(): return await self.cli.request(method, url, **kwargs)\n",
@@ -2888,13 +2887,13 @@
28882887
"name": "stdout",
28892888
"output_type": "stream",
28902889
"text": [
2891-
"Set to 2025-10-27 07:27:07.025956\n"
2890+
"Set to 2025-11-19 08:56:35.141231\n"
28922891
]
28932892
},
28942893
{
28952894
"data": {
28962895
"text/plain": [
2897-
"'Session time: 2025-10-27 07:27:07.025956'"
2896+
"'Session time: 2025-11-19 08:56:35.141231'"
28982897
]
28992898
},
29002899
"execution_count": null,
@@ -3511,7 +3510,7 @@
35113510
{
35123511
"data": {
35133512
"text/plain": [
3514-
"'Cookie was set at time 07:28:31.264910'"
3513+
"'Cookie was set at time 08:56:36.257555'"
35153514
]
35163515
},
35173516
"execution_count": null,
@@ -3917,6 +3916,25 @@
39173916
" return dict(workspace=dict(root=path, uuid=uuid))"
39183917
]
39193918
},
3919+
{
3920+
"cell_type": "code",
3921+
"execution_count": null,
3922+
"id": "e27908c0",
3923+
"metadata": {},
3924+
"outputs": [],
3925+
"source": [
3926+
"#| export\n",
3927+
"@patch\n",
3928+
"def get_client(self:FastHTML, asink=False, **kw):\n",
3929+
" \"Get an httpx client with session cookes set from `**kw`\"\n",
3930+
" signer = itsdangerous.TimestampSigner(self.secret_key)\n",
3931+
" data = b64encode(dumps(kw).encode())\n",
3932+
" data = signer.sign(data)\n",
3933+
" client = httpx.AsyncClient() if asink else httpx.Client()\n",
3934+
" client.cookies.update({self.session_cookie: data.decode()})\n",
3935+
" return client"
3936+
]
3937+
},
39203938
{
39213939
"cell_type": "markdown",
39223940
"id": "474e14b4",

0 commit comments

Comments
 (0)