Skip to main content

High-performance, schema-agnostic event bus for AI runtime workloads

Project description

Net Python

High-performance, schema-agnostic event bus for AI runtime workloads.

Installation

pip install net-mesh

The package publishes as net-mesh on PyPI but imports as from net import ... (the in-source module name is preserved). For the higher-level Pythonic surface (generators, typed channels, dataclass/Pydantic support), install net-mesh-sdk instead — it depends on this package.

Cargo features

The PyO3 binding wraps a feature-gated Rust crate. Wheels published to PyPI ship with every feature enabled, but anyone building from source with maturin develop / maturin build needs to ask for the features they want — symbols from a disabled feature are silently absent from net._net and the matching from net import ... block in net/__init__.py quietly skips them.

Cargo feature Surface (Python classes) Notes
cortex Redex, RedexFile, RedexEvent, TasksAdapter, MemoriesAdapter, Task, Memory, NetDb, CortexError, RedexError, NetDbError Required by meshos and dataforts.
meshdb MeshQuery, MeshQueryRunner, QueryBuilder, Predicate, ResultRow, AggregateResult, JoinedRow, LineageEntry, CachePolicy, ExecuteOptions, GroupKey, WindowBoundary, InMemoryChainReader, MeshDbError Independent.
meshos MeshOsDaemonSdk, MeshOsDaemonHandle, MeshOsSdkError Pulls in compute + cortex.
dataforts BlobRef, BlobError, MeshBlobAdapter, blob_publish, blob_resolve, adapter registry Pulls in cortex + net.
compute DaemonRuntime, DaemonHandle, CausalEvent, DaemonError, MigrationHandle, MigrationError Required by meshos and groups.
groups ReplicaGroup, ForkGroup, StandbyGroup, GroupError Pulls in compute.
deck DeckClient, OperatorIdentity, AdminCommands, SnapshotStream, StatusSummaryStream, IceCommands, OperatorRegistry, AdminVerifier, DeckSdkError Pulls in meshos.
net NetMesh, NetStream, NetKeypair, Identity, token / channel-auth helpers Encrypted mesh transport + per-peer streams.
redis RedisStreamDedup Redis Streams adapter dedup helper.
jetstream (no Python surface yet) NATS JetStream adapter — Rust-only.

All are enabled by default in Cargo.toml. Disable with maturin develop --no-default-features --features "<list>" if you need a thinner wheel.

The net.__init__ module try-imports each family conditionally, so a wheel built without (say) meshos won't surface MeshOsDaemonSdk and the corresponding from net import MeshOsDaemonSdk raises ImportError cleanly.

Quick Start

from net import Net

# Create event bus (defaults to CPU core count shards)
bus = Net()

# Ingest events - fast path with raw JSON strings (23M+ ops/sec)
bus.ingest_raw('{"token": "hello", "index": 0}')

# Or use dict for convenience (4M+ ops/sec)
bus.ingest({"token": "world", "index": 1})

# Batch ingestion for maximum throughput
events = [f'{{"token": "tok_{i}"}}' for i in range(10000)]
count = bus.ingest_raw_batch(events)

# Poll events
response = bus.poll(limit=100)
for event in response:
    print(event.raw)
    # Or parse to dict
    data = event.parse()

# Check stats
stats = bus.stats()
print(f"Ingested: {stats.events_ingested}, Dropped: {stats.events_dropped}")

# Shutdown
bus.shutdown()

Context Manager

with Net(num_shards=4) as bus:
    bus.ingest_raw('{"data": "value"}')
# Automatically shuts down

Configuration

bus = Net(
    num_shards=8,                    # Number of parallel shards
    ring_buffer_capacity=1_048_576,  # Events per shard (must be power of 2)
    backpressure_mode="drop_oldest", # What to do when full
)

Async API

Every I/O surface ships a parallel Async* class. The sync API stays unchanged — pick whichever shape fits the caller; both share the same underlying mesh / adapter / handle and interoperate freely.

Quickstart

import asyncio
from net import NetMesh, AsyncNetMesh, AsyncMeshRpc

async def main():
    mesh = NetMesh("127.0.0.1:9000", "00" * 32)
    mesh.start()

    amesh = AsyncNetMesh(mesh)            # Arc::clone — shares the same MeshNode
    arpc = AsyncMeshRpc(mesh)             # same — shared service registry

    await amesh.connect("127.0.0.1:9001", peer_pub, peer_node_id)
    reply = await arpc.call(peer_node_id, "echo", b"hello")
    print(reply)

asyncio.run(main())

Available Async* classes

Sync class Async sibling What's awaitable
NetMesh AsyncNetMesh connect, accept, push_to, poll, subscribe_channel, unsubscribe_channel, publish, announce_capabilities, shutdown
NetStream AsyncNetStream send, send_with_retry, send_blocking
MeshRpc AsyncMeshRpc call, call_service, call_streaming, call_client_stream, call_duplex, serve / serve_client_stream / serve_duplex (each accepts async def handlers)
RpcStream AsyncRpcStream PEP 525 async for chunk in stream:
ClientStreamCall AsyncClientStreamCall send, finish
DuplexCall / DuplexSink / DuplexStream AsyncDuplexCall / AsyncDuplexSink / AsyncDuplexStream send, finish_sending, async-iter on the read half
TypedMeshRpc AsyncTypedMeshRpc Codec-wrapped variants of every AsyncMeshRpc shape
MemoriesAdapter AsyncMemoriesAdapter wait_for_seq, wait_for_token, watch_memories, snapshot_and_watch_memories
MemoryWatchIter AsyncMemoryWatchIter PEP 525 async iter
TasksAdapter AsyncTasksAdapter wait_for_seq, wait_for_token, watch_tasks, snapshot_and_watch_tasks
TaskWatchIter AsyncTaskWatchIter PEP 525 async iter
RedexFile AsyncRedexFile tail() returns an AsyncRedexTailIter
RedexTailIter AsyncRedexTailIter PEP 525 async iter
DaemonRuntime AsyncDaemonRuntime start, shutdown, spawn, spawn_from_snapshot, stop, snapshot, deliver, start_migration, start_migration_with
MigrationHandle AsyncMigrationHandle wait, wait_with_timeout, cancel
MeshBlobAdapter AsyncMeshBlobAdapter store, fetch, fetch_range, exists, repair_blob
blob_publish / blob_resolve (free fns) async_blob_publish / async_blob_resolve Awaitable versions of the free functions
RegistryClient AsyncRegistryClient list, spawn, unregister
FoldQueryClient AsyncFoldQueryClient query_latest, query_summarize_now
MeshQueryRunner AsyncMeshQueryRunner execute
DeckClient AsyncDeckClient close; getters stay sync; .admin is AsyncAdminCommands; .ice is AsyncIceCommands; .snapshots() / .status_summary_stream() return async iters
AdminCommands AsyncAdminCommands drain, enter_maintenance, exit_maintenance, cordon, uncordon, drop_replicas, invalidate_placement, restart_all_daemons, clear_avoid_list
IceCommands / IceProposal / SimulatedIceProposal AsyncIceCommands / AsyncIceProposal / AsyncSimulatedIceProposal Factories stay sync; proposal.simulate() and simulated.commit() are awaitable
SnapshotStream / StatusSummaryStream AsyncSnapshotStream / AsyncStatusSummaryStream PEP 525 async iter
MeshOsDaemonSdk / MeshOsDaemonHandle AsyncMeshOsDaemonSdk / AsyncMeshOsDaemonHandle shutdown / next_control / graceful_shutdown are awaitable; constructed via from_sync(sync_inst) (consumes the sync sibling)

Mixing sync and async

Async* classes wrap the same Rust Arc<...> as their sync counterparts. A server registered via MeshRpc.serve(...) is callable from AsyncMeshRpc.call(...) and vice versa — same for every adapter. Mix shapes within a single program freely; just pick the one that matches the caller (sync script: sync API; asyncio handler: async API).

