|
358 | 358 | " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n", |
359 | 359 | "<script src=\"https://unpkg.com/htmx.org@next/dist/htmx.min.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script><script>\n", |
360 | 360 | " function sendmsg() {\n", |
361 | | - " window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');\n", |
| 361 | + " window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');\n", |
362 | 362 | " }\n", |
363 | 363 | " window.onload = function() {\n", |
364 | 364 | " sendmsg();\n", |
365 | | - " document.body.addEventListener('htmx:afterSettle', sendmsg);\n", |
366 | | - " document.body.addEventListener('htmx:wsAfterMessage', sendmsg);\n", |
| 365 | + " document.body.addEventListener('htmx:afterSettle', sendmsg);\n", |
| 366 | + " document.body.addEventListener('htmx:wsAfterMessage', sendmsg);\n", |
367 | 367 | " };</script> </head>\n", |
368 | 368 | " <body>\n", |
369 | 369 | " <h1>bar</h1>\n", |
|
678 | 678 | "name": "stderr", |
679 | 679 | "output_type": "stream", |
680 | 680 | "text": [ |
681 | | - "/Users/jhoward/Documents/GitHub/fasthtml/fasthtml/core.py:185: UserWarning: `nope has no type annotation and is not a recognised special name, so is ignored.\n", |
| 681 | + "/Users/wgilliam/development/projects/aai/fasthtml/fasthtml/core.py:188: UserWarning: `nope has no type annotation and is not a recognised special name, so is ignored.\n", |
682 | 682 | " if arg!='resp': warn(f\"`{arg} has no type annotation and is not a recognised special name, so is ignored.\")\n" |
683 | 683 | ] |
684 | 684 | }, |
|
1252 | 1252 | " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n", |
1253 | 1253 | "<script src=\"https://unpkg.com/htmx.org@next/dist/htmx.min.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script><script>\n", |
1254 | 1254 | " function sendmsg() {\n", |
1255 | | - " window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');\n", |
| 1255 | + " window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');\n", |
1256 | 1256 | " }\n", |
1257 | 1257 | " window.onload = function() {\n", |
1258 | 1258 | " sendmsg();\n", |
1259 | | - " document.body.addEventListener('htmx:afterSettle', sendmsg);\n", |
1260 | | - " document.body.addEventListener('htmx:wsAfterMessage', sendmsg);\n", |
| 1259 | + " document.body.addEventListener('htmx:afterSettle', sendmsg);\n", |
| 1260 | + " document.body.addEventListener('htmx:wsAfterMessage', sendmsg);\n", |
1261 | 1261 | " };</script> </head>\n", |
1262 | 1262 | " <body>\n", |
1263 | 1263 | " <h1>hi</h1>\n", |
|
1288 | 1288 | "- `(Meta(property='image'), Meta(property='site_name'))` defines two meta tags, which are both placed in the head." |
1289 | 1289 | ] |
1290 | 1290 | }, |
| 1291 | + { |
| 1292 | + "cell_type": "markdown", |
| 1293 | + "id": "403fcae3", |
| 1294 | + "metadata": {}, |
| 1295 | + "source": [ |
| 1296 | + "## APIRouter" |
| 1297 | + ] |
| 1298 | + }, |
| 1299 | + { |
| 1300 | + "cell_type": "markdown", |
| 1301 | + "id": "f8138c07", |
| 1302 | + "metadata": {}, |
| 1303 | + "source": [ |
| 1304 | + "`APIRouter` is useful when you want to split your application routes across multiple `.py` files that are part of a single FastHTMl application. It accepts an optional `prefix` argument that will be applied to all routes within that instance of `APIRouter`.\n", |
| 1305 | + "\n", |
| 1306 | + "Below we define several hypothetical product related routes in a `products.py` and then demonstrate how they can seamlessly be incorporated into a FastHTML app instance." |
| 1307 | + ] |
| 1308 | + }, |
| 1309 | + { |
| 1310 | + "cell_type": "code", |
| 1311 | + "execution_count": null, |
| 1312 | + "id": "5988523d", |
| 1313 | + "metadata": {}, |
| 1314 | + "outputs": [], |
| 1315 | + "source": [ |
| 1316 | + "# products.py\n", |
| 1317 | + "ar = APIRouter(prefix=\"/products\")\n", |
| 1318 | + "\n", |
| 1319 | + "@ar(\"/all\")\n", |
| 1320 | + "def all_products(req):\n", |
| 1321 | + " return Div(\n", |
| 1322 | + " \"Welcome to the Products Page! Click the button below to look at the details for product 42\",\n", |
| 1323 | + " Div(\n", |
| 1324 | + " Button(\n", |
| 1325 | + " \"Details\",\n", |
| 1326 | + " hx_get=req.url_for(\"details\", pid=42),\n", |
| 1327 | + " hx_target=\"#products_list\",\n", |
| 1328 | + " hx_swap=\"outerHTML\",\n", |
| 1329 | + " ),\n", |
| 1330 | + " ),\n", |
| 1331 | + " id=\"products_list\",\n", |
| 1332 | + " )\n", |
| 1333 | + "\n", |
| 1334 | + "\n", |
| 1335 | + "@ar.get(\"/{pid}\", name=\"details\")\n", |
| 1336 | + "def details(pid: int):\n", |
| 1337 | + " return f\"Here are the product details for ID: {pid}\"" |
| 1338 | + ] |
| 1339 | + }, |
| 1340 | + { |
| 1341 | + "cell_type": "markdown", |
| 1342 | + "id": "c7b2325a", |
| 1343 | + "metadata": {}, |
| 1344 | + "source": [ |
| 1345 | + "Since we specified the `prefix=/products` in our hypothetical `products.py` file, all routes defined in that file will be found under `/products`." |
| 1346 | + ] |
| 1347 | + }, |
| 1348 | + { |
| 1349 | + "cell_type": "code", |
| 1350 | + "execution_count": null, |
| 1351 | + "id": "001ff0b8", |
| 1352 | + "metadata": {}, |
| 1353 | + "outputs": [ |
| 1354 | + { |
| 1355 | + "name": "stdout", |
| 1356 | + "output_type": "stream", |
| 1357 | + "text": [ |
| 1358 | + "/products/all\n", |
| 1359 | + "/products/{pid}\n" |
| 1360 | + ] |
| 1361 | + } |
| 1362 | + ], |
| 1363 | + "source": [ |
| 1364 | + "print(str(ar.rt_funcs.all_products))\n", |
| 1365 | + "print(str(ar.rt_funcs.details))" |
| 1366 | + ] |
| 1367 | + }, |
| 1368 | + { |
| 1369 | + "cell_type": "code", |
| 1370 | + "execution_count": null, |
| 1371 | + "id": "49795185", |
| 1372 | + "metadata": {}, |
| 1373 | + "outputs": [], |
| 1374 | + "source": [ |
| 1375 | + "# main.py\n", |
| 1376 | + "# from products import ar\n", |
| 1377 | + "\n", |
| 1378 | + "app, rt = fast_app()\n", |
| 1379 | + "ar.to_app(app)\n", |
| 1380 | + "\n", |
| 1381 | + "@rt\n", |
| 1382 | + "def index():\n", |
| 1383 | + " return Div(\n", |
| 1384 | + " \"Click me for a look at our products\",\n", |
| 1385 | + " hx_get=ar.rt_funcs.all_products,\n", |
| 1386 | + " hx_swap=\"outerHTML\",\n", |
| 1387 | + " )" |
| 1388 | + ] |
| 1389 | + }, |
| 1390 | + { |
| 1391 | + "cell_type": "markdown", |
| 1392 | + "id": "7eb1a33f", |
| 1393 | + "metadata": {}, |
| 1394 | + "source": [ |
| 1395 | + "Note how you can reference our python route functions via `APIRouter.rt_funcs` in your `hx_{http_method}` calls like normal." |
| 1396 | + ] |
| 1397 | + }, |
1291 | 1398 | { |
1292 | 1399 | "cell_type": "markdown", |
1293 | 1400 | "id": "311ca66a", |
|
1306 | 1413 | "name": "stdout", |
1307 | 1414 | "output_type": "stream", |
1308 | 1415 | "text": [ |
1309 | | - "Alexis\n", |
1310 | | - "Missing required field: username\n" |
| 1416 | + "404 Not Found\n", |
| 1417 | + "404 Not Found\n" |
1311 | 1418 | ] |
1312 | 1419 | }, |
1313 | 1420 | { |
1314 | 1421 | "data": { |
1315 | 1422 | "text/plain": [ |
1316 | | - "<Response [400 Bad Request]>" |
| 1423 | + "<Response [404 Not Found]>" |
1317 | 1424 | ] |
1318 | 1425 | }, |
1319 | 1426 | "execution_count": null, |
|
1353 | 1460 | "name": "stdout", |
1354 | 1461 | "output_type": "stream", |
1355 | 1462 | "text": [ |
1356 | | - "unknown name\n" |
| 1463 | + "404 Not Found\n" |
1357 | 1464 | ] |
1358 | 1465 | } |
1359 | 1466 | ], |
|
1383 | 1490 | "name": "stdout", |
1384 | 1491 | "output_type": "stream", |
1385 | 1492 | "text": [ |
1386 | | - "{\"a\":1,\"b\":\"foo\",\"nm\":\"me\"}\n" |
| 1493 | + "404 Not Found\n" |
1387 | 1494 | ] |
1388 | 1495 | } |
1389 | 1496 | ], |
|
1422 | 1529 | "name": "stdout", |
1423 | 1530 | "output_type": "stream", |
1424 | 1531 | "text": [ |
1425 | | - "{\"a\":\"1\",\"b\":\"foo\"}\n" |
| 1532 | + "404 Not Found\n" |
1426 | 1533 | ] |
1427 | 1534 | } |
1428 | 1535 | ], |
|
1454 | 1561 | "name": "stdout", |
1455 | 1562 | "output_type": "stream", |
1456 | 1563 | "text": [ |
1457 | | - "{\"a\":\"1\",\"b\":\"foo\"}\n" |
| 1564 | + "404 Not Found\n" |
1458 | 1565 | ] |
1459 | 1566 | } |
1460 | 1567 | ], |
|
1486 | 1593 | "name": "stdout", |
1487 | 1594 | "output_type": "stream", |
1488 | 1595 | "text": [ |
1489 | | - "{\"a\":1,\"b\":\"foo\"}\n" |
| 1596 | + "404 Not Found\n" |
1490 | 1597 | ] |
1491 | 1598 | } |
1492 | 1599 | ], |
|
1518 | 1625 | "name": "stdout", |
1519 | 1626 | "output_type": "stream", |
1520 | 1627 | "text": [ |
1521 | | - "a: 1; b: foo\n" |
| 1628 | + "404 Not Found\n" |
1522 | 1629 | ] |
1523 | 1630 | } |
1524 | 1631 | ], |
|
1552 | 1659 | "name": "stdout", |
1553 | 1660 | "output_type": "stream", |
1554 | 1661 | "text": [ |
1555 | | - " <title>It worked!</title>\n", |
1556 | | - "<main class=\"container\"> <h1>It worked!</h1>\n", |
1557 | | - " <p>15, Lorem</p>\n", |
1558 | | - "</main>\n" |
| 1662 | + "404 Not Found\n" |
1559 | 1663 | ] |
1560 | 1664 | } |
1561 | 1665 | ], |
|
1599 | 1703 | "name": "stdout", |
1600 | 1704 | "output_type": "stream", |
1601 | 1705 | "text": [ |
1602 | | - "\n" |
| 1706 | + "404 Not Found\n" |
1603 | 1707 | ] |
1604 | 1708 | }, |
1605 | 1709 | { |
1606 | 1710 | "data": { |
1607 | 1711 | "text/plain": [ |
1608 | | - "'Cookie was set at time 14:16:57.084240'" |
| 1712 | + "'404 Not Found'" |
1609 | 1713 | ] |
1610 | 1714 | }, |
1611 | 1715 | "execution_count": null, |
|
1647 | 1751 | { |
1648 | 1752 | "data": { |
1649 | 1753 | "text/plain": [ |
1650 | | - "HttpHeader(k='set-cookie', v='now=\"2024-10-24 14:16:57.121212\"; Path=/; SameSite=lax')" |
| 1754 | + "HttpHeader(k='set-cookie', v='now=\"2024-12-04 13:45:24.154187\"; Path=/; SameSite=lax')" |
1651 | 1755 | ] |
1652 | 1756 | }, |
1653 | 1757 | "execution_count": null, |
|
1689 | 1793 | "name": "stdout", |
1690 | 1794 | "output_type": "stream", |
1691 | 1795 | "text": [ |
1692 | | - "Set to 2024-10-24 14:16:57.168313\n" |
| 1796 | + "Set to 2024-12-04 13:45:24.159764\n" |
1693 | 1797 | ] |
1694 | 1798 | }, |
1695 | 1799 | { |
1696 | 1800 | "data": { |
1697 | 1801 | "text/plain": [ |
1698 | | - "'Session time: 2024-10-24 14:16:57.168313'" |
| 1802 | + "'Session time: 2024-12-04 13:45:24.159764'" |
1699 | 1803 | ] |
1700 | 1804 | }, |
1701 | 1805 | "execution_count": null, |
|
1864 | 1968 | "name": "stdout", |
1865 | 1969 | "output_type": "stream", |
1866 | 1970 | "text": [ |
1867 | | - "Headers({'access-control-allow-origin': '*', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': '*', 'content-length': '0', 'set-cookie': 'session_=eyJhdXRoIjogIjIwMjQtMTAtMjQgMTQ6MTY6NTcuMTY4MzEzIn0=.ZxnKOQ.8x-zyM5rd59ix3PtADan0qfL-bk; path=/; Max-Age=31536000; httponly; samesite=lax'})\n" |
| 1971 | + "Headers({'access-control-allow-origin': '*', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': '*', 'content-length': '0', 'set-cookie': 'session_=eyJhdXRoIjogIjIwMjQtMTItMDQgMTM6NDU6MjQuMTU5NzY0In0=.Z1DNdA.GV-NVoOnJeambm9_uE3crhoGH34; path=/; Max-Age=31536000; httponly; samesite=lax'})\n" |
1868 | 1972 | ] |
1869 | 1973 | } |
1870 | 1974 | ], |
|
1913 | 2017 | " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n", |
1914 | 2018 | "<script src=\"https://unpkg.com/htmx.org@next/dist/htmx.min.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected]/fasthtml.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js\"></script><script src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"></script><script>\n", |
1915 | 2019 | " function sendmsg() {\n", |
1916 | | - " window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');\n", |
| 2020 | + " window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');\n", |
1917 | 2021 | " }\n", |
1918 | 2022 | " window.onload = function() {\n", |
1919 | 2023 | " sendmsg();\n", |
1920 | | - " document.body.addEventListener('htmx:afterSettle', sendmsg);\n", |
1921 | | - " document.body.addEventListener('htmx:wsAfterMessage', sendmsg);\n", |
| 2024 | + " document.body.addEventListener('htmx:afterSettle', sendmsg);\n", |
| 2025 | + " document.body.addEventListener('htmx:wsAfterMessage', sendmsg);\n", |
1922 | 2026 | " };</script> </head>\n", |
1923 | 2027 | " <body>\n", |
1924 | 2028 | " <div>nope</div>\n", |
|
0 commit comments