diff --git a/fasthtml/core.py b/fasthtml/core.py index 2bffa593..291b016d 100644 --- a/fasthtml/core.py +++ b/fasthtml/core.py @@ -145,14 +145,14 @@ def form2dict(form: FormData) -> dict: async def parse_form(req: Request) -> FormData: "Starlette errors on empty multipart forms, so this checks for that situation" ctype = req.headers.get("Content-Type", "") - if ctype=='application/json': return await req.json() - if not ctype.startswith("multipart/form-data"): return await req.form() - try: boundary = ctype.split("boundary=")[1].strip() - except IndexError: raise HTTPException(400, "Invalid form-data: no boundary") - min_len = len(boundary) + 6 - clen = int(req.headers.get("Content-Length", "0")) - if clen <= min_len: return FormData() - return await req.form() + if ctype.startswith("multipart/form-data"): + try: boundary = ctype.split("boundary=")[1].strip() + except IndexError: raise HTTPException(400, "Invalid form-data: no boundary") + if int(req.headers.get("Content-Length", "0")) <= len(boundary) + 6: return FormData() + return await req.form() + await req.body() # Cache body for non-multipart request types + return await req.json() if ctype == 'application/json' else await req.form() + # %% ../nbs/api/00_core.ipynb #089fe388 async def _from_body(conn, p, data): @@ -368,7 +368,7 @@ def _to_xml(req, resp, indent): "Convert response to XML string with target URL resolution" resp = _apply_ft(resp) _find_targets(req, resp) - return to_xml(resp, indent) + return to_xml(resp, indent=indent) # %% ../nbs/api/00_core.ipynb #f1e3ed2d _iter_typs = (tuple,list,map,filter,range,types.GeneratorType) diff --git a/nbs/api/00_core.ipynb b/nbs/api/00_core.ipynb index 8d7e40ae..ef6657ca 100644 --- a/nbs/api/00_core.ipynb +++ b/nbs/api/00_core.ipynb @@ -131,7 +131,7 @@ { "data": { "text/plain": [ - "datetime.datetime(2026, 2, 21, 14, 0)" + "datetime.datetime(2026, 3, 2, 14, 0)" ] }, "execution_count": null, @@ -244,7 +244,7 @@ " 'client': ('127.0.0.1', 8000),\n", " 'server': ('127.0.0.1', 8000),\n", " }\n", - " receive = lambda: {\"body\": b\"\", \"more_body\": False}\n", + " async def receive(): return {\"type\": \"http.request\", \"body\": b\"\", \"more_body\": False}\n", " return Request(scope, receive)" ] }, @@ -573,14 +573,13 @@ "async def parse_form(req: Request) -> FormData:\n", " \"Starlette errors on empty multipart forms, so this checks for that situation\"\n", " ctype = req.headers.get(\"Content-Type\", \"\")\n", - " if ctype=='application/json': return await req.json()\n", - " if not ctype.startswith(\"multipart/form-data\"): return await req.form()\n", - " try: boundary = ctype.split(\"boundary=\")[1].strip()\n", - " except IndexError: raise HTTPException(400, \"Invalid form-data: no boundary\")\n", - " min_len = len(boundary) + 6\n", - " clen = int(req.headers.get(\"Content-Length\", \"0\"))\n", - " if clen <= min_len: return FormData()\n", - " return await req.form()" + " if ctype.startswith(\"multipart/form-data\"):\n", + " try: boundary = ctype.split(\"boundary=\")[1].strip()\n", + " except IndexError: raise HTTPException(400, \"Invalid form-data: no boundary\")\n", + " if int(req.headers.get(\"Content-Length\", \"0\")) <= len(boundary) + 6: return FormData()\n", + " return await req.form()\n", + " await req.body() # Cache body for non-multipart request types\n", + " return await req.json() if ctype == 'application/json' else await req.form()\n" ] }, { @@ -834,6 +833,34 @@ " return await _find_ps(req, data, req.headers, params)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "26df5218", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ok body=19\n" + ] + } + ], + "source": [ + "def g(test_val:int=0, item:HttpHeader=None, htmx:HtmxHeaders=None, api:ApiReturn=None): ...\n", + "\n", + "async def f(req):\n", + " await _wrap_req(req, _params(g))\n", + " body = await req.body()\n", + " return Response(f\"ok body={len(body)}\")\n", + "\n", + "client = TestClient(Starlette(routes=[Route('/', f, methods=['POST'])]))\n", + "r = client.post('/', data={'test_val': '21', 'k': 'a', 'v': 'b'}, headers={'HX-Request': '1'})\n", + "test_eq(r.status_code, 200)\n", + "print(r.text)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1238,7 +1265,7 @@ " \"Convert response to XML string with target URL resolution\"\n", " resp = _apply_ft(resp)\n", " _find_targets(req, resp)\n", - " return to_xml(resp, indent)" + " return to_xml(resp, indent=indent)" ] }, { @@ -2226,13 +2253,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipymini_43537/1456865336.py:30: UserWarning: `self has no type annotation and is not a recognised special name, so is ignored.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ + "/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipymini_43537/1456865336.py:30: UserWarning: `self has no type annotation and is not a recognised special name, so is ignored.\n", " if arg!='resp': warn(f\"`{arg} has no type annotation and is not a recognised special name, so is ignored.\")\n" ] }, @@ -2295,34 +2316,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "
Text.
\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + " \n", + " \n", + "
Text.
\n", + " \n", "\n" ] } @@ -2343,48 +2340,12 @@ "name": "stdout", "output_type": "stream", "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "
Text.
\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + " \n", + " \n", + " \n", + "
Text.
\n", + " \n", + " \n", "\n" ] } @@ -2405,34 +2366,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "
Text.
\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + " \n", + " \n", + "
Text.
\n", + " \n", "\n" ] } @@ -2476,34 +2413,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "
Text.
\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + " \n", + " \n", + "
Text.
\n", + " \n", "\n" ] } @@ -2999,13 +2912,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "200\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "200\n", "content1,content2\n" ] } @@ -3460,13 +3367,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipymini_43537/1456865336.py:30: UserWarning: `self has no type annotation and is not a recognised special name, so is ignored.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ + "/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipymini_43537/1456865336.py:30: UserWarning: `self has no type annotation and is not a recognised special name, so is ignored.\n", " if arg!='resp': warn(f\"`{arg} has no type annotation and is not a recognised special name, so is ignored.\")\n" ] } @@ -3566,13 +3467,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipymini_43537/1456865336.py:30: UserWarning: `self has no type annotation and is not a recognised special name, so is ignored.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ + "/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/ipymini_43537/1456865336.py:30: UserWarning: `self has no type annotation and is not a recognised special name, so is ignored.\n", " if arg!='resp': warn(f\"`{arg} has no type annotation and is not a recognised special name, so is ignored.\")\n" ] } @@ -3671,76 +3566,16 @@ "name": "stdout", "output_type": "stream", "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " FastHTML page\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " \n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + " \n", + " \n", + " \n", + " FastHTML page\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", "\n" ] }, @@ -3944,13 +3779,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Starting slow task\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "Starting slow task\n", "Finished slow task\n" ] } @@ -4003,41 +3832,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sleeping for 0.0s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Slept for 0.0s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sleeping for 0.001s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Slept for 0.001s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sleeping for 0.002s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "Sleeping for 0.0s\n", + "Slept for 0.0s\n", + "Sleeping for 0.001s\n", + "Slept for 0.001s\n", + "Sleeping for 0.002s\n", "Slept for 0.002s\n" ] } @@ -4063,41 +3862,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sleeping for 0.0s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Slept for 0.0s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sleeping for 0.001s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Slept for 0.001s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sleeping for 0.002s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "Sleeping for 0.0s\n", + "Slept for 0.0s\n", + "Sleeping for 0.001s\n", + "Slept for 0.001s\n", + "Sleeping for 0.002s\n", "Slept for 0.002s\n" ] } @@ -4122,41 +3891,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Sleeping for 0.0s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Slept for 0.0s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sleeping for 0.001s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Slept for 0.001s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sleeping for 0.002s\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "Sleeping for 0.0s\n", + "Slept for 0.0s\n", + "Sleeping for 0.001s\n", + "Slept for 0.001s\n", + "Sleeping for 0.002s\n", "Slept for 0.002s\n" ] }, diff --git a/nbs/ref/defining_xt_component.ipynb b/nbs/ref/defining_xt_component.ipynb index b5bdb42b..eb9c74dd 100644 --- a/nbs/ref/defining_xt_component.ipynb +++ b/nbs/ref/defining_xt_component.ipynb @@ -343,7 +343,7 @@ ], "source": [ "div_NotStr = NotStr('
I look like a string but render with to_xml
') \n", - "to_xml(div_NotStr)" + "div_NotStr" ] }, { @@ -355,13 +355,7 @@ "source": [] } ], - "metadata": { - "kernelspec": { - "display_name": "python3", - "language": "python", - "name": "python3" - } - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 5 }