Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d34861be93 | ||
|
|
457e4eed3f |
@@ -95,11 +95,11 @@ jobs:
|
||||
"DLP_IO_PACKAGE_VERSION=$packageVersion" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
Write-Host "package source: $tag @ $headCommit"
|
||||
|
||||
- name: Test Python 3.10-3.14
|
||||
- name: Test Python 3.8-3.14
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
foreach ($pythonVersion in @("3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
foreach ($pythonVersion in @("3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
& py "-$pythonVersion" --version
|
||||
if ($LASTEXITCODE -ne 0) { throw "Python $pythonVersion is required" }
|
||||
& py "-$pythonVersion" -m pip install -r requirements-dev.txt --progress-bar off
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
"dlp_io-$version-py3-none-any.whl",
|
||||
"dlp_io-$version.tar.gz"
|
||||
)
|
||||
foreach ($pythonVersion in @("3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
foreach ($pythonVersion in @("3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
$abi = $pythonVersion.Replace(".", "")
|
||||
$wheel = "dlp_io-$version-cp$abi-cp$abi-win_amd64.whl"
|
||||
$env:DLP_IO_PYD = "1"
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
}
|
||||
}
|
||||
$bareAssets = @(Get-ChildItem -LiteralPath $assetsDir -File | ForEach-Object Name)
|
||||
if ($bareAssets.Count -ne 6) {
|
||||
if ($bareAssets.Count -ne 8) {
|
||||
throw "bare asset set mismatch: $($bareAssets -join ', ')"
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pythonVersion in @("3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
foreach ($pythonVersion in @("3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
$abi = $pythonVersion.Replace(".", "")
|
||||
$pydWheel = (Resolve-Path (Join-Path dist "dlp_io-$($env:DLP_IO_PACKAGE_VERSION)-cp$abi-cp$abi-win_amd64.whl")).Path
|
||||
$pydRoot = Join-Path $env:TEMP ("dlp-io-pyd-smoke-$abi-" + $PID)
|
||||
@@ -232,7 +232,7 @@ jobs:
|
||||
throw "unsafe bare smoke path: $bareRootFull"
|
||||
}
|
||||
try {
|
||||
foreach ($pythonVersion in @("3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
foreach ($pythonVersion in @("3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14")) {
|
||||
$abi = $pythonVersion.Replace(".", "")
|
||||
$sandbox = Join-Path $bareRootFull "cp$abi"
|
||||
New-Item -ItemType Directory -Force $sandbox | Out-Null
|
||||
@@ -293,7 +293,7 @@ jobs:
|
||||
"## 产物",
|
||||
"",
|
||||
"- ``dlp_io-$version-py3-none-any.whl``:纯 Python wheel,pip 安装,跨平台",
|
||||
"- ``dlp_io-$version-cp3XX-cp3XX-win_amd64.whl``:pyd wheel,pip 安装,Windows 按解释器版本选用(cp310-cp314)",
|
||||
"- ``dlp_io-$version-cp3XX-cp3XX-win_amd64.whl``:pyd wheel,pip 安装,Windows 按解释器版本选用(cp38-cp314)",
|
||||
"- ``dlp_io-$version.tar.gz``:sdist 源码包",
|
||||
"- ``dlp_io.py`` / ``dlp_io.cp3XX-win_amd64.pyd``:免安装单文件,直接放进项目目录即可 ``import dlp_io``",
|
||||
"",
|
||||
|
||||
@@ -2,7 +2,30 @@
|
||||
|
||||
io-compatible file reads through an approved Python helper on Windows DLP hosts.
|
||||
|
||||
完整文档见 [docs/DLP_IO_LIBRARY.md](docs/DLP_IO_LIBRARY.md)。
|
||||
完整文档见 [docs/DLP_IO_LIBRARY.md](docs/DLP_IO_LIBRARY.md);DLP 环境实测特征与判定/写入逻辑推导见 [docs/DLP_ENVIRONMENT_NOTES.md](docs/DLP_ENVIRONMENT_NOTES.md)。
|
||||
|
||||
## DLP 程序化特征与实现逻辑
|
||||
|
||||
在透明加密 DLP 环境下实测到的关键事实(详见上面的特征文档):
|
||||
|
||||
- **文件属性不可区分**:加密/未加密文件的 attrib、ADS、大小完全一致,只有内容可判。
|
||||
- **同尺寸流式加密**:密文无附加头尾、无静态 magic,且进程按身份分白名单——
|
||||
`python.exe`/`cmd.exe`/`powershell.exe` 读加密文件得到明文(透明解密),
|
||||
`certutil.exe` 等非白名单工具得到磁盘密文。
|
||||
- **白名单读透明 ≠ 写加密**:写入是否被加密因机器策略而异,必须运行时探测。
|
||||
|
||||
对应的实现逻辑:
|
||||
|
||||
- **读取始终明文**:打包 EXE(非白名单)经 stdin 注入 helper 源码拉起白名单
|
||||
`python.exe`,由它读明文、经认证 Named Pipe 回传(python 按身份白名单,
|
||||
任何父进程拉起都有效)。
|
||||
- **`is_encrypted(path)` 加密判定**:先比 certutil 原始哈希与当前进程视图哈希,
|
||||
不等即加密;相等时存在歧义(未加密,或 EXE 读加密文件),再比 helper 视图与
|
||||
本地视图的前 64 KiB 定案——Python/pyd/EXE 三种环境都正确。
|
||||
- **写入始终未加密**:按文件后缀探测直写是否落盘明文(写探针文件后用 certutil
|
||||
原始哈希校验);若直写会被加密,自动切换到 PowerShell stdin 管道中转写盘,
|
||||
字节不落地任何临时文件;两通道均不可用则抛 `DlpWriteError`,绝不静默写密文。
|
||||
可用 `DLP_IO_WRITE_CHANNEL=direct|powershell` 强制指定通道。
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -12,7 +35,7 @@ io-compatible file reads through an approved Python helper on Windows DLP hosts.
|
||||
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io
|
||||
```
|
||||
|
||||
pip 会自动选择最匹配的 wheel:Windows 上 CPython 3.10–3.14 安装对应的 pyd wheel(整个库由 Cython 编译为单个 `.pyd`),其余环境安装纯 Python wheel。如需固定版本:
|
||||
pip 会自动选择最匹配的 wheel:Windows 上 CPython 3.8–3.14 安装对应的 pyd wheel(整个库由 Cython 编译为单个 `.pyd`),其余环境安装纯 Python wheel。如需固定版本:
|
||||
|
||||
```powershell
|
||||
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io==0.1.1
|
||||
@@ -29,10 +52,10 @@ pytest
|
||||
|
||||
## 构建发布
|
||||
|
||||
发布流程由 `.gitea/workflows/publish-dlp-io.yaml` 驱动:推送 `dlp-io-vX.Y.Z` 格式的 annotated tag,Windows runner(Python 3.10–3.14)会自动完成测试、构建,并把产物上传到 Gitea Release:
|
||||
发布流程由 `.gitea/workflows/publish-dlp-io.yaml` 驱动:推送 `dlp-io-vX.Y.Z` 格式的 annotated tag,Windows runner(Python 3.8–3.14)会自动完成测试、构建,并把产物上传到 Gitea Release:
|
||||
|
||||
- `dlp_io-X.Y.Z-py3-none-any.whl`(纯 Python,跨平台)
|
||||
- `dlp_io-X.Y.Z-cp310` … `cp314-win_amd64.whl`(5 个 pyd wheel,Cython 编译,Windows 按解释器版本选用)
|
||||
- `dlp_io-X.Y.Z-cp38` … `cp314-win_amd64.whl`(7 个 pyd wheel,Cython 编译,Windows 按解释器版本选用)
|
||||
- `dlp_io-X.Y.Z.tar.gz`(sdist)
|
||||
- `dlp_io.py` 和 5 个裸 `dlp_io.cp3XX-win_amd64.pyd`(免安装单文件,放进项目目录即可 import)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from enum import Enum
|
||||
from multiprocessing.context import AuthenticationError
|
||||
from multiprocessing.connection import Client
|
||||
|
||||
__version__ = "0.2.0"
|
||||
__version__ = "0.3.0"
|
||||
|
||||
__all__ = [
|
||||
"DlpConfig",
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# DLP 环境特征调研与检测/写入实现逻辑
|
||||
|
||||
本文记录在本机(启用透明加密 DLP 的 Windows)上对 `tests/DLP加密文件.pptx` 与
|
||||
`tests/DLP解密文件.pptx` 两个样本的实测结论、探索过程,以及 `dlp_io` 据此实现的
|
||||
`is_encrypted()` 判定逻辑和未加密写入通道选择逻辑。所有结论都来自实测,不同 DLP
|
||||
产品/策略可能不同,库的应对方式是运行时探测而非硬编码假设。
|
||||
|
||||
## 一、样本事实
|
||||
|
||||
两个样本文件内容相同(解密后 SHA-256 均为
|
||||
`968b5db1…d2947100`),大小完全相同(2,466,572 字节)。加密文件在磁盘上存密文,
|
||||
前 16 字节对比:
|
||||
|
||||
```
|
||||
明文视图: 504b0304 0a000000 0000874e e2400000 (标准 ZIP/PPTX 头 "PK\x03\x04")
|
||||
密文视图: 181b031a 1510197c 0a19 0e7c 6f726c5f (流式加密, 相同明文 0x00 在不同偏移密文不同)
|
||||
```
|
||||
|
||||
特征:同尺寸、全文流式加密、无附加文件头/尾、无静态 magic 可识别。
|
||||
|
||||
## 二、文件属性层面无法区分(重要结论)
|
||||
|
||||
对两个样本逐项对比,全部一致:
|
||||
|
||||
- `attrib`:都只有 `A` 属性(无 EFS 的 E 属性等差异)
|
||||
- `dir /r`:均无附加数据流(ADS)
|
||||
- 文件大小、时间戳:一致
|
||||
|
||||
**结论:无法通过任何文件元数据判断加密状态,唯一可靠的判别特征是内容。**
|
||||
这正是 `is_encrypted()` 采用内容对比的原因。
|
||||
|
||||
## 三、进程白名单模型(实测矩阵)
|
||||
|
||||
DLP 透明加密按「进程身份」决定是否介入读写。用「读取加密样本看到什么」实测:
|
||||
|
||||
| 进程 | 父进程 | 读加密样本视图 | 结论 |
|
||||
|------|--------|----------------|------|
|
||||
| `python.exe` | 任意(含非白名单 bash) | 明文 (PK 头) | **身份白名单**,不继承、不降级 |
|
||||
| `cmd.exe`(`type`) | 任意 | 明文 | 白名单 |
|
||||
| `powershell.exe` | 任意 | 明文 | 白名单(读透明) |
|
||||
| `certutil.exe` | bash / cmd | 密文 | **非白名单, raw 视图** |
|
||||
| Git Bash msys 工具(`head`/`sha256sum`/`cat`) | bash | 密文 | 非白名单 |
|
||||
| `git.exe`(`hash-object`) | bash | 明文 | 白名单(两样本 blob hash 相同) |
|
||||
|
||||
关键推论:
|
||||
|
||||
1. **helper 桥接设计成立**:打包 EXE(非白名单)拉起 `python.exe` 子进程,python 按身份
|
||||
仍是白名单,读到的就是明文——这是库读取链路的根基。
|
||||
2. **`cmd /c type` 不能当密文通道**:cmd 是白名单,读到的同样是明文。社区直觉「用 cmd
|
||||
读原始内容」在本 DLP 上不成立。
|
||||
3. **certutil 是本机可用的 raw 视图工具**:但 DLP 拦截白名单进程直接拉起它
|
||||
(python → certutil 报 `WinError 786`),必须经 `cmd /c certutil …` 中转。
|
||||
4. **`cmd /c` 中文路径坑**:把整条命令作为单个字符串传参会触发 cmd 的引号剥离规则,
|
||||
中文文件名丢失(`ERROR_FILE_NOT_FOUND`);用列表参数(subprocess 自动逐个加引号)
|
||||
则中文路径正常。
|
||||
|
||||
写入侧实测:
|
||||
|
||||
| 写入方 | 落盘结果(raw 视图校验) |
|
||||
|--------|--------------------------|
|
||||
| `python.exe` 直写(.bin / .pptx 后缀) | 明文(本机策略不加密 python 写入) |
|
||||
| `powershell.exe`(stdin 管道 → `[IO.File]`) | 明文 |
|
||||
| `cmd /c more > f` | **破坏二进制**(0x00→CRLF、TAB→空格),不可用 |
|
||||
| `cmd /c copy /b con f` | 挂起等待控制台输入,不可用 |
|
||||
| `certutil -decode - f` | 不支持 stdin 输入,不可用 |
|
||||
| `findstr ^ f` | raw 密文视图,但按行重新包装(0x0A→CRLF 注入),只能看头部不能还原内容 |
|
||||
|
||||
注意:白名单读透明 ≠ 写加密。本机 python/powershell 读透明但写入落盘明文;
|
||||
「白名单进程写入会被加密」的策略在其他机器上仍可能存在——所以写入通道必须
|
||||
运行时探测,不能假设。
|
||||
|
||||
## 四、探索过程(方法记录)
|
||||
|
||||
1. **多通道哈希对比**:`certutil -hashfile`(非白名单视图)对两样本哈希不同
|
||||
(33d3… vs 968b…),python 读取两样本哈希相同(968b…)——证明 DLP 生效、
|
||||
python 白名单、certutil 非白名单,一条命令同时确认三件事。
|
||||
2. **头部字节对比**:`head -c 64 | xxd`(msys 工具,raw 视图)看到密文头,确认
|
||||
同尺寸流加密、无附加头。
|
||||
3. **文件属性对比**:`attrib` / `dir /r` 无差异——排除元数据检测路线。
|
||||
4. **写入通道候选实验**:逐一验证 more/copy con/certutil -decode/findstr 全部
|
||||
有二进制安全性或可用性问题,最终 `powershell [Console]::OpenStandardInput()`
|
||||
管道方案通过全字节(含 0x00/0x1A/0xFF)哈希校验。
|
||||
5. **误报纠正**:`python → cmd /c type` 一度显示明文,先怀疑「白名单继承」,
|
||||
后用 `bash → cmd /c type`(仍是明文)和 `bash → python`(明文)交叉验证,
|
||||
确认 python/cmd 均为身份白名单而非继承。
|
||||
|
||||
## 五、is_encrypted() 判定逻辑
|
||||
|
||||
判据:文件已加密 ⟺ 白名单视图 ≠ 非白名单(raw)视图。
|
||||
|
||||
难点:当前进程自身是否白名单是未知的(Python/pyd 是白名单,打包 EXE 不是),
|
||||
单次对比存在歧义。`dlp_io.is_encrypted(path)` 用两阶段消歧:
|
||||
|
||||
```
|
||||
h_raw = certutil -hashfile(path) # 非白名单 raw 视图(经 cmd /c 中转)
|
||||
h_native = hashlib(当前进程读 path)
|
||||
|
||||
if h_raw != h_native:
|
||||
return True # 视图不一致: 文件已加密, 且当前进程是白名单
|
||||
|
||||
# 歧义分支: 文件未加密, 或文件已加密但当前进程是非白名单 EXE
|
||||
head_helper = helper(白名单 python)读前 64 KiB
|
||||
head_native = 当前进程读前 64 KiB
|
||||
return head_helper != head_native
|
||||
```
|
||||
|
||||
三种运行环境全部正确:
|
||||
|
||||
| 环境 | 加密文件 | 未加密文件 |
|
||||
|------|----------|------------|
|
||||
| Python / pyd(白名单) | 阶段一即判定 True | 阶段二两视图一致 → False |
|
||||
| 打包 EXE(非白名单) | 阶段一相等(都看到密文)→ 阶段二 helper 明文头 ≠ 本地密文头 → True | 阶段二一致 → False |
|
||||
|
||||
代价:未加密文件需一次完整 certutil 哈希 + 一次本地完整读取 + helper 前 64 KiB
|
||||
读取;大文件应缓存结果。库故意不引入「期望 magic 表」——内容对比与文件类型无关。
|
||||
|
||||
## 六、未加密写入通道选择逻辑
|
||||
|
||||
目标:任何环境下 `dlp_io.open(path, "w"/"a"/"x")` 落盘都是未加密文件。
|
||||
|
||||
```
|
||||
首次写某后缀时:
|
||||
1. 环境变量 DLP_IO_WRITE_CHANNEL=direct|powershell → 强制, 跳过探测
|
||||
2. 直写探测: temp 目录写探针文件(同后缀, 含 0x00/0x1A/0xFF 等边界字节)
|
||||
→ certutil raw 哈希 == 内容哈希 ?
|
||||
- 一致 → DIRECT(EXE 环境恒真; 本机 python 也真)
|
||||
- certutil 不可用 → 视为无 DLP → DIRECT
|
||||
3. 否则 PowerShell 中转探测: 同 payload 经 stdin 管道由 powershell 子进程
|
||||
([Console]::OpenStandardInput → [IO.File]::Open) 写盘 → 再校验 raw 哈希
|
||||
- 一致 → POWERSHELL
|
||||
4. 都失败 → raise DlpWriteError(绝不静默写出密文)
|
||||
结果按后缀缓存(DLP 写策略常按文档类型区分)。
|
||||
```
|
||||
|
||||
中转通道要点:字节只走 stdin 内存管道,不落地任何临时文件(白名单进程写的临时
|
||||
文件本身可能被加密,会造成「密文套娃」);`FileMode` 映射 `w→Create`、
|
||||
`a→Append`、`x→CreateNew`;`x` 模式在通道选择前先做本地存在性检查,保证
|
||||
`FileExistsError` 语义与原生一致。
|
||||
|
||||
已知边界:探测文件写在 temp 目录,若 DLP 策略按目录区分,探针结论可能与目标
|
||||
目录不符——此时用 `DLP_IO_WRITE_CHANNEL` 显式指定。
|
||||
|
||||
## 七、相关已知问题
|
||||
|
||||
- 保护区内 `git push` 必现 `fatal: not a git repository`(DLP 拦截 push 进程
|
||||
对 `.git` 的访问模式),解法见 `.kimi-code/skills/git-push-dlp/`(robocopy
|
||||
镜像 `.git` 到 TEMP 后从 stage 目录推送)。
|
||||
- 提交样本 fixture 时注意:本机 `git.exe` 是白名单视图,commit 会把加密样本按
|
||||
明文存入仓库。测试用例已按环境自适应编写(依据本机 raw 视图与本地视图是否
|
||||
一致判断期望),CI(无 DLP)与 DLP 开发机都能通过。
|
||||
@@ -2,16 +2,18 @@
|
||||
|
||||
`dlp-io` 为 Windows DLP 透明加密环境提供接近 `io.open` 的 Python 文件 API。非白名单进程负责业务逻辑;文件读取由已获 DLP 白名单授权的 Python helper 完成,再通过经过认证的 Windows Named Pipe 返回明文字节;文件写入通过通道探测保证落盘为未加密格式。另提供 `is_encrypted()`,在读取前判断文件是否处于 DLP 加密状态。
|
||||
|
||||
当前版本为 `dlp-io==0.2.0`,distribution 名是 `dlp-io`,import 名是 `dlp_io`。
|
||||
当前版本为 `dlp-io==0.3.0`,distribution 名是 `dlp-io`,import 名是 `dlp_io`。
|
||||
|
||||
DLP 环境的实测特征(白名单矩阵、密文样本字节对比、各写入候选通道的可用性实验)与判定/写入逻辑的完整推导记录见 [DLP_ENVIRONMENT_NOTES.md](DLP_ENVIRONMENT_NOTES.md)。
|
||||
|
||||
## 安装
|
||||
|
||||
### 方式一:Gitea PyPI registry(推荐)
|
||||
|
||||
registry 已开放匿名下载,一条命令即可安装;pip 会自动选择最匹配的 wheel:Windows 上 CPython 3.10–3.14 会得到对应的 pyd wheel(整个库编译为单个 `.pyd`),其余环境得到纯 Python wheel。
|
||||
registry 已开放匿名下载,一条命令即可安装;pip 会自动选择最匹配的 wheel:Windows 上 CPython 3.8–3.14 会得到对应的 pyd wheel(整个库编译为单个 `.pyd`),其余环境得到纯 Python wheel。
|
||||
|
||||
```powershell
|
||||
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io==0.2.0
|
||||
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io==0.3.0
|
||||
```
|
||||
|
||||
Package 页面:<https://gitea.docker.antior.cn/antior/-/packages/pypi/dlp-io>
|
||||
@@ -24,10 +26,10 @@ Release 页面:<https://gitea.docker.antior.cn/antior/dlp-io/releases>
|
||||
|
||||
每个版本包含以下文件:
|
||||
|
||||
- `dlp_io-X.Y.Z-py3-none-any.whl`:纯 Python wheel,适用于任何 CPython >= 3.10 环境。
|
||||
- `dlp_io-X.Y.Z-cp310-cp310-win_amd64.whl` 至 `cp314`:pyd wheel,Windows 专用,按解释器版本区分;整个库由 Cython 编译为单个二进制 `.pyd`,功能与纯 Python 版完全一致,可按对源码保护的要求选用。
|
||||
- `dlp_io-X.Y.Z-py3-none-any.whl`:纯 Python wheel,适用于任何 CPython >= 3.8 环境。
|
||||
- `dlp_io-X.Y.Z-cp38-cp38-win_amd64.whl` 至 `cp314`:pyd wheel,Windows 专用,按解释器版本区分;整个库由 Cython 编译为单个二进制 `.pyd`,功能与纯 Python 版完全一致,可按对源码保护的要求选用。
|
||||
- `dlp_io-X.Y.Z.tar.gz`:sdist 源码包。
|
||||
- `dlp_io.py` 与 `dlp_io.cp310-win_amd64.pyd` 至 `cp314`:免安装单文件,与 wheel 内容一致;直接放进项目目录(或加入 `PYTHONPATH`)即可 `import dlp_io`,适合不便使用 pip 的环境。
|
||||
- `dlp_io.py` 与 `dlp_io.cp38-win_amd64.pyd` 至 `cp314`:免安装单文件,与 wheel 内容一致;直接放进项目目录(或加入 `PYTHONPATH`)即可 `import dlp_io`,适合不便使用 pip 的环境。
|
||||
|
||||
按解释器版本选择对应资产,可直接用 pip 安装 URL。Windows PowerShell:
|
||||
|
||||
|
||||
+3
-1
@@ -7,11 +7,13 @@ name = "dlp-io"
|
||||
dynamic = ["version"]
|
||||
description = "io-compatible file reads through an approved Python helper on Windows DLP hosts"
|
||||
readme = "docs/DLP_IO_LIBRARY.md"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
|
||||
@@ -42,7 +42,7 @@ class PackageClassifier(Enum):
|
||||
|
||||
class WorkflowStep(Enum):
|
||||
CLONE_TAG = "Clone immutable package tag"
|
||||
TEST_MATRIX = "Test Python 3.10-3.14"
|
||||
TEST_MATRIX = "Test Python 3.8-3.14"
|
||||
BUILD = "Build and check distribution"
|
||||
SMOKE = "Smoke test release artifacts"
|
||||
RELEASE = "Create Gitea release with wheels"
|
||||
@@ -108,7 +108,7 @@ def test_pyproject_declares_structured_public_package_contract() -> None:
|
||||
project = config["project"]
|
||||
|
||||
assert PackageName(canonicalize_name(project["name"])) is PackageName.DLP_IO
|
||||
assert SpecifierSet(project["requires-python"]) == SpecifierSet(">=3.10")
|
||||
assert SpecifierSet(project["requires-python"]) == SpecifierSet(">=3.8")
|
||||
assert {DynamicField(item) for item in project["dynamic"]} == {
|
||||
DynamicField.VERSION
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user