typact 0.2.1__tar.gz → 0.2.2a2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- typact-0.2.2a2/CHANGELOG.md +74 -0
- typact-0.2.2a2/CONTRIBUTING.md +35 -0
- typact-0.2.2a2/MANIFEST.in +7 -0
- typact-0.2.2a2/PKG-INFO +324 -0
- typact-0.2.2a2/README.md +292 -0
- typact-0.2.2a2/README.zh-CN.md +687 -0
- typact-0.2.2a2/RELEASING.md +45 -0
- typact-0.2.2a2/pyproject.toml +62 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/__init__.py +6 -1
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/http_client.py +117 -28
- typact-0.2.2a2/src/typact/core/events.py +19 -0
- typact-0.2.2a2/src/typact/core/retry.py +61 -0
- typact-0.2.2a2/src/typact/py.typed +0 -0
- typact-0.2.2a2/src/typact.egg-info/PKG-INFO +324 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/SOURCES.txt +8 -0
- {typact-0.2.1 → typact-0.2.2a2}/tests/test_http_client_request.py +67 -0
- typact-0.2.2a2/tests/test_response_retry.py +180 -0
- {typact-0.2.1 → typact-0.2.2a2}/tests/test_runtime_integration.py +77 -9
- typact-0.2.1/PKG-INFO +0 -596
- typact-0.2.1/README.md +0 -581
- typact-0.2.1/pyproject.toml +0 -25
- typact-0.2.1/src/typact/core/retry.py +0 -28
- typact-0.2.1/src/typact.egg-info/PKG-INFO +0 -596
- {typact-0.2.1 → typact-0.2.2a2}/LICENSE +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/setup.cfg +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/base.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/body.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/cookie.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/file.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/form.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/header.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/path.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/query.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/multipart_builder.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/request_builder.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/url_builder.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/decorator.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/metadata.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/file_converter.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/json_converter.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/response_converter.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/sse_converter.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/stream_converter.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/core/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/core/errors.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/core/types.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/auth.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/base.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/log.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/trace.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/aiohttp.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/base.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/httpx.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/mock.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/urllib.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/testing/__init__.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact/testing/mock_runtime.py +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/dependency_links.txt +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/requires.txt +0 -0
- {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本文件区分未发布改动与已在 PyPI 发布的版本。当前预发布版本为 0.2.2a2,正式版 0.2.2 尚未发布。
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.2.2a2] - 2026-09-22
|
|
8
|
+
|
|
9
|
+
### 变更
|
|
10
|
+
|
|
11
|
+
- 最低 Python 版本从 3.13 下调至 3.10,并在 Linux 与 Windows CI 中覆盖 Python 3.10 至 3.14。
|
|
12
|
+
- 英文 README 和文档成为默认入口,原中文 README 保留为 `README.zh-CN.md`。
|
|
13
|
+
|
|
14
|
+
## [0.2.2a1] - 2026-09-14
|
|
15
|
+
|
|
16
|
+
### 新增
|
|
17
|
+
|
|
18
|
+
- 请求生命周期事件:`request`、`retry`、`response`、`failure`;支持同步和异步监听器。
|
|
19
|
+
- `RetryConfig.should_retry_response`:对完整 `typact.Response` 执行同步布尔判断,支持业务响应码重试。
|
|
20
|
+
- 公开 `default_should_retry_response`,允许自定义策略复用默认 HTTP 状态码规则。
|
|
21
|
+
- GitHub Actions 测试、安装包与文档检查;手动准备 Draft Release 的工作流。
|
|
22
|
+
- `py.typed`、项目链接、贡献指南、发布规范与历史发行审计记录。
|
|
23
|
+
|
|
24
|
+
### 行为说明
|
|
25
|
+
|
|
26
|
+
- 原有 `retry_status_codes` 保持支持;同时指定非 None 的状态码集合和响应回调会报配置错误。
|
|
27
|
+
- 回调完全接管普通响应判断,仍受方法、次数和退避限制;回调错误直接传播。
|
|
28
|
+
- 流式/SSE 请求不支持完整响应回调,需路由级状态码策略或关闭重试。原有首分片前重试规则不变。
|
|
29
|
+
|
|
30
|
+
### 工程调整
|
|
31
|
+
|
|
32
|
+
- 流式集成测试按完整字节内容验证,避免依赖不同平台与 Runtime 的网络分块边界。
|
|
33
|
+
- 测试服务器在客户端提前断开时清理连接,避免 Windows CI 拆除服务器时挂起;测试增加有界超时诊断。
|
|
34
|
+
- 标准文档构建与 Sites 专用打包分离;使用 Node 22 和固定前端依赖版本。
|
|
35
|
+
- 修正文档 canonical 站点地址,增加 AtomGit/GitHub 托管入口。
|
|
36
|
+
- 补录历史附注标签与 GitHub Releases,不代表发布了新的 PyPI 版本。
|
|
37
|
+
|
|
38
|
+
## [0.2.1] - 2026-08-31
|
|
39
|
+
|
|
40
|
+
- 新增路由级超时和重试策略覆盖,适用于 HTTP 装饰器与 `client.request()`。
|
|
41
|
+
- 未指定时继承 Client 配置,显式 `None` 关闭对应继承。
|
|
42
|
+
- 流式请求仅在第一个分片交付前允许重试。
|
|
43
|
+
|
|
44
|
+
## [0.2.0] - 2026-08-17
|
|
45
|
+
|
|
46
|
+
- 请求超时、网络异常与状态码重试,使用指数退避。
|
|
47
|
+
- SSE 与普通流式响应转换。
|
|
48
|
+
- 修正 httpx Runtime 的 Cookie 处理。
|
|
49
|
+
|
|
50
|
+
## [0.2.0a1] - 2026-08-13
|
|
51
|
+
|
|
52
|
+
- 0.2.0 系列测试发行。发布包的 Python 源码与后续 0.2.0 一致。
|
|
53
|
+
- 历史 Git 没有相同版本元数据的提交;标签使用经校验的 PyPI sdist 恢复快照。
|
|
54
|
+
|
|
55
|
+
## [0.1.4] - 2026-07-24
|
|
56
|
+
|
|
57
|
+
- 统一公共 `Response` 类型,保留 `SimpleResponse` 兼容别名。
|
|
58
|
+
- 支持原始响应、文本与字节下载。
|
|
59
|
+
|
|
60
|
+
## [0.1.3] - 2026-07-14
|
|
61
|
+
|
|
62
|
+
- 早期发行修订,与 0.1.2 的 Python 源码相同。
|
|
63
|
+
- 历史标签使用 PyPI sdist 恢复快照,不将版本元数据修订描述为新增功能。
|
|
64
|
+
|
|
65
|
+
## [0.1.2] - 2026-07-14
|
|
66
|
+
|
|
67
|
+
- `FileData` 承载文件内容、文件名和媒体类型,`File` 负责参数声明。
|
|
68
|
+
- MIT 许可证。历史标签使用 PyPI sdist 恢复快照。
|
|
69
|
+
|
|
70
|
+
## [0.1.1] - 2026-07-14
|
|
71
|
+
|
|
72
|
+
- 首个保留在 PyPI 的 Typact 版本,支持声明式 HTTP 请求、参数注解、响应转换、多 Runtime 和认证扩展。
|
|
73
|
+
|
|
74
|
+
历史日期采用 PyPI 首次上传日期。版本映射、原始 sdist 地址及哈希见 [发行审计记录](docs/release-history.json)。原始发布包是历史事实来源;恢复快照不代表找到了原始发布提交。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 贡献指南
|
|
2
|
+
|
|
3
|
+
Typact 是开源 HTTP 客户端库。设计变更应考虑通用场景、公共 API、可选依赖、不同 Runtime 的一致性,以及已有用户的升级成本。
|
|
4
|
+
|
|
5
|
+
## 开发环境
|
|
6
|
+
|
|
7
|
+
Python 3.10 至 3.14;使用 uv 管理环境:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
uv sync --all-extras --group dev
|
|
11
|
+
uv run --no-sync pytest -q
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
CI 在 Windows 和 Linux 上运行上述 Python 版本的完整测试,包含 urllib、httpx 和 aiohttp。Python 元数据允许更新版本,但未经 CI 验证的版本不在当前保证范围内。
|
|
15
|
+
|
|
16
|
+
文档使用 Node.js 22,版本记录在 `docs-site/.node-version`:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
cd docs-site
|
|
20
|
+
npm ci
|
|
21
|
+
npm run build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
标准静态构建不需要 Sites 配置;仅在已配置 `.openai/hosting.json` 的专用环境中使用 `npm run build:sites`。Netlify 使用同一标准构建命令。Windows 上请使用约定的 Node 22,避免已观察到的 Node 24.19.0 退出断言。
|
|
25
|
+
|
|
26
|
+
## 提交要求
|
|
27
|
+
|
|
28
|
+
- 每次变更聚焦一个问题,新增公开行为需补文档和边界测试。
|
|
29
|
+
- 修改重试、认证、超时、取消或流式行为时,说明组合语义与资源生命周期。
|
|
30
|
+
- 保持可选 HTTP 依赖可选;核心安装不可因新增功能而强制导入它们。
|
|
31
|
+
- 将用户可见变化写入 `CHANGELOG.md` 的 Unreleased 部分。
|
|
32
|
+
- 公共 API 的删除、更名或默认语义变化必须说明迁移方式;不要用实现更简单作为唯一理由。
|
|
33
|
+
- 不提交凭据、依赖缓存或生成的构建产物。不要改写已公开的版本标签。
|
|
34
|
+
|
|
35
|
+
发布流程见 [RELEASING.md](RELEASING.md)。
|
typact-0.2.2a2/PKG-INFO
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: typact
|
|
3
|
+
Version: 0.2.2a2
|
|
4
|
+
Summary: Build production-ready Python API clients with FastAPI-style declarations.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://typact.zzq.jl.cn
|
|
7
|
+
Project-URL: Documentation, https://typact.zzq.jl.cn/docs/getting-started/
|
|
8
|
+
Project-URL: GitHub, https://github.com/zzqjlcn/typact
|
|
9
|
+
Project-URL: AtomGit, https://atomgit.com/zhangzhanqi/typact
|
|
10
|
+
Project-URL: Issues, https://github.com/zzqjlcn/typact/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/zzqjlcn/typact/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: http,api-client,async,typing,declarative,fastapi,pydantic,sse,retry
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Framework :: AsyncIO
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: pydantic>=2.13.4
|
|
27
|
+
Provides-Extra: aiohttp
|
|
28
|
+
Requires-Dist: aiohttp>=3.14.1; extra == "aiohttp"
|
|
29
|
+
Provides-Extra: httpx
|
|
30
|
+
Requires-Dist: httpx>=0.28.1; extra == "httpx"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# <img src="https://typact.zzq.jl.cn/favicon.svg" alt="Typact" width="28" height="28" style="vertical-align: middle;"/> Typact
|
|
34
|
+
|
|
35
|
+
[](https://typact.zzq.jl.cn)
|
|
36
|
+
[](https://pypi.org/project/typact/)
|
|
37
|
+
[](https://pypi.org/project/typact/)
|
|
38
|
+
[](https://github.com/zzqjlcn/typact/actions/workflows/ci.yml)
|
|
39
|
+
[](https://github.com/zzqjlcn/typact/blob/main/LICENSE)
|
|
40
|
+
|
|
41
|
+
[Documentation](https://typact.zzq.jl.cn) · [PyPI](https://pypi.org/project/typact/) · [中文 README](https://github.com/zzqjlcn/typact/blob/main/README.zh-CN.md) · [Changelog](https://github.com/zzqjlcn/typact/blob/main/CHANGELOG.md)
|
|
42
|
+
|
|
43
|
+
**Build production-ready Python API clients with FastAPI-style declarations.**
|
|
44
|
+
|
|
45
|
+
Typact turns an annotated Python function into an executable HTTP contract. Declare the request parameters and return type once; Typact handles request building, transport, response validation, retries, authentication, streaming, and test doubles.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pydantic import BaseModel
|
|
49
|
+
|
|
50
|
+
from typact import HttpClient, Path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class User(BaseModel):
|
|
54
|
+
id: int
|
|
55
|
+
name: str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
client = HttpClient("https://api.example.com")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@client.get("/users/{user_id}")
|
|
62
|
+
async def get_user(user_id: int = Path()) -> User:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
user = await get_user(1)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Why Typact?
|
|
70
|
+
|
|
71
|
+
Handwritten clients tend to repeat the same plumbing: assemble URLs and headers, serialize bodies, check status codes, validate JSON, retry transient failures, refresh tokens, and mock the network in tests. Typact keeps that behavior behind one typed contract.
|
|
72
|
+
|
|
73
|
+
- **FastAPI-style declarations** with `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, and `File`.
|
|
74
|
+
- **Validated return types** powered by Pydantic v2 `TypeAdapter`.
|
|
75
|
+
- **Production request policies** including timeouts, exponential backoff, response-aware retries, and token refresh.
|
|
76
|
+
- **Streaming support** for raw byte/text streams and typed Server-Sent Events.
|
|
77
|
+
- **Pluggable transports** using the standard library, httpx, aiohttp, or a custom Runtime.
|
|
78
|
+
- **Deterministic tests** through a Mock Runtime that records the request Typact built.
|
|
79
|
+
- **Small core install** with Pydantic as the only required dependency.
|
|
80
|
+
|
|
81
|
+
Typact is async-first and targets Python 3.10 through 3.14.
|
|
82
|
+
|
|
83
|
+
## Installation
|
|
84
|
+
|
|
85
|
+
The default Runtime uses Python's standard library:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install typact
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Install an optional transport when you need httpx or aiohttp:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pip install "typact[httpx]"
|
|
95
|
+
pip install "typact[aiohttp]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quick start
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import asyncio
|
|
102
|
+
|
|
103
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
104
|
+
|
|
105
|
+
from typact import Body, HttpClient, Path, Query
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Todo(BaseModel):
|
|
109
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
110
|
+
|
|
111
|
+
user_id: int = Field(alias="userId")
|
|
112
|
+
id: int | None = None
|
|
113
|
+
title: str
|
|
114
|
+
completed: bool
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
client = HttpClient("https://jsonplaceholder.typicode.com")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@client.get("/todos/{todo_id}")
|
|
121
|
+
async def get_todo(todo_id: int = Path()) -> Todo:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@client.get("/todos")
|
|
126
|
+
async def list_todos(user_id: int = Query(alias="userId")) -> list[Todo]:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@client.post("/todos")
|
|
131
|
+
async def create_todo(todo: Todo = Body()) -> Todo:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def main():
|
|
136
|
+
todo = await get_todo(1)
|
|
137
|
+
todos = await list_todos(user_id=1)
|
|
138
|
+
created = await create_todo(
|
|
139
|
+
Todo(user_id=1, title="Try Typact", completed=False)
|
|
140
|
+
)
|
|
141
|
+
print(todo, todos[0], created)
|
|
142
|
+
await client.close()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
asyncio.run(main())
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Production request policies
|
|
149
|
+
|
|
150
|
+
Configure shared timeout and retry behavior on the client, then override it per route when an endpoint needs different semantics:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from typact import HttpClient, Path, RetryConfig
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
client = HttpClient(
|
|
157
|
+
"https://api.example.com",
|
|
158
|
+
timeout=10,
|
|
159
|
+
retry_config=RetryConfig(max_retries=3, initial_delay=0.5),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@client.get(
|
|
164
|
+
"/reports/{report_id}",
|
|
165
|
+
timeout=60,
|
|
166
|
+
retry_config=RetryConfig(max_retries=2),
|
|
167
|
+
)
|
|
168
|
+
async def get_report(report_id: int = Path()) -> dict:
|
|
169
|
+
pass
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Retries are disabled by default. When enabled, the default policy retries network and timeout failures plus `429`, `502`, `503`, and `504` responses for idempotent methods.
|
|
173
|
+
|
|
174
|
+
Typact can also retry a successful HTTP response that carries a transient business error:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from typact import Response, RetryConfig, default_should_retry_response
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def should_retry(response: Response) -> bool:
|
|
181
|
+
if default_should_retry_response(response):
|
|
182
|
+
return True
|
|
183
|
+
data = response.json()
|
|
184
|
+
return (
|
|
185
|
+
response.status_code == 200
|
|
186
|
+
and isinstance(data, dict)
|
|
187
|
+
and data.get("code") in {"SYSTEM_BUSY", "RATE_LIMITED"}
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
client = HttpClient(
|
|
192
|
+
"https://api.example.com",
|
|
193
|
+
retry_config=RetryConfig(
|
|
194
|
+
max_retries=3,
|
|
195
|
+
should_retry_response=should_retry,
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
See the [Runtime guide](https://typact.zzq.jl.cn/docs/runtime/) for inheritance rules, streaming behavior, and lifecycle events.
|
|
201
|
+
|
|
202
|
+
## Authentication and cross-cutting behavior
|
|
203
|
+
|
|
204
|
+
Interceptors keep authentication, logging, and tracing out of endpoint declarations:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from typact import BearerTokenInterceptor, HttpClient, InterceptorChain
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
client = HttpClient(
|
|
211
|
+
"https://api.example.com",
|
|
212
|
+
interceptor_chain=InterceptorChain(
|
|
213
|
+
request_interceptors=[BearerTokenInterceptor("your-token")],
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Built-in interceptors include bearer tokens, refreshable bearer tokens, API keys, trace IDs, and logging.
|
|
219
|
+
|
|
220
|
+
## Files and streaming
|
|
221
|
+
|
|
222
|
+
Upload multipart files with `FileData`:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from typact import File, FileData
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@client.post("/upload")
|
|
229
|
+
async def upload(file: FileData = File()) -> dict:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
await upload(
|
|
234
|
+
FileData(
|
|
235
|
+
content=b"hello",
|
|
236
|
+
filename="hello.txt",
|
|
237
|
+
content_type="text/plain",
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Use `AsyncIterator[bytes]` or `AsyncIterator[str]` for raw streams. Other item types are decoded from Server-Sent Events:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from collections.abc import AsyncIterator
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@client.get("/events")
|
|
249
|
+
async def events() -> AsyncIterator[dict]:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
async for event in events():
|
|
254
|
+
print(event)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Streaming requires the httpx or aiohttp Runtime. Retries stop after the first chunk has been delivered, preventing duplicate data.
|
|
258
|
+
|
|
259
|
+
## Pluggable Runtimes
|
|
260
|
+
|
|
261
|
+
The default `UrllibRuntime` needs no extra HTTP dependency. Existing httpx and aiohttp clients can be injected when you need their connection settings or ecosystem integrations:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
import httpx
|
|
265
|
+
|
|
266
|
+
from typact import HttpClient, HttpxRuntime
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
client = HttpClient(
|
|
270
|
+
"https://api.example.com",
|
|
271
|
+
client_runtime=HttpxRuntime(httpx.AsyncClient(timeout=30)),
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Endpoint declarations and return types stay unchanged when the Runtime changes.
|
|
276
|
+
|
|
277
|
+
## Testing without a server
|
|
278
|
+
|
|
279
|
+
The Mock Runtime replaces the transport boundary and records every built request:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from typact import HttpClient, MockRuntime, Query
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
runtime = MockRuntime()
|
|
286
|
+
runtime.add_response(
|
|
287
|
+
"GET",
|
|
288
|
+
"https://api.example.com/items",
|
|
289
|
+
json_data={"items": []},
|
|
290
|
+
)
|
|
291
|
+
client = HttpClient("https://api.example.com", client_runtime=runtime)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@client.get("/items")
|
|
295
|
+
async def list_items(page: int = Query(1)) -> dict:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
result = await list_items(page=2)
|
|
300
|
+
assert result == {"items": []}
|
|
301
|
+
assert runtime.requests[0].params == {"page": 2}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Documentation and examples
|
|
305
|
+
|
|
306
|
+
- [Getting started](https://typact.zzq.jl.cn/docs/getting-started/)
|
|
307
|
+
- [Parameter annotations](https://typact.zzq.jl.cn/docs/annotations/)
|
|
308
|
+
- [Runtimes, retries, streaming, and events](https://typact.zzq.jl.cn/docs/runtime/)
|
|
309
|
+
- [Interceptors](https://typact.zzq.jl.cn/docs/interceptors/)
|
|
310
|
+
- [Testing](https://typact.zzq.jl.cn/docs/testing/)
|
|
311
|
+
- [`examples/`](https://github.com/zzqjlcn/typact/tree/main/examples)
|
|
312
|
+
|
|
313
|
+
## Development
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
uv sync --all-extras --group dev
|
|
317
|
+
uv run --no-sync pytest -q
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Design changes must account for public API clarity, Runtime consistency, backward compatibility, and migration cost. See [CONTRIBUTING.md](https://github.com/zzqjlcn/typact/blob/main/CONTRIBUTING.md) and [RELEASING.md](https://github.com/zzqjlcn/typact/blob/main/RELEASING.md) before contributing or publishing.
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
Typact is released under the [MIT License](https://github.com/zzqjlcn/typact/blob/main/LICENSE).
|