from net import NetMesh, MeshRpc, AsyncMeshRpc, MemoriesAdapter, AsyncMemoriesAdapter

mesh = NetMesh("127.0.0.1:9000", "00" * 32)
# A server, registered from sync code
def echo(req: bytes) -> bytes:
    return req
MeshRpc(mesh).serve("echo", echo)

# An async caller against the same mesh — sees the same server
async def caller():
    arpc = AsyncMeshRpc(mesh)
    return await arpc.call_service("echo", b"hi")

Cancellation

asyncio.wait_for(...) cancellation propagates to the substrate's cancel registry on every nRPC call. The wrapped task drops, the substrate's Mesh::cancel(token) fires, and the in-flight call returns RpcCancelledError:

import asyncio
from net import AsyncMeshRpc, RpcCancelledError

async def with_timeout(arpc):
    try:
        return await asyncio.wait_for(
            arpc.call(target, "slow", b"..."),
            timeout=0.5,
        )
    except (asyncio.TimeoutError, RpcCancelledError):
        # Either is fine — the underlying call is cancelled either way.
        return None

Streaming shapes propagate cancellation per construction — cancelling mid-async for terminates the entire stream, not just one pull. Non-mesh subsystems (channel publish, capability announce, mesh handshake) don't have substrate cancel tokens yet; asyncio cancellation still drops the awaiting tokio task, but the in-flight network round-trip may complete in the background.

async def server handlers

AsyncMeshRpc.serve (and the typed AsyncTypedMeshRpc.serve) detect inspect.iscoroutinefunction(handler) at register time and route the coroutine through a dedicated dispatch path. Sync handlers keep the spawn_blocking shape; async handlers run as native coroutines:

async def handler(req: bytes) -> bytes:
    chunk = await fetch_from_upstream(req)
    return chunk

AsyncMeshRpc(mesh).serve("upstream", handler)

Cancellation propagates end-to-end. When a caller's asyncio.wait_for(rpc.call(...), timeout=...) expires, the substrate fires a CANCEL frame to the server; the dispatched coroutine sees asyncio.CancelledError raised inside its await. Wrap critical sections in try/except asyncio.CancelledError if you need cleanup before re-raising.

Single dispatcher loop — keep handlers cooperative. All async def handlers in the process share one event loop. A handler that does blocking Python I/O (time.sleep, requests.get, raw socket.recv, etc.) stalls every other in-flight handler. Always await async equivalents:

# ✗ Bad — blocks the dispatcher loop for 5 seconds.
async def handler(req: bytes) -> bytes:
    time.sleep(5.0)
    return req

# ✓ Good — yields back to the loop.
async def handler(req: bytes) -> bytes:
    await asyncio.sleep(5.0)
    return req

# ✓ Good — push blocking work to a thread-pool slot.
async def handler(req: bytes) -> bytes:
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(None, blocking_call, req)

Migration cookbook

The mechanical translation:

# Before — sync
def listener(mesh):
    rpc = MeshRpc(mesh)
    reply = rpc.call(target, "svc", b"...")
    return reply

# After — async
async def listener(mesh):
    rpc = AsyncMeshRpc(mesh)
    reply = await rpc.call(target, "svc", b"...")
    return reply

For loops over streams:

# Before — sync
for chunk in rpc.call_streaming(...):
    consume(chunk)

# After — async
async for chunk in arpc.call_streaming(...):
    await consume(chunk)

For server-pushed watches (watch_tasks, watch_memories, tail, subscribe_logs, etc.):

# Before — sync
it = adapter.watch_memories(source="bot")
for batch in it:
    render(batch)

# After — async
async_adapter = AsyncMemoriesAdapter(adapter)
it = await async_adapter.watch_memories(source="bot")
async for batch in it:
    await render(batch)

The sync Adapter.watch_memories(...) returns an iterator directly; the async equivalent is an awaitable yielding an async iterator (one extra await), because the underlying stream construction itself may run on the bridge runtime.

Net Encrypted UDP Transport

Net provides encrypted point-to-point UDP transport for high-performance scenarios:

from net import Net, generate_net_keypair
import os

# Generate keypair for responder
keypair = generate_net_keypair()
psk = os.urandom(32).hex()

# Responder side
responder = Net(
    num_shards=2,
    net_bind_addr='127.0.0.1:9001',
    net_peer_addr='127.0.0.1:9000',
    net_psk=psk,
    net_role='responder',
    net_secret_key=keypair.secret_key,
    net_public_key=keypair.public_key,
    net_reliability='light',  # 'none', 'light', or 'full'
)

# Initiator side (knows responder's public key)
initiator = Net(
    num_shards=2,
    net_bind_addr='127.0.0.1:9000',
    net_peer_addr='127.0.0.1:9001',
    net_psk=psk,
    net_role='initiator',
    net_peer_public_key=keypair.public_key,
)

# Use as normal
initiator.ingest_raw('{"event": "data"}')

Backpressure Modes

  • "drop_newest" - Reject new events when buffer is full
  • "drop_oldest" - Evict oldest events to make room
  • "fail_producer" - Raise an error

NAT traversal (optimization, not correctness)

Two NATed peers already reach each other through the mesh's routed-handshake path. NAT traversal opens a shorter direct path when the NAT shape allows it; it's never required for connectivity. Every method below is safe to call regardless of NAT type — a failed punch or a traversal: * RuntimeError is not a connectivity failure, traffic keeps riding the relay. The whole surface is a no-op when the native module was built without --features nat-traversal: every call raises RuntimeError("traversal: unsupported").

from net import NetMesh

mesh = NetMesh(bind_addr="0.0.0.0:9000", psk="00" * 32)

mesh.reclassify_nat()

klass  = mesh.nat_type()            # "open" | "cone" | "symmetric" | "unknown"
reflex = mesh.reflex_addr()         # "203.0.113.5:9001" or None

observed = mesh.probe_reflex(peer_node_id)   # "ip:port"

# Attempt a direct connection via the pair-type matrix.
# `coordinator` mediates the punch when the matrix picks one.
# Always returns — inspect stats to learn which path won.
mesh.connect_direct(peer_node_id, peer_pubkey_hex, coordinator_node_id)

# Cumulative counters — all int, monotonic.
s = mesh.traversal_stats()
s.punches_attempted   # coordinator mediated a PunchRequest + Introduce
s.punches_succeeded   # ack arrived AND direct handshake landed
s.relay_fallbacks     # landed on the routed path after skip/fail

Operators with a known-public address skip the classifier sweep entirely. The override pins "open" + the supplied address on every capability announcement; call announce_capabilities() after to propagate (the setter resets the rate-limit floor so the next announce is guaranteed to broadcast).

mesh.set_reflex_override('203.0.113.5:9001')
mesh.announce_capabilities(caps)
# later:
mesh.clear_reflex_override()
mesh.announce_capabilities(caps)

Traversal failures surface as RuntimeError with a stable traversal: <kind>[: <detail>] message prefix. The <kind> discriminator is one of reflex-timeout | peer-not-reachable | transport | rendezvous-no-relay | rendezvous-rejected | punch-failed | port-map-unavailable | unsupported. Match on the prefix for machine-readable branching:

try:
    mesh.connect_direct(peer_node_id, peer_pubkey_hex, coord_id)
except RuntimeError as e:
    msg = str(e)
    if msg.startswith("traversal: unsupported"):
        ...   # native module built without --features nat-traversal
    elif msg.startswith("traversal: peer-not-reachable"):
        ...

"unsupported" is the signal that the bindings are linked unconditionally and the native module doesn't have the feature — callers can branch cleanly without probing for symbol presence.

Channels (distributed pub/sub)

Named pub/sub over the encrypted mesh. Publishers register channels with access policy; subscribers ask to join via a membership subprotocol; publish fans payloads out to every current subscriber.

from net import NetMesh, ChannelAuthError, ChannelError

