# Python Bindings

The Python bindings live in the sibling workspace crate `mcrx-core-py`.

## Build

From the repository root:

```bash
pip install ./mcrx-core-py
```

For local development:

```bash
cd mcrx-core-py
maturin develop
```

## Binding Shape

The Python API is intentionally centered on the multicast use case rather than
on a direct transliteration of the Rust ownership model.

Core objects:

- `Context`
- `Subscription`
- `Packet`
- `PacketWithMetadata`
- `ReceiveMetadata`

Async helpers:

- `AsyncSubscription`
- `add_reader()`

That gives Python callers the four pieces that tend to matter most:

1. create and manage a multicast context
2. join and leave subscriptions
3. receive packets with source, group, port, and optional pktinfo-style metadata
4. integrate those subscriptions into `asyncio`

## Basic Example

```python
from mcrx_core import Context

ctx = Context()
sub = ctx.add_subscription("239.1.2.3", 5000, interface="192.168.1.20")
sub.join()

packet = sub.recv_nowait()
if packet is not None:
    print(packet.source_addr, packet.source_port, packet.group, packet.payload)
```

For SSM:

```python
sub = ctx.add_subscription(
    "ff3e::8000:1234",
    5000,
    source="fd00::10",
    interface="fd00::20",
)
sub.join()
```

## Asyncio

For direct await-style use:

```python
from mcrx_core import AsyncSubscription

async_sub = AsyncSubscription(sub)
packet = await async_sub.recv()
detailed = await async_sub.recv_with_metadata()
```

For callback-style use:

```python
from mcrx_core import add_reader

handle = add_reader(sub, lambda packet: print(packet.payload))
```

### Event Loop Behavior

On selector-based loops, the helper uses `loop.add_reader()` and the
subscription file descriptor directly.

On platforms or loops where `add_reader()` is not available, such as the
default Windows asyncio loop, it falls back to a thin async polling task over
the same non-blocking `recv_nowait()` methods.

That keeps the Rust core runtime-agnostic while still giving Python users an
event-loop story out of the box.

## Notes

- `mcrx-core` remains a pure Rust crate with no PyO3 or `cdylib` packaging.
- `mcrx-core-py` depends on `mcrx-core` by path inside the same workspace.
- The Python bindings are layered on top of the same non-blocking receive path
  as the Rust API.
- `Context.recv_any_nowait()` and per-subscription receive methods are both
  exposed.
- `Subscription.fileno()` is available on Unix for applications that want to
  register their own selector or callback integrations.
