Python API¶
The package exposes a small typed API for programmatic use.
Inspection entry points¶
inspect_extension(path) -> ExtensionReport¶
Inspect a single extension module file. Returns an ExtensionReport even for
unreadable files (the error field carries the message).
A file the tool understands the format of but cannot read completely — a
damaged PE import or export directory, for instance — still yields a verdict
built from the symbols that were recovered, with incomplete carrying the
reason. Checking report.has_error on the enclosing PackageReport treats such
a read as a failure, and the CLI exits with code 2 for it, because an audit
that could not see every symbol must not report success.
ExtensionReport.incomplete is set when part of the symbol table was
unreadable but names were recovered; ExtensionReport.error means nothing
could be read at all and the verdicts are placeholders.
inspect_package(name_or_path) -> PackageReport¶
Inspect an installed package. name_or_path is either:
- An import name string (e.g.
"torchaudio") — resolved via the import system. - A
Path(or path-string with a separator) — treated as a file or directory.
The returned PackageReport separates Python extension modules
(.extensions) from bundled internal libraries (.bundled_libs); both
contribute to the torch ABI verdict.
A directory is scanned for the host's library formats (.so / .pyd /
.dylib on Linux and macOS, .pyd / .dll on Windows). A file named
explicitly is inspected whatever platform built it: the built-in PE parser makes
a Windows .pyd / .dll readable from Linux or macOS, while ELF and Mach-O
inspection still requires the host's nm.
inspect_site_packages(directory=None) -> EnvironmentReport¶
Walk a site-packages directory, returning one PackageReport per package that
contains compiled libraries — extension modules, bundled libraries, or both.
directory=None defaults to the active interpreter's purelib.
Result types¶
All result dataclasses are frozen, slotted, and fully typed.
@dataclass(frozen=True, slots=True)
class CPythonABIVerdict:
intent: bool # filename carries an `abi3` tag
compliant: bool # all Py*/_Py* symbols are in the stable set, and the
# extension links the version-agnostic python3.dll
violations: tuple[str, ...]
python_dll: str | None # Windows Python DLL it imports from, if known
@dataclass(frozen=True, slots=True)
class TorchABIVerdict:
uses_torch: bool
stable: bool
unstable_symbols: tuple[str, ...]
stable_shim_count: int
min_torch_version: str | None
version_defining_symbols: tuple[str, ...]
unknown_shim_symbols: tuple[str, ...] # shims newer than the vendored data
@dataclass(frozen=True, slots=True)
class ExtensionReport:
path: Path
cpython: CPythonABIVerdict
torch: TorchABIVerdict
error: str | None # nothing could be read; verdicts are placeholders
incomplete: str | None # part of the symbol table was unreadable
@dataclass(frozen=True, slots=True)
class PackageReport:
name: str
root: Path
extensions: tuple[ExtensionReport, ...] # PyInit_*-bearing libraries
bundled_libs: tuple[ExtensionReport, ...] # other compiled libs in the package
error: str | None
# @property has_error -> True if the package or any library has an
# `error` or an `incomplete` read (CLI exits 2)
# @property torch_verdict -> "torch-stable" | "torch-unstable" | "no-torch" | "error"
# @property min_torch_version -> "2.10.0"-style floor across all libs, or None
# @property min_torch_display -> same, ">="-prefixed when unknown newer shims are present
# @property cpython_verdict -> "abi3-compliant" | "not-abi3" | "mixed" | "no-extensions"
@dataclass(frozen=True, slots=True)
class EnvironmentReport:
site_packages: Path
packages: tuple[PackageReport, ...]
python_dll¶
On Windows the extension's dependency list is available, and a Stable ABI
build must import from python3.dll — the forwarder that re-exports the stable
surface for whichever interpreter is installed. python_dll records what was
actually linked: python3.dll for a compliant build, pythonXXX.dll (including
python3XXt.dll / python3XX_d.dll free-threaded and debug builds) for one
pinned to a single interpreter. A pinned dependency forces compliant to
False even when every symbol is stable, since pythonXXX.dll exports the same
stable names. The field is None on ELF and Mach-O, where the dependency list
is not read.
incomplete¶
incomplete means the verdict was built from a partial symbol table. The names
recovered before the damage are kept and classified, so the verdict reflects
real usage rather than looking clean, but the read is not treated as a success:
report.has_error becomes True, the CLI prints the reason, and the process
exits with code 2 (taking precedence over a --check violation code of 1).
Formatters¶
format_json accepts any of the three top-level report types and emits
indented JSON. The table formatters are package- and environment-specific.
Minimum torch version¶
The stable ABI grows over time: new aoti_torch_* / torch_* C-shim entry
points appear in later releases. torch_abi_audit.torch_versions maps each
stable shim symbol to the torch release that introduced it, using a vendored
copy of PyTorch's
shim_function_versions.txt.
That manifest lists only 2.10.0+ symbols, so a second vendored file,
baseline_shim_symbols.json, records the fixed set that existed at 2.9.0. A
shim symbol in neither file is newer than our data and is surfaced rather than
silently treated as 2.9.0 (which would misdate wheels built against a future
torch).
from torch_abi_audit import BASELINE_VERSION, minimum_version, symbol_version
symbol_version("torch_has_storage") # (2, 14, 0)
symbol_version("aoti_torch_get_dim") # None (in the 2.9.0 baseline, not the manifest)
# minimum_version returns (version_tuple, defining_symbols, unknown_symbols):
# version is the highest floor across the *known* symbols (BASELINE_VERSION,
# 2.9.0, when the stable ABI shipped, if none are newer); unknown lists shims in
# neither data file.
minimum_version(["aoti_torch_aten_full", "torch_has_storage"])
# -> ((2, 14, 0), ("torch_has_storage",), ())
minimum_version(["aoti_torch_get_dim", "aoti_torch_from_the_future"])
# -> ((2, 9, 0), (), ("aoti_torch_from_the_future",))
classify_symbols already runs this for you: the resulting TorchABIVerdict
carries min_torch_version (a "2.10.0"-style string), version_defining_symbols,
and unknown_shim_symbols. Refresh the vendored data with
python scripts/update_shim_versions.py and python scripts/generate_baseline_shims.py.
Example¶
from torch_abi_audit import inspect_site_packages
env = inspect_site_packages()
unstable = [p.name for p in env.packages if p.torch_verdict == "torch-unstable"]
print("Packages using unstable libtorch ABI:", unstable)
# Which minimum torch version does each stable-ABI package need?
for p in env.packages:
if p.min_torch_version:
print(f"{p.name}: needs torch >= {p.min_torch_version}")