pub = NetMesh('127.0.0.1:9001', '42' * 32)
try:
    pub.register_channel(
        'sensors/temp',
        visibility='global',      # or 'subnet-local' | 'parent-visible' | 'exported'
        reliable=True,
        priority=2,
        max_rate_pps=1000,
    )

    # Subscriber side (after handshake with pub):
    # sub.subscribe_channel(pub.node_id, 'sensors/temp')

    # Fan a payload out to all subscribers.
    report = pub.publish(
        'sensors/temp',
        b'{"celsius": 22.5}',
        reliability='reliable',
        on_failure='best_effort',
        max_inflight=32,
    )
    print(f"{report['delivered']}/{report['attempted']} subscribers received")
finally:
    pub.shutdown()

# Typed errors for ACL outcomes:
# try: sub.subscribe_channel(peer_id, 'restricted')
# except ChannelAuthError: ...   # publisher denied
# except ChannelError: ...       # unknown channel / other rejection

Channel names always cross the binding as strings (not the u16 hash) to avoid ACL bypass via collision. The Python binding does not yet expose a dedicated per-channel receive API; that is a follow-up.

CortEX & NetDb (event-sourced state)

Typed, event-sourced state on top of RedEX — tasks and memories with filterable queries and sync watch iterators. Includes the snapshot_and_watch primitive whose race fix landed on v2, so you can safely "paint what's there now, then react to changes" without losing updates that race during construction.

from net import NetDb, CortexError

db = NetDb.open(origin_hash=0xABCDEF01, with_tasks=True, with_memories=True)
tasks = db.tasks

try:
    seq = tasks.create(1, 'write docs', 100)
    tasks.wait_for_seq(seq)   # block until the fold has applied
except CortexError as e:
    # adapter-level failure (RedEX I/O, fold halted, etc.)
    ...

# Snapshot + watch, one atomic call — no race.
snap, it = tasks.snapshot_and_watch_tasks(status='pending')
print('initial:', len(snap), 'pending tasks')
for batch in it:
    print('update:', len(batch), 'pending tasks')
    if len(batch) == 0:
        it.close()    # idempotent; ends the iterator
        break

db.close()

Standalone adapters

If you only need one model, skip the NetDb facade:

from net import Redex, TasksAdapter

redex = Redex(persistent_dir='/var/lib/net/redex')
tasks = TasksAdapter.open(redex, origin_hash=0xABCDEF01, persistent=True)

MemoriesAdapter exposes the same shape with store / retag / pin / unpin / delete / list_memories / watch_memories / snapshot_and_watch_memories.

Raw RedEX file (no CortEX fold)

For domain-agnostic persistent logs — your own event schema, no fold, no typed adapter — open a RedexFile directly from a Redex. The tail is a sync Python iterator; call close() or let StopIteration fire when the file closes.

from net import Redex, RedexError

redex = Redex(persistent_dir='/var/lib/net/events')
file = redex.open_file(
    'analytics/clicks',
    persistent=True,
    fsync_interval_ms=100,           # or fsync_every_n=1000
    retention_max_events=1_000_000,
)

# Append (or batch-append).
seq = file.append(b'{"url": "/home"}')
# `append_batch` returns the first-seq int of the batch, or `None`
# for an empty input. The `None` return is the explicit "I
# appended nothing" signal — pre-`bugfixes-8` it returned `0`,
# which collided with the legitimate "first event of a non-empty
# batch landed at seq 0" return.
first = file.append_batch([b'{"a": 1}', b'{"a": 2}'])

# Tail — backfills the retained range, then streams live appends.
try:
    for event in file.tail(from_seq=0):
        print(event.seq, bytes(event.payload))
        if should_stop:
            break           # idempotent; ends the iterator via close()
except RedexError as e:
    ...

file.close()

Errors from the RedEX surface raise RedexError (invalid channel name, bad config, append / tail / sync / close failures).

Cross-node RedEX replication

RedEX channels can replicate across the mesh. Opt in per channel by passing replication=True plus tunables on the open_file call. The default (no replication kwarg) keeps the channel single-node and adds zero wire traffic. Replicated channels carry N copies of the log; the leader is the single writer, replicas catch up via pull-based sync. Failover uses a deterministic nearest-RTT election with NodeId tie-break.

from net import NetMesh, Redex

mesh = NetMesh(bind_addr='127.0.0.1:0', psk='...')
redex = Redex(persistent_dir='/var/lib/net/events')

# Install the per-Redex replication router on the mesh.
# Idempotent — safe to call from multiple paths.
redex.enable_replication(mesh)

file = redex.open_file(
    'orders/audit',
    persistent=True,
    replication=True,
    replication_factor=3,                 # 1..16; default 3
    replication_heartbeat_ms=500,         # min 100; default 500
    replication_placement='standard',     # 'standard' | 'pinned' | 'colocation_strict'
    # replication_pinned_nodes=[node_a, node_b, node_c],   # required when placement='pinned'
    # replication_leader_pinned=some_node_id,
    replication_on_under_capacity='withdraw',   # 'withdraw' (default) | 'evict_oldest'
    replication_budget_fraction=0.5,
)
file.append(b'event payload')

The leader handles every append locally; replicas observe the leader's heartbeat tail_seq, issue SYNC_REQUEST on lag, apply chunks via SYNC_RESPONSE. When the leader closes (or the replica's believed leader goes silent past 3 × replication_heartbeat_ms), the surviving replicas run the deterministic election and one becomes the new leader within microseconds.

Operator surface on Redex:

  • enable_replication(mesh) — install replication wiring. Required before open_file(..., replication=True).
  • replication_runtime_count() -> int — registered per-channel runtimes.
  • replication_prometheus_text() -> str — Prometheus-text render of the seven per-channel metric shapes (*_lag_seconds, *_sync_bytes_total, *_leader_changes_total, *_under_capacity_total, *_skip_ahead_total, *_election_thrash_total, *_witness_withdrawals_total). Returns the empty string when replication isn't enabled; pipe straight into an HTTP scrape body without branching.
# HTTP scrape handler (Flask example)
@app.route('/metrics')
def metrics():
    return redex.replication_prometheus_text(), 200, {'Content-Type': 'text/plain'}

Disk-pressure handling: when a replica's local file rejects an append (heap-segment cap or disk write-fail), the configured replication_on_under_capacity policy fires — 'withdraw' drops the replica role (capability tag withdrawn; peers re-route to a healthy holder); 'evict_oldest' runs retention sweep + retries (requires retention_max_* kwargs to be set on the same call).

Validation surfaces RedexError before the call reaches the underlying coordinator, so misconfigured replication_factor (out of [1, 16]), replication_placement='pinned' without replication_pinned_nodes, or unknown enum strings fail fast.

Why snapshot_and_watch_*?

Calling list_tasks() then watch_tasks() takes two independent state reads. A mutation landing between them would be silently lost under the old skip(1) implementation. The atomic primitive returns the snapshot and an iterator seeded so that any divergent initial emission is forwarded through instead of dropped — see docs/STORAGE_AND_CORTEX.md.

Dataforts (greedy cache, gravity, blob refs, read-your-writes)

Dataforts is the compositional data plane on top of RedEX / CortEX / capability-index / proximity-graph. The PyO3 module surfaces it as Redex methods, top-level blob helpers, and a WriteToken + wait_for_token pair on Tasks / Memories. Built behind the dataforts Cargo feature; pre-built wheels on PyPI ship with the feature enabled. A build without it raises RedexError from enable_greedy_dataforts(...) with "requires the 'dataforts' feature; rebuild with --features dataforts" rather than failing silently.

