Skip to content

Python API

The package exposes a small typed API for programmatic use.

Inspection entry points

from torch_abi_audit import (
    inspect_extension,
    inspect_package,
    inspect_site_packages,
)

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

from torch_abi_audit.report import (
    format_json,
    format_package_table,
    format_environment_table,
)

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}")