Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python API Guide

Public Modules

BTPC supports concise root imports and canonical domain imports with identical objects. The stable public modules are:

  • btpc.errors for the exception hierarchy.
  • btpc.types for protocol modes, hashes, parse options, and byte/path values.
  • btpc.metainfo for parsed metainfo, files, and validation reports.
  • btpc.creation for creation options, results, cancellation, and functions.
  • btpc.verification for mismatch values, reports, and verification.

For example, btpc.Metainfo is btpc.metainfo.Metainfo. The compiled btpc._native module and btpc._conversion helpers are private implementation details and are not exported through btpc.__all__.

The public package is btpc; btpc._native is private. The package is typed, ships py.typed, supports CPython 3.11–3.14, and delegates parsing, hashing, traversal, Merkle construction, creation, and verification to Rust.

Workflow Reference

Public API Index

Root imports are aliases of the canonical objects below. Each object is documented once on its defining-module page.

Package version

btpc.__version__ is the installed package version string. The module names btpc.creation, btpc.metainfo, btpc.verification, btpc.types, and btpc.errors are also available from the package root.

Create, Read, Verify

from pathlib import Path

from btpc import CreateOptions, TorrentMode, create

payload = Path("payload")
result = create(
    payload,
    "payload.torrent",
    options=CreateOptions(
        mode=TorrentMode.HYBRID,
        piece_length=16_384,
        threads=1,
        creation_date=0,
        trackers=(("https://tracker.example/announce",),),
        nodes=(("router.example", 6881),),
    ),
)
torrent = result.metainfo
assert torrent.mode is TorrentMode.HYBRID
assert torrent.verify(payload).is_valid
print(torrent.info_hash_v1, torrent.info_hash_v2)
print(torrent.magnet())

create_bytes() returns the same CreateResult without writing a destination. result.metainfo creates and caches a validated object on first access without copying the generated bytes through Python or reading the payload again. CreateOptions accepts sequences and copies tracker tiers, web seeds, and nodes into immutable tuples at construction. Invalid sequence shapes and element types raise TypeError at construction. Creation includes created by = btpc/<version> by default. Set CreateOptions(created_by="my-tool") to override it or CreateOptions(omit_created_by=True) to omit the field. Creator metadata is top-level and does not change applicable info hashes. create() writes atomically and requires overwrite=True to replace a file. Pass durable=True to also sync the destination directory after publication where the platform supports directory syncing. Creation options match core defaults: v1, automatic piece length, conservative automatic threads, no timestamp, and no optional metadata.

Byte-Safe Inspection

Protocol identities are bytes:

from btpc import Metainfo

torrent = Metainfo.read("payload.torrent")
print(torrent.name)       # bytes
print(torrent.name_text)  # str | None
for file in torrent.files:
    print(file.path, file.path_text, file.is_padding)

from_bytes() accepts bytes, bytearray, and contiguous buffer objects. Exact source bytes remain in original_bytes; to_bytes() returns canonical bencode. For parseable noncanonical input those byte sequences, and therefore source versus canonical info hashes, can differ by design.

Metainfo and CreateResult are immutable, non-subclassable facades over owned native Rust objects. Parsing does not eagerly create Python copies of source or canonical bytes, file entries, trackers, web seeds, unknown fields, or validation details. Those values are converted once on first property access and then cached with stable property identity. Canonical serialization is also lazy in the Rust core. These native-backed objects are intentionally not picklable; serialize a Metainfo with to_bytes(canonical=False) or to_bytes(), and serialize a CreateResult through its bytes property.

The private extension returns typed immutable _NativeMetainfo, _NativeTorrentFile, _NativeValidationReport, _NativeCreateResult, _NativePayloadMismatch, and _NativeVerificationReport objects rather than snapshot dictionaries. Public applications should continue using the btpc facades; the underscore-prefixed native classes remain implementation details.

Hybrid files includes validated v1 alignment entries. These have TorrentFile.is_padding == True, a reserved .pad path, and no v2 pieces root.

Both loading methods use conservative native limits by default. Pass ParseOptions(max_total_input=..., max_owned_allocation=..., max_integer_digits=...) to set explicit limits; violations raise ResourceLimitError before an oversized input, digit run, or owned snapshot is accepted.

Editing

