ktav (Python)
Python bindings for Ktav — a plain configuration format. JSON-shape, no required quotes, no commas, dotted keys. Powered by Rust under the hood.
Languages: English · Русский · 简体中文
Playground: convert JSON / YAML / TOML / INI ⇄ Ktav in your browser at ktav-lang.github.io.
Specification: this package implements Ktav. The format is
versioned and maintained independently of this package — see
ktav-lang/spec for the formal
document.
Install
pip install ktav
The default backend is native and includes our bundled Rust core in a matching platform wheel. No Python runtime dependency is required. Wheels target Python 3.9+ on supported platforms.
If no wheel matches, install from source: the native extension is compiled with Rust through maturin, so a Rust toolchain and Python development headers are needed. Source installation does not switch to pure mode.
Pure Python
For a universal wheel, install the existing py3-none-any artifact explicitly:
pip install ./ktav-0.8.1-py3-none-any.whl
Then select the pure API directly:
from ktav import pure
doc = pure.loads("server.port: 8080")
text = pure.dumps({"server": {"port": 8080}})
formatted = pure.format(text)
The pure backend provides the full parser, writer, formatter, and Ktav errors using only the Python standard library. It does not require third-party packages and does not load _core, WASM, or invoke network or subprocesses. It is explicitly selected; the native default never silently falls back to it.
tomli may appear in test or development extras for tooling on older Python versions; it is not a runtime dependency.
from ktav import pure
config = pure.loads(open("config.ktav").read())
text = pure.dumps({"port": 8080})
formatted = pure.format(open("config.ktav").read())
Pure mode implements the full public operation set — loads, loads_strict, dumps, emit_canonical, dumps_force_strings, format, canonical_from_source (plus load/dump) — with the same ten-field error envelope, against spec 0.8.1. Prefer it when you have no Rust toolchain, when no wheel exists for your platform, for embedding, or when you want an implementation you can audit in pure Python. The honest tradeoff: pure mode runs at Python speed, not the native extension's — no speed claims are made either way. There is no global backend flag and no silent fallback: you choose a backend by choosing which module you import.
Since 0.8.1 the pure backend also ships as a real install artifact: the universal pure wheel ktav-0.8.1-py3-none-any.whl (same distribution name, same version) is published on PyPI alongside the native platform wheels. To install it, download/select the pure wheel (py3-none-any) from the release or PyPI files; pip prefers a matching platform wheel automatically when one exists. The pure wheel installs with no Rust toolchain, no compiler and no maturin, and provides exactly the ktav.pure backend — the native top-level functions then raise a clear ImportError pointing at from ktav import pure.
Quick start
Parse — read typed fields straight off the dict
import ktav
src = """
service: web
port: 8080
ratio: 0.75
tls: true
tags: [
prod
eu-west-1
]
db.host: primary.internal
db.timeout: 30
"""
cfg = ktav.loads(src)
service: str = cfg["service"]
port: int = cfg["port"]
ratio: float = cfg["ratio"]
tls: bool = cfg["tls"]
tags: list[str] = cfg["tags"]
db_host: str = cfg["db"]["host"]
db_timeout: int = cfg["db"]["timeout"]
Walk — dispatch on the runtime type
for k, v in cfg.items():
if v is None:
kind = "null"
elif isinstance(v, bool):
kind = f"bool={v}" # bool first — True is also an int!
elif isinstance(v, int):
kind = f"int={v}"
elif isinstance(v, float):
kind = f"float={v}"
elif isinstance(v, str):
kind = f"str={v!r}"
elif isinstance(v, list):
kind = f"array({len(v)})"
elif isinstance(v, dict):
kind = f"object({len(v)})"
print(f"{k} -> {kind}")
Build & render — construct a document in code
doc = {
"name": "frontend",
"port": 8443,
"tls": True,
"ratio": 0.95,
"upstreams": [
{"host": "a.example", "port": 1080},
{"host": "b.example", "port": 1080},
],
"notes": None,
}
text = ktav.dumps(doc)
# name: frontend
# port: 8443
# tls: true
# ratio: 0.95
# upstreams: [
# { host: a.example port: 1080 }
# { host: b.example port: 1080 }
# ]
# notes: null
A complete runnable version lives in examples/basic.py.
Format — normalise a file, keep the comments
import ktav
print(ktav.format(open("config.ktav").read()))
ktav.format rewrites a document in canonical form (spec § 5.9) while
preserving every comment verbatim. Blank-line runs collapse to one and
blank padding inside brackets is dropped, so formatting is a fixed
point — safe to run in a pre-commit hook. Key order is never changed.
Three functions produce canonical output and they differ in what they accept and what they keep:
| input | comments | blank lines | |
|---|---|---|---|
ktav.format(src) |
source text | kept | kept (runs collapse to one) |
ktav.canonical_from_source(src) |
source text | dropped | dropped |
ktav.emit_canonical(obj) |
a Python object | none to keep | none to keep |
Pick by what you are holding: emit_canonical when you have an object,
canonical_from_source when you have text and want no Python value
built in between, format when you want the document's own comments and
grouping to survive.
Unlike the JavaScript bindings, canonical_from_source buys you no extra
numeric fidelity here — Python distinguishes int from float, so the
Ktav Integer/Float distinction survives loads, and
emit_canonical(loads(src)) produces the same bytes as
canonical_from_source(src). Both spell a float by the canonical rule
(§ 5.9.8) rather than by copying the source, so 1.23456789012345678901
becomes 1.2345678901234567 down either path.
ktav.dumps_force_strings(obj) renders like dumps but coerces every
leaf scalar — integer, float, bool, null — to a String via the raw ::
marker. Compounds keep their structure.
Four entry points mirror the standard library json module:
| Function | Purpose |
|---|---|
ktav.loads(s) |
Parse a Ktav string (or UTF-8 bytes). |
ktav.dumps(obj) |
Serialise a native Python value. |
ktav.load(fp) |
Parse from a file-like object. |
ktav.dump(obj, fp) |
Serialise to a file-like object. |
load / dump accept both text-mode and binary-mode files.
For validation at trust boundaries, ktav.loads_strict(s) applies the
specification's canonical-scalar rules and raises KtavDecodeError for a
lossy scalar spelling. Canonical writer forms such as 1e-3 and 1e10 are
accepted and produce the same native values as loads.
Type mapping
| Ktav | Python |
|---|---|
null |
None |
true / false |
bool |
| bare integer (fits signed 64-bit) | int |
| integer beyond 64-bit | str |
| bare decimal | float |
| other scalar | str |
[ ... ] |
list |
{ ... } |
dict |
Ktav types numbers by lexical form — a bare port: 8080 is an
int, ratio: 0.5 a float, and anything that isn't a bare number
stays a str. Force a numeric-looking value to stay a string with
:: (zip:: 01007).
The Integer domain matches the native extension's (signed 64-bit): an
integer literal beyond i64 parses as a str, re-emitted via the raw
:: marker so it stays a string — the same behaviour in both backends.
dict preserves insertion order (Python 3.7+ guarantee), matching the
ordered-object semantics of Ktav.
Serialisation is the inverse:
- Python
int→ bare integer (including arbitrary-precision bigints; reading such a literal back yields astr— see above). - Python
float→ bare decimal (decimal point always present;NaN/±Infinityare rejected — Ktav does not represent them). - Python
tupleis accepted as an array, for symmetry withlist. - Non-
strkeys in adictraiseKtavEncodeError.
Key escaping
Since spec 0.6.4 a literal . or : inside a key segment is written
with a backslash:
a\.b: v
a\:b: v
x.y\.z: v
a."b.c".d: v
"\u0041": v
A literal backslash in a key is \\. Since spec 0.7, a whole segment
may instead use double quotes, single quotes or backticks. Quotes only
delimit key segments, not values; \uXXXX decodes a Unicode code point.
Errors
import ktav
try:
ktav.loads("x: [")
except ktav.KtavDecodeError as e:
print("decode:", e)
try:
ktav.dumps({"v": float("nan")})
except ktav.KtavEncodeError as e:
print("encode:", e)
# Catching the base class catches either.
try:
ktav.loads("a: 1\na: 2")
except ktav.KtavError:
...
| Exception | Raised by | Base |
|---|---|---|
KtavError |
(base) | Exception |
KtavDecodeError |
loads / load |
KtavError |
KtavEncodeError |
dumps / dump |
KtavError |
Both backends raise the same exceptions carrying the identical
ten-field envelope. One deliberate delta: for a str input containing
literal lone surrogates, pure mode raises KtavDecodeError with the
error category InvalidUtf8 and a nonempty span over the malformed
bytes, where the native extension surfaces a UnicodeEncodeError.
Since 0.7.1 every raised instance also carries the structured error
envelope as attributes: error, reason, line, line_text, span,
path, body, canonical, spec_section. Absent information is
None, never a missing attribute. span is {"start": …, "end": …}
byte offsets into the UTF-8 source; path is the list of exact decoded
key segments (a key literally named a.b is one segment, never split).
str(e) stays a human-readable message — the raw envelope is never
substituted for it.
e.message carries that same text as an attribute, so str(e) == e.message. It is redundant here by design: this binding has always
used the core's own rendering for str(e), and the attribute exists so
the envelope has the same ten-field shape in Python that it has in every
other language — where message is the only way to reach the text. Use
it rather than rebuilding a sentence from error, line and body: a
reassembled message differs between bindings, and this one does not.
Philosophy
Ktav is intentionally small. Its five design principles
(from spec/CONTRIBUTING.md):
- Locality — a line's meaning does not depend on another line.
- One sentence — any new rule fits in one sentence of the spec.
- No whitespace sensitivity (line breaks aside).
- No magic types — the format never decides
"8080"means a number. - Explicit over clever —
::is verbose on purpose.
The Python bindings honour this: they add no schema inference, no
auto-casting, no defaulting. If you want typing, you do it at the
boundary with your own tool — pydantic, dataclasses, attrs —
against the native Python structures this library returns.
Other Ktav implementations
spec— specification + conformance suiterust— reference Rust crate (cargo add ktav); these Python bindings are a thin PyO3 wrapper around itcsharp— C# / .NET (dotnet add package Ktav)golang— Go (go get github.com/ktav-lang/golang)java— Java / JVM (io.github.ktav-lang:ktavon Maven Central)js— JS / TS (npm install @ktav-lang/ktav)php— PHP (composer require ktav-lang/ktav)
Two backends ship in this package. The native backend is the
default: the top-level ktav API is a thin PyO3 wrapper around the
Rust core (0.8.1). The pure backend, ktav.pure, is a complete
parser and generator in pure Python — no compiled extension required.
There is no global backend flag and no silent fallback: you choose a
backend by importing it, and CI asserts byte-identical canonical
output across both. The pure backend also ships as the universal pure
wheel (ktav-0.8.1-py3-none-any.whl), published alongside the native
platform wheels.
Versioning
This package follows Semantic Versioning with the
pre-1.0 convention that a MINOR bump is breaking. The package version
and the ktav crate version move together. ktav.__spec_version__
reports the Ktav format version this binding supports.
Development
See CONTRIBUTING.md for the dev setup, test layout, and the contribution workflow.
Support the project
The author has many ideas that could be broadly useful to IT worldwide — not limited to Ktav. Realizing them requires funding. If you'd like to help, please reach out at phpcraftdream@gmail.com.
License
MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.
Metadata
Release files for ktav 0.8.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ktav-0.8.1.tar.gz | 109.5 kB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| ktav-0.8.1-py3-none-any.whl | Python 3 | none | any | Details |
| ktav-0.8.1-cp39-abi3-win_arm64.whl | CPython 3.9 | abi3 | Windows ARM64 | Details |
| ktav-0.8.1-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| ktav-0.8.1-cp39-abi3-musllinux_1_2_x86_64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ x86-64 | Details |
| ktav-0.8.1-cp39-abi3-musllinux_1_2_aarch64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ ARM64 | Details |
| ktav-0.8.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| ktav-0.8.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| ktav-0.8.1-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| ktav-0.8.1-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 3.5 MB
Release files / ktav-0.8.1.tar.gz
| Download URL | ktav-0.8.1.tar.gz |
|---|---|
| Size | 109.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d5f500c44877244c02a588a67df2e746fb6655915ce9fadec4001a717a8619dc
|
|
BLAKE2b-256 checksum How to use checksums |
a6446a271c1f1396aaac3267a35bfa245bf484e5db04ef76a83a7e9e0b89d9ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-py3-none-any.whl
| Download URL | ktav-0.8.1-py3-none-any.whl |
|---|---|
| Size | 49.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8353dad79c233c15621f036336e154c21aa8d0cf2a64a9e299674b67f1984809
|
|
BLAKE2b-256 checksum How to use checksums |
6d097803866ef7d255f1a0299789b6226292f3fd806ef2d3c89b273171c26fa8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-win_arm64.whl
| Download URL | ktav-0.8.1-cp39-abi3-win_arm64.whl |
|---|---|
| Size | 310.7 kB |
| Tags | CPython 3.9 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
9229323071b6af4e832a864bb23c2772a2113ceeaa940e338206915a522eaace
|
|
BLAKE2b-256 checksum How to use checksums |
785aa03f392b079aaaaa6be0ffe0747a76add03d0ab002ae255781ea71ca35e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-win_amd64.whl
| Download URL | ktav-0.8.1-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 319.0 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
330eae8e1682ee58ebb9621d8906a2bf4f0b01dcdc0459c2c67f8fc3644ab206
|
|
BLAKE2b-256 checksum How to use checksums |
b46f5b07edd806dc03f073c8235b366560a14cf4918be782dba5e79a0b9f0ed2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-musllinux_1_2_x86_64.whl
| Download URL | ktav-0.8.1-cp39-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 607.8 kB |
| Tags | CPython 3.9 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
70f946254576c4e7dd84b34a6c5b3e3dcfdff66bb00318ff1566da30a1af2802
|
|
BLAKE2b-256 checksum How to use checksums |
6cab4e90fd74b37f4e7073243d4a4a4b2d79bb5004d7bb9bb76fb1074ea0dc3a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-musllinux_1_2_aarch64.whl
| Download URL | ktav-0.8.1-cp39-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 559.4 kB |
| Tags | CPython 3.9 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
ccdd70311c872bbf6ba2fa810afcc5d548aa12cd953a71b2920962923458ff1a
|
|
BLAKE2b-256 checksum How to use checksums |
b057e7a673e63ebe29cc58496f166333b862b453c7620abda7280f57701716c0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | ktav-0.8.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 395.9 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
87cab469e872dc7ebecea8d16adba20ae62833fc74ca853f2740dd66dacdb1d3
|
|
BLAKE2b-256 checksum How to use checksums |
ab990cead3299a3746d5de9cf579dcd365c0addf5ee10bcdbb221e49ca423d1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | ktav-0.8.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 380.9 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
98a908319fe362fdfa6c57f6bdfeb98d8ca2ee4ffad42971b15f19b4ac589dc7
|
|
BLAKE2b-256 checksum How to use checksums |
b3cb33f0ec493f2ec80b66182adf01faf785a1794afb4bfe37c60d74dd663fad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | ktav-0.8.1-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 372.7 kB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
6d1c1190df3d1c9d6ae08c84fb44155c73086095c486bc3cf1eda3cbc424df0b
|
|
BLAKE2b-256 checksum How to use checksums |
023f84a785290f70b65a670283c8c0ecf217ad0f65933c7ac3c01e1c695ace8d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logRelease files / ktav-0.8.1-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | ktav-0.8.1-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 387.9 kB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
b48d3abfff2158ae278dd901202c311b7c1174dc76ed2306049d0caa6867c78d
|
|
BLAKE2b-256 checksum How to use checksums |
26b977314f3591c8d5ba798ef0f5413e6713a880da2829c730793966077f6bff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 22, 2026.
Transparency logSecurity for ktav 0.8.1
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.
Full provenance
All 10 files in this release have been uploaded with provenance metadata.
Attestation for ktav-0.8.1.tar.gz
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 Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.