Python bindings (pybind11) — Phase 0 v1 #7

Merged
gx merged 4 commits from feat/python-bindings into main 2026-06-13 21:34:30 +01:00
Owner

Закрывает #6.

Что внутри

Полный subscriber-side wrapper над cuframes C ABI:

  • CuframesSubscriber + CuframesFrame — RAII-классы, оба context managers
  • DLPack exportframe.dlpack_y(), frame.dlpack_uv(), frame.__dlpack__() для PyTorch/CuPy zero-copy
  • Per-subscriber CUDA stream — kwarg consumer_stream (cudaStream_t как int)
  • Health statsframes_received, timeouts, errors, last_seq, gap_count, stats() dict
  • Error taxonomyCuframesError + 8 subclasses, маппинг через exception_for(int err)
  • GIL release на блокирующих вызовах (subscriber_create, next_frame)
  • Thread-safety contract в docstring

Сборка и тесты

cmake -B build-python -DBUILD_PYTHON_BINDINGS=ON
cmake --build build-python -j
cd python && pytest tests/ -v   # 10/10 passed

Wheel:

cd python && pip install -e . --no-build-isolation

Что не вошло в Phase 0

  • Publisher API (только subscriber-side)
  • Packet ring (encoded video)
  • Async callback wrapper
  • Реальный ring_occupancy / drop counter из C API — counted в pybind как gap_count (proxy для NEWEST_ONLY mode)
  • Smoke test реального subscribe требует Docker IPC namespace; counted-error-path тесты покрывают error mapping

Документация

docs/python.md (~250 строк): quick start, API reference, integration с PyTorch / CuPy, reconnect-loop pattern, per-stream usage, pitch alignment важности, thread-safety, error taxonomy, backpressure, Phase 0 limitations.

Архитектурный контекст

Это блокирующая dep для goldix-smart-home/yolo-world-detector Phase 1 (open-vocabulary object detector — параллельный consumer cuframes). Архитектор подтвердил подход «pybind first, не ctypes shortcut» — иначе 2 incompatible Python entry points в core lib.

См. также:

Закрывает #6. ## Что внутри Полный subscriber-side wrapper над cuframes C ABI: - **`CuframesSubscriber`** + **`CuframesFrame`** — RAII-классы, оба context managers - **DLPack export** — `frame.dlpack_y()`, `frame.dlpack_uv()`, `frame.__dlpack__()` для PyTorch/CuPy zero-copy - **Per-subscriber CUDA stream** — kwarg `consumer_stream` (cudaStream_t как int) - **Health stats** — `frames_received`, `timeouts`, `errors`, `last_seq`, `gap_count`, `stats()` dict - **Error taxonomy** — `CuframesError` + 8 subclasses, маппинг через `exception_for(int err)` - **GIL release** на блокирующих вызовах (`subscriber_create`, `next_frame`) - **Thread-safety contract** в docstring ## Сборка и тесты ```bash cmake -B build-python -DBUILD_PYTHON_BINDINGS=ON cmake --build build-python -j cd python && pytest tests/ -v # 10/10 passed ``` Wheel: ```bash cd python && pip install -e . --no-build-isolation ``` ## Что не вошло в Phase 0 - Publisher API (только subscriber-side) - Packet ring (encoded video) - Async callback wrapper - Реальный `ring_occupancy` / drop counter из C API — counted в pybind как `gap_count` (proxy для NEWEST_ONLY mode) - Smoke test реального subscribe требует Docker IPC namespace; counted-error-path тесты покрывают error mapping ## Документация `docs/python.md` (~250 строк): quick start, API reference, integration с PyTorch / CuPy, reconnect-loop pattern, per-stream usage, pitch alignment важности, thread-safety, error taxonomy, backpressure, Phase 0 limitations. ## Архитектурный контекст Это блокирующая dep для `goldix-smart-home/yolo-world-detector` Phase 1 (open-vocabulary object detector — параллельный consumer cuframes). Архитектор подтвердил подход «pybind first, не ctypes shortcut» — иначе 2 incompatible Python entry points в core lib. См. также: - [yolo-world-detector#1 Epic](http://server:3000/goldix-smart-home/yolo-world-detector/issues/1) - архитектурный review в чат-сессии 2026-06-13
gx added 4 commits 2026-06-13 21:34:09 +01:00
Каркас Python-пакета `cuframes`:
- python/pyproject.toml — scikit-build-core конфиг
- python/CMakeLists.txt — pybind11 module через FetchContent
- python/src/_native.cpp — module entry, error таксономия,
  enum mirrors (PixelFormat, SubscriberMode), version