Metainfo.edit() returns a newly validated object. Top-level-only edits such as trackers or comments preserve the exact original info bytes and hashes, including noncanonical source encoding. Edits inside info, including private, source, or file attributes, canonicalize the updated dictionary and recompute applicable hashes. to_bytes() remains the explicit fully canonical output choice.

Save edits with edited.write("edited.torrent"). This publishes atomically and preserves the edited object’s exact encoding and info hashes by default. Existing files require overwrite=True; durable=True requests parent-directory syncing. write(canonical=True) explicitly normalizes bencoding. For noncanonical source info this can change hashes. The compatibility default of to_bytes() remains canonical; use to_bytes(canonical=False) for exact bytes without file I/O.

from btpc import UNCHANGED

edited = torrent.edit(comment="release")
assert edited.info_hash_v1 == torrent.info_hash_v1

unchanged = edited.edit(comment=UNCHANGED)
removed = edited.edit(comment=None)

Optional edit fields use a three-state contract: UNCHANGED preserves the current value, None removes it, and a typed value sets it. The former paired set_* keywords were intentionally removed before 1.0 rather than silently reinterpreted.

Metainfo equality compares exact original source bytes in native code, including noncanonical encodings and top-level metadata. Metainfo is intentionally unhashable before 1.0 so equality never requires copying the full source into a Python bytes object or committing to a process-hash implementation.

Progress and Cancellation

from btpc import CancellationToken, CreateOptions, create_bytes

cancel = CancellationToken()

def progress(bytes_hashed: int, total_bytes: int, pieces_hashed: int) -> None:
    if bytes_hashed > total_bytes // 2:
        cancel.cancel()

create_bytes(
    "payload",
    options=CreateOptions(threads=1),
    progress=progress,
    cancellation=cancel,
)

Long-running native work releases the GIL. Callbacks reacquire it safely and an exception from a callback cancels the core operation before propagating.

The extension itself currently requires the GIL, including on free-threaded CPython builds. Subinterpreters are rejected by PyO3 and are not supported; see the compatibility guide.

Errors

All public failures derive from BtpcError and may expose offset, field, or path:

  • BencodeError: syntax or canonical bencode failure.
  • MetainfoError: invalid protocol field or invariant.
  • PathError: filesystem/path failure.
  • ResourceLimitError: configured parse or ownership budget exceeded.
  • VerificationError: verification-category failure.
  • UnsupportedError: unsupported policy or object.
  • CancelledError: cooperative cancellation.

Native filesystem errors attach a platform-native pathlib.Path directly. Unix byte paths use Python’s filesystem surrogate handling, while Windows paths retain their original UTF-16 units without an intermediate UTF-8 conversion.

A normal verification mismatch is returned in PayloadVerificationReport rather than raised. Inspect mismatches, each containing a stable MismatchKind, path, and optional piece index.

Optional metadata inspection

Validated inspection exposes trackers, web_seeds, nodes, source, comment, created_by, and creation_date from the same native optional-metadata model used by Rust and the CLI. Hosts and textual metadata remain bytes; node ports and creation dates are integers.

The comment_text, source_text, and created_by_text properties return UTF-8 text or None when absent or undecodable. trackers_text, web_seeds_text, and nodes_text return decoded immutable collections, or None if any string is not UTF-8. Empty collections remain empty tuples. Raw properties retain all bytes. Check the raw property to distinguish absence from a decoding failure before using a nullable text view in an edit, because None removes a field.

torrent.validation exposes the cached report from construction. validate() returns the same report for compatibility; it does not repeat validation. Invalid metainfo raises during construction, so this report’s is_valid is always true.

Unknown extension values

Metainfo.unknown_fields returns immutable UnknownField objects rather than keys alone. Each object includes its raw byte key, a recursive value made from Python integers and bytes plus BencodeList/BencodeDictionary, the exact encoded source bytes for the key/value entry, and its half-open span in original_bytes. Arbitrary-size integers and non-UTF-8 dictionary keys remain lossless.

Pass the same recursive values to Metainfo.edit(raw_top_level=...) to preserve or replace extension dictionaries and lists. Reserved protocol keys remain rejected, and BencodeDictionary rejects duplicate keys while sorting unique raw keys canonically.

BencodeList supports iteration, indexing, slicing, membership, count, and index. Slicing returns another immutable BencodeList. BencodeDictionary supports key iteration, lookup, membership, get, keys(), values(), and dict(value) conversion. Its existing items attribute remains an immutable tuple, not a method.