The Python bindings live in the sibling workspace crate mctx-core-py.
From the repository root:
pip install ./mctx-core-pyFor local development:
cd mctx-core-py
maturin developThe Python API is intentionally centered on the multicast sender use case rather than on a direct transliteration of the Rust ownership model.
Core objects:
ContextPublicationSendReport
Async helper:
AsyncPublication
That gives Python callers the four pieces that tend to matter most:
- create and manage a multicast sender context
- configure publications with explicit source and outgoing-interface control
- send packets and inspect the resolved source/destination details
- integrate a non-blocking sender into
asyncio
from mctx_core import Context
ctx = Context()
publication = ctx.add_publication(
"239.1.2.3",
5000,
source="192.168.1.20",
interface="192.168.1.20",
)
report = publication.send(b"hello multicast")
print(report.source_addr, report.destination, report.bytes_sent)For IPv6 same-host SSM-style testing:
publication = ctx.add_publication(
"ff31::8000:1234",
5000,
source="::1",
interface="::1",
)
report = publication.send(b"hello ipv6 multicast")
print(publication.announce_tuple())The binding keeps the same distinction as the Rust crate:
source: exact local sender IP to bind before transmittinginterface: outgoing multicast interface selected by local IP addressinterface_index: outgoing IPv6 multicast interface selected by interface index
If you provide an IPv6 source, mctx-core binds that exact address and uses
it for the effective sender IP. If you provide an IPv6 interface address and
do not provide a source, mctx-core binds to that exact interface address.
For IPv6 SSM-oriented testing, the receiver's source filter keys off the exact
observed sender IP, so source= is usually the most important knob.
For direct await-style use:
import asyncio
from mctx_core import AsyncPublication
async def main() -> None:
async_publication = AsyncPublication(publication)
report = await async_publication.send(b"hello from asyncio")
print(report.bytes_sent)
asyncio.run(main())On selector-based loops, AsyncPublication uses loop.add_writer() and the
publication file descriptor directly after a non-blocking send reports
BlockingIOError.
On platforms or loops where add_writer() is not available, such as the
default Windows asyncio loop, it falls back to a thin async polling loop over
the same Publication.send() call.
There is intentionally no callback-style add_writer() helper here. For UDP
sender sockets, write readiness is usually level-triggered and effectively
always-on, which makes a long-lived callback registration noisy and
surprising.
mctx-coreremains a pure Rust crate with no PyO3 orcdylibpackaging.mctx-core-pydepends onmctx-coreby path inside the same workspace.- The Python bindings are layered on top of the same non-blocking send path as the Rust API.
Publication.source_addr()returns the effective local sender IP selected by the socket, whilePublication.configured_source_addrexposes the explicit configured bind address when one was requested.