Add your description here
Project description
Typact
Typact 是一个面向 Python 的声明式、类型安全、可插拔 Runtime 的 HTTP 服务调用框架。
它让你用类似 FastAPI 参数声明的方式定义远程 HTTP API,同时把请求构建、运行时传输、响应转换、认证、日志、Mock 测试拆成清晰的模块。
from pydantic import BaseModel
from typact import HttpClient, Path
class User(BaseModel):
id: int
name: str
client = HttpClient("https://api.example.com")
@client.get("/users/{user_id}")
async def get_user(user_id: int = Path()) -> User:
pass
user = await get_user(1)
特性
- 声明式 HTTP Client:用装饰器描述远程 API,而不是手写请求代码。
- 类型安全响应转换:基于 Pydantic v2
TypeAdapter将响应数据转换为模型、列表或任意类型。 - FastAPI 风格参数注解:支持
Path、Query、Header、Cookie、Body、Form、File。 - 多 Runtime:默认使用标准库
urllib,也可按需安装httpx或aiohttp。 - 拦截器链:支持请求前和响应后的扩展点,可用于认证、日志、Trace 等。
- Mock Runtime:无需启动服务即可测试声明式 client 的请求构建结果。
- 文件上传:支持通过
File(...)构建multipart/form-data所需的files参数。
安装
核心安装只依赖 pydantic,默认 Runtime 使用 Python 标准库 urllib。
pip install typact
如果需要 httpx Runtime:
pip install "typact[httpx]"
如果需要 aiohttp Runtime:
pip install "typact[aiohttp]"
本仓库本地开发:
uv sync
快速开始
import asyncio
from pydantic import BaseModel, ConfigDict, Field
from typact import Body, Header, HttpClient, Path, Query
class Todo(BaseModel):
model_config = ConfigDict(populate_by_name=True)
user_id: int = Field(alias="userId")
id: int | None = None
title: str
completed: bool
client = HttpClient("https://jsonplaceholder.typicode.com")
@client.get("/todos/{todo_id}")
async def get_todo(
todo_id: int = Path(),
request_id: str = Header("req-001", alias="X-Request-Id"),
) -> Todo:
pass
@client.get("/todos")
async def query_todos(
user_id: int | None = Query(None, alias="userId"),
) -> list[Todo]:
pass
@client.post("/todos")
async def create_todo(todo: Todo = Body()) -> Todo:
pass
async def main():
todo = await get_todo(1)
print(todo)
todos = await query_todos(user_id=1)
print(todos[0])
created = await create_todo(
Todo(user_id=1, title="hello typact", completed=False)
)
print(created)
await client.close()
if __name__ == "__main__":
asyncio.run(main())
更多完整示例见 examples/:
examples/jsonplaceholder_demo.pyexamples/auth_demo.pyexamples/file_upload_demo.py
参数注解
Typact 当前提供以下声明式参数:
from typact import Body, Cookie, File, Form, Header, Path, Query
Path
@client.get("/users/{user_id}")
async def get_user(user_id: int = Path()) -> dict:
pass
Query
@client.get("/users")
async def list_users(page: int = Query(1), keyword: str | None = Query(None)) -> list[dict]:
pass
Header
@client.get("/profile")
async def get_profile(request_id: str = Header(alias="X-Request-Id")) -> dict:
pass
Body
@client.post("/users")
async def create_user(payload: dict = Body()) -> dict:
pass
Form
@client.post("/auth/login")
async def login(
name: str = Form(),
password: str = Form(),
) -> dict:
pass
File
from typact import File, FileData
@client.post("/upload")
async def upload_avatar(
avatar: FileData = File(alias="file"),
) -> dict:
pass
await upload_avatar(
FileData(
content=b"hello typact",
filename="avatar.txt",
content_type="text/plain",
)
)
File(...) 只声明文件字段的名称、默认值和是否必填;FileData(...)
描述本次上传的内容、文件名和媒体类型。构建请求时,它们会被转换为 Runtime
可直接消费的 RequestConfig.files。
只需要上传字节内容时,也可以直接使用 bytes 参数:
@client.post("/upload/raw")
async def upload_raw(file: bytes = File()) -> dict:
pass
await upload_raw(b"hello typact")
Runtime
Typact 的核心不会绑定某个 HTTP 库。HttpClient 默认使用 UrllibRuntime,不需要安装额外依赖。
from typact import HttpClient
client = HttpClient("https://api.example.com")
使用 httpx:
import httpx
from typact import HttpClient, HttpxRuntime
client = HttpClient(
"https://api.example.com",
client_runtime=HttpxRuntime(httpx.AsyncClient(timeout=30)),
)
使用 aiohttp:
from typact import AioHttpRuntime, HttpClient
client = HttpClient(
"https://api.example.com",
client_runtime=AioHttpRuntime(),
)
使用 Mock:
from typact import HttpClient, MockRuntime, Path
runtime = MockRuntime()
runtime.add_response(
"GET",
"http://test.local/users/1",
json_data={"id": 1, "name": "typact"},
)
client = HttpClient("http://test.local", client_runtime=runtime)
@client.get("/users/{user_id}")
async def get_user(user_id: int = Path()) -> dict:
pass
拦截器
拦截器可以在请求发送前或响应转换前处理数据。
from typact import (
ApiKeyInterceptor,
BearerTokenInterceptor,
HttpClient,
InterceptorChain,
)
client = HttpClient(
"https://api.example.com",
interceptor_chain=InterceptorChain(
request_interceptors=[
BearerTokenInterceptor("token"),
ApiKeyInterceptor("api-key"),
],
),
)
内置拦截器:
BearerTokenInterceptorRefreshableBearerTokenInterceptorApiKeyInterceptorTraceIdInterceptorLoggingInterceptor
Token 刷新
如果 token 会过期,可以使用 CallableTokenProvider 和 RefreshableBearerTokenInterceptor。
当请求返回 401 时,Typact 会调用 refresh_token() 刷新 token,并用新 token 自动重试一次。
from typact import (
CallableTokenProvider,
HttpClient,
InterceptorChain,
RefreshableBearerTokenInterceptor,
)
async def login() -> str:
# 在这里调用 /auth/login,并返回新的 token
return "new-token"
token_provider = CallableTokenProvider(
login,
token="expired-token",
)
client = HttpClient(
"https://api.example.com",
interceptor_chain=InterceptorChain(
request_interceptors=[
RefreshableBearerTokenInterceptor(token_provider),
],
),
)
默认请求头是:
Authorization: Bearer <token>
如果你的接口和一些内部系统一样,要求直接传 token:
Authorization: <token>
可以设置 scheme=None:
RefreshableBearerTokenInterceptor(
token_provider,
scheme=None,
)
文件上传
from typact import File, FileData, HttpClient
client = HttpClient("https://api.example.com")
@client.post("/upload")
async def upload_file(
file: FileData = File(),
) -> dict:
pass
await upload_file(
FileData(
content=b"hello",
filename="hello.txt",
content_type="text/plain",
)
)
文件名和媒体类型属于每次调用的数据,因此由 FileData 携带,而不是固定在
接口声明中。content 支持 bytes 和二进制文件对象;如果不需要指定文件名或
媒体类型,可以让接口接收 bytes = File() 并直接传入字节内容。
项目结构
src/typact/
├── annotations/ # Path / Query / Header / Cookie / Body / Form / File
├── builder/ # URL、请求、multipart 构建
├── client/ # HttpClient、路由装饰器、RouteDefinition
├── converter/ # 响应转换器
├── core/ # RequestConfig、SimpleResponse
├── interceptor/ # 认证、日志、Trace、拦截器链
├── runtime/ # urllib、httpx、aiohttp、mock
└── testing/ # 测试辅助导出
设计理念
Typact 的核心流水线是:
Decorator
-> RouteDefinition
-> RequestBuilder
-> InterceptorChain
-> ClientRuntime
-> ResponseConverter
每层只做一件事:
- Decorator 收集函数签名和返回类型。
- RequestBuilder 将调用参数转换成
RequestConfig。 - InterceptorChain 负责横切逻辑。
- Runtime 只负责发送请求并返回
SimpleResponse。 - ResponseConverter 只负责把响应转换成目标类型。
开发
安装依赖:
uv sync
运行示例:
uv run python examples/jsonplaceholder_demo.py
uv run python examples/auth_demo.py
uv run python examples/file_upload_demo.py
编译检查:
uv run python -m compileall src examples
路线图
- 更完整的 multipart 文件上传能力
- Retry / Backoff
- OpenTelemetry
- OpenAPI 生成器
- SSE / Stream 响应转换
- Record / Replay 测试工具
License
本项目基于 MIT License 开源。
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file typact-0.1.3.tar.gz.
File metadata
- Download URL: typact-0.1.3.tar.gz
- Upload date:
- Size: 20.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b61571171da7683386ddb499c9b8d86da933114374b96d4b4b5b483bd0d15d0e
|
|
| MD5 |
228e38d4ab8d9f5f5ad6fe364991764c
|
|
| BLAKE2b-256 |
934b6758799cd25720c8ba92c1d307505a527899cec65249e5b2bb1897f75af2
|
File details
Details for the file typact-0.1.3-py3-none-any.whl.
File metadata
- Download URL: typact-0.1.3-py3-none-any.whl
- Upload date:
- Size: 25.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e365059e3844ad26e5bfbe433404a126e634505bac3992a903a70a0acdf87e01
|
|
| MD5 |
a7214febbf109cfeff0c3fb4b40613c3
|
|
| BLAKE2b-256 |
5d7ac37261d07904acbc8130465953994ad9fad2669c01e37fc9bf23be1342a3
|