- python/cuframes/__init__.py — re-export публичного API
- python/tests/test_smoke.py — smoke tests без real subscribe
- python/README.md — статус + build instructions
- CMakeLists.txt — подключение python/ при BUILD_PYTHON_BINDINGS=ON

Реальный subscriber/frame wrapper в следующих коммитах
(tasks #198-#202).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
py::exception<T>(...) уже возвращает Python class object. Дополнительный
.attr("__class__") давал metaclass (type), из-за чего issubclass()
проверка для всех subexc возвращала False.

Verify: pytest tests/test_smoke.py — 5/5 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Реализует subscriber-side wrapper над cuframes_subscriber_* и
cuframes_frame_* C API.

Что добавлено:
- CuframesFrame — owning RAII wrapper над cuframes_frame_t*
  - properties: cuda_ptr, format, width, height, pitch_y, pitch_uv,
    seq, pts_ns, released
  - release() idempotent
  - context manager (__enter__/__exit__) — release при выходе
  - после release() property access бросает CuframesError

- CuframesSubscriber — owning RAII wrapper над cuframes_subscriber_t*
  - конструктор с key/consumer_name/mode/cuda_device/connect_timeout_ms
  - next_frame(timeout_ms) → CuframesFrame
  - close() idempotent
  - context manager
  - GIL released на блокирующих вызовах (create, next_frame)

- subscribe() — module-level factory shortcut

Архитектурные решения:
- GIL release в py::gil_scoped_release на subscriber_create и _next —
  чтобы другие Python потоки могли работать пока ждём frame
- consumer_stream передаётся как nullptr в Phase 0 (default stream);
  per-subscriber stream в task #201
- Frame держит raw pointer на subscriber, refcount Python-стороной;
  если subscriber уничтожен раньше, frame.release() становится no-op

Smoke tests расширены до 8 — добавлены проверки exposed API и
error mapping на subscribe к несуществующему publisher'у.

Verify: pytest tests/test_smoke.py — 8/8 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
python: DLPack + health stats + CUDA stream + docs (tasks #199-#202)
build / cmake build (CUDA 12.4, Ubuntu 22.04) (pull_request) Failing after 1m50s
build / ffmpeg filter patch (out-of-tree) (pull_request) Has been skipped
afc2dd7fff
#199 DLPack export:
- frame.dlpack_y() / .dlpack_uv() — explicit multi-plane access для NV12
- frame.__dlpack__() / __dlpack_device__() — protocol для torch/cupy
- Capsule deleter правильно держит refcount на frame_keep_alive,
  releases shape/strides arrays. CUDA pointer принадлежит frame.

#200 Health/stats counters:
- frames_received, timeouts, errors — per-call counters
- last_seq, gap_count — proxy для drop count (NEWEST_ONLY mode)
- last_frame_pts_ns
- stats() — snapshot dict для MQTT health publish
- counted в pybind layer т.к. C API не expose'ит ring_occupancy

#201 Per-subscriber CUDA stream + thread-safety:
- consumer_stream kwarg в subscribe() — int (cudaStream_t pointer)
- subscriber.consumer_stream property
- Thread-safety contract в docstring CuframesSubscriber
- next_frame() передаёт consumer_stream_ в cuframes_subscriber_next

#202 Smoke test + docs:
- 10/10 pytest passed (расширен +2 теста на consumer_stream)
- docs/python.md (~250 строк): quick start, API reference, integration
  с PyTorch/CuPy, reconnect-loop pattern, per-stream usage,
  pitch alignment, thread-safety, error taxonomy, backpressure,
  Phase 0 limitations

Verify build + tests:
  cmake -B build-python -DBUILD_PYTHON_BINDINGS=ON
  cmake --build build-python -j
  pytest python/tests/ -v   # 10/10

Закрывает Phase 0 issue gx/cuframes#6.
Разблокирует goldix-smart-home/yolo-world-detector Phase 1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gx merged commit 7f4bdfcaab into main 2026-06-13 21:34:30 +01:00
gx deleted branch feat/python-bindings 2026-06-13 21:34:30 +01:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gx/cuframes#7