python: CuframesSubscriber + CuframesFrame wrapper (task #198)
Реализует 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>
This commit is contained in:
@@ -49,3 +49,36 @@ def test_error_hierarchy():
|
||||
cuframes.CuframesInternal,
|
||||
]:
|
||||
assert issubclass(sub, cuframes.CuframesError)
|
||||
|
||||
|
||||
def test_subscriber_class_exposed():
|
||||
"""CuframesSubscriber/CuframesFrame exposed как public classes."""
|
||||
assert hasattr(cuframes, "CuframesSubscriber")
|
||||
assert hasattr(cuframes, "CuframesFrame")
|
||||
assert hasattr(cuframes, "subscribe")
|
||||
|
||||
|
||||
def test_subscribe_to_missing_publisher_raises():
|
||||
"""Subscribe к несуществующему publisher → CuframesError (subclass)
|
||||
после connect_timeout_ms.
|
||||
|
||||
Этот тест работает на любом хосте (без живого cuframes-pub) — мы
|
||||
верифицируем что error path работает и маппит CUFRAMES_ERR_*
|
||||
в правильный Python exception.
|
||||
"""
|
||||
import pytest
|
||||
with pytest.raises(cuframes.CuframesError):
|
||||
cuframes.subscribe(
|
||||
"definitely-not-existing-publisher-xyz",
|
||||
connect_timeout_ms=100,
|
||||
)
|
||||
|
||||
|
||||
def test_subscriber_repr_when_unable_to_connect():
|
||||
"""Лёгкий тест что repr не падает и close idempotent."""
|
||||
import pytest
|
||||
try:
|
||||
sub = cuframes.subscribe("nope-xyz", connect_timeout_ms=100)
|
||||
except cuframes.CuframesError:
|
||||
return # ожидаемо
|
||||
pytest.fail("subscribe должно было выкинуть exception")
|
||||
|
||||
Reference in New Issue
Block a user