Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
316f307034 | ||
|
|
d34861be93 | ||
|
|
457e4eed3f |
@@ -293,7 +293,7 @@ jobs:
|
|||||||
"## 产物",
|
"## 产物",
|
||||||
"",
|
"",
|
||||||
"- ``dlp_io-$version-py3-none-any.whl``:纯 Python wheel,pip 安装,跨平台",
|
"- ``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 按解释器版本选用(cp310-cp314);3.8/3.9 请用纯 Python wheel",
|
||||||
"- ``dlp_io-$version.tar.gz``:sdist 源码包",
|
"- ``dlp_io-$version.tar.gz``:sdist 源码包",
|
||||||
"- ``dlp_io.py`` / ``dlp_io.cp3XX-win_amd64.pyd``:免安装单文件,直接放进项目目录即可 ``import dlp_io``",
|
"- ``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.
|
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
|
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.10–3.14 安装对应的 pyd wheel(整个库由 Cython 编译为单个 `.pyd`),3.8/3.9 及其余环境安装纯 Python wheel(库要求 Python >= 3.8)。如需固定版本:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io==0.1.1
|
py -m pip install --index-url https://gitea.docker.antior.cn/api/packages/antior/pypi/simple dlp-io==0.1.1
|
||||||
@@ -32,7 +55,7 @@ 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.10–3.14)会自动完成测试、构建,并把产物上传到 Gitea Release:
|
||||||
|
|
||||||
- `dlp_io-X.Y.Z-py3-none-any.whl`(纯 Python,跨平台)
|
- `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-cp310` … `cp314-win_amd64.whl`(5 个 pyd wheel,Cython 编译,Windows 按解释器版本选用;3.8/3.9 使用纯 Python wheel)
|
||||||
- `dlp_io-X.Y.Z.tar.gz`(sdist)
|
- `dlp_io-X.Y.Z.tar.gz`(sdist)
|
||||||
- `dlp_io.py` 和 5 个裸 `dlp_io.cp3XX-win_amd64.pyd`(免安装单文件,放进项目目录即可 import)
|
- `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.context import AuthenticationError
|
||||||
from multiprocessing.connection import Client
|
from multiprocessing.connection import Client
|
||||||
|
|
||||||
__version__ = "0.2.0"
|
__version__ = "0.3.0"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DlpConfig",
|
"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` 为 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(推荐)
|
### 方式一:Gitea PyPI registry(推荐)
|
||||||
|
|
||||||
registry 已开放匿名下载,一条命令即可安装;pip 会自动选择最匹配的 wheel:Windows 上 CPython 3.10–3.14 会得到对应的 pyd wheel(整个库编译为单个 `.pyd`),其余环境得到纯 Python wheel。
|
registry 已开放匿名下载,一条命令即可安装;pip 会自动选择最匹配的 wheel:Windows 上 CPython 3.10–3.14 会得到对应的 pyd wheel(整个库编译为单个 `.pyd`),3.8/3.9 及其余环境得到纯 Python wheel(库要求 Python >= 3.8,pyd 版仅覆盖 CI 预装的解释器版本)。
|
||||||
|
|
||||||
```powershell
|
```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>
|
Package 页面:<https://gitea.docker.antior.cn/antior/-/packages/pypi/dlp-io>
|
||||||
@@ -24,8 +26,8 @@ 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-py3-none-any.whl`:纯 Python wheel,适用于任何 CPython >= 3.8 环境。
|
||||||
- `dlp_io-X.Y.Z-cp310-cp310-win_amd64.whl` 至 `cp314`:pyd wheel,Windows 专用,按解释器版本区分;整个库由 Cython 编译为单个二进制 `.pyd`,功能与纯 Python 版完全一致,可按对源码保护的要求选用。
|
- `dlp_io-X.Y.Z-cp310-cp310-win_amd64.whl` 至 `cp314`:pyd wheel,Windows 专用,按解释器版本区分;整个库由 Cython 编译为单个二进制 `.pyd`,功能与纯 Python 版完全一致,可按对源码保护的要求选用。3.8/3.9 无 pyd wheel,使用纯 Python wheel。
|
||||||
- `dlp_io-X.Y.Z.tar.gz`:sdist 源码包。
|
- `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.cp310-win_amd64.pyd` 至 `cp314`:免安装单文件,与 wheel 内容一致;直接放进项目目录(或加入 `PYTHONPATH`)即可 `import dlp_io`,适合不便使用 pip 的环境。
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -7,11 +7,13 @@ name = "dlp-io"
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
description = "io-compatible file reads through an approved Python helper on Windows DLP hosts"
|
description = "io-compatible file reads through an approved Python helper on Windows DLP hosts"
|
||||||
readme = "docs/DLP_IO_LIBRARY.md"
|
readme = "docs/DLP_IO_LIBRARY.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.8"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Operating System :: Microsoft :: Windows",
|
"Operating System :: Microsoft :: Windows",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ def test_pyproject_declares_structured_public_package_contract() -> None:
|
|||||||
project = config["project"]
|
project = config["project"]
|
||||||
|
|
||||||
assert PackageName(canonicalize_name(project["name"])) is PackageName.DLP_IO
|
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"]} == {
|
assert {DynamicField(item) for item in project["dynamic"]} == {
|
||||||
DynamicField.VERSION
|
DynamicField.VERSION
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user