Python shared memory library
Project description
pymembus
Python bindings for libmembus, a
small shared-memory IPC library.
pymembus is useful when multiple local processes need to exchange data with
low overhead:
- raw shared memory blocks with
memmap - broadcast message queues with
memmsg - command channels with
memcmd - fixed-schema shared key/value state with
memkv - video ring buffers with
memvid - audio ring buffers with
memaud - simple readiness polling with
select()
All shared-memory names in the examples use POSIX-style names such as
"/myshare". On Linux, stale objects can remain after a crash; each type has a
remove(name) helper for cleanup.
Contents
- Install
- Build From Source
- Run Tests
- Quick Start
- API Guide
- Diagnostics
- Command Line Helpers
- Troubleshooting
- Development Notes
Install
From PyPI:
python3 -m pip install pymembus
If you build from source, install system build dependencies first. On Debian or Ubuntu:
sudo apt-get update
sudo apt-get install -y build-essential git cmake libboost-all-dev
sudo apt-get install -y python3 python3-pip
Optional tools used by this repository's CMake documentation targets:
sudo apt-get install -y doxygen graphviz go-md2man
Build From Source
Install this checkout into your active Python environment:
python3 -m pip install .
Or build with CMake directly:
cmake -S . -B ./bld -DCMAKE_BUILD_TYPE=Release
cmake --build ./bld -j
The source build fetches pinned third-party dependencies when needed:
libmembusv1.2.0pybind11v2.13.6
libmembus 1.2.0 requires C++20 and Boost stacktrace support. The Python build
requires CMake 3.30 or newer.
To uninstall a pip install:
python3 -m pip uninstall -y pymembus
To build distribution artifacts:
python3 setup.py sdist
python3 setup.py bdist_wheel
Run Tests
The test suite uses pytest. If you built with CMake:
cmake -S . -B ./bld -DCMAKE_BUILD_TYPE=Release
cmake --build ./bld --target pymembus-test
You can also run pytest from the repository root:
python3 -m pytest -v
Wheel builds made through scikit-build do not run the CMake test target during
install; run tests explicitly with one of the commands above.
The pytest configuration in pyproject.toml limits collection to
src/pytest/py and adds bld/lib to PYTHONPATH, so it will not try to run
vendored tests under bld/_deps.
The suite covers maps, messages, commands, key/value stores, video/audio
formats, NumPy buffer sharing, diagnostics, and select(). NumPy-specific tests
are skipped when NumPy is not installed.
Quick Start
import pymembus
if hasattr(pymembus, "pymembus"):
pymembus = pymembus.pymembus
name = "/quickstart"
pymembus.memmsg.remove(name)
tx = pymembus.memmsg()
rx = pymembus.memmsg()
assert tx.open(name, 1024, True, True) # writer, create
assert rx.open(name, 1024, False, False) # reader, attach
assert tx.write("hello")
message, overrun = rx.read_with_overrun(0)
assert message == "hello"
assert not overrun
rx.close()
tx.close()
pymembus.memmsg.remove(name)
API Guide
The snippets below assume the normalized import pattern from the quick start:
import pymembus
if hasattr(pymembus, "pymembus"):
pymembus = pymembus.pymembus
Raw Shared Memory: memmap
memmap gives direct access to a named shared-memory block.
import pymembus
name = "/my_map"
pymembus.memmap.remove(name)
writer = pymembus.memmap()
reader = pymembus.memmap()
assert writer.open(name, 1024, True, True)
assert writer.write("hello") == 5
assert reader.open(name, 0, False, False, True) # read-only attach
assert reader.read(5) == "hello"
view = memoryview(writer)
assert view.shape == (1024,)
assert not view.readonly
readonly_view = memoryview(reader)
assert readonly_view.readonly
del readonly_view
del view
reader.close()
writer.close()
pymembus.memmap.remove(name)
Parameters for memmap.open(name, size, create=False, new=False, read_only=False):
create: create the object if it does not existnew: remove any existing object firstread_only: attach without write permissions
Broadcast Messages: memmsg
memmsg is a single-writer, multi-reader message queue. Every reader receives
every message independently.
name = "/my_messages"
pymembus.memmsg.remove(name)
tx = pymembus.memmsg()
rx1 = pymembus.memmsg()
rx2 = pymembus.memmsg()
assert tx.open(name, 4096, True, True)
assert rx1.open(name, 4096, False, False)
assert rx2.open(name, 4096, False, False)
assert tx.write("frame-ready")
assert rx1.read_with_overrun(0) == ("frame-ready", False)
assert rx2.read_with_overrun(0) == ("frame-ready", False)
tx.close()
rx1.close()
rx2.close()
Use poll() for non-blocking readiness checks:
if rx1.poll():
msg = rx1.read(0)
When a reader falls behind far enough that the writer overwrites unread
messages, read_with_overrun() returns ("", True).
Command Channels: memcmd
memcmd is a multi-writer command channel. It is useful for control paths, for
example sending commands from a UI process to a capture process.
name = "/camera_commands"
pymembus.memcmd.remove(name)
receiver = pymembus.memcmd()
sender = pymembus.memcmd()
assert receiver.open(name, 1024, True, True) # bReader=True, bCreate=True
assert sender.open(name, 1024)
assert sender.write("pan-left")
cmd, overrun = receiver.read_with_overrun(0)
assert cmd == "pan-left"
assert not overrun
sender.close()
receiver.close()
Shared State: memkv
memkv is a fixed-schema key/value store. The owner creates the store and sets
slot names; any process can then read or write values.
name = "/camera_state"
pymembus.memkv.remove(name)
owner = pymembus.memkv()
assert owner.create(name, 3, 16, 64, True)
assert owner.setName(0, "mode")
assert owner.setName(1, "count")
assert owner.setName(2, "status")
peer = pymembus.memkv()
assert peer.open(name)
assert peer.setValue("mode", "auto")
value, stale = peer.getValue("mode")
assert value == "auto"
assert not stale
epoch = peer.getEpoch()
assert owner.setValue("status", "ready")
changed, epoch = peer.getChanged(epoch)
assert changed == {"status": "ready"}
peer.close()
owner.close()
getValue() returns (value, stale). stale is true if the lock-free read did
not settle before its retry limit.
Video Ring Buffers: memvid
memvid stores packed video frames in a shared ring buffer. Use
video_format values instead of old numeric bits-per-pixel values.
name = "/video"
pymembus.memvid.remove(name)
video = pymembus.memvid()
assert video.open(
name,
True,
640,
480,
pymembus.video_format.rgb24,
30,
4,
)
slot = video.getPtr(0)
assert video.setVpts(slot, 123456)
assert video.setApts(slot, 123000)
assert video.next(1) == 1
assert video.getSeq() == 1
assert video.getFrameSeq(slot) == 1
assert video.getFormatName() == "RGB24"
frame = memoryview(video[slot])
assert frame.shape == (480, 640, 3)
del frame
video.close()
pymembus.memvid.remove(name)
Supported video formats:
gray8rgb24bgr24rgba32bgra32yuyv422uyvy422
NumPy can view frame buffers without copying:
import numpy as np
frame = np.array(video[slot], copy=False)
frame[10, 10] = [255, 0, 0]
del frame
video.close()
pymembus.memvid.remove(name)
Audio Ring Buffers: memaud
memaud stores PCM audio buffers in a shared ring buffer. Use audio_format
values instead of old numeric bits-per-sample values.
name = "/audio"
pymembus.memaud.remove(name)
audio = pymembus.memaud()
assert audio.open(
name,
True,
2,
pymembus.audio_format.s16le,
48000,
50,
4,
)
slot = audio.getPtr(0)
assert audio.setPts(slot, 123456)
assert audio.next(1) == 1
assert audio.getChannels() == 2
assert audio.getSampleRate() == 48000
assert audio.getFormatName() == "S16LE"
buf = memoryview(audio[slot])
assert buf.shape == (960, 2)
del buf
audio.close()
pymembus.memaud.remove(name)
Supported audio formats:
u8s16les24les32lef32lef64le
s24le is exposed as raw bytes because Python and NumPy do not have a native
24-bit integer scalar type.
Buffer Lifetime And Read-Only Views
memmap, memvid, and memaud expose Python's buffer protocol. A writer that
created a share exports writable buffers. A reader opened read-only, or with
open_existing() for video/audio, exports read-only buffers.
Keep one lifetime rule in mind for video and audio buffers: release
memoryview or NumPy arrays before calling close() on the owning object.
pymembus intentionally raises RuntimeError if a video or audio mapping is
closed while exported frame buffers still exist, because those buffers would
otherwise point at unmapped shared memory.
Waiting On Multiple Sources: select()
select(wait_ms, conditions) polls a list of Python callables and returns the
zero-based index of the first ready condition, or -1 on timeout.
idx = pymembus.select(100, [
lambda: video.getSeq() > last_video_seq,
lambda: commands.poll(),
])
if idx == 0:
read_video_frame()
elif idx == 1:
handle_command()
The conditions should be cheap, non-consuming readiness checks.
Diagnostics
Most API calls return False, -1, or an empty string on failure. Check
last_error() or last_error_message() immediately after a failed call:
missing = pymembus.memmap()
if not missing.open("/does-not-exist", 0, False):
assert pymembus.last_error() == pymembus.errc.open_failed
print(pymembus.last_error_message())
Common error codes include:
open_failedcreate_failedmap_failedsize_mismatchinvalid_layoutnot_openaccess_deniedmessage_too_largelock_timeouttimeoutoverrun
Command Line Helpers
After installation, the package may install a pymembus helper command on
Linux:
pymembus help
pymembus files
pymembus info version
sudo pymembus uninstall
pymembus info <variable> accepts values such as name, description, url,
version, build, company, author, lib, include, bin, and share.
Troubleshooting
ModuleNotFoundError: No module named 'pymembus'
Build the extension first:
cmake -S . -B ./bld -DCMAKE_BUILD_TYPE=Release
cmake --build ./bld -j
Then run tests from the repository root:
python3 -m pytest -v
The repository's pytest config adds bld/lib to PYTHONPATH.
Pytest tries to run pybind11 tests
This happens when pytest recursively scans build artifacts. The repository config sets:
norecursedirs = ["bld", "_skbuild", "dist", "build", ".git", "*.egg-info"]
If you use a custom pytest command, prefer:
python3 -m pytest -v src/pytest/py
A share already exists or has an invalid layout
Remove stale shared-memory objects before creating a fresh one:
pymembus.memmsg.remove("/my_messages")
pymembus.memvid.remove("/video")
pymembus.memaud.remove("/audio")
The 1.2.0 libmembus wire formats validate shared-memory headers. Old shares
created by earlier library versions may be rejected with invalid_layout.
Doxygen or Graphviz warnings during build
The CMake build may emit documentation warnings from Doxygen or Graphviz. They do not affect the Python extension or pytest suite.
Development Notes
- Project metadata lives in
PROJECT.txt. - The Python extension source is in
src/py/cpp/main.cpp. - Tests live in
src/pytest/py/test.py. - The fallback
libmembusdependency is configured insrc/libmembus.cmake. - See
UPDATE.mdfor the libmembus 1.2.0 migration report.
References
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
File details
Details for the file pymembus-1.0.2.tar.gz.
File metadata
- Download URL: pymembus-1.0.2.tar.gz
- Upload date:
- Size: 81.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
817f9189b5e096a7664c450e88bfa4f17b6cce5da38bd8f40cfc3d0df0948614
|
|
| MD5 |
5100328348182226214f935faddfc3ed
|
|
| BLAKE2b-256 |
98a0033124ea2dd68c64f3d9aaf6ae2ce8df8cbae118e2ab708940cbc5808916
|