Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

[*.{yml,yaml}]
indent_size = 2
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
77 changes: 61 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,78 @@ name: CI

on:
push:
branches: [main, master]
branches: [main, master, 'codex/**']
pull_request:
workflow_call:
inputs:
ref:
type: string
required: false
default: ''
workflow_dispatch:

permissions:
contents: read

env:
PYTHONUTF8: '1'

jobs:
test:
runs-on: ubuntu-24.04
timeout-minutes: 10
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
os: [ubuntu-24.04]
python-version: ['3.11', '3.12', '3.13', '3.14']
include:
- os: windows-2025
python-version: '3.13'
- os: macos-15
python-version: '3.13'
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
- name: 安装开发工具和模板
run: python -m pip install -e ".[dev]"
- run: ruff check .
- run: ruff format --check src tests scripts
- run: mypy src
- run: python -m pytest -q
- name: 改名后构建、重建、独立安装及真实HTTP与类型消费
run: python scripts/verify_package.py --rename
- name: 未改名的模板也必须正确打包
if: matrix.os == 'ubuntu-24.04' && matrix.python-version == '3.13'
run: python scripts/verify_package.py
- name: 最低直接依赖
if: matrix.os == 'ubuntu-24.04' && matrix.python-version == '3.11'
run: python scripts/verify_package.py --rename --minimum

audit:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
- run: python -m pip install pip-audit==2.9.0
- name: 仅解析和审计运行依赖,不将开发工具误算成SDK依赖
shell: bash
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Lint with ruff
run: ruff check .
- name: Type-check with mypy
run: mypy src
- name: Run tests
run: pytest -q
python - <<'PYCODE'
import os, pathlib, tomllib
config = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
path = pathlib.Path(os.environ["RUNNER_TEMP"]) / "sdk-runtime-requirements.txt"
path.write_text("\n".join(config["project"]["dependencies"]) + "\n", encoding="utf-8")
PYCODE
python -m pip_audit --strict -r "$RUNNER_TEMP/sdk-runtime-requirements.txt"
114 changes: 80 additions & 34 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,92 @@
name: Publish

# Publishes the package to PyPI using Trusted Publishing (OIDC) — no API token
# is stored in the repository.
#
# IMPORTANT (template users):
# 1. This workflow is inert until you (a) rename the package to a real name
# via scripts/rename_template.py and (b) configure a "Trusted Publisher"
# for that package name on PyPI (and optionally TestPyPI) pointing at this
# repo + the `publish.yml` workflow + an `v*.*.*` tag scope. See docs/PUBLISHING.md.
# 2. The template ships with the placeholder name `example` and is NOT meant
# to be published as-is.
# 3. To validate against TestPyPI first, uncomment the `repository-url` line
# below and configure a TestPyPI Trusted Publisher for the same name.

# 模板默认不可上传;复制后须显式启用仓库变量并配置受保护环境与PyPI信任。
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: '已在main中验证的版本标签,例如v0.4.0'
required: true
type: string
target:
description: '目标索引'
required: true
type: choice
options: [testpypi, pypi]
default: testpypi

permissions:
contents: read
id-token: write

jobs:
publish:
preflight:
if: github.ref == 'refs/heads/main' && github.repository != 'guyue55/py_example' && vars.ENABLE_PYPI_PUBLISH == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 10
environment: pypi
timeout-minutes: 5
outputs:
sha: ${{ steps.ref.outputs.sha }}
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
python-version: "3.13"

- name: Build distributions
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
- name: 标签格式、main归属与非占位元数据
id: ref
env:
RELEASE_TAG: ${{ inputs.tag }}
shell: bash
run: |
python -m pip install --upgrade build
python -m build

