Unfazed Endpoint
Endpoint 是 unfazed 中上承路由分发,下接 service 逻辑处理的中间层,其作用具体为:
- 定义路由请求参数和响应
- 连接 route 路由分发
- 连接 service 逻辑处理
- 供 openapi 文档生成
快速开始
创建一个简单的 endpoint。
# endpoints.py
import typing as t
from unfazed.http import HttpRequest, HttpResponse
async def endpoint1(request: HttpRequest) -> HttpResponse:
return HttpResponse(content="Hello, World!")
将其连接到 route。
# routes.py
from unfazed.route import path
from .endpoints import endpoint1
patterns = [
path("/", endpoint=endpoint1),
]
以上便是一个简单的 endpoint 的创建。
请求参数
endpoint 可以预定义请求参数,用于校验请求参数以及 openapi 文档生成。目前请求参数支持
- Path
- Query
- Body
- Header
- Cookie
- Form
- File
请求参数使用 typing.Annotated 注解进行定义, 比如 ctx: typing.Annotated[Context, Query()]
,以此来表示这是一个 Query 参数。unfazed 会自行解析请求参数,并将其传递给 endpoint 函数。
下面分别介绍这些请求参数的使用。
Path
import typing as t
from pydantic import BaseModel, Field
from unfazed.http import HttpRequest, JsonResponse
from unfazed.route import params as p
# 1
class Pth1(BaseModel):
path1: str
path2: int = Field(default=1)
class Pth2(BaseModel):
path3: int
path4: str = Field(default="123")
class RespE1(BaseModel):
path1: str
path2: int
path3: int
path4: str
path5: str
path6: str
async def endpoint1(
request: HttpRequest,
pth1: t.Annotated[Pth1, p.Path()], # 2
pth2: t.Annotated[Pth2, p.Path()],
path5: t.Annotated[str, p.Path(default="foo")],
path6: str, # 3
) -> JsonResponse:
r = RespE1(
path1=pth1.path1,
path2=pth1.path2,
path3=pth2.path3,
path4=pth2.path4,
path5=path5,
path6=path6,
)
return JsonResponse(r)
# routes.py
from unfazed.route import path
from .endpoints import endpoint1
patterns = [
path("/{path1}/{path2}/{path3}/{path4}/{path5}/{path6}", endpoint=endpoint1),
]
解释
- 请求参数可以采用 BaseModel 类型或者 python 基本类型
- 使用 t.Annotated[Pth1, p.Path()] 定义参数类型,p.Path() 用于标记此为路径参数
- path6 是因为在 path 中定义了
{path6}
,unfazed 会优先解析为路径参数 - 参数定义可以为多个不同的 BaseModel 类型以及 python 基本类型的组合
- 参数定义可以使用 Field 定义默认值
Query
class Qry1(BaseModel):
query1: str
query2: int = Field(default=1)
class Qry2(BaseModel):
query3: int
query4: str = Field(default="123")
class RespE2(BaseModel):
query1: str
query2: int
query3: int
query4: str
query5: str
query6: str
async def endpoint2(
request: HttpRequest,
qry1: t.Annotated[Qry1, p.Query()],
qry2: t.Annotated[Qry2, p.Query()],
query5: t.Annotated[str, p.Query(default="foo")],
query6: str,
) -> JsonResponse:
r = RespE2(
query1=qry1.query1,
query2=qry1.query2,
query3=qry2.query3,
query4=qry2.query4,
query5=query5,
query6=query6,
)
return JsonResponse(r)
# routes.py
patterns = [
path("/query", endpoint=endpoint2),
]
# >>> http://domain/query?query1=foo&query2=1&query3=2&query4=bar&query5=baz&query6=qux
解释
- 参数定义中未使用 t.Annotated 时,如果类型为 python 基本类型,会被自动识别为 Query 参数
Header
class Hdr1(BaseModel):
header1: str
header2: int = Field(default=1)
class Hdr2(BaseModel):
header3: int
header4: str = Field(default="123")
class RespE3(BaseModel):
header1: str
header2: int
header3: int
header4: str
header5: str
async def endpoint3(
request: HttpRequest,
hdr1: t.Annotated[Hdr1, p.Header()],
hdr2: t.Annotated[Hdr2, p.Header()],
header5: t.Annotated[str, p.Header(default="foo")],
) -> JsonResponse:
r = RespE3(
header1=hdr1.header1,
header2=hdr1.header2,
header3=hdr2.header3,
header4=hdr2.header4,
header5=header5,
)
return JsonResponse(r)
# routes.py
"""
scope = {
"type": "http",
"method": "GET",
"scheme": "https",
"server": ("www.example.org", 80),
"path": "/header",
"headers": [
(b"header1", b"foo"),
(b"header2", b"1"),
(b"header3", b"2"),
(b"header4", b"foo2"),
(b"header5", b"foo3"),
],
"query_string": b"",
}
"""
patterns = [
path("/header", endpoint=endpoint3),
]
Cookie
class Ckie1(BaseModel):
cookie1: str
cookie2: int = Field(default=1)
class Ckie2(BaseModel):
cookie3: int
cookie4: str = Field(default="123")
class RespE4(BaseModel):
cookie1: str
cookie2: int
cookie3: int
cookie4: str
cookie5: str
async def endpoint4(
request: HttpRequest,
ck1: t.Annotated[Ckie1, p.Cookie()],
ck2: t.Annotated[Ckie2, p.Cookie()],
cookie5: t.Annotated[str, p.Cookie(default="foo")],
) -> JsonResponse:
r = RespE4(
cookie1=ck1.cookie1,
cookie2=ck1.cookie2,
cookie3=ck2.cookie3,
cookie4=ck2.cookie4,
cookie5=cookie5,
)
return JsonResponse(r)
# routes.py
"""
scope = {
"type": "http",
"method": "GET",
"scheme": "https",
"server": ("www.example.org", 80),
"path": "/",
"headers": [
(
b"cookie",
b"cookie1=foo; cookie2=1; cookie3=2; cookie4=foo2; cookie5=foo3",
),
],
"query_string": b"",
}
"""
patterns = [
path("/", endpoint=endpoint4),
]
Json
class Body1(BaseModel):
body1: str
body2: int = Field(default=1)
model_config = {
"json_schema_extra": {"foo": "bar"},
}
class Body2(BaseModel):
body3: int
body4: str = Field(default="123")
class Body3(BaseModel):
body5: str
class RespE5(BaseModel):
body1: str
body2: int
body3: int
body4: str
body5: str
async def endpoint5(
request: HttpRequest,
bd1: t.Annotated[Body1, p.Json()],
bd2: t.Annotated[Body2, p.Json()],
bd3: Body3,
body6: t.Annotated[str, p.Json(default="foo")],
) -> JsonResponse:
r = RespE5(
body1=bd1.body1,
body2=bd1.body2,
body3=bd2.body3,
body4=bd2.body4,
body5=bd3.body5,
)
return JsonResponse(r)
# scope
{
"type": "http.request",
"body": b'{"body1": "foo", "body2": 1, "body3": 2, "body4": "foo2", "body5": "foo3"}',
"more_body": False,
}
解释:
- Body 中 media 类型为 application/json 参数使用 p.Json() 标记
- 在没有 t.Annotated 的情况下,如果参数类型为 BaseModel,则会被自动识别为 Json 参数
Form
class Form1(BaseModel):
form1: str
form2: int = Field(default=1)
class Form2(BaseModel):
form3: int
form4: str = Field(default="123")
class RespE6(BaseModel):
form1: str
form2: int
form3: int
form4: str
form5: str
async def endpoint6(
request: HttpRequest,
form1: t.Annotated[Form1, p.Form()],
form2: t.Annotated[Form2, p.Form()],
form5: t.Annotated[str, p.Form(default="foo")],
*args: t.Any,
**kw: t.Any,
) -> JsonResponse:
r = RespE6(
form1=form1.form1,
form2=form1.form2,
form3=form2.form3,
form4=form2.form4,
form5=form5,
)
return JsonResponse(r)
# scope
{
"type": "http.request",
"body": b"form1=foo&form2=1&form3=2&form4=foo2&form5=foo3",
"more_body": False,
}
File
from unfazed.file import UploadFile
class RespFile(BaseModel):
file_name: str | None
async def endpoint19(
request: HttpRequest,
file1: t.Annotated[UploadFile, p.File()],
) -> JsonResponse:
return JsonResponse(
RespFile(
file_name=file1.filename,
)
)
- UploadFile 直接继承自 starlette.datastructures.UploadFile
- 参数类型为 UploadFile 时,使用 p.File() 标记