处理处理程序

FastHTML中的处理程序是如何工作的
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.route

app.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 动词匹配(例如,getpostputdelete),则自动使用该 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 结构,包括 HeadBody 组件。当返回一个完整的 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}' 使用了三个路径参数:

  1. path: 一个 Starlette 内置类型,匹配任何路径段
  2. fn: 不带扩展名的文件名
  3. 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 方法的路由中,可以使用 getpost 方法(可以通过使用 app.getdef getmethods 参数中的 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 自动识别通常放置在 中的元素(如 TitleMeta)并相应地放置它们,而其他元素则放在 中。

在这个例子中: - (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)
r
Missing 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 字段,则该函数使用默认值。如果 dognameNone,函数返回提供的 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 是一个包含 ab 字段的命名元组。

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').text
Set 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’。