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


@client.post("/upload")
async def upload_avatar(
    avatar: bytes = File(
        alias="file",
        filename="avatar.txt",
        content_type="text/plain",
    ),
) -> dict:
    pass

File(...) 会被构建为 Runtime 可直接消费的 RequestConfig.files

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, HttpClient


client = HttpClient("https://api.example.com")


@client.post("/upload")
async def upload_file(
    file: bytes = File(
        filename="hello.txt",
        content_type="text/plain",
    ),
) -> dict:
    pass

如果直接传入 httpx 风格 tuple,Typact 会原样传递:

await upload_file(("hello.txt", b"hello", "text/plain"))

项目结构

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

TBD

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.1.tar.gz (18.5 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.1-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

Details for the file typact-0.1.1.tar.gz.

File metadata

  • Download URL: typact-0.1.1.tar.gz
  • Upload date:
  • Size: 18.5 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.1.tar.gz
Algorithm Hash digest
SHA256 25490427f4dd9fbd74db66e6ea6f8bfc8b3dd31bd6561ae66451f8017d88c898
MD5 4ec854167aa6f8d0d483a57c7a698d7b
BLAKE2b-256 7739429a88b81169a9b4b3c41f6e0daf20f82257dd10df484c2445b01eae1a5d

See more details on using hashes here.

File details

Details for the file typact-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: typact-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 23.8 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 951c0968f10c8568beb9e8b82b4b996abde3acc135ba367a820d6035e0da79af
MD5 4ba3183553d2314eb61308fd0f28dae6
BLAKE2b-256 bba003160d8c7acb0ce7af3f8b0fb2951b43f1947220b38805c82c2369082814

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