Four phases:

  • Phase 1 — Greedy-LRU caching. Five-axis admission (scope + proximity + capability-preference + colocation + storage-cap)
    • bandwidth budget gate per-channel cache files. Cold channels evict under cluster-cap pressure and withdraw their causal:<hex> advertisement.
  • Phase 3 — BlobRef + blob adapters. A 4-byte-magic + version + 32-byte BLAKE3 + size + URI reference whose bytes live in the caller's storage (S3 / Ceph / IPFS / local FS). Adapters implement fetch / store (sync or async def); the filesystem adapter ships in-tree via register_filesystem_blob_adapter. Custom adapters register through register_blob_adapter(adapter_id, instance). Async adapters run on a binding-owned event loop on a dedicated thread — adapter coroutines never share the calling thread's event loop, so an aiobotocore / httpx.AsyncClient / SQLAlchemy async engine inside the adapter is safe.
  • Phase 4 — Data gravity. Per-chain read-rate counters with exponential decay. Threshold-crossing emissions stamp heat:<hex>=<rate> onto the chain's capability announcement; greedy weights cache pulls by heat × scope-match × proximity.
  • Phase 5 — Read-your-writes. Every tasks.create, memories.insert, etc. returns a WriteToken { origin_hash, seq } (frozen, hashable; constructor is #[pyo3(doc_hidden)] — tokens are unforgeable only against the issuing adapter via origin binding). tasks.wait_for_token(token, deadline_ms=…) blocks until the local fold has applied that seq; tracks both applied_through_seq and folded_through_seq so a stalled fold raises CortexError. deadline_ms=0 is a non-blocking poll (no scheduling).
from net import (
    NetMesh, Redex, Tasks,
    register_filesystem_blob_adapter, blob_publish, blob_resolve,
)

mesh = NetMesh(bind_addr='0.0.0.0:7000', psk='...')
redex = Redex(persistent_dir='/var/lib/net/redex')

# Phase 1 — wire greedy into the mesh inbound dispatch.
redex.enable_greedy_dataforts(
    mesh,
    scopes=['region:us'],
    total_cap_bytes=1 << 30,         # 1 GiB cluster-cap
    per_channel_cap_bytes=64 << 20,
)

# Phase 4 — layer gravity on top.
redex.enable_gravity_for_greedy(
    mesh,
    emit_threshold_ratio=1.5,
    decay_half_life_secs=300,
)

# Phase 3 — filesystem adapter (ships in-tree).
register_filesystem_blob_adapter('local', '/var/blobs')
ref = blob_publish('local', 'local://obj/payload', some_bytes)
back = blob_resolve(ref)

# Phase 5 — read-your-writes.
tasks = Tasks.open(redex, origin_hash=mesh.origin_hash)
result = tasks.create(1, 'first', 100)
tasks.wait_for_token(result.token, deadline_ms=250)
# tasks.wait_for_token(token, deadline_ms=0) → non-blocking poll

# Diagnostics.
print(redex.greedy_cached_channel_count())
print(redex.greedy_prometheus_text())

The canonical channel hash is 64-bit (channel_hash(name) returns int in the u64 range). The per-packet wire NetHeader channel_hash stays u16 — fast-path filter hint, may bucket-collide at scale; ACL / config / cache / RYW decisions key on the canonical 64-bit hash via registry disambiguation. The PermissionToken wire form is 169 bytes (PermissionToken::WIRE_SIZE in the Rust core).

Redis Streams consumer-side dedup helper

The Net Redis adapter writes a stable dedup_id field on every XADD entry: {producer_nonce:hex}:{shard_id}:{sequence_start}:{i}. Combined with the bus's persistent producer-nonce path (producer_nonce_path on EventBusConfig), the id is stable across both within-process retries AND cross-process restart — the MULTI/EXEC-timeout race becomes filterable at consume time.

RedisStreamDedup is the consumer-side helper, exposed on the net PyO3 module:

from net import RedisStreamDedup
import redis

# ~10k events/sec * 1 min dedup window → ~600,000.
dedup = RedisStreamDedup(capacity=600_000)

r = redis.Redis(host="localhost", port=6379)
cursor = "0"
while True:
    # XRANGE bounds are INCLUSIVE on both ends. After the first
    # page we must use the exclusive form `(<id>` so we don't
    # re-read the entry the cursor points at — a vanilla
    # `min=cursor` loop spins forever once the cursor reaches the
    # tail and the same entry is returned every iteration.
    start = cursor if cursor == "0" else f"({cursor}"
    entries = r.xrange("net:shard:0", min=start, max="+", count=100)
    for entry_id, fields in entries:
        dedup_id = fields.get(b"dedup_id", b"").decode()
        if not dedup_id:
            # No dedup_id → older entry or non-Net producer; skip
            # dedup and process as-is.
            process(entry_id, fields)
            continue
        if not dedup.is_duplicate(dedup_id):
            process(entry_id, fields)
        cursor = entry_id.decode()
    if not entries:
        break

Surface:

dedup = RedisStreamDedup()                # default capacity 4096
dedup = RedisStreamDedup(capacity=N)      # explicit; 0 → 1
dedup.is_duplicate(id: str) -> bool       # test-and-insert
dedup.len                                 # property — tracked-id count
dedup.capacity                            # property — configured cap
dedup.is_empty                            # property
dedup.clear()                             # reset (e.g. on consumer-group rebalance)

The helper is transport-agnostic — bring your own redis-py / aioredis / equivalent client; it just answers the dedup question against an in-memory LRU. Concurrency: each handle wraps a Rust Mutex<RedisStreamDedup>, so concurrent calls from multiple Python threads are safe but serialize. Production-shape is one helper per consumer thread.

Security Surface (Stage A–E)

The mesh layer surfaces the same identity / capabilities / subnets / channel-auth story that the Rust SDK and the TypeScript / Node SDKs ship. Full staging and rationale: docs/SDK_SECURITY_SURFACE_PLAN.md. Python-binding parity details: docs/SDK_PYTHON_PARITY_PLAN.md.

Identity + permission tokens

Every node has an ed25519 identity; permission tokens are ed25519- signed delegations that authorize a subject to publish / subscribe / delegate / admin on a channel, optionally with further delegation depth.

from net import Identity, parse_token, verify_token, delegate_token

alice = Identity.generate()
bob = Identity.generate()

# Alice issues Bob a subscribe+delegate token good for 5 min, with
# one re-delegation hop remaining. `ttl_seconds=0` raises
# `TokenError` — a zero TTL would mint a born-expired token that
# every receiver would reject as `Expired`, leaving the issuer to
# diagnose the misuse from receiver-side log lines.
token = alice.issue_token(
    subject=bob.entity_id,
    scope=["subscribe", "delegate"],
    channel="sensors/temp",
    ttl_seconds=300,
    delegation_depth=1,
)
assert verify_token(token) is True

# Bob re-delegates to Carol; depth drops to 0 (leaf).
carol = Identity.generate()
child = delegate_token(bob, token, carol.entity_id, ["subscribe"])
assert parse_token(child)["delegation_depth"] == 0

Capability announcements + peer discovery

Announce hardware / software / model / tool / tag fingerprints, then query the local capability index with a filter.

mesh.announce_capabilities({
    "hardware": {
        "cpu_cores": 16,
        "memory_gb": 64,
        "gpu": {"vendor": "nvidia", "model": "h100", "vram_gb": 80},
    },
    "models": [{"model_id": "llama-3.1-70b", "family": "llama",
                "context_length": 128_000}],
    "tags": ["gpu", "prod"],
})

gpu_peers = mesh.find_nodes({
    "require_gpu": True,
    "gpu_vendor": "nvidia",
    "min_vram_gb": 40,
})

Scoped discovery (reserved scope:* tags)

A provider can narrow who its query result reaches by tagging its CapabilitySet with reserved scope:* tags. Queries call mesh.find_nodes_scoped(filter, scope) to filter candidates. The wire format and forwarders are untouched — enforcement is purely query-side.

# GPU pool advertised to one tenant only.
mesh.announce_capabilities({
    "tags": ["model:llama3-70b", "scope:tenant:oem-123"],
})

# Tenant-scoped query — returns this node + any Global (untagged) peers.
oem_nodes = mesh.find_nodes_scoped(
    {"require_tags": ["model:llama3-70b"]},
    {"kind": "tenant", "tenant": "oem-123"},
)

Accepted scope dict shapes: {"kind": "any"} (default), {"kind": "global_only"}, {"kind": "same_subnet"}, {"kind": "tenant", "tenant": "<id>"}, {"kind": "tenants", "tenants": [...]}, {"kind": "region", "region": "<name>"}, {"kind": "regions", "regions": [...]}. Reserved announcement tags: scope:subnet-local (visible only under same_subnet), scope:tenant:<id>, scope:region:<name> — strictest scope wins. Untagged peers resolve to Global and stay visible under permissive queries. Full design: docs/SCOPED_CAPABILITIES_PLAN.md.

Capability propagation is multi-hop, bounded by MAX_CAPABILITY_HOPS = 16 with (origin, version) dedup on every forwarder. capability_gc_interval_ms controls both the index TTL sweep and the dedup cache eviction. See docs/MULTIHOP_CAPABILITY_PLAN.md.

Subnets

Nodes can bind to a hierarchical SubnetId (1–4 levels, each 0–255) directly, or derive one from announced tags via a SubnetPolicy.

# Explicit subnet.
mesh = NetMesh("127.0.0.1:9000", PSK, subnet=[3, 7, 2])

# Or derive from tags.
mesh = NetMesh(
    "127.0.0.1:9001", PSK,
    subnet_policy={
        "rules": [
            {"tag_prefix": "region:", "level": 0,
             "values": {"eu": 1, "us": 2, "apac": 3}},
            {"tag_prefix": "zone:", "level": 1,
             "values": {"a": 1, "b": 2, "c": 3}},
        ]
    },
)

Channel authentication

Publishers set publish_caps / subscribe_caps / require_token on register_channel. Subscribers present a PermissionToken via the optional token=bytes kwarg on subscribe_channel.

mesh.register_channel(
    "gpu/jobs",
    subscribe_caps={"require_gpu": True, "min_vram_gb": 16},
    require_token=True,
)

# Subscriber side, with a token issued by the publisher:
mesh.subscribe_channel(publisher_node_id, "gpu/jobs", token=token_bytes)

Denied subscribes raise ChannelAuthError (a subclass of ChannelError); malformed tokens raise TokenError whose message has the form "token: <kind>" (invalid_signature, expired, delegation_exhausted, …). Successful subscribes populate an AuthGuard bloom filter on the publisher so every subsequent publish admits the subscriber in constant time. An expiry sweep (default 30 s) evicts subscribers whose tokens age out; a per- peer auth-failure rate limiter throttles bad-token storms. Cross- SDK behaviour is fixed by the Rust integration suite; see tests/channel_auth.rs and tests/channel_auth_hardening.rs.

nRPC (request / response over the mesh)

nRPC is the request/response convention layer riding on top of the pub/sub mesh + CortEX folds. Built with the cortex feature (maturin's default picks it up). The native module exposes two layers:

  • Raw bytesnet.MeshRpc (pyclass): serve(service, fn) -> ServeHandle, call(target, service, bytes) -> bytes, call_service(service, bytes) -> bytes, call_streaming(target, service, bytes) -> RpcStream, find_service_nodes(service) -> list[int]. Synchronous calls release the GIL across runtime.block_on(...) so other Python threads run; handler callbacks dispatch under tokio::task::spawn_blocking so GIL acquisition doesn't starve the runtime.
  • Typed wrappernet.mesh_rpc.TypedMeshRpc: JSON encode/decode at the binding boundary so user code works with plain Python objects (dicts, lists, strings, numbers, bools, None). The default codec is json.dumps / json.loads — to send dataclasses, Pydantic models, or numpy arrays, convert to a JSON-serializable shape before calling (e.g. dataclasses.asdict(x) or model.model_dump()); a non- serializable input raises RpcCodecError before the call hits the wire so the diagnostic points at the call site rather than the server. Resilience helpers (RetryPolicy, HedgePolicy, CircuitBreaker) plus the typed exception classes (RpcNoRouteError, RpcTimeoutError, RpcServerError, RpcTransportError, RpcCodecError, RpcAppError, BreakerOpenError) live here too.
from net import NetMesh
from net.mesh_rpc import (
    NRPC_TYPED_BAD_REQUEST,
    RetryPolicy,
    RpcServerError,
    TypedMeshRpc,
)

server = NetMesh("127.0.0.1:9001", "42" * 32)
client = NetMesh("127.0.0.1:9000", "42" * 32)
# (handshake omitted — see "Net Encrypted UDP Transport")

# Server side: register a typed handler. ServeHandle is a context
# manager — `with` ensures unregister on exit.
server_rpc = TypedMeshRpc.from_mesh(server)
def echo_sum(req: dict) -> dict:
    return {"echo": req["text"], "sum": sum(req["numbers"])}

with server_rpc.serve("echo_sum", echo_sum):
    client_rpc = TypedMeshRpc.from_mesh(client)
    reply = client_rpc.call(
        server.node_id(), "echo_sum",
        {"text": "hi", "numbers": [1, 2, 3]},
        opts={"deadline_ms": 200},
    )
    # reply == {"echo": "hi", "sum": 6}

# Streaming responses iterate decoded chunks until EOF or terminal error.
# `stream_window_initial` is the canonical key; `stream_window` is an
# alias accepted for ergonomic parity.
stream = client_rpc.call_streaming(
    target_node_id, "tail", {"tail": "events"},
    opts={"deadline_ms": 5000, "stream_window_initial": 8},
)
for chunk in stream:
    process(chunk)
# stream.close() emits CANCEL to the server (best-effort).
# stream.grant(n) issues an explicit credit publish for batched cadence.

# Resilience helpers: retry / hedge / circuit breaker.
policy = RetryPolicy(max_attempts=4, initial_backoff_ms=50, max_backoff_ms=1000)
client_rpc.call_with_retry(target_node_id, "echo", {"hi": 1}, policy)

Status codes + error model

Every caller-side failure is a typed exception whose message carries the canonical nrpc:<kind>: <detail> prefix. The set of kinds is fixed by the cross-binding contract: no_route, timeout, server_error, transport, codec_encode, codec_decode, breaker_open. The classify_error(e) helper in net.mesh_rpc extracts the kind string from a caught exception's message — useful for fallback paths where discriminating without isinstance is awkward (e.g. when the native module wasn't built and every typed alias collapses to plain Exception):

from net.mesh_rpc import classify_error

try:
    rpc.call(target, "echo", body, opts={"deadline_ms": 200})
except Exception as e:
    kind = classify_error(e)
    if kind == "no_route":
        ...      # capability index didn't surface a target
    elif kind == "timeout":
        ...      # caller-side deadline elapsed
    elif kind == "server_error":
        ...      # str(e) carries `status=0xNNNN message=...`
Constant Hex Meaning
NRPC_TYPED_BAD_REQUEST 0x8000 Typed handler couldn't decode the request body.
NRPC_TYPED_HANDLER_ERROR 0x8001 Typed handler ran but returned an exception.

Cross-binding contract spec — including the canonical cross_lang_echo_sum service used by every binding's wire-format compat test — lives in ../../README.md#nrpc. The Python binding's own compat suite is at tests/test_cross_lang_compat.py.

Compute (daemons + migration)

Run MeshDaemons directly from Python. DaemonRuntime owns the factory table, the per-daemon hosts, and the Registering → Ready → ShuttingDown lifecycle gate that decides when inbound migrations may land. Daemons are any Python object whose process(event) returns a list of bytes/bytearray payloads — the runtime wraps each output in a causal link and forwards it.

Build the native module with the compute feature (maturin picks it up on the default build) and import from net. Full design notes: docs/SDK_COMPUTE_SURFACE_PLAN.md.

from net import DaemonRuntime, NetMesh, Identity, CausalEvent


class EchoDaemon:
    """Stateless echo — ships every event's payload straight back."""

    name = "echo"

    def process(self, event: CausalEvent) -> list[bytes]:
        return [bytes(event.payload)]

    # Optional: snapshot() / restore(state) for migration-capable daemons.


mesh = NetMesh("127.0.0.1:9000", "42" * 32)
rt = DaemonRuntime(mesh)

# 1. Register factories BEFORE flipping the runtime to Ready.
rt.register_factory("echo", lambda: EchoDaemon())

# 2. Ready the runtime — after this point spawn / migration accept.
rt.start()

# 3. Spawn a daemon; Identity pins the ed25519 keypair so
#    origin_hash / entity_id stay stable across migrations.
identity = Identity.generate()
handle = rt.spawn("echo", identity)
print(f"origin = 0x{handle.origin_hash:08x}")

# 4. Manually feed an event for testing; real delivery happens
#    via the mesh's causal chain.
event = CausalEvent(handle.origin_hash, sequence=1, payload=b"hello")
outputs = rt.deliver(handle.origin_hash, event)

# 5. Clean shutdown.
rt.stop(handle.origin_hash)
rt.shutdown()

process must be synchronous — the core's contract is sync, and the PyO3 bridge re-attaches the GIL for the duration of each call. Raising inside process surfaces as DaemonError on the caller.

Migration

start_migration(origin_hash, source_node, target_node) orchestrates the six-phase cutover (Snapshot → Transfer → Restore → Replay → Cutover → Complete). The source seals the daemon's ed25519 seed into the outbound snapshot using the target's X25519 static pubkey; the target rebuilds the daemon via the factory registered under the same kind, replays any events that arrived during transfer, then activates.

from net import MigrationError, migration_error_kind

try:
    mig = rt.start_migration(handle.origin_hash, src_node_id, dst_node_id)
    # mig.phase  — "snapshot" | "transfer" | "restore" | ...
    # mig.source_node / mig.target_node
    mig.wait()                      # blocks to completion
except MigrationError as e:
    kind = migration_error_kind(e)
    if kind == "not-ready":               ...  # target start() didn't run
    elif kind == "factory-not-found":     ...  # target missing this kind
    elif kind == "compute-not-supported": ...  # target has no DaemonRuntime
    elif kind == "state-failed":          ...  # snapshot / restore threw
    elif kind == "identity-transport-failed": ...  # seal / unseal failed
    # ...see SDK_COMPUTE_SURFACE_PLAN.md for the full enum

start_migration_with(origin, src, dst, opts) exposes options such as seal_seed=False for test scenarios. On the target node, call rt.register_migration_target_identity(kind, identity) before any migration of that kind lands; without it the runtime rejects sealed-seed envelopes with migration_error_kind == "identity-transport-failed".

Surface at a glance

Method Description
DaemonRuntime(mesh) Construct against an existing NetMesh
rt.register_factory(kind, fn) Install a factory (before start())
rt.start() / rt.shutdown() Flip the lifecycle gate
rt.spawn(kind, identity, config=None) Spawn a local daemon
rt.spawn_from_snapshot(kind, identity, bytes, config=None) Rehydrate
rt.stop(origin) Stop a local daemon
rt.snapshot(origin) Capture bytes for persistence / migration
rt.deliver(origin, event) Feed an event (returns list[bytes])
rt.start_migration(origin, src, dst) Orchestrate a live migration
rt.register_migration_target_identity(kind, id) Pin unseal keypair on target for kind
handle.origin_hash / entity_id / stats() Per-daemon identity + stats
DaemonError / MigrationError Typed exceptions; migration_error_kind(e) parses e.kind

Compute Groups (Replica / Fork / Standby)

HA / scaling overlays on top of DaemonRuntime. Build the native module with the groups feature (implies compute) to expose ReplicaGroup, ForkGroup, StandbyGroup, and the GroupError exception class.

  • ReplicaGroup — N interchangeable copies of a daemon. Deterministic identity from group_seed + index, so a replacement respawned on another node has a stable origin_hash. Load-balances inbound events across healthy members; auto-replaces on node failure.
  • ForkGroup — N independent daemons forked from a common parent at fork_seq. Unique keypairs, shared ancestry via a verifiable ForkRecord.
  • StandbyGroup — active-passive replication. One member processes events; standbys hold snapshots and catch up via sync_standbys(). On active failure the most-synced standby promotes and replays the events buffered since the last sync.
from net import (
    DaemonRuntime, ForkGroup, GroupError, ReplicaGroup, StandbyGroup,
    group_error_kind,
)

rt = DaemonRuntime(mesh)
rt.register_factory("counter", lambda: CounterDaemon())

# --- ReplicaGroup ----------------------------------------------------
replicas = ReplicaGroup.spawn(
    rt, "counter",
    replica_count=3,
    group_seed=bytes([0x11] * 32),
    lb_strategy="consistent-hash",   # or "round-robin" / "least-load"
                                     #    / "least-connections" / "random"
)
origin = replicas.route_event({"routing_key": "user:42"})
rt.deliver(origin, event)
replicas.scale_to(5)                 # grow
replicas.on_node_failure(failed_node_id)   # respawn elsewhere

# --- ForkGroup -------------------------------------------------------
forks = ForkGroup.fork(
    rt, "counter",
    parent_origin=0xABCDEF01,
    fork_seq=42,
    fork_count=3,
    lb_strategy="round-robin",
)
assert forks.verify_lineage()
for record in forks.fork_records():
    print(record["forked_origin"], record["fork_seq"])

# --- StandbyGroup ----------------------------------------------------
hot = StandbyGroup.spawn(
    rt, "counter",
    member_count=3,                  # 1 active + 2 standbys
    group_seed=bytes([0x77] * 32),
)
rt.deliver(hot.active_origin, event)
hot.sync_standbys()                  # periodic catchup
# On active-node failure:
# new_origin = hot.on_node_failure(failed_node_id)  # auto-promotes

Typed errors

Failures raise GroupError (a subclass of DaemonError). Use group_error_kind(e) to parse the discriminator from the Rust side's daemon: group: <kind>[: detail] message prefix:

from net import GroupError, group_error_kind

try:
    ReplicaGroup.spawn(rt, "never-registered",
                       replica_count=2, group_seed=bytes(32))
except GroupError as e:
    kind = group_error_kind(e)
    if kind == "not-ready":               ...  # runtime.start() didn't run
    elif kind == "factory-not-found":     ...  # kind wasn't registered
    elif kind == "no-healthy-member":     ...  # routed on an all-down group
    elif kind == "invalid-config":        ...  # e.g. replica_count == 0
    elif kind in ("placement-failed",
                  "registry-failed",
                  "daemon"):              ...  # core failure — read e args

Full staging, wire formats, and rationale: docs/SDK_GROUPS_SURFACE_PLAN.md. Core semantics live in the main README.md#daemons.

Performance Tips

  1. Use ingest_raw() for maximum throughput - Pass pre-serialized JSON strings
  2. Use ingest_raw_batch() for bulk operations - Reduces per-call overhead
  3. Increase ring_buffer_capacity - Larger buffers handle bursts better
  4. Match num_shards to CPU cores - Default is optimal for most cases

Cargo features

The five feature flags that gate the storage / query / OS surfaces on this binding. Wheels built without a feature silently omit its symbols — the build never warns. bindings/python/python/net/__init__.py try-imports each block individually, so from net import Redex raises ImportError (not AttributeError) when the underlying _net extension was compiled without cortex.

Feature Surface enabled in the PyO3 module
cortex Redex, RedexFile, TasksAdapter, MemoriesAdapter, NetDb, Task, Memory, watch iterators, RedexError, CortexError, NetDbError
redex-disk Disk-backed persistence for RedEX — the persistent_dir ctor arg and Persistent: true on open_file. Without it the persistent path returns RedexError.
netdb NetDb composition over Tasks + Memories (requires cortex). The net_netdb_* FFI entry points ship with this feature.
meshdb MeshQuery, MeshQueryRunner, QueryBuilder, Predicate, InMemoryChainReader, the rest of the query layer, plus the libnet_meshdb cdylib.
meshos MeshOsDaemonSdk, MeshOsDaemonHandle, plus the libnet_meshos cdylib.

Enable at build time:

# Dev install into the active venv:
maturin develop --features "cortex netdb redex-disk meshdb meshos"

# Or produce a wheel:
maturin build --release --features "cortex netdb redex-disk meshdb meshos"

PyPI wheels published as net-mesh ship with every feature enabled; the flags above only matter for source builds.

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

net_mesh-0.27.7.tar.gz (6.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

net_mesh-0.27.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded PyPymanylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded PyPymanylinux: glibc 2.28+ ARM64

net_mesh-0.27.7-cp315-cp315t-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.15tmanylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp315-cp315-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.15manylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp314-cp314t-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp314-cp314t-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.28+ ARM64

net_mesh-0.27.7-cp314-cp314-win_arm64.whl (4.1 MB view details)

Uploaded CPython 3.14Windows ARM64

net_mesh-0.27.7-cp314-cp314-win_amd64.whl (4.4 MB view details)

Uploaded CPython 3.14Windows x86-64

net_mesh-0.27.7-cp314-cp314-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp314-cp314-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ ARM64

net_mesh-0.27.7-cp314-cp314-macosx_11_0_arm64.whl (4.4 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

net_mesh-0.27.7-cp314-cp314-macosx_10_12_x86_64.whl (4.9 MB view details)

Uploaded CPython 3.14macOS 10.12+ x86-64

net_mesh-0.27.7-cp313-cp313-win_arm64.whl (4.2 MB view details)

Uploaded CPython 3.13Windows ARM64

net_mesh-0.27.7-cp313-cp313-win_amd64.whl (4.5 MB view details)

Uploaded CPython 3.13Windows x86-64

net_mesh-0.27.7-cp313-cp313-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp313-cp313-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ ARM64

net_mesh-0.27.7-cp313-cp313-macosx_11_0_arm64.whl (4.5 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

net_mesh-0.27.7-cp313-cp313-macosx_10_12_x86_64.whl (4.9 MB view details)

Uploaded CPython 3.13macOS 10.12+ x86-64

net_mesh-0.27.7-cp312-cp312-win_arm64.whl (4.1 MB view details)

Uploaded CPython 3.12Windows ARM64

net_mesh-0.27.7-cp312-cp312-win_amd64.whl (4.5 MB view details)

Uploaded CPython 3.12Windows x86-64

net_mesh-0.27.7-cp312-cp312-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp312-cp312-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ ARM64

net_mesh-0.27.7-cp312-cp312-macosx_11_0_arm64.whl (4.4 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

net_mesh-0.27.7-cp312-cp312-macosx_10_12_x86_64.whl (4.9 MB view details)

Uploaded CPython 3.12macOS 10.12+ x86-64

net_mesh-0.27.7-cp311-cp311-win_arm64.whl (4.2 MB view details)

Uploaded CPython 3.11Windows ARM64

net_mesh-0.27.7-cp311-cp311-win_amd64.whl (4.5 MB view details)

Uploaded CPython 3.11Windows x86-64

net_mesh-0.27.7-cp311-cp311-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp311-cp311-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ ARM64

net_mesh-0.27.7-cp311-cp311-macosx_11_0_arm64.whl (4.5 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

net_mesh-0.27.7-cp311-cp311-macosx_10_12_x86_64.whl (4.9 MB view details)

Uploaded CPython 3.11macOS 10.12+ x86-64

net_mesh-0.27.7-cp310-cp310-win_amd64.whl (4.5 MB view details)

Uploaded CPython 3.10Windows x86-64

net_mesh-0.27.7-cp310-cp310-manylinux_2_28_x86_64.whl (5.1 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.28+ x86-64

net_mesh-0.27.7-cp310-cp310-manylinux_2_28_aarch64.whl (4.9 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.28+ ARM64

File details

Details for the file net_mesh-0.27.7.tar.gz.

File metadata

  • Download URL: net_mesh-0.27.7.tar.gz
  • Upload date:
  • Size: 6.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7.tar.gz
Algorithm Hash digest
SHA256 57e658e9d868467b726b25fae5ffafec0452ff99f86868a9760948182f65d28a
MD5 c9e48130969189d7578c53b09e92c15c
BLAKE2b-256 6cb48ea202fb0f9fc2a4f874bae04e5f2fb77b4017ffe0604d6bd8538b2caec5

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 024f43d9c6b1acddb4fb932ab163cf3d0e4cf36a64e57db53f9902d4637b4ac1
MD5 8d670d71813a8466849c75ca38270c1d
BLAKE2b-256 a6f48abd79aa4d85e2ebbb6475aeb77c7cb8a36fbcdd1ac3c32b3dab73f0d738

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 8d88d1ff3f5ee1e71339c8506b083415c0fc10b6e5e869f21838708507c19c91
MD5 d4e15957717c0ce97aa860ab41041168
BLAKE2b-256 7cc32757cba1d5cfd25581001b6c63072d80219b1381cf9348039c5dbf2a94c7

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp315-cp315t-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp315-cp315t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ff96dd7d80b51aac0074d5c054694cfe0996554de0dac2c2c77ae368fd6cbeeb
MD5 8509aa11538c07d95ab5bb4587c24e7e
BLAKE2b-256 9e48aecf25f3871bf734c816960096d8a5acc0728e8178a48c7840f1f4014b6a

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp315-cp315-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp315-cp315-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ce275656993ca5eac017638aff13b75a26c177a37a6da73b7adb1cfb9dd9f9a4
MD5 5fd6abad27952848c1fd3fee84e54b98
BLAKE2b-256 112d586c8fd128a75a63f1cd9eb6eeeb41b2eacbc25ca45874c77b4b2fb2e0d2

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314t-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 dbc691fa069fff5a29b19343cae834416c61e84e8fe9d50042bd463706c69b12
MD5 18caa4b0709c1eebf2e39c64679c2c0a
BLAKE2b-256 87d5a36d41f64141bdeba7724e2491879a9836e8cfd8b7535a6d92b92bba6e13

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314t-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314t-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 063e58c15dd247a6fb84599b83e0a8d3a2193d9f96771a86ca76e9d6f9a2d61c
MD5 04754b5e1308383708a84a4d4b3dc130
BLAKE2b-256 3dcf8223424f2f06a406b5930ba8f16a93fb323f60fb9fe743be07515a5b9c5e

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314-win_arm64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp314-cp314-win_arm64.whl
  • Upload date:
  • Size: 4.1 MB
  • Tags: CPython 3.14, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314-win_arm64.whl
Algorithm Hash digest
SHA256 776abefe25ec47ad92b49375c2ff48ee24926e4df518c4127918678a47e8a9d9
MD5 33ae18727080cea3ee5a6045587ae8b2
BLAKE2b-256 34c7a73049bfd1753fcb99dc3f7395088002e2cd2ad4af349942a5df806befb6

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 4.4 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 b0f351c2e3ed9807b43eace34cb3783ff54d3315552a71997cea2e49c08d7c69
MD5 5097ccf16a8eca2a0439c53b5bc2b4cd
BLAKE2b-256 1e2c0059e30a7863d1cb9b33fb4e799001fdd8066bc28c852a4e141745c85196

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 fc32ab2e2d70d6acc6e76a13e0607891abb0170bf2f51d9eceddbd6bb2c35375
MD5 dec4e1900d8080875492fab82c6f0e74
BLAKE2b-256 468c319eae07d4d2178141db817817f26748a09b2e1c9416ee2629f9101c0c5f

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 3f345a7bdafdbaac058511d5cf2fab9eb18377df71b535005150f22d022eaf9a
MD5 a2735c372553c7c6f7351b1110c52cae
BLAKE2b-256 cd506f3442cedb91974570dd35773b7a6033c30e0b208aa546d02df634f3eb1b

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 89668e3bf40541f9017112c0d15a24b033b4a5f31f324a69f67f5d8615c84fde
MD5 dea85fb206800ae4466f0ddd7630b26f
BLAKE2b-256 f1fd80aa50384ae797bf1648d15e4fbf442e463a5213427fd0ebcf179bc132cd

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp314-cp314-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp314-cp314-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 fe350210e0c00f4781bb1899c91c836d764e534a17fdeabcc0cde8ebf4c82bdc
MD5 b6a9441554a36a7f745a61b600732ea2
BLAKE2b-256 eed69dc07c4680ca2c66d869a6875436f3bc835848295dd3f3358a0658e0e699

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp313-cp313-win_arm64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp313-cp313-win_arm64.whl
  • Upload date:
  • Size: 4.2 MB
  • Tags: CPython 3.13, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp313-cp313-win_arm64.whl
Algorithm Hash digest
SHA256 b549409d1ba353ffa284816f7bc90eec30fd7a4e94eb8135e70a9639abaf489e
MD5 2a241d6fc72f2a2b1e1ed99001a5af9f
BLAKE2b-256 3aea45faaa768b8dcf78459719cf6fc67d48b03f1c00768cfa3a5d21e2d9473c

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 4.5 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 240354c5a15dadf9f836f99412d1d6345eba0f89f2304df0b76bab5cefb0f771
MD5 e2d6edc4335748e4c80bfbb1e74504a4
BLAKE2b-256 df70c2af06c4311cd09ffb15266c5af64891f291728c7bc3c49441bcbd7ba8eb

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e9b03ae568f9c18a9e54cc7522a21caae50075eab67be35336c523133e7b23d2
MD5 9be18041218de74ef39b03052b344c14
BLAKE2b-256 d2efdd87b196f7f3e4d49d8acb1691ae4a37e74ce64b630687a9de4e110af36d

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp313-cp313-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp313-cp313-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 d661a0c773dd35bb787d08aa110eadd1bae601b7be66bcd5d01b7152c79a3e17
MD5 f85a8f46aa920ab9dc95485a6222e291
BLAKE2b-256 c27b128ac939ab47348cad569b7109ed9be44293f3100f61a9a113eed39333ef

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 cee441eb2b2cb00c03f98bdd050ef3e5eed7d59f490e306214ebe0c328abe1b6
MD5 8ffd3b91b167ce9db0954e7c9fb833d0
BLAKE2b-256 7a4a5e0d5d4fc86d46a2dff5f42fb58a40659dddf424fd4f4da86e3ef52b1d2a

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp313-cp313-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp313-cp313-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 83a05e4aef62895521d92020ddf253b108608192107f49ab1fb804d6d252c753
MD5 78cc7a7cf7b261c69894fd43afdcb27f
BLAKE2b-256 1cbb9913e5efe8ecd4e65ee492f8c7b8f4a80e24137d45738ad415c1643e7baa

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp312-cp312-win_arm64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp312-cp312-win_arm64.whl
  • Upload date:
  • Size: 4.1 MB
  • Tags: CPython 3.12, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp312-cp312-win_arm64.whl
Algorithm Hash digest
SHA256 820a4c28fbe172c15d3aee1b2602d3a1ded2dcc824bf42f5a5e5ab25dd59102c
MD5 952cbbda1f6bd1c67822c89c11933d33
BLAKE2b-256 ee40490574d65cd3997e9becdb1355d035296df2bfaea76b74452200fe3c4f8c

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 4.5 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 495d3d057641f80a29b442afd5fc076de2aa21009a085a1c62f211555c7a39c7
MD5 dcfb5944549d1869a4de9840b74eface
BLAKE2b-256 e7dd70a754a8b4e1a6c430ba8f2085a0ca6caeefd6a225364a4d62870380f09e

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cf400d6aa4711023195a8c8a2f9907cdcfd726bd416562dc448ebdf3b5ceeb92
MD5 8b0dcae605c63525d5a0985a6e1627fd
BLAKE2b-256 548b3d4f29bf563e967e4d0feda83db1d600926fa56d36b984f4f287cd2fd275

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp312-cp312-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp312-cp312-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 7e73c6cedecd0f5d3ed38eab5d99d6a18540eb3b89597d589b8de9dbab0de9fa
MD5 607d210bc420d398c08b9ce11dec1492
BLAKE2b-256 8d5ed3ac52d1146b2583b3f28e067390809b749f3384bfe8439f12655a5abdd7

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6f77b6225cdb05e693effc12abc8893577fa7e08d3a656b46683e914dbd561fc
MD5 8ff85e8d285ee68df1a64f19fe5facb6
BLAKE2b-256 5eaf2757ff99090500cc2b7564ae6afae43ccce5dac5ea6cbaa83f68c49e68ad

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp312-cp312-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp312-cp312-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b5cc678cd1af3a4f50d65c9da3718fa66d3e7950c5f1f3a548b13b1111c8853e
MD5 0a708db3119254e1dcf2dd6d060ab976
BLAKE2b-256 d6ec2cf81b7f2293c5294a3829104312c29a9f1dd80705a8acea19d0fedd22df

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp311-cp311-win_arm64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp311-cp311-win_arm64.whl
  • Upload date:
  • Size: 4.2 MB
  • Tags: CPython 3.11, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp311-cp311-win_arm64.whl
Algorithm Hash digest
SHA256 3e3e18879f2cff43571ed82fb63324cb20164a0cb48e8fd6a8432e029a485c3e
MD5 8bc3bbfae0c49d0eeb85db736e97f3de
BLAKE2b-256 aac88795749f6b6e62f386cbb4da11a6d79a4a981a90556a6f683abaa4bc55f9

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 4.5 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 31f4366662e9373a9f5d68a46c88793d170597a4c1c14e604bfcc1b42564f004
MD5 70b80325b3484a9eee7cac5e9c4c5472
BLAKE2b-256 215b563e7c0295a4bcb5d9d3aee40664edcd1431f9c3bb363eade3d638e29a0f

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ece769e00367569ada01c54f060cf29059bbf9eba1a5c6cf0d842efb29d1694f
MD5 ad305bffec3581b9d99500af5c941751
BLAKE2b-256 90c0baa8cf311aca411af60b8ca7185225e265443b4c8734cb44d343ff9c5f56

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp311-cp311-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp311-cp311-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 7861041940efa4f26085870da363dce76e715922f61f29bd933b8b60544d0782
MD5 f8ad6ce3fa6feceefa891dbbd4703296
BLAKE2b-256 2d7de1642ef87508f5bed3cac5b7d66b9313a7a421b40e5b92aab363b7d1cdb3

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c57898341364a6ef78276c9763c3b81970a7fcf0c7a16232a347129bafd045d7
MD5 1f308b2d2842a7693efbc129a684c767
BLAKE2b-256 1acfb5135bcc5e50c544724d0cfa219676d5938d3619d83afe232ffd16a7a42d

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp311-cp311-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 21b5e610aed81ca8cf554ac1a77761e84a6696601ceda3749b43f64cc3f7eeda
MD5 85e364ac469ab1686335cd0cb9abe910
BLAKE2b-256 b2e38338c3a4fe770b4d4254a1f5e5fc239263050c3dea74b114a74bceca8546

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: net_mesh-0.27.7-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 4.5 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for net_mesh-0.27.7-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 4806fcbaaedd0ca465d2d502a3f438cc1d4631ca36d00daee45b2e4bcce5e49f
MD5 4ddf1f2a08e01a3978dd298b22b3cfcc
BLAKE2b-256 c88dbef9b0c2691d3da178bd955f4c1756d4c32c81ecbc8a89346922ab290f48

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7364c6a58f898cc03308dbfb45b95da7a5070276f11a7d2c7c5e090cfad76f3b
MD5 f1a3fc0333440ac436976f09fd2fa54a
BLAKE2b-256 ef5c2ef35e242e47c3c80520ab03149f1bb260ef27f296ae63334d55e77749b5

See more details on using hashes here.

File details

Details for the file net_mesh-0.27.7-cp310-cp310-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for net_mesh-0.27.7-cp310-cp310-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 b47117f9ea64ab98c8e346e9e64a351141c37d448a300a8beb7d6b1b79fee682
MD5 20a97246cea268a2d4c523790e8383e3
BLAKE2b-256 0a0cb6afd743b3d62d778a09a1222bffeb73a0162855d77e2e01ba49bbafb8bb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page