from fasthtml.common import *
from collections import namedtuple
from typing import TypedDict
from datetime import datetime
import json,time处理处理程序
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 装饰器通过添加一个 rt() 方法来修改 yoyo 函数。该方法返回与处理程序相关联的路由路径。这是一种方便的方式,可以动态引用处理程序函数的路由。
在这个例子中, yoyo.to() 被用作 hx_post 的值。这意味着当点击 div 时,它会触发一个 HTMX POST 请求到 yoyo 处理器的路由。这种方法通过避免硬编码的路由字符串并在路由变化时自动更新,允许灵活的、干燥的代码。
这个模式在较大型的应用中特别有用,在这些应用中路由可能会发生变化,或者在构建需要动态引用其自身路由的可重用组件时。
@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/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>
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!
在 FastHTML 中,index 函数是一个特殊的处理器。当在没有参数的情况下通过 @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’的路线的URL(我们之前定义为‘/user/{nm}’),以‘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 方法的路由中,可以使用 get 和 post 方法(可以通过使用 app.get、def get 或 methods 参数中的 app.route 来实现)。
@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 特定的行为,如客户端重定向,非常有用。
在这个例子中,我们定义了一个处理程序,它返回一个 HtmxResponseHeaders 对象,带有一个 location 参数,该参数在响应中设置 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 重定向。这对于将用户重定向到不同的页面或外部网址很有用。
在这个例子中:
- 我们定义了一个处理程序,返回一个
Redirect对象,其 URL 为 “http://example.org”。 - 该
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/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>
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 自动识别通常放置在
中的元素(如 Title 和 Meta)并相应地放置它们,而其他元素则放在 中。
在这个例子中: - (Title('hi'), H1('hi')) 定义了标题和主标题。标题放在头部,H1 放在主体。 - (Meta(property='image'), Meta(property='site_name')) 定义了两个元标签,它们都放在头部。
API路由器
APIRouter 在你想将应用程序路由分割到多个属于单个 FastHTMl 应用程序的 .py 文件时非常有用。它接受一个可选的 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",
)注意你可以像平常一样通过 APIRouter.rt_funcs 在你的 hx_{http_method} 调用中引用我们的 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错误请求响应,并附带错误信息。
@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 为 None,函数返回提供的 dogname 或‘未知名称’。
@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"}
您可以使用数据类来定义结构化表单数据。在这个例子中, 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 组件用于创建一个具有标题和主要内容的页面。它会自动生成一个 ,包含提供的标题,将内容包装在一个带有 “container” 类的 标签中,并向头部添加一个 title。
当使用JSON数据发出请求时: - 将“Content-Type”头设置为“application/json” - 将JSON数据作为字符串提供在请求的 data 参数中
Cookie、会话、文件上传等
@rt("/setcookie")
def get(): return cookie('now', datetime.now())
@rt("/getcookie")
def get(now:parsed_date): return f'Cookie was set at time {now.time()}'
print(cli.get('/setcookie').text)
time.sleep(0.01)
cli.get('/getcookie').text
'Cookie was set at time 16:19:27.811570'
处理函数可以设置和获取 cookies。在此示例中:
-
/setcookie路由设置一个名为 ‘now’ 的cookie,值为当前日期时间。 - 该
/getcookie路由获取“now”cookie并返回其值。
cookie() 函数用于创建一个 cookie 响应。FastHTML 在设置 cookie 时会自动将 datetime 对象转换为字符串,并在检索时将其解析回日期对象。
cookie('now', datetime.now())HttpHeader(k='set-cookie', v='now="2025-01-30 16:19:29.997374"; Path=/; SameSite=lax')
该 cookie() 函数返回一个带有 ‘set-cookie’ 键的 HttpHeader 对象。您可以将其与 FT 元素一起作为元组返回,以及 FastHTML 在响应中支持的任何其他内容。
app = FastHTML(secret_key='soopersecret')
cli = Client(app)
rt = app.route@rt("/setsess")
def get(sess, foo:str=''):
now = datetime.now()
sess['auth'] = str(now)
return f'Set to {now}'
@rt("/getsess")
def get(sess): return f'Session time: {sess["auth"]}'
print(cli.get('/setsess').text)
time.sleep(0.01)
cli.get('/getsess').textSet to 2025-01-30 16:19:31.078650
'Session time: 2025-01-30 16:19:31.078650'
会话在请求之间存储和检索数据。要使用会话,您应该用一个 secret_key 初始化 FastHTML 应用程序。这用于加密签名会话使用的 cookie。
处理函数中的 sess 参数提供对会话数据的访问。您可以使用字典样式访问来设置和获取会话变量。
@rt("/upload")
async def post(uf:UploadFile): return (await uf.read()).decode()
with open('../../CHANGELOG.md', 'rb') as f:
print(cli.post('/upload', files={'uf':f}, data={'msg':'Hello'}).text[:15])# Release notes
处理函数可以使用 Starlette 的 UploadFile 类型接受文件上传。在这个例子中:
-
/upload路由接受一个名为uf的文件上传。 - 该
UploadFile对象提供了一个异步read()方法来访问文件内容。 - 我们使用
await来异步读取文件内容并将其解码为字符串。
我们在处理函数中添加了 async 因为它使用 await 异步读取文件内容。在 Python 中,任何使用 await 的函数都必须声明为 async。这允许该函数异步运行,潜在地通过在等待文件读取时不阻塞其他操作来提高性能。
app.static_route('.md', static_path='../..')
print(cli.get('/README.md').text[:10])# FastHTML
FastHTML 应用程序的 static_route 方法允许从给定目录提供具有指定扩展名的静态文件。在此示例中:
.md文件从../..目录提供服务(当前目录向上两级)。- 访问
/README.md会返回该目录下 README.md 文件的内容。
help(app.static_route_exts)Help on method static_route_exts in module fasthtml.core:
static_route_exts(prefix='/', static_path='.', exts='static') method of fasthtml.core.FastHTML instance
Add a static route at URL path `prefix` with files from `static_path` and `exts` defined by `reg_re_param()`
app.static_route_exts()
assert cli.get('/README.txt').status_code == 404
print(cli.get('/README.txt').text[:50])404 Not Found
FastHTML应用程序的static_route_exts方法允许从给定目录提供具有指定扩展名的静态文件。默认情况下:
- 它从当前目录 (' . ') 提供文件。
- 它使用了'静态'正则表达式,包括常见的静态文件扩展名,如'ico'、'gif'、'jpg'、'css'、'js'等。
- URL前缀设置为‘/’。
‘static’正则表达式由FastHTML使用以下代码定义:
reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|html|map")@rt("/form-submit/{list_id}")
def options(list_id: str):
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': '*',
}
return Response(status_code=200, headers=headers)
print(cli.options('/form-submit/2').headers)Headers({'access-control-allow-origin': '*', 'access-control-allow-methods': 'POST', 'access-control-allow-headers': '*', 'content-length': '0', 'set-cookie': 'session_=eyJhdXRoIjogIjIwMjUtMDEtMzAgMTY6MTk6MzEuMDc4NjUwIn0=.Z5vtZA.1ooY2RCWopWAbLYDy6660g_LlHI; path=/; Max-Age=31536000; httponly; samesite=lax'})
FastHTML 处理程序可以处理 OPTIONS 请求并设置自定义头部。在这个例子中:
-
/form-submit/{list_id}路由处理OPTIONS请求。 - 自定义头部被设置以允许跨源请求 (CORS)。
- 该函数返回一个 Starlette
Response对象,状态码为 200,并带有自定义头部。
您可以从处理程序函数返回任何Starlette响应类型,这样在需要时可以完全控制响应。
def _not_found(req, exc): return Div('nope')
app = FastHTML(exception_handlers={404:_not_found})
cli = Client(app)
rt = app.route
r = cli.get('/')
print(r.text) <!doctype html>
<html>
<head>
<title>FastHTML page</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/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>
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>
<div>nope</div>
</body>
</html>
FastHTML允许您定义自定义异常处理程序 - 在这种情况下,一个自定义的404(未找到)处理函数 _not_found,它返回一个 Div 组件,文本为‘nope’。