Skip to content

Commit 2af4272

Browse files
authored
Merge branch 'main' into fix/add-default-title
2 parents f5e2375 + e0ef241 commit 2af4272

File tree

7 files changed

+42
-38
lines changed

7 files changed

+42
-38
lines changed

fasthtml/components.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ def show(ft,*rest):
5050
"hx_disabled_elt": Literal["this", "next", "previous"] | str,
5151
"hx_history": Literal["false"] | str,
5252
"hx_params": Literal["*", "none"] | str,
53-
"hx_replace_url": Literal["true", "false"] | str,
5453
"hx_validate": Literal["true", "false"],
5554
}
5655
hx_attrs_annotations |= {o: str for o in set(hx_attrs) - set(hx_attrs_annotations.keys())}

fasthtml/core.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -435,20 +435,20 @@ async def _wrap_call(f, req, params):
435435

436436
# %% ../nbs/api/00_core.ipynb
437437
htmx_exts = {
438-
"head-support": "https://unpkg.com/[email protected].1/head-support.js",
439-
"preload": "https://unpkg.com/htmx-ext-preload@2.0.1/preload.js",
440-
"class-tools": "https://unpkg.com/[email protected]/class-tools.js",
441-
"loading-states": "https://unpkg.com/[email protected]/loading-states.js",
442-
"multi-swap": "https://unpkg.com/[email protected]/multi-swap.js",
443-
"path-deps": "https://unpkg.com/[email protected]/path-deps.js",
438+
"head-support": "https://unpkg.com/[email protected].3/head-support.js",
439+
"preload": "https://unpkg.com/htmx-ext-preload@2.1.0/preload.js",
440+
"class-tools": "https://unpkg.com/[email protected]/class-tools.js",
441+
"loading-states": "https://unpkg.com/[email protected]/loading-states.js",
442+
"multi-swap": "https://unpkg.com/[email protected]/multi-swap.js",
443+
"path-deps": "https://unpkg.com/[email protected]/path-deps.js",
444444
"remove-me": "https://unpkg.com/[email protected]/remove-me.js",
445-
"ws": "https://unpkg.com/[email protected].1/ws.js",
445+
"ws": "https://unpkg.com/[email protected].2/ws.js",
446446
"chunked-transfer": "https://unpkg.com/[email protected]/transfer-encoding-chunked.js"
447447
}
448448