- name: Publish to PyPI via Trusted Publishing
uses: pypa/gh-action-pypi-publish@release/v1
# with:
# repository-url: https://test.pypi.org/legacy/ # uncomment for TestPyPI
set -euo pipefail
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
test "$(git cat-file -t "refs/tags/$RELEASE_TAG")" = tag
release_sha=$(git rev-parse --verify "refs/tags/$RELEASE_TAG^{commit}")
git merge-base --is-ancestor "$release_sha" origin/main
git checkout --detach "$release_sha"
python scripts/check_release.py --tag "$RELEASE_TAG"
echo "sha=$release_sha" >> "$GITHUB_OUTPUT"
quality:
needs: preflight
uses: ./.github/workflows/ci.yml
with:
ref: ${{ needs.preflight.outputs.sha }}
build:
needs: [preflight, quality]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ needs.preflight.outputs.sha }}
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
- run: python -m pip install -e ".[dev]"
- run: python -m build
- run: python scripts/verify_package.py --artifacts dist
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: distributions-${{ needs.preflight.outputs.sha }}
path: |
dist/*.whl
dist/*.tar.gz
if-no-files-found: error
retention-days: 7
publish:
needs: [preflight, build]
runs-on: ubuntu-24.04
timeout-minutes: 5
environment: ${{ inputs.target }}
permissions:
id-token: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: distributions-${{ needs.preflight.outputs.sha }}
path: dist/
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
with:
repository-url: ${{ inputs.target == 'testpypi' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ venv.bak/
.idea/
.vscode/
*.swp
*.swo
*.swo

# 本机工作状态,不进入模板或发布包
runtime/
.workbuddy/
.mypy_cache/
27 changes: 13 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
## 项目概述

`py_example` 是一个 **Python SDK / HTTP 客户端模板**:基于 `requests` 封装了
认证(Bearer / Basic / ApiKey)、传输层重试(429 与 5xx 指数退避 + jitter)与
认证(Bearer / Basic / ApiKey)、受控只读重试(429 与 5xx 指数退避 + jitter)与
类型化异常。它**不是成品库**,而是供他人 fork 后改名的脚手架。

- 占位包名:`example`(发布前必须改名)。
- 构建后端:setuptools(PEP 621 元数据),非 Poetry。
- 目标 Python:`>=3.9`,CI 矩阵 3.9–3.13。
- 目标 Python:`>=3.11`,CI 矩阵 3.11–3.14。
- 模板本身**不发布**到 PyPI;改名后才具备发布条件。

## 目录结构

```
src/example/ 包源码(client / auth / exceptions)
tests/ 测试(用真实本地 HTTP server,非外部依赖桩)
tests/ 单元Mock + 真实本地HTTP + 模板工具测试(证据分开)
scripts/rename_template.py 一键改名 example → 真名
docs/PUBLISHING.md 发布到 PyPI(Trusted Publishing)指南
developer_guide.md 开发指南
Expand All @@ -33,16 +33,17 @@ ruff format . # 格式化
mypy src # 类型检查
python -m build # 构建 sdist + wheel
python scripts/rename_template.py <新包名> # 改名(支持 --dry-run)
pre-commit run --all-files # 本地跑全部 pre-commit 钩子(CI 同款)
python scripts/verify_package.py --rename
python scripts/verify_package.py --rename --minimum
```

## 代码风格

- Lint/格式化:ruff(行宽 100,`target-version = py39`)。
- Lint/格式化:ruff(行宽 100,`target-version = py311`)。
- 导入排序:isort(ruff 内置,`select = ["E","F","I","W"]`)。
- **刻意不强制 UP(pyupgrade)规则**,保持对 3.9 的低门槛;当前 pre-commit **不引入**
- **刻意不强制 UP(pyupgrade)规则**,避免批量改写无关代码;当前 pre-commit **不引入**
`pyupgrade` 钩子,避免 PEP 585(`dict[...]` 替代 `Dict[...]` 等)自动改写突破该边界。
- 类型:mypy 约束 `<2`(兼容 3.9),未对全部函数强制 `disallow_untyped_defs`。
- 类型:mypy 约束 `<2`,未对全部函数强制 `disallow_untyped_defs`。
- 库日志:每个模块用 `logging.getLogger(__name__)`,包在 `__init__` 挂
`NullHandler`,不要给库加 root handler。

Expand All @@ -61,17 +62,15 @@ pre-commit run --all-files # 本地跑全部 pre-commit 钩子(CI 同款)
例:`feat(client): 增加超时参数`、`docs(publish): 补充 Trusted Publisher 说明`。
- 分支:`main` / `master` 受 CI 保护;功能走分支 + PR。

## 发布(Trusted Publishing,默认惰性)
## 发布(默认关闭)

- 发布流程见 `docs/PUBLISHING.md`。`.github/workflows/publish.yml` 在推送
`v*.*.*` 标签时,通过 OIDC(无需 token)自动发布到 PyPI。
- 发布前必须:① 用 `rename_template.py` 改名;② 在 PyPI 配置同名
Trusted Publisher(environment 名 `pypi`);③ 在 PyPI 上确认包名未被占用。
- **不可逆**:正式发布前务必先在 TestPyPI 跑通一次。
唯一流程入口为 docs/PUBLISHING.md;原模板仓库不能上传占位包。复制并改名、替换元数据、显式启用仓库变量并配置受保护环境后,只能从main手动选择已验证Tag上传。构建与OIDC上传分离,不给测试或构建任务id-token权限。Tag推送不会触发上传。

准备发布不等于正式发布,不打Tag、不上传索引,除非用户明确授权。已发布Tag不移动、资产不覆盖。源码与wheel/源码包重建、模板改名、安装后的真实HTTP和类型消费都必须验证。

## 边界(不要随意改动)

- 不要切换 HTTP 库到 `httpx`(模板以 `requests` 为最小依赖示范)。
- 不要引入 Copier / cookiecutter 等额外脚手架(保持零模板引擎依赖)。
- 不要改动 `requires-python` 下限,除非明确需要。
- Python>=3.11是0.4维护基线;调整须说明支持状态和依赖安全依据,不为测试通过忽略漏洞。
- 不要删除 `NullHandler` 或 `__all__`,它们是库的基本契约。
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/),
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。

## [0.4.0] - 待发布

### 修复与安全默认
- 改名后User-Agent测试不再硬编码旧包名;拒绝Python关键字和路径式包名,AI入口及动态版本引用同步。
- 默认关闭写操作状态码重试与自动重定向;读取错误不重放,写入重试须显式允许并由调用方保证幂等。
- 真正启用jitter,修复单请求timeout冲突和调用方headers被修改;错误消息不默认回显远端正文。
- 基于维护和安全状态将Python下限提升至3.11,Requests>=2.33、urllib3>=2.8;setuptools最低77.0.3匹配SPDX许可写法。

### 工程与验证
- 添加类型标记、本地真实HTTP回归、模板工具和占位发布保护测试。
- CI扩展解释器及Windows/macOS,增加改名、wheel消费、类型消费、最低依赖和无Git源码包重建验证。
- 发布改为显式启用、手动选择Tag,质量门禁、构建与OIDC上传分离;修正授权文档。

## [0.3.0] - 2026-10-19

### 新增 (Added)
Expand Down
6 changes: 5 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ include README.md
include LICENSE
recursive-include tests *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude * *.py[co]
recursive-include src py.typed
recursive-include scripts *.py
recursive-include docs *.md
include AGENTS.md developer_guide.md CHANGELOG.md
Loading
Loading