Skip to content

Commit 33c4b1f

Browse files
authored
Merge pull request #55 from AnswerDotAI/better-post-error-message
Raise error on missing params
2 parents 1e1313c + 459b964 commit 33c4b1f

File tree

2 files changed

+83
-18
lines changed

2 files changed

+83
-18
lines changed

fasthtml/core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ async def _find_p(req, arg:str, p:Parameter):
157157
if res is empty or res is None:
158158
frm = await req.form()
159159
res = _formitem(frm, arg)
160-
# Use default param if needed
160+
# Raise 400 error if the param does not include a default
161+
if (res is empty or res is None) and p.default is empty: raise HTTPException(400, f"Missing required field: {arg}")
162+
# If we have a default, return that if we have no value
161163
if res is empty or res is None: res = p.default
162164
# We can cast str and list[str] to types; otherwise just return what we have
163165
if not isinstance(res, (list,str)) or anno is empty: return res

nbs/00_core.ipynb

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
{
146146
"data": {
147147
"text/plain": [
148-
"datetime.datetime(2024, 7, 11, 14, 0)"
148+
"datetime.datetime(2024, 7, 17, 14, 0)"
149149
]
150150
},
151151
"execution_count": null,
@@ -662,7 +662,9 @@
662662
" if res is empty or res is None:\n",
663663
" frm = await req.form()\n",
664664
" res = _formitem(frm, arg)\n",
665-
" # Use default param if needed\n",
665+
" # Raise 400 error if the param does not include a default\n",
666+
" if (res is empty or res is None) and p.default is empty: raise HTTPException(400, f\"Missing required field: {arg}\")\n",
667+
" # If we have a default, return that if we have no value\n",
666668
" if res is empty or res is None: res = p.default\n",
667669
" # We can cast str and list[str] to types; otherwise just return what we have\n",
668670
" if not isinstance(res, (list,str)) or anno is empty: return res\n",
@@ -1031,7 +1033,7 @@
10311033
{
10321034
"data": {
10331035
"text/plain": [
1034-
"'08b63b51-be3a-4f54-8d26-4cd27eb17c0d'"
1036+
"'2c25d03b-24f2-4946-833a-50ac832a1576'"
10351037
]
10361038
},
10371039
"execution_count": null,
@@ -1453,6 +1455,58 @@
14531455
"test_eq(r.headers['mykey'], 'myval')"
14541456
]
14551457
},
1458+
{
1459+
"cell_type": "code",
1460+
"execution_count": null,
1461+
"id": "a3596991",
1462+
"metadata": {},
1463+
"outputs": [],
1464+
"source": [
1465+
"@app.post('/profile/me')\n",
1466+
"def profile_update(username: str): return username"
1467+
]
1468+
},
1469+
{
1470+
"cell_type": "code",
1471+
"execution_count": null,
1472+
"id": "277b1db0",
1473+
"metadata": {},
1474+
"outputs": [],
1475+
"source": [
1476+
"# Working post request, this is our control\n",
1477+
"test_r(cli, '/profile/me', 'Alexis', 'post',\n",
1478+
" data={'username' : 'Alexis'})"
1479+
]
1480+
},
1481+
{
1482+
"cell_type": "code",
1483+
"execution_count": null,
1484+
"id": "93d18dfa",
1485+
"metadata": {},
1486+
"outputs": [],
1487+
"source": [
1488+
"# Failing post request due to missing required username parameter\n",
1489+
"try:\n",
1490+
" test_r(cli, '/profile/me', 'Alexis', 'post', data={})\n",
1491+
"except AssertionError as e:\n",
1492+
" assert \"Missing required field: username\" in str(e)"
1493+
]
1494+
},
1495+
{
1496+
"cell_type": "code",
1497+
"execution_count": null,
1498+
"id": "fdb9239c",
1499+
"metadata": {},
1500+
"outputs": [],
1501+
"source": [
1502+
"# Example post request with parameter that has a default value\n",
1503+
"@app.post('/pet/dog')\n",
1504+
"def pet_dog(dogname: str = None): return dogname\n",
1505+
"\n",
1506+
"# Working post request with optional parameter\n",
1507+
"test_r(cli, '/pet/dog', '', 'post', data={})"
1508+
]
1509+
},
14561510
{
14571511
"cell_type": "code",
14581512
"execution_count": null,
@@ -1495,11 +1549,28 @@
14951549
"execution_count": null,
14961550
"id": "48f0a45e",
14971551
"metadata": {},
1498-
"outputs": [],
1552+
"outputs": [
1553+
{
1554+
"ename": "AssertionError",
1555+
"evalue": "==:\nNot Found\n{\"a\":1,\"b\":\"foo\",\"nm\":\"me\"}",
1556+
"output_type": "error",
1557+
"traceback": [
1558+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
1559+
"\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
1560+
"Cell \u001b[0;32mIn[89], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m d \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(a\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m, b\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mfoo\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m \u001b[43mtest_r\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcli\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m/bodie/me\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m{\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43ma\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m:1,\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mb\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m:\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mfoo\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m,\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mnm\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m:\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mme\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m}\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mpost\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43md\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# This test should not be working, as the \"nm\" arguement isn't provided and there isn't a default\u001b[39;00m\n\u001b[1;32m 5\u001b[0m test_r(cli, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m/bodied/\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m{\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124ma\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m:\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m1\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m,\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m:\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfoo\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m}\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mpost\u001b[39m\u001b[38;5;124m'\u001b[39m, data\u001b[38;5;241m=\u001b[39md)\n",
1561+
"Cell \u001b[0;32mIn[66], line 3\u001b[0m, in \u001b[0;36mtest_r\u001b[0;34m(cli, path, exp, meth, hx, **kwargs)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtest_r\u001b[39m(cli, path, exp, meth\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mget\u001b[39m\u001b[38;5;124m'\u001b[39m, hx\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m hx: kwargs[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mheaders\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m {\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mhx-request\u001b[39m\u001b[38;5;124m'\u001b[39m:\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m1\u001b[39m\u001b[38;5;124m\"\u001b[39m}\n\u001b[0;32m----> 3\u001b[0m \u001b[43mtest_eq\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mgetattr\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mcli\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmeth\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtext\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mexp\u001b[49m\u001b[43m)\u001b[49m\n",
1562+
"File \u001b[0;32m~/.virtualenvs/fasthtml/lib/python3.10/site-packages/fastcore-1.5.51-py3.10.egg/fastcore/test.py:37\u001b[0m, in \u001b[0;36mtest_eq\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m 35\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtest_eq\u001b[39m(a,b):\n\u001b[1;32m 36\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m`test` that `a==b`\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m---> 37\u001b[0m \u001b[43mtest\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43mb\u001b[49m\u001b[43m,\u001b[49m\u001b[43mequals\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcname\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m==\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n",
1563+
"File \u001b[0;32m~/.virtualenvs/fasthtml/lib/python3.10/site-packages/fastcore-1.5.51-py3.10.egg/fastcore/test.py:27\u001b[0m, in \u001b[0;36mtest\u001b[0;34m(a, b, cmp, cname)\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m`assert` that `cmp(a,b)`; display inputs and `cname or cmp.__name__` if it fails\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m cname \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m: cname\u001b[38;5;241m=\u001b[39mcmp\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\n\u001b[0;32m---> 27\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m cmp(a,b),\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00ma\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mb\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n",
1564+
"\u001b[0;31mAssertionError\u001b[0m: ==:\nNot Found\n{\"a\":1,\"b\":\"foo\",\"nm\":\"me\"}"
1565+
]
1566+
}
1567+
],
14991568
"source": [
15001569
"d = dict(a=1, b='foo')\n",
15011570
"\n",
15021571
"test_r(cli, '/bodie/me', '{\"a\":1,\"b\":\"foo\",\"nm\":\"me\"}', 'post', data=d)\n",
1572+
"# This test should not be working, as the \"nm\" arguement isn't\n",
1573+
"# provided and there isn't a default\n",
15031574
"test_r(cli, '/bodied/', '{\"a\":\"1\",\"b\":\"foo\"}', 'post', data=d)\n",
15041575
"test_r(cli, '/bodie2/', 'a: 1; b: foo', 'post', data={'a':1})\n",
15051576
"test_r(cli, '/bodient/', '{\"a\":\"1\",\"b\":\"foo\"}', 'post', data=d)\n",
@@ -1536,7 +1607,7 @@
15361607
{
15371608
"data": {
15381609
"text/plain": [
1539-
"'Cookie was set at time 17:11:37.078633'"
1610+
"'Cookie was set at time 23:41:43.695239'"
15401611
]
15411612
},
15421613
"execution_count": null,
@@ -1577,13 +1648,13 @@
15771648
"name": "stdout",
15781649
"output_type": "stream",
15791650
"text": [
1580-
"Set to 2024-07-11 16:59:55.536116\n"
1651+
"Set to 2024-07-17 23:41:43.724183\n"
15811652
]
15821653
},
15831654
{
15841655
"data": {
15851656
"text/plain": [
1586-
"'Session time: 16:59:55.536116'"
1657+
"'Session time: 23:41:43.724183'"
15871658
]
15881659
},
15891660
"execution_count": null,
@@ -1611,11 +1682,11 @@
16111682
"\n",
16121683
"<!-- do not remove -->\n",
16131684
"\n",
1614-
"## 0.1.4\n",
1685+
"## 0.1.6\n",
16151686
"\n",
16161687
"### New Features\n",
16171688
"\n",
1618-
"- `ScriptX`\n"
1689+
"- `File` fu\n"
16191690
]
16201691
}
16211692
],
@@ -1715,14 +1786,6 @@
17151786
"#|hide\n",
17161787
"import nbdev; nbdev.nbdev_export()"
17171788
]
1718-
},
1719-
{
1720-
"cell_type": "code",
1721-
"execution_count": null,
1722-
"id": "ff0f002c",
1723-
"metadata": {},
1724-
"outputs": [],
1725-
"source": []
17261789
}
17271790
],
17281791
"metadata": {

0 commit comments

Comments
 (0)