Skip to main content

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 风格参数注解:支持 PathQueryHeaderCookieBodyFormFile
  • 多 Runtime:默认使用标准库 urllib,也可按需安装 httpxaiohttp
  • 拦截器链:支持请求前和响应后的扩展点,可用于认证、日志、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.py
  • examples/auth_demo.py
  • examples/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"),
        ],
    ),
)

内置拦截器:

  • BearerTokenInterceptor
  • RefreshableBearerTokenInterceptor
  • ApiKeyInterceptor
  • TraceIdInterceptor
  • LoggingInterceptor

Token 刷新

如果 token 会过期,可以使用 CallableTokenProviderRefreshableBearerTokenInterceptor

当请求返回 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typact-0.1.3.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typact-0.1.3-py3-none-any.whl (25.0 kB view details)

Uploaded Python 3

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

Hashes for typact-0.1.3.tar.gz
Algorithm Hash digest
SHA256 b61571171da7683386ddb499c9b8d86da933114374b96d4b4b5b483bd0d15d0e
MD5 228e38d4ab8d9f5f5ad6fe364991764c
BLAKE2b-256 934b6758799cd25720c8ba92c1d307505a527899cec65249e5b2bb1897f75af2

See more details on using hashes here.

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

Hashes for typact-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e365059e3844ad26e5bfbe433404a126e634505bac3992a903a70a0acdf87e01
MD5 a7214febbf109cfeff0c3fb4b40613c3
BLAKE2b-256 5d7ac37261d07904acbc8130465953994ad9fad2669c01e37fc9bf23be1342a3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page