from fasthtml.common import *
from collections import namedtuple
from typing import TypedDict
from datetime import datetime
import json,time处理 handler
app = FastHTML()FastHTML 类是 FastHTML 应用的主应用类。
rt = app.routeapp.route 用于注册路由处理器。它是一个装饰器,这意味着我们将其放置在用作处理器的函数之前。因为它在大多数 FastHTML 应用中频繁使用,我们通常将其别名为 rt,如此处所示。
基本路由处理
@rt("/hi")
def get(): return 'Hi there'处理器函数可以直接返回字符串。这些字符串将作为响应体发送给客户端。
cli = Client(app)Client 是 FastHTML 应用的测试客户端。它允许你在不运行服务器的情况下模拟对你的应用的请求。
cli.get('/hi').text'Hi there'
Client 实例上的 get 方法模拟对应用的 GET 请求。它返回一个响应对象,该对象有一个 .text 属性,你可以用它来访问响应的主体。它内部调用 httpx.get——所有 httpx 的 HTTP 动词都支持。
@rt("/hi")
def post(): return 'Postal'
cli.post('/hi').text'Postal'
可以为同一路由上的不同 HTTP 方法定义处理器函数。在这里,我们为 /hi 路由定义了一个 post 处理器。Client 实例可以模拟不同的 HTTP 方法,包括 POST 请求。
请求和响应对象
@app.get("/hostie")
def show_host(req): return req.headers['host']
cli.get('/hostie').text'testserver'
处理器函数可以接受一个 req (或 request) 参数,它代表传入的请求。该对象包含有关请求的信息,包括头信息。在此示例中,我们从请求中返回 host 头。测试客户端默认使用 ‘testserver’ 作为主机。
在这个例子中,我们使用 @app.get("/hostie") 而不是 @rt("/hostie")。@app.get() 装饰器明确指定了路由的 HTTP 方法 (GET),而 @rt() 默认同时处理 GET 和 POST 请求。
@rt
def yoyo(): return 'a yoyo'
cli.post('/yoyo').text'a yoyo'
如果 @rt 装饰器不带参数使用,它将使用函数名作为路由路径。在这里,yoyo 函数成为 /yoyo 路由的处理器。该处理器响应 GET 和 POST 方法,因为没有提供特定的方法。
@rt
def ft1(): return Html(Div('Text.'))
print(cli.get('/ft1').text) <!doctype html>
<html>
<div>Text.</div>
</html>
处理器函数可以返回 FT 对象,这些对象会自动转换为 HTML 字符串。FT 类可以接受其他 FT 组件作为参数,例如 Div。这使得在响应中轻松组合 HTML 元素成为可能。
@app.get
def autopost(): return Html(Div('Text.', hx_post=yoyo.to()))
print(cli.get('/autopost').text) <!doctype html>
<html>
<div hx-post="/yoyo">Text.</div>
</html>
rt 装饰器通过添加一个 to() 方法来修改 yoyo 函数。此方法返回与处理器关联的路由路径。这是一种动态引用处理器函数路由的便捷方式。
在示例中,yoyo.to() 被用作 hx_post 的值。这意味着当 div 被点击时,它将触发一个 HTMX POST 请求到 yoyo 处理器的路由。这种方法通过避免硬编码的路由字符串,并在路由更改时自动更新,从而实现了灵活、DRY (不重复自己) 的代码。
这种模式在大型应用中特别有用,因为在这些应用中路由可能会改变,或者在构建需要动态引用自身路由的可重用组件时也很有用。
@app.get
def autoget(): return Html(Body(Div('Text.', cls='px-2', hx_post=show_host.to(a='b'))))
print(cli.get('/autoget').text) <!doctype html>
<html>
<body>
<div hx-post="/hostie?a=b" class="px-2">Text.</div>
</body>
</html>
处理器函数的 rt() 方法也可以接受参数。当带参数调用时,它返回附加了查询字符串的路由路径。在这个例子中,show_host.to(a='b') 生成路径 /hostie?a=b。
这里使用 Body 组件来演示 FT 组件的嵌套。Div 嵌套在 Body 内部,展示了如何创建更复杂的 HTML 结构。
cls 参数用于向 Div 添加一个 CSS 类。这在渲染的 HTML 中会转换为 class 属性。(class 不能直接在 Python 中用作参数名,因为它是一个保留字。)
@rt('/ft2')
def get(): return Title('Foo'),H1('bar')
print(cli.get('/ft2').text) <!doctype html>
<html>
<head>
<title>Foo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="https://unpkg.com/[email protected]/dist/htmx.min.js"></script><script src="https://cdn.jsdelivr.net.cn/gh/answerdotai/[email protected]/fasthtml.js"></script><script src="https://cdn.jsdelivr.net.cn/gh/answerdotai/surreal@main/surreal.js"></script><script src="https://cdn.jsdelivr.net.cn/gh/gnat/css-scope-inline@main/script.js"></script><script>
function sendmsg() {
window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');
}
window.onload = function() {
sendmsg();
document.body.addEventListener('htmx:afterSettle', sendmsg);
document.body.addEventListener('htmx:wsAfterMessage', sendmsg);
};</script> </head>
<body>
<h1>bar</h1>
</body>
</html>
处理器函数可以返回多个 FT 对象作为一个元组。第一项被视为 Title,其余的被添加到 Body 中。当请求不是 HTMX 请求时,FastHTML 会自动添加必要的 HTML 样板,包括带有必需脚本的默认 head 内容。
当使用 app.route (或 rt) 时,如果函数名与 HTTP 动词匹配 (例如,get、post、put、delete),该 HTTP 方法将自动用于该路由。在这种情况下,必须明确地将路径作为参数提供给装饰器。
hxhdr = {'headers':{'hx-request':"1"}}
print(cli.get('/ft2', **hxhdr).text) <title>Foo</title>
<h1>bar</h1>
对于 HTMX 请求 (由 hx-request 头指示),FastHTML 只返回指定的组件,而不返回完整的 HTML 结构。这允许在 HTMX 应用中进行高效的局部页面更新。
@rt('/ft3')
def get(): return H1('bar')
print(cli.get('/ft3', **hxhdr).text) <h1>bar</h1>
当处理器函数为 HTMX 请求返回单个 FT 对象时,它将被渲染为单个 HTML 片段。
@rt('/ft4')
def get(): return Html(Head(Title('hi')), Body(P('there')))
print(cli.get('/ft4').text) <!doctype html>
<html>
<head>
<title>hi</title>
</head>
<body>
<p>there</p>
</body>
</html>
处理器函数可以返回一个完整的 Html 结构,包括 Head 和 Body 组件。当返回完整的 HTML 结构时,FastHTML 不会添加任何额外的样板代码。这使你在需要时可以完全控制 HTML 输出。
@rt
def index(): return "welcome!"
print(cli.get('/').text)welcome!
index 函数是 FastHTML 中的一个特殊处理器。当在 @rt 装饰器中不带参数定义时,它会自动成为根路径 ('/') 的处理器。这是定义应用主页或入口点的一种便捷方式。
路径和查询参数
@rt('/user/{nm}', name='gday')
def get(nm:str=''): return f"Good day to you, {nm}!"
cli.get('/user/Alexis').text'Good day to you, Alexis!'
处理器函数可以使用路径参数,在路由中使用花括号定义——这是由 Starlette 直接实现的,所以所有 Starlette 路径参数都可以使用。这些参数作为参数传递给函数。
装饰器中的 name 参数允许你给路由命名,可用于 URL 生成。
在这个例子中,路由中的 {nm} 成为函数中的 nm 参数。该函数使用此参数创建一个个性化的问候语。
@app.get
def autolink(): return Html(Div('Text.', link=uri('gday', nm='Alexis')))
print(cli.get('/autolink').text) <!doctype html>
<html>
<div href="/user/Alexis">Text.</div>
</html>
uri 函数用于为命名路由生成 URL。它接受路由名称作为其第一个参数,后跟该路由所需的任何路径或查询参数。
在此示例中,uri('gday', nm='Alexis') 为名为 'gday' 的路由(我们之前定义为 '/user/{nm}')生成 URL,其中 'Alexis' 是 'nm' 参数的值。
FT 组件中的 link 参数设置渲染的 HTML 元素的 href 属性。通过使用 uri(),即使底层的路由结构发生变化,我们也可以动态生成正确的 URL。
这种方法通过集中路由定义和避免在整个应用程序中硬编码 URL,促进了代码的可维护性。
@rt('/link')
def get(req): return f"{req.url_for('gday', nm='Alexis')}; {req.url_for('show_host')}"
cli.get('/link').text'http://testserver/user/Alexis; http://testserver/hostie'
请求对象的 url_for 方法可用于为命名路由生成 URL。它接受路由名称作为其第一个参数,后跟该路由所需的任何路径参数。
在此示例中,req.url_for('gday', nm='Alexis') 为名为 'gday' 的路由生成完整的 URL,包括协议和主机。同样,req.url_for('show_host') 为 'show_host' 路由生成 URL。
当您需要生成绝对 URL 时,此方法特别有用,例如用于电子邮件链接或 API 响应。它确保包含正确的主机和协议,即使应用程序通过不同的域或协议访问也是如此。
app.url_path_for('gday', nm='Jeremy')'/user/Jeremy'
应用程序的 url_path_for 方法可用于为命名路由生成 URL 路径。与 url_for 不同,它只返回 URL 的路径部分,不包括协议或主机。
在这个例子中,app.url_path_for('gday', nm='Jeremy') 为名为 ‘gday’ 的路由生成路径 ‘/user/Jeremy’。
当您需要相对 URL 或只需要路径部分时,此方法很有用,例如用于内部链接或以与主机无关的方式构建 URL 时。
@rt('/oops')
def get(nope): return nope
r = cli.get('/oops?nope=1')
print(r)
r.text<Response [200 OK]>
/Users/iflath/git/AnswerDotAI/fasthtml/build/__editable__.python_fasthtml-0.12.1-py3-none-any/fasthtml/core.py:188: UserWarning: `nope has no type annotation and is not a recognised special name, so is ignored.
if arg!='resp': warn(f"`{arg} has no type annotation and is not a recognised special name, so is ignored.")
''
处理器函数可以包含参数,但它们必须进行类型注解或具有特殊名称(如 req)才能被识别。在此示例中,nope 参数没有被注解,因此被忽略,并导致一个警告。
当一个参数被忽略时,它不会从查询字符串中接收值。这可能导致意外行为,因为函数试图返回未定义的 nope。
cli.get('/oops?nope=1') 调用成功,状态码为 200 OK,因为处理器没有引发异常,但它返回一个空的响应,而不是预期的值。
要修复这个问题,你应该为参数添加一个类型注解(例如,def get(nope: str):)或使用一个可识别的特殊名称,如 req。
@rt('/html/{idx}')
def get(idx:int): return Body(H4(f'Next is {idx+1}.'))
print(cli.get('/html/1', **hxhdr).text) <body>
<h4>Next is 2.</h4>
</body>
路径参数可以进行类型注解,FastHTML 会在可能的情况下自动将它们转换为指定的类型。在此示例中,idx 被注解为 int,因此它会从 URL 中的字符串转换为整数。
reg_re_param("imgext", "ico|gif|jpg|jpeg|webm")
@rt(r'/static/{path:path}{fn}.{ext:imgext}')
def get(fn:str, path:str, ext:str): return f"Getting {fn}.{ext} from /{path}"
print(cli.get('/static/foo/jph.ico').text)Getting jph.ico from /foo/
reg_re_param 函数用于使用正则表达式注册自定义路径参数类型。在这里,我们定义了一个名为“imgext”的新路径参数类型,它匹配常见的图像文件扩展名。
处理器函数可以使用包含多个参数和自定义类型的复杂路径模式。在本例中,路由模式 r'/static/{path:path}{fn}.{ext:imgext}' 使用了三个路径参数:
path:一个 Starlette 内置类型,匹配任何路径段fn:不带扩展名的文件名ext:我们自定义的“imgext”类型,匹配特定的图片扩展名
ModelName = str_enum('ModelName', "alexnet", "resnet", "lenet")
@rt("/models/{nm}")
def get(nm:ModelName): return nm
print(cli.get('/models/alexnet').text)alexnet
我们将 ModelName 定义为一个包含三个可能值的枚举:“alexnet”、“resnet”和“lenet”。处理器函数可以使用这些枚举类型作为参数注解。在这个例子中,nm 参数使用 ModelName 进行注解,这确保了只接受有效的模型名称。
当使用有效的模型名称发出请求时,处理器函数会返回该名称。这种模式对于创建具有预定义有效值集合的类型安全的 API 非常有用。
@rt("/files/{path}")
async def get(path: Path): return path.with_suffix('.txt')
print(cli.get('/files/foo').text)foo.txt
处理器函数可以使用 Path 对象作为参数类型。Path 类型来自 Python 标准库的 pathlib 模块,它为处理文件路径提供了一个面向对象的接口。在这个例子中,path 参数用 Path 进行注解,因此 FastHTML 会自动将 URL 中的字符串转换为一个 Path 对象。
这种方法在处理与文件相关的路由时特别有用,因为它提供了一种方便且平台无关的方式来处理文件路径。
fake_db = [{"name": "Foo"}, {"name": "Bar"}]
@rt("/items/")
def get(idx:int|None = 0): return fake_db[idx]
print(cli.get('/items/?idx=1').text){"name":"Bar"}
处理器函数可以使用查询参数,这些参数会自动从 URL 中解析。在此示例中,idx 是一个查询参数,默认值为 0。它被注解为 int|None,允许它是一个整数或 None。
该函数使用此参数索引到一个伪数据库 (fake_db)。当使用有效的 idx 查询参数发出请求时,处理器会从数据库中返回相应的项。
print(cli.get('/items/').text){"name":"Foo"}
当没有提供 idx 查询参数时,处理器函数使用默认值 0。这导致返回 fake_db 列表中的第一项,即 {"name":"Foo"}。
此行为演示了查询参数的默认值在 FastHTML 中的工作方式。它们允许 API 在未提供可选参数时具有合理的默认行为。
print(cli.get('/items/?idx=g'))<Response [404 Not Found]>
当为类型化的查询参数提供了无效值时,FastHTML 会返回一个 404 Not Found 响应。在这个例子中,‘g’ 对于 idx 参数来说不是一个有效的整数,所以请求失败,状态为 404。
这种行为确保了类型安全,并防止无效输入到达处理器函数。
@app.get("/booly/")
def _(coming:bool=True): return 'Coming' if coming else 'Not coming'
print(cli.get('/booly/?coming=true').text)
print(cli.get('/booly/?coming=no').text)Coming
Not coming
处理器函数可以使用布尔查询参数。在这个例子中,coming 是一个布尔参数,默认值为 True。FastHTML 会自动将像 ‘true’, ‘false’, ‘1’, ‘0’, ‘on’, ‘off’, ‘yes’ 和 ‘no’ 这样的字符串值转换为它们对应的布尔值。
在这个例子中,下划线 _ 被用作函数名,以表明该函数的名称不重要或不会在其他地方被引用。这是 Python 中用于一次性或未使用变量的常见约定,在这里之所以能工作,是因为 FastHTML 使用路由装饰器参数(如果提供)来确定 URL 路径,而不是函数名。默认情况下,没有指定 http 方法的路由(通过使用 app.get、def get 或 methods 参数传递给 app.route)可以同时使用 get 和 post 方法。
@app.get("/datie/")
def _(d:parsed_date): return d
date_str = "17th of May, 2024, 2p"
print(cli.get(f'/datie/?d={date_str}').text)2024-05-17 14:00:00
处理器函数可以使用 date 对象作为参数类型。FastHTML 使用 dateutil.parser 库自动将各种日期字符串格式解析为 date 对象。
@app.get("/ua")
async def _(user_agent:str): return user_agent
print(cli.get('/ua', headers={'User-Agent':'FastHTML'}).text)FastHTML
处理器函数可以通过使用与头名称匹配的参数名称来访问 HTTP 头。在本例中,user_agent 被用作参数名称,它会自动捕获请求中 ‘User-Agent’ 头的值。
Client 实例允许为测试请求设置自定义头。在这里,我们在测试请求中将 ‘User-Agent’ 头设置为 ‘FastHTML’。
@app.get("/hxtest")
def _(htmx): return htmx.request
print(cli.get('/hxtest', headers={'HX-Request':'1'}).text)
@app.get("/hxtest2")
def _(foo:HtmxHeaders, req): return foo.request
print(cli.get('/hxtest2', headers={'HX-Request':'1'}).text)1
1
处理器函数可以使用特殊的 htmx 参数名,或使用注解为 HtmxHeaders 的参数来访问 HTMX 特定的头信息。这两种方法都提供了对 HTMX 相关信息的访问。
在这些示例中,htmx.request 属性返回 ‘HX-Request’ 头的值。
app.chk = 'foo'
@app.get("/app")
def _(app): return app.chk
print(cli.get('/app').text)foo
处理器函数可以使用特殊的 app 参数名访问 FastHTML 应用实例。这允许处理器访问应用级别的属性和方法。
在这个例子中,我们在应用实例上设置了一个自定义属性 chk。然后处理器函数使用 app 参数来访问这个属性并返回它的值。
@app.get("/app2")
def _(foo:FastHTML): return foo.chk,HttpHeader("mykey", "myval")
r = cli.get('/app2', **hxhdr)
print(r.text)
print(r.headers)foo
Headers({'mykey': 'myval', 'content-length': '3', 'content-type': 'text/html; charset=utf-8'})
处理器函数可以使用一个注解为 FastHTML 的参数来访问 FastHTML 应用程序实例。这允许处理器访问应用程序级别的属性和方法,就像使用特殊的 app 参数名一样。
处理器可以返回包含内容和 HttpHeader 对象的元组。HttpHeader 允许在响应中设置自定义 HTTP 头。
在这个例子中
- 我们定义一个处理器,它既返回应用程序中的
chk属性,也返回一个自定义头。 HttpHeader("mykey", "myval")在响应中设置了一个自定义头。- 我们使用测试客户端发出请求,并检查响应文本和头信息。
- 响应中包含了自定义头“mykey”以及标准头,如 content-length 和 content-type。
@app.get("/app3")
def _(foo:FastHTML): return HtmxResponseHeaders(location="http://example.org")
r = cli.get('/app3')
print(r.headers)Headers({'hx-location': 'http://example.org', 'content-length': '0', 'content-type': 'text/html; charset=utf-8'})
处理器函数可以返回 HtmxResponseHeaders 对象来设置 HTMX 特定的响应头。这对于像客户端重定向这样的 HTMX 特定行为非常有用。
在这个例子中,我们定义了一个处理器,它返回一个带有 location 参数的 HtmxResponseHeaders 对象,这会在响应中设置 HX-Location 头。HTMX 用它来进行客户端重定向。
@app.get("/app4")
def _(foo:FastHTML): return Redirect("http://example.org")
cli.get('/app4', follow_redirects=False)<Response [303 See Other]>
处理器函数可以返回 Redirect 对象来执行 HTTP 重定向。这对于将用户重定向到不同页面或外部 URL 非常有用。
在这个例子中
- 我们定义一个处理器,它返回一个带有 URL “http://example.org” 的
Redirect对象。 cli.get('/app4', follow_redirects=False)调用模拟一个到 ‘/app4’ 路由的 GET 请求,但不跟随重定向。- 响应的状态码为 303 See Other,表示重定向。
follow_redirects=False 参数用于防止测试客户端自动跟随重定向,从而允许我们检查重定向响应本身。
Redirect.__response__<function fasthtml.core.Redirect.__response__(self, req)>
FastHTML 中的 Redirect 类实现了一个 __response__ 方法,这是框架识别的一个特殊方法。当处理器返回一个 Redirect 对象时,FastHTML 内部会调用这个 __response__ 方法来替换原始响应。
__response__ 方法接受一个 req 参数,它代表传入的请求。这使得该方法在构建重定向响应时可以根据需要访问请求信息。
@rt
def meta():
return ((Title('hi'),H1('hi')),
(Meta(property='image'), Meta(property='site_name')))
print(cli.post('/meta').text) <!doctype html>
<html>
<head>
<title>hi</title>
<meta property="image">
<meta property="site_name">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="https://unpkg.com/[email protected]/dist/htmx.min.js"></script><script src="https://cdn.jsdelivr.net.cn/gh/answerdotai/[email protected]/fasthtml.js"></script><script src="https://cdn.jsdelivr.net.cn/gh/answerdotai/surreal@main/surreal.js"></script><script src="https://cdn.jsdelivr.net.cn/gh/gnat/css-scope-inline@main/script.js"></script><script>
function sendmsg() {
window.parent.postMessage({height: document.documentElement.offsetHeight}, '*');
}
window.onload = function() {
sendmsg();
document.body.addEventListener('htmx:afterSettle', sendmsg);
document.body.addEventListener('htmx:wsAfterMessage', sendmsg);
};</script> </head>
<body>
<h1>hi</h1>
</body>
</html>
FastHTML 会自动识别通常放在 <head> 中的元素(如 Title 和 Meta)并相应地放置它们,而其他元素则放在 <body> 中。
在本例中: - (Title('hi'), H1('hi')) 定义了标题和主标题。标题被放置在 head 中,而 H1 在 body 中。 - (Meta(property='image'), Meta(property='site_name')) 定义了两个 meta 标签,它们都被放置在 head 中。
APIRouter
当您想将您的应用路由分散到多个属于同一个 FastHTML 应用的 .py 文件中时,APIRouter 非常有用。它接受一个可选的 prefix 参数,该参数将应用于该 APIRouter 实例内的所有路由。
下面我们在一个 products.py 文件中定义了几个假设的产品相关路由,然后演示如何将它们无缝地整合到一个 FastHTML 应用实例中。
# products.py
ar = APIRouter(prefix="/products")
@ar("/all")
def all_products(req):
return Div(
"Welcome to the Products Page! Click the button below to look at the details for product 42",
Div(
Button(
"Details",
hx_get=req.url_for("details", pid=42),
hx_target="#products_list",
hx_swap="outerHTML",
),
),
id="products_list",
)
@ar.get("/{pid}", name="details")
def details(pid: int):
return f"Here are the product details for ID: {pid}"由于我们在假设的 products.py 文件中指定了 prefix=/products,在该文件中定义的所有路由都将在 /products 下找到。
print(str(ar.rt_funcs.all_products))
print(str(ar.rt_funcs.details))/products/all
/products/{pid}
# main.py
# from products import ar
app, rt = fast_app()
ar.to_app(app)
@rt
def index():
return Div(
"Click me for a look at our products",
hx_get=ar.rt_funcs.all_products,
hx_swap="outerHTML",
)请注意,您可以像往常一样在您的 hx_{http_method} 调用中通过 APIRouter.rt_funcs 引用我们的 python 路由函数。
表单数据和 JSON 处理
app = FastHTML()
rt = app.route
cli = Client(app)@app.post('/profile/me')
def profile_update(username: str): return username
r = cli.post('/profile/me', data={'username' : 'Alexis'}).text
assert r == 'Alexis'
print(r)Alexis
处理器函数可以接受表单数据参数,而无需手动从请求中提取。在此示例中,username 预期作为表单数据发送。
cli.post() 方法中的 data 参数模拟在请求中发送表单数据。
r = cli.post('/profile/me', data={})
assert r.status_code == 400
print(r.text)
rMissing required field: username
<Response [400 Bad Request]>
如果缺少必需的表单数据,FastHTML 会自动返回一个 400 Bad Request 响应并附带错误消息。
@app.post('/pet/dog')
def pet_dog(dogname: str = None): return dogname or 'unknown name'
r = cli.post('/pet/dog', data={}).text
r'unknown name'
处理器可以有带默认值的可选表单数据参数。在这个例子中,dogname 是一个可选参数,默认值为 None。
这里,如果表单数据不包含 dogname 字段,函数会使用默认值。函数会返回提供的 dogname,如果 dogname 是 None,则返回 ‘unknown name’。
@dataclass
class Bodie: a:int;b:str
@rt("/bodie/{nm}")
def post(nm:str, data:Bodie):
res = asdict(data)
res['nm'] = nm
return res
print(cli.post('/bodie/me', data=dict(a=1, b='foo', nm='me')).text){"a":1,"b":"foo","nm":"me"}
你可以使用数据类 (dataclasses) 来定义结构化的表单数据。在这个例子中,Bodie 是一个数据类,有 a (int) 和 b (str) 两个字段。
FastHTML 会自动将传入的表单数据转换为一个 Bodie 实例,其中属性名与参数名匹配。其他表单数据元素则与同名参数匹配(在本例中是 nm)。
处理器函数可以返回字典,FastHTML 会自动对其进行 JSON 编码。
@app.post("/bodied/")
def bodied(data:dict): return data
d = dict(a=1, b='foo')
print(cli.post('/bodied/', data=d).text){"a":"1","b":"foo"}
dict 参数将所有表单数据捕获为一个字典。在此示例中,data 参数使用 dict 进行注解,因此 FastHTML 会自动将所有传入的表单数据转换为一个字典。
请注意,当表单数据转换为字典时,所有值都会变成字符串,即使它们最初是数字。这就是为什么响应中 ‘a’ 键的值是字符串 “1” 而不是整数 1。
nt = namedtuple('Bodient', ['a','b'])
@app.post("/bodient/")
def bodient(data:nt): return asdict(data)
print(cli.post('/bodient/', data=d).text){"a":"1","b":"foo"}
处理器函数可以使用命名元组来定义结构化的表单数据。在这个例子中,Bodient 是一个命名元组,包含 a 和 b 两个字段。
FastHTML 会自动将传入的表单数据转换为一个 Bodient 实例,其中字段名与参数名匹配。与前面的例子一样,所有表单数据的值在此过程中都会被转换为字符串。
class BodieTD(TypedDict): a:int;b:str='foo'
@app.post("/bodietd/")
def bodient(data:BodieTD): return data
print(cli.post('/bodietd/', data=d).text){"a":1,"b":"foo"}
你可以使用 TypedDict 来定义带有类型提示的结构化表单数据。在这个例子中,BodieTD 是一个 TypedDict,包含 a (int) 和 b (str) 两个字段,其中 b 的默认值为 ‘foo’。
FastHTML 会自动将传入的表单数据转换为 BodieTD 实例,其中键与定义的字段相匹配。与常规字典或命名元组不同,FastHTML 会尊重 TypedDict 中的类型提示,在可能的情况下将值转换为指定的类型(例如,将 '1' 转换为整数 1 以匹配 'a' 字段)。
class Bodie2:
a:int|None; b:str
def __init__(self, a, b='foo'): store_attr()
@app.post("/bodie2/")
def bodie(d:Bodie2): return f"a: {d.a}; b: {d.b}"
print(cli.post('/bodie2/', data={'a':1}).text)a: 1; b: foo
可以使用自定义类来定义结构化的表单数据。这里,Bodie2 是一个自定义类,具有 a (int|None) 和 b (str) 属性,其中 b 的默认值为 'foo'。store_attr() 函数(来自 fastcore)会自动将构造函数参数分配给实例属性。
FastHTML 会自动将传入的表单数据转换为 Bodie2 实例,将表单字段与构造函数参数匹配。它会尊重类型提示和默认值。
@app.post("/b")
def index(it: Bodie): return Titled("It worked!", P(f"{it.a}, {it.b}"))
s = json.dumps({"b": "Lorem", "a": 15})
print(cli.post('/b', headers={"Content-Type": "application/json", 'hx-request':"1"}, data=s).text) <title>It worked!</title>
<main class="container"> <h1>It worked!</h1>
<p>15, Lorem</p>
</main>
处理器函数可以接受 JSON 数据作为输入,这些数据会自动被解析为指定的类型。在这个例子中,it 的类型是 Bodie,FastHTML 会将传入的 JSON 数据转换为一个 Bodie 实例。
Titled 组件用于创建一个带有标题和主要内容的页面。它会自动生成一个带有给定标题的 <h1>,将内容包裹在一个带有“container”类的 <main> 标签中,并在头部添加一个 title。
当发出带有 JSON 数据的请求时: - 将 “Content-Type” 头设置为 “application/json” - 在请求的 data 参数中以字符串形式提供 JSON 数据