449449
# %% ../nbs/api/00_core.ipynb
450-
htmxsrc = Script(src="https://unpkg.com/[email protected].3/dist/htmx.min.js")
451-
fhjsscr = Script(src="https://cdn.jsdelivr.net/gh/answerdotai/[email protected].4/fasthtml.js")
450+
htmxsrc = Script(src="https://unpkg.com/[email protected].4/dist/htmx.min.js")
451+
fhjsscr = Script(src="https://cdn.jsdelivr.net/gh/answerdotai/[email protected].12/fasthtml.js")
452452
surrsrc = Script(src="https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js")
453453
scopesrc = Script(src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js")
454454
viewport = Meta(name="viewport", content="width=device-width, initial-scale=1, viewport-fit=cover")
@@ -668,10 +668,11 @@ def __dir__(self): return list(self._funcs.keys())
668668
# %% ../nbs/api/00_core.ipynb
669669
class APIRouter:
670670
"Add routes to an app"
671-
def __init__(self, prefix:str|None=None):
671+
def __init__(self, prefix:str|None=None, body_wrap=noop_body):
672672
self.routes,self.wss = [],[]
673673
self.rt_funcs = RouteFuncs() # Store wrapped route function for discoverability
674674
self.prefix = prefix if prefix else ""
675+
self.body_wrap = body_wrap
675676

676677
def _wrap_func(self, func, path=None):
677678
name = func.__name__
@@ -681,12 +682,12 @@ def _wrap_func(self, func, path=None):
681682
if name not in all_meths: setattr(self.rt_funcs, name, wrapped)
682683
return wrapped
683684

684-
def __call__(self, path:str=None, methods=None, name=None, include_in_schema=True, body_wrap=noop_body):
685+
def __call__(self, path:str=None, methods=None, name=None, include_in_schema=True, body_wrap=None):
685686
"Add a route at `path`"
686687
def f(func):
687688
p = self.prefix + ("/" + ('' if path.__name__=='index' else func.__name__) if callable(path) else path)
688689
wrapped = self._wrap_func(func, p)
689-
self.routes.append((func, p, methods, name, include_in_schema, body_wrap))
690+
self.routes.append((func, p, methods, name, include_in_schema, body_wrap or self.body_wrap))
690691
return wrapped
691692
return f(path) if callable(path) else f
692693

fasthtml/js.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def light_media(
2323
def dark_media(
2424
css: str # CSS to be included in the dark media query
2525
):
26-
"Render dark media for nught mode views"
26+
"Render dark media for night mode views"
2727
return Style('@media (prefers-color-scheme: dark) {%s}' %css)
2828

2929
# %% ../nbs/api/03_js.ipynb
@@ -56,7 +56,7 @@ def KatexMarkdownJS(
5656

5757
# %% ../nbs/api/03_js.ipynb
5858
def HighlightJS(
59-
sel='pre code', # CSS selector for code elements. Default is industry standard, be careful before adjusting it
59+
sel='pre code:not([data-highlighted="yes"])', # CSS selector for code elements. Default is industry standard, be careful before adjusting it
6060
langs:str|list|tuple='python', # Language(s) to highlight
6161
light='atom-one-light', # Light theme
6262
dark='atom-one-dark' # Dark theme

nbs/api/00_core.ipynb

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@
131131
{
132132
"data": {
133133
"text/plain": [
134-
"datetime.datetime(2024, 12, 14, 14, 0)"
134+
"datetime.datetime(2024, 12, 20, 14, 0)"
135135
]
136136
},
137137
"execution_count": null,
@@ -1176,14 +1176,14 @@
11761176
"source": [
11771177
"#| export\n",
11781178
"htmx_exts = {\n",
1179-
" \"head-support\": \"https://unpkg.com/[email protected].1/head-support.js\", \n",
1180-
" \"preload\": \"https://unpkg.com/htmx-ext-preload@2.0.1/preload.js\", \n",
1181-
" \"class-tools\": \"https://unpkg.com/[email protected]/class-tools.js\", \n",
1182-
" \"loading-states\": \"https://unpkg.com/[email protected]/loading-states.js\", \n",
1183-
" \"multi-swap\": \"https://unpkg.com/[email protected]/multi-swap.js\", \n",
1184-
" \"path-deps\": \"https://unpkg.com/[email protected]/path-deps.js\", \n",
1179+
" \"head-support\": \"https://unpkg.com/[email protected].3/head-support.js\",\n",
1180+
" \"preload\": \"https://unpkg.com/htmx-ext-preload@2.1.0/preload.js\",\n",
1181+
" \"class-tools\": \"https://unpkg.com/[email protected]/class-tools.js\",\n",
1182+
" \"loading-states\": \"https://unpkg.com/[email protected]/loading-states.js\",\n",
1183+
" \"multi-swap\": \"https://unpkg.com/[email protected]/multi-swap.js\",\n",
1184+
" \"path-deps\": \"https://unpkg.com/[email protected]/path-deps.js\",\n",
11851185
" \"remove-me\": \"https://unpkg.com/[email protected]/remove-me.js\",\n",
1186-
" \"ws\": \"https://unpkg.com/[email protected].1/ws.js\",\n",
1186+
" \"ws\": \"https://unpkg.com/[email protected].2/ws.js\",\n",
11871187
" \"chunked-transfer\": \"https://unpkg.com/[email protected]/transfer-encoding-chunked.js\"\n",
11881188
"}"
11891189
]
@@ -1196,8 +1196,8 @@
11961196
"outputs": [],
11971197
"source": [
11981198
"#| export\n",
1199-
"htmxsrc = Script(src=\"https://unpkg.com/[email protected].3/dist/htmx.min.js\")\n",
1200-
"fhjsscr = Script(src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected].4/fasthtml.js\")\n",
1199+
"htmxsrc = Script(src=\"https://unpkg.com/[email protected].4/dist/htmx.min.js\")\n",
1200+
"fhjsscr = Script(src=\"https://cdn.jsdelivr.net/gh/answerdotai/[email protected].12/fasthtml.js\")\n",
12011201
"surrsrc = Script(src=\"https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js\")\n",
12021202
"scopesrc = Script(src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\")\n",
12031203
"viewport = Meta(name=\"viewport\", content=\"width=device-width, initial-scale=1, viewport-fit=cover\")\n",
@@ -2478,13 +2478,13 @@
24782478
"name": "stdout",
24792479
"output_type": "stream",
24802480
"text": [
2481-
"Set to 2024-12-14 12:38:57.886589\n"
2481+
"Set to 2024-12-20 19:21:33.748637\n"
24822482
]
24832483
},
24842484
{
24852485
"data": {
24862486
"text/plain": [
2487-
"'Session time: 2024-12-14 12:38:57.886589'"
2487+
"'Session time: 2024-12-20 19:21:33.748637'"
24882488
]
24892489
},
24902490
"execution_count": null,
@@ -2691,10 +2691,11 @@
26912691
"#| export\n",
26922692
"class APIRouter:\n",
26932693
" \"Add routes to an app\"\n",
2694-
" def __init__(self, prefix:str|None=None): \n",
2694+
" def __init__(self, prefix:str|None=None, body_wrap=noop_body): \n",
26952695
" self.routes,self.wss = [],[]\n",
26962696
" self.rt_funcs = RouteFuncs() # Store wrapped route function for discoverability\n",
26972697
" self.prefix = prefix if prefix else \"\"\n",
2698+
" self.body_wrap = body_wrap\n",
26982699
"\n",
26992700
" def _wrap_func(self, func, path=None):\n",
27002701
" name = func.__name__\n",
@@ -2704,12 +2705,12 @@
27042705
" if name not in all_meths: setattr(self.rt_funcs, name, wrapped)\n",
27052706
" return wrapped\n",
27062707
"\n",
2707-
" def __call__(self, path:str=None, methods=None, name=None, include_in_schema=True, body_wrap=noop_body):\n",
2708+
" def __call__(self, path:str=None, methods=None, name=None, include_in_schema=True, body_wrap=None):\n",
27082709
" \"Add a route at `path`\"\n",
27092710
" def f(func):\n",
27102711
" p = self.prefix + (\"/\" + ('' if path.__name__=='index' else func.__name__) if callable(path) else path)\n",
27112712
" wrapped = self._wrap_func(func, p)\n",
2712-
" self.routes.append((func, p, methods, name, include_in_schema, body_wrap))\n",
2713+
" self.routes.append((func, p, methods, name, include_in_schema, body_wrap or self.body_wrap))\n",
27132714
" return wrapped\n",
27142715
" return f(path) if callable(path) else f\n",
27152716
" \n",
@@ -3004,7 +3005,7 @@
30043005
{
30053006
"data": {
30063007
"text/plain": [
3007-
"'Cookie was set at time 12:38:58.050326'"
3008+
"'Cookie was set at time 19:21:34.644743'"
30083009
]
30093010
},
30103011
"execution_count": null,

nbs/api/01_components.ipynb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@
145145
" \"hx_disabled_elt\": Literal[\"this\", \"next\", \"previous\"] | str, \n",
146146
" \"hx_history\": Literal[\"false\"] | str,\n",
147147
" \"hx_params\": Literal[\"*\", \"none\"] | str,\n",
148-
" \"hx_replace_url\": Literal[\"true\", \"false\"] | str, \n",
149148
" \"hx_validate\": Literal[\"true\", \"false\"],\n",
150149
"}\n",
151150
"hx_attrs_annotations |= {o: str for o in set(hx_attrs) - set(hx_attrs_annotations.keys())}\n",

nbs/api/03_js.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"def dark_media(\n",
9090
" css: str # CSS to be included in the dark media query\n",
9191
" ):\n",
92-
" \"Render dark media for nught mode views\"\n",
92+
" \"Render dark media for night mode views\"\n",
9393
" return Style('@media (prefers-color-scheme: dark) {%s}' %css)"
9494
]
9595
},
@@ -228,7 +228,7 @@
228228
"source": [
229229
"#| export\n",
230230
"def HighlightJS(\n",
231-
" sel='pre code', # CSS selector for code elements. Default is industry standard, be careful before adjusting it\n",
231+
" sel='pre code:not([data-highlighted=\"yes\"])', # CSS selector for code elements. Default is industry standard, be careful before adjusting it\n",
232232
" langs:str|list|tuple='python', # Language(s) to highlight\n",
233233
" light='atom-one-light', # Light theme\n",
234234
" dark='atom-one-dark' # Dark theme\n",

nbs/tutorials/by_example.ipynb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,9 @@
10331033
"metadata": {},
10341034
"outputs": [],
10351035
"source": [
1036+
"from fasthtml.common import *\n",
1037+
"from starlette.testclient import TestClient\n",
1038+
"\n",
10361039
"app = FastHTML()\n",
10371040
"cli = TestClient(app)"
10381041
]
@@ -1093,7 +1096,7 @@
10931096
"source": [
10941097
"reg_re_param(\"imgext\", \"ico|gif|jpg|jpeg|webm\")\n",
10951098
"\n",
1096-
"@app.get(r'/static/{path:path}{fn}.{ext:imgext}')\n",
1099+
"@app.get(r'/static/{path:path}/{fn}.{ext:imgext}')\n",
10971100
"def get_img(fn:str, path:str, ext:str): return f\"Getting {fn}.{ext} from /{path}\"\n",
10981101
"\n",
10991102
"cli.get('/static/foo/jph.ico').text"
@@ -1179,7 +1182,7 @@
11791182
"fake_db = [{\"name\": \"Foo\"}, {\"name\": \"Bar\"}]\n",
11801183
"\n",
11811184
"@app.get(\"/items/\")\n",
1182-
"def read_item(idx:int|None = 0): return fake_db[idx]\n",
1185+
"def read_item(idx: int = 0): return fake_db[idx]\n",
11831186
"\n",
11841187
"print(cli.get('/items/?idx=1').text)"
11851188
]
@@ -1198,6 +1201,7 @@
11981201
}
11991202
],
12001203
"source": [
1204+
"# Equivalent to `/items/?idx=0`.\n",
12011205
"print(cli.get('/items/').text)"
12021206
]
12031207
},
@@ -1905,7 +1909,7 @@
19051909
"}\n",
19061910
"```\n",
19071911
"\n",
1908-
"The [AI Pictionary example](https://github.com/AnswerDotAI/fasthtml-example/tree/main/ai_pictionary) uses a larger chunk of custom JavaScript to handle the drawing canvas. It's a good example of the type of application where running code on the client side makes the most sense, but still shows how you can integrate it with FastHTML on the server side to add functionality (like the AI responses) easily.\n",
1912+
"The [AI Pictionary example](https://github.com/AnswerDotAI/fasthtml-example/tree/main/03_pictionary) uses a larger chunk of custom JavaScript to handle the drawing canvas. It's a good example of the type of application where running code on the client side makes the most sense, but still shows how you can integrate it with FastHTML on the server side to add functionality (like the AI responses) easily.\n",
19091913
"\n",
19101914
"Adding styling with custom CSS and libraries such as tailwind is done the same way we add custom JavaScript. The [doodle example](https://github.com/AnswerDotAI/fasthtml-example/tree/main/doodle) uses [Doodle.CSS](https://github.com/chr15m/DoodleCSS) to style the page in a quirky way."
19111915
]
@@ -1944,7 +1948,7 @@
19441948
"6. Fetches and displays the URL of our app.\n",
19451949
"7. By default, mounts a `/app/data` folder on the cloud to our app's root folder. The app is run in `/app` by default, so from our app anything we store in `/data` will persist across restarts.\n",
19461950
"\n",
1947-
"A final note about Railway: We can add secrets like API keys that can be accessed as environment variables from our apps via ['Variables'](https://docs.railway.app/guides/variables). For example, for the image app (TODO link), we can add a `REPLICATE_API_KEY` variable, and then in `main.py` we can access it as `os.environ['REPLICATE_API_KEY']`.\n",
1951+
"A final note about Railway: We can add secrets like API keys that can be accessed as environment variables from our apps via ['Variables'](https://docs.railway.app/guides/variables). For example, for the [image generation app](https://github.com/AnswerDotAI/fasthtml-example/tree/main/image_app_simple), we can add a `REPLICATE_API_KEY` variable, and then in `main.py` we can access it as `os.environ['REPLICATE_API_KEY']`.\n",
19481952
"\n",
19491953
"\n",
19501954
"\n",

0 commit comments

Comments
 (0)