Coverage Tools
Code coverage generation for the project. Supports GCC (lcov), Clang (LLVM), and MSVC (OpenCppCoverage) with automatic compiler detection, and produces consistent HTML and JSON reports across all three.
Packaged as coverage-tool — installable standalone in any
CMake/CTest project, not just this repo. See
Installing / using in another repo.
Table of Contents
- Quick Start
- Requirements
- Installing / using in another repo
- Usage
- Visual Studio .sln without CMake
- Output
- Configuration —
coverage.toml - Architecture
- Compiler-specific notes
- HTML report module
- Running the tests
- Troubleshooting
Quick Start
# Auto-detect compiler, generate both HTML and JSON (default)
coverage-tool --build=<path/to/build>
# JSON only
coverage-tool --build=build --output=json
# HTML only
coverage-tool --build=build --output=html
# Use a custom config file
coverage-tool --build=build --config=my_coverage.toml
# Override the source folder and add extra exclusions
coverage-tool --build=build --filter=Src --exclude-patterns="*Generated*,*Benchmark*"
# Fail the run if line coverage drops below a threshold
coverage-tool --build=build --min-coverage=80
Not installed yet? Install the PyPI package (preferred) or run from a clone:
pip install coverage-tool
# until the first PyPI release:
pip install "git+https://github.com/KhwarizmiAnalytix/coverage-tool.git"
# from a clone, without installing:
PYTHONPATH=src python3 -m coverage_tool.run_coverage --build=build
Requirements
| Compiler | Required tools |
|---|---|
| GCC | lcov, genhtml, gcov (install via apt install lcov / brew install lcov) |
| Clang | llvm-profdata, llvm-cov (part of your LLVM installation) |
| MSVC | OpenCppCoverage on Windows |
Optional but recommended for all three: ctest on PATH (ships with
CMake) — used for accurate test-executable discovery, see
Test discovery.
Python 3.11+ is required for built-in TOML support (tomllib). On Python
3.10 and older, pip installing the package pulls in tomli automatically
(declared in pyproject.toml).
Installing / using in another repo
Install from PyPI (or GitHub until the first release is published):
pip install coverage-tool
# pip install "git+https://github.com/KhwarizmiAnalytix/coverage-tool.git"
Copy examples/coverage.toml to the consumer
repository root and adjust filter / modules for that tree. The tool
reads ./coverage.toml (cwd) or the file next to .git — it does not
read config from site-packages.
This registers two console scripts:
coverage-tool— the main coverage generator (run_coverage.py:main)coverage-tool-html— the standalone JSON→HTML CLI (html_report/cli.py:main)
Then, from the consuming project:
coverage-tool --build=build
Compiler detection reads the standard CMake CMAKE_CXX_COMPILER_ID cache
variable (present in any CMake project) — no project-specific setup
required. Test discovery prefers ctest --show-only=json-v1 when available,
so it doesn't depend on any particular {module}CxxTests-style naming
convention either (see Test discovery). What is still
an opinionated default is the filter = "Library" module-scan root and the
exe_pattern/test_dir_template glob fallback used only when CTest
metadata isn't available — override these in your own coverage.toml if
your layout differs; nothing in the code itself assumes a particular
project name or directory structure.
A consuming project calls the Python API after pip install coverage-tool:
from coverage_tool import get_coverage
Usage
CLI
coverage-tool --build=PATH [OPTIONS]
| Argument | Default | Description |
|---|---|---|
--build=PATH |
(required) | Build directory. Accepts absolute or relative paths (resolved against CWD, script dir, or project root). |
--compiler=NAME |
auto-detect | Force gcc, clang, or msvc. Use this for .sln builds or when auto-detection fails. |
--config=PATH |
auto-detected | Path to coverage.toml. Falls back to built-in defaults if not found. |
--filter=FOLDER |
Library (from config) |
Source folder containing modules to analyse. All non-hidden subdirectories are treated as modules. |
--output=FORMAT |
html-and-json (from config) |
json, html, or html-and-json. |
--exclude-patterns=LIST |
(none) | Comma-separated extra patterns to exclude (e.g. "*Generated*,*Benchmark*"). Merged with the defaults from config. |
--min-coverage=PERCENT |
(none, from config) | Minimum required line-coverage percent; exits non-zero if not met. Requires JSON output. |
--verbose |
off | Print extra diagnostic output. |
-h / --help |
— | Show detailed help. |
Compiler resolution order: --compiler flag → compiler in coverage.toml → auto-detection.
Python API — get_coverage()
from coverage_tool import get_coverage
exit_code = get_coverage(
compiler="auto", # "clang" | "gcc" | "msvc" | "auto"
build_folder="build",
source_folder="Library",
output_folder=None, # defaults to build_folder/coverage_report
exclude_patterns=["*Benchmark*"],
verbose=False,
output_format="html-and-json",
project_root="/path/to/project",
min_coverage=None, # e.g. 80.0 to gate on 80% line coverage
)
Minimum coverage gate
Set --min-coverage=PERCENT (or min_coverage in coverage.toml /
get_coverage()) to fail the run — non-zero exit code, clear error message
— when line coverage falls short:
coverage-tool --build=build --min-coverage=80
# Error: Line coverage 74.30% is below the required minimum of 80.00%
The gate reads the summary.line_coverage.percent value from the JSON
summary each backend returns, so it requires --output=json or
html-and-json (the default) — --output=html alone has no JSON to check
against and raises an error if --min-coverage is also set.
HTML report standalone CLI
Convert an existing JSON coverage report to HTML:
coverage-tool-html --json=coverage_report/coverage_summary.json --output=my_html
# or, unwrapped:
python -m coverage_tool.html_report.cli --json=coverage_report/coverage_summary.json --output=my_html
Visual Studio .sln without CMake
When your project uses a Visual Studio Solution (.sln) instead of CMake,
there is no CMakeCache.txt for the tool to read. The compiler is still
detected automatically through a fallback chain, but you can also override it
explicitly.
Automatic detection (no extra flags needed)
The tool tries the following in order until one succeeds:
| Step | What is checked | Detected as |
|---|---|---|
| 1 | CMakeCache.txt → CMAKE_CXX_COMPILER_ID |
gcc / clang / msvc |
| 2 | .sln or .vcxproj in --build dir or up to 2 parent dirs |
msvc |
| 3 | VS environment variables (VCINSTALLDIR, VSCMD_ARG_TGT_ARCH, …) |
msvc |
| 4 | cl.exe present in PATH |
msvc |
Steps 2–4 are specifically designed for .sln projects. If you run the tool
from a VS Developer Command Prompt (or after calling vcvarsall.bat),
detection via environment variables (step 3) will succeed automatically.
Explicit override (most reliable)
# Fastest option — skip detection entirely
coverage-tool --build=x64\Release --compiler=msvc --filter=Library
# Or set it permanently in coverage.toml so you never need the flag
# coverage.toml
[coverage]
compiler = "msvc"
Where to point --build
For a typical Visual Studio output layout, --build should point to the
directory that contains your compiled binaries and test executables:
MyProject/
├── MyProject.sln
├── x64/
│ ├── Release/ ← pass this as --build
│ │ ├── MyTests.exe
│ │ └── *.dll
│ └── Debug/
└── Library/ ← pass this as --filter (or set filter in coverage.toml)
coverage-tool --build=x64\Release --compiler=msvc --filter=Library
If your binaries are in a non-standard location, configure the search
directories in coverage.toml:
[coverage.tests]
search_dirs = [".", "x64/Release", "x64/Debug", "bin/Release", "bin/Debug"]
Finding OpenCppCoverage
OpenCppCoverage must be installed on Windows. Download from github.com/OpenCppCoverage/OpenCppCoverage.
The tool searches for it in this order:
opencppcoverage_pathincoverage.tomlOPENCPPCOVERAGE_PATHenvironment variable- System
PATH C:\Program Files\OpenCppCoverage\C:\Program Files (x86)\OpenCppCoverage\
Output
All output is written to <build_dir>/coverage_report/ by default
(configurable via coverage_report_dir).
coverage_report/
├── coverage_summary.json # Cobertura-compatible JSON (format v2.0)
├── html/
│ ├── index.html # Summary page with per-file and per-directory tables
│ └── <module>/<file>.html # Line-by-line annotated source pages
│
│ # MSVC only:
├── raw/
│ ├── <TestName>.xml # Cobertura XML from OpenCppCoverage
│ └── <TestName>.cov # Binary coverage data
│
│ # GCC intermediates (in build_dir, not coverage_report):
├── coverage.info
└── coverage_filtered.info
coverage_summary.json schema
{
"metadata": {
"format_version": "2.0",
"generator": "coverage_tool",
"schema": "cobertura-compatible"
},
"summary": {
"line_coverage": { "total": 0, "covered": 0, "uncovered": 0, "percent": 0.0 },
"function_coverage": { "total": 0, "covered": 0, "uncovered": 0, "percent": 0.0 },
"region_coverage": { "total": 0, "covered": 0, "uncovered": 0, "percent": 0.0 }
},
"files": [
{
"file": "/abs/path/to/source.cpp",
"line_coverage": { "total": 120, "covered": 95, "uncovered": 25, "percent": 79.17 },
"function_coverage": { "total": 10, "covered": 9, "uncovered": 1, "percent": 90.0 }
}
]
}
This schema is now produced by a single shared function
(coverage_formats.build_summary_json) for all three compilers, so
function_coverage/region_coverage are always present and consistently
computed rather than varying by backend.
Configuration — coverage.toml
Place coverage.toml at the consumer repository root (or pass
--config=PATH). The file is optional — all values have built-in
defaults so the tool works out of the box without it. A starter file is
examples/coverage.toml.
The config is loaded once and cached. Pass --config=PATH on the CLI to
use a non-standard location.
All options
[coverage]
# Subfolder within the project containing modules to analyse
filter = "Library"
# Default source folder passed to compiler modules
source_folder = "Library"
# Default output format: "json" | "html" | "html-and-json"
output_format = "html-and-json"
# Name of the output directory (relative to build_dir)
coverage_report_dir = "coverage_report"
# Minimum required line-coverage percent. Commented out (disabled) by
# default — see "Minimum coverage gate" above.
# min_coverage = 80.0
[coverage.exclude]
# lcov / OpenCppCoverage glob-style patterns to exclude from coverage
patterns = [
"*ThirdParty*",
"*Testing*",
"/usr/*",
]
# LLVM -ignore-filename-regex patterns (Clang only)
llvm_ignore_regex = [
".*Testing[/\\\\].*",
".*Serialization[/\\\\].*",
".*ThirdParty[/\\\\].*",
]
[coverage.project]
# Filenames/directories whose presence marks the project root
markers = [".git", ".gitignore", "pyproject.toml"]
[coverage.tests]
# Directories (relative to build_dir) searched for test executables
# — fallback only; ctest --show-only=json-v1 is tried first when available.
search_dirs = ["bin", "bin/Debug", "bin/Release", "lib", "tests"]
# Glob patterns identifying test executables
patterns = ["*Test*", "*test*", "*CxxTests*"]
# Test executable name template — {module} is replaced at runtime (Clang/MSVC).
# Also used to match a CTest test name when no CTest LABELS match is found.
exe_pattern = "{module}CxxTests"
# .profraw filename template (Clang only)
profraw_pattern = "{module}CxxTests.profraw"
# Working directory for test execution, relative to build_dir (Clang only,
# fallback path — the CTest-reported WORKING_DIRECTORY is preferred).
# {filter} and {module} are replaced at runtime
test_dir_template = "{filter}/{module}/Testing/Cxx"
[coverage.gcc]
# Error types passed to lcov --ignore-errors (comma-joined at runtime)
lcov_ignore_errors = ["mismatch", "negative", "gcov"]
[coverage.msvc]
# Seconds before the test-verification run times out
verify_timeout = 30
# Seconds before the OpenCppCoverage instrumented run times out
coverage_timeout = 120
# Explicit path to OpenCppCoverage.exe — leave empty to auto-detect
# Auto-detection order: OPENCPPCOVERAGE_PATH env var → system PATH → common install dirs
opencppcoverage_path = ""
[coverage.html]
# Theme values — documented for future use; not yet wired into HtmlGenerator
primary_color = "#007bff"
covered_color = "#28a745"
uncovered_color = "#dc3545"
neutral_color = "#6c757d"
background_color = "#f5f5f5"
max_width = "1200px"
font_family = "Arial, sans-serif"
code_font_family = "'Courier New', monospace"
Customising exclude patterns
Patterns passed via --exclude-patterns on the CLI are merged with the
defaults in coverage.toml (union, no duplicates). To replace the defaults
entirely, set patterns = [] in coverage.toml and rely on the CLI flag.
# Add extra patterns on top of the configured defaults
coverage-tool --build=build --exclude-patterns="*Generated*,*Serialization*"
MSVC timeouts
If OpenCppCoverage hangs on large test suites, increase the timeouts:
[coverage.msvc]
verify_timeout = 60
coverage_timeout = 300
Architecture
File map
coverage-tool/
├── pyproject.toml Package metadata, console-script entry points
├── examples/coverage.toml Starter config to copy into a consumer repo
├── src/coverage_tool/
│ ├── __init__.py Public API: get_coverage, HtmlGenerator, JsonHtmlGenerator
│ ├── run_coverage.py Main CLI entry point & Python API (get_coverage)
│ ├── common.py Shared utilities, config loader, CTest discovery
│ ├── clang_coverage.py Clang/LLVM implementation (llvm-profdata, llvm-cov)
│ ├── gcc_coverage.py GCC implementation (lcov, genhtml, gcov)
│ ├── msvc_coverage.py MSVC implementation (OpenCppCoverage)
│ ├── coverage_formats/ Shared LCOV/Cobertura parsers (see below)
│ │ ├── model.py Canonical FileCoverage model + build_summary_json
│ │ ├── lcov.py LCOV parser — used by both GCC and Clang
│ │ └── cobertura.py Cobertura XML parser — used by MSVC
│ └── html_report/
│ ├── __init__.py Exports HtmlGenerator, JsonHtmlGenerator
│ ├── __main__.py Entry point for `python -m coverage_tool.html_report`
│ ├── cli.py Standalone CLI (--json → --output)
│ ├── html_generator.py Direct coverage data → HTML
│ ├── json_html_generator.py JSON report → HTML
│ ├── directory_aggregator.py Directory-level metric rollup
│ ├── templates.py Shared HTML/CSS templates
│ └── custom.css External stylesheet
└── tests/
├── test_coverage_formats.py Fixture-based parser tests
├── test_html_report.py HTML report module tests
└── fixtures/ Sample .lcov / .xml files
Every backend parses its own raw format (LCOV for GCC/Clang, Cobertura XML
for MSVC) into the same coverage_formats.FileCoverage model before
building the JSON summary or handing data to HtmlGenerator — a parsing
correctness fix (see the DA:-recompute note in lcov.py) applies to every
backend at once instead of needing to be re-applied per compiler.
Data flow
build_dir/
└── CMakeCache.txt
│
▼ detect_compiler()
┌─────────────┐
│ run_coverage│ ◄── coverage.toml (load_config)
└──────┬──────┘
│
┌─────┼──────────┐
▼ ▼ ▼
gcc clang msvc
coverage coverage coverage
│ │ │
▼ ▼ ▼
lcov.parse_lcov cobertura.parse_cobertura ◄── coverage_formats/
│ │ │ (shared parsing + FileCoverage model)
└─────┼──────────┘
│ build_summary_json() / to_line_dicts()
▼
coverage_report/
├── coverage_summary.json
└── html/
├── index.html
└── <file>.html
▲
HtmlGenerator
(html_report/)
Config resolution order for each value:
- CLI flag (highest priority — overrides everything)
coverage.toml(auto-detected or--configpath)- Built-in defaults in
common.CONFIG(lowest priority)
Test discovery
Clang and MSVC need to actually run each module's test executable. Rather
than only guessing its path from a naming template, common.py first asks
CTest for the ground truth:
ctest --show-only=json-v1
This returns each registered test's exact command line, working directory,
and LABELS. Module → test matching then goes, in order:
- CTest
LABELS— a test whose labels include the module name (case-insensitive exact match). This is the strongest signal since it doesn't depend on any executable-naming convention at all — any project whose CMake labels tests by module (e.g.set_tests_properties(MyTest PROPERTIES LABELS "MyModule")) gets accurate discovery for free. - CTest test name ==
exe_pattern.format(module=...)— matches this tool's own{module}CxxTestsconvention when present. - CTest test name contains the module name — last-resort substring match.
- Template/glob fallback —
build_dir/bin/{module}CxxTests— used only whenctestisn't onPATHor the build wasn't configured with CTest.
Because step 1 doesn't depend on any naming convention, this is the part of
the tool most likely to work unmodified against a different CMake/CTest
project — as long as its tests carry a LABELS property naming their
module (set_tests_properties(MyTest PROPERTIES LABELS "MyModule")).
Compiler-specific notes
GCC / lcov
Prerequisites: Build with -fprofile-arcs -ftest-coverage (or
--coverage). Compiler is auto-detected from CMakeCache.txt
(CMAKE_CXX_COMPILER_ID) — no manual setting needed.
What happens:
lcov --capturecollects.gcdafiles frombuild_dir.lcov --removestrips excluded patterns.- Filtered data is parsed (
coverage_formats.parse_lcov) into JSON and/or rendered to HTML.
Intermediate files (coverage.info, coverage_filtered.info) are written
directly to build_dir. The final report goes to coverage_report/.
Common issue — empty coverage.info: no .gcda files were generated.
Verify tests actually ran and that the build used the coverage flags.
Clang / LLVM
Prerequisites: Build with -fprofile-instr-generate -fcoverage-mapping.
Compiler is auto-detected from CMakeCache.txt (CMAKE_CXX_COMPILER_ID) —
no manual setting needed.
What happens (per module):
prepare_llvm_coverage()finds each module's test via CTest (or the naming template as fallback — see Test discovery) and runs it withLLVM_PROFILE_FILE=<module>CxxTests.profraw.llvm-profdata mergemerges all.profrawfiles intoall-merged.profdata.llvm-cov export -format=lcovproducescoverage.lcov.coverage_formats.parse_lcovparses it into JSON and/or HTML.
The naming-template fallback (used only without CTest metadata) is driven by config:
[coverage.tests]
exe_pattern = "{module}CxxTests"
profraw_pattern = "{module}CxxTests.profraw"
test_dir_template = "{filter}/{module}/Testing/Cxx"
MSVC / OpenCppCoverage
Prerequisites: Windows only. MSVC build. OpenCppCoverage installed.
What happens:
- Each test executable (found via
discover_test_executablesglob search) is run throughOpenCppCoverage.exewith--export_type=html,--export_type=cobertura, and--export_type=binary. - Cobertura XML files (in
raw/) are parsed once viacoverage_formats.parse_coberturaand shared between JSON summary generation and the detailed HTML report (previously two independent parses of the same XML). - Line-by-line HTML is generated from the parsed data using
HtmlGenerator.
Finding OpenCppCoverage (in order):
opencppcoverage_pathincoverage.tomlOPENCPPCOVERAGE_PATHenvironment variable- System
PATH C:\Program Files\OpenCppCoverage\C:\Program Files (x86)\OpenCppCoverage\
HTML report module
The html_report/ package can be used independently of the coverage runner.
From Python
from coverage_tool.html_report import HtmlGenerator, JsonHtmlGenerator
# Generate from raw line coverage data
gen = HtmlGenerator(output_dir="my_report", source_root="/project/Library")
gen.generate_report(
covered_lines={"src/foo.cpp": {1, 2, 5}},
uncovered_lines={"src/foo.cpp": {3, 4}},
execution_counts={"src/foo.cpp": {1: 10, 2: 3, 5: 1}},
)
# Generate from a coverage_summary.json file
jgen = JsonHtmlGenerator(output_dir="my_report")
jgen.generate_from_json("coverage_report/coverage_summary.json")
From the CLI
coverage-tool-html --json=coverage_summary.json --output=html_out --verbose
# or
python -m coverage_tool.html_report.cli --json=coverage_summary.json --output=html_out
Classes
| Class | Module | Purpose |
|---|---|---|
HtmlGenerator |
html_generator.py |
Builds HTML from raw covered_lines / uncovered_lines dicts |
JsonHtmlGenerator |
json_html_generator.py |
Builds HTML from a coverage_summary.json file or dict |
DirectoryAggregator |
directory_aggregator.py |
Rolls up line counts to directory level for the index page |
Running the tests
pip install -e ".[test]"
python -m pytest tests/ -v
Expected: parser tests, HTML report tests, and discovery tests.
Publishing to PyPI
Releases are published from GitHub Actions (.github/workflows/publish.yml)
using Trusted Publishing.
- Create the PyPI project
coverage-tool(first upload can also be a manualpython -m build && twine upload dist/*). - On PyPI → Publishing → add a trusted publisher:
- Owner:
KhwarizmiAnalytix - Repository:
coverage-tool - Workflow:
publish.yml - Environment: (leave empty, or match a GitHub Environment if you add one)
- Owner:
- Tag a release (
v1.0.0) or use Publish to PyPI workflow_dispatch.
Until that is configured, consumers install from GitHub:
pip install "git+https://github.com/KhwarizmiAnalytix/coverage-tool.git"
The test suite covers:
coverage_formats.parse_lcov— multi-DA:records per line (the LF:/LH: undercount fix), full/zero coverage, malformed records, missing filecoverage_formats.parse_cobertura— per-line hits, malformed<line>entries, missing filecoverage_formats.build_summary_json— aggregation, 0%/100% coverageHtmlGenerator— report creation, execution counts, HTML contentJsonHtmlGenerator— JSON file and dict inputs, file pages, metrics- CLI interface — valid JSON, missing JSON, verbose flag
- Edge cases — empty data, 100% coverage, 0% coverage
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
CMakeCache.txt not found |
Build directory is wrong or the project was never configured | Check --build points to the CMake build directory |
| No compiler-id variable found | CMake cache exists but CMAKE_CXX_COMPILER_ID is missing or unrecognised |
Reconfigure with the correct CMake toolchain, or pass --compiler explicitly |
coverage.info is empty (GCC) |
No .gcda files — tests didn't run or build is missing --coverage flag |
Run the tests first; verify the build flags |
No profraw files generated (Clang) |
Test executable not found or failed to run | Check CTest registered the test (ctest --show-only=json-v1), or that exe_pattern in coverage.toml matches the actual binary name |
OpenCppCoverage not found (MSVC) |
Tool not installed or not on PATH | Install from the GitHub releases page; set OPENCPPCOVERAGE_PATH or opencppcoverage_path in config |
No modules found in <source_dir> |
--filter folder doesn't exist under the project root |
Verify filter in coverage.toml or pass --filter explicitly |
--min-coverage was requested but no JSON coverage summary was generated |
--output=html was used together with --min-coverage |
Use --output=json or html-and-json (the default) when gating on coverage |
| TOML config silently ignored | Python < 3.11 and tomli not installed |
pip install coverage-tool (pulls in tomli automatically) |
Release files for coverage-tool 2026.9.14
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| coverage_tool-2026.9.14.tar.gz | 63.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| coverage_tool-2026.9.14-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 124.2 kB
Release files / coverage_tool-2026.9.14.tar.gz
| Download URL | coverage_tool-2026.9.14.tar.gz |
|---|---|
| Size | 63.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
94c45abd5bccb5a1622e468b6017a2b848d6d69f65465ce10cbe9d4b953622ad
|
|
BLAKE2b-256 checksum How to use checksums |
b3d46045633b1f893037a4044c2a908514674bd382a256960e8a0864bdb7a0f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / coverage_tool-2026.9.14-py3-none-any.whl
| Download URL | coverage_tool-2026.9.14-py3-none-any.whl |
|---|---|
| Size | 60.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fbf8fd5ca00fc7832a2f7250d7697222d4bde47e6442aea6b735cd4fe23a492b
|
|
BLAKE2b-256 checksum How to use checksums |
02232a2795ab023069d4df71f68fb8d59a53a86f4e587f32bc296b247acd1e2b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log