zerodds-py v1.0 — Spec Coverage
Audit of the vendor spec docs/specs/zerodds-py-1.0.md against the tree
as it stands.
Source: docs/specs/zerodds-py-1.0.md (vendor spec).
crates/py/ is a PyO3 bridge: the DCPS entities from crates/dcps/ as
the extension module zerodds._core, with a Python package above it that
carries IdlTopic over generated code and the ctypes loader against
libzerodds. Topic types have one codec: the code zerodds-idlc generate --lang python emits (the Python backend of crates/lang-rules)
over the wire runtime in runtime/python/zerodds_wire/. Without
--features extension-module the crate is only the placeholder lib;
maturin builds the module.
Beside it stands the XRCE endpoint SDK in
endpoints/python/zerodds_endpoint.py, which this spec does not
describe.
Implementation:
crates/py/· docs.rs — PyO3 bridge (src/) + Python package (python/zerodds/) + test tree (python/tests/) + examples + sphinx tree.
§1 Architecture
§1.1 Module Layout
Spec: §1.1 — crate layout with Cargo.toml (crate-type cdylib+rlib),
pyproject.toml for maturin, src/lib.rs+ffi.rs,
python/zerodds/{__init__,topic,aio,loader}.py, python/tests/ with
_generated/, examples/, docs/ sphinx, and runtime/python/.
Repo: crates/py/Cargo.toml (crate-type = ["cdylib", "rlib"],
feature extension-module), crates/py/pyproject.toml
(build-backend = "maturin", python-source = "python",
module-name = "zerodds._core"), crates/py/build.rs
(-undefined dynamic_lookup on Darwin), crates/py/src/lib.rs,
crates/py/src/ffi.rs, crates/py/python/zerodds/__init__.py,
crates/py/python/zerodds/topic.py, crates/py/python/zerodds/aio.py,
crates/py/python/zerodds/loader.py, crates/py/python/tests/
(fixtures: tests/_generated/*.idl and their zerodds-idlc output),
crates/py/examples/, crates/py/docs/conf.py,
runtime/python/zerodds_wire/.
maturin build turns that into an abi3 wheel holding _core.abi3.so
and the Python modules.
Tests: crates/py/python/tests/test_docs.py::test_examples_parse,
crates/py/python/tests/test_docs.py::test_literalincludes_point_at_existing_examples,
crates/py/python/tests/test_smoke.py::test_version_exposed
Status: done
§1.2 PyO3 module zerodds._core — PyClasses
One item per PyClass. Mapping from DDS 1.4 §2.2.2.
§1.2.1 DomainParticipantFactory (PyClass)
Spec: §1.2 + §2.1 — PyClass DomainParticipantFactory with
instance(), create_participant(domain_id),
create_participant_offline(domain_id),
create_participant_fast(domain_id).
Repo: crates/py/src/ffi.rs (PyFactory) — instance as a
#[staticmethod], the three constructors over
zerodds_dcps::DomainParticipantFactory; create_participant_fast
sets RuntimeConfig { tick_period: 20 ms, spdp_period: 100 ms }.
Tests: crates/py/python/tests/test_smoke.py::test_factory_singleton_and_offline_participant,
crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live,
crates/py/python/tests/test_loader_smoke.py::test_loader_factory_singleton
Status: done
§1.2.2 DomainParticipant (PyClass)
Spec: §1.2 + §2.2 — PyClass DomainParticipant with domain_id,
topics_len, discovered_participants_count,
create_{bytes,shape}_topic, create_{publisher,subscriber},
assert_liveliness, ignore_{participant,topic,publication,subscription},
contains_entity, get_discovered_{topics,participants}.
Repo: crates/py/src/ffi.rs (PyParticipant) — plus
create_keyed_topic and create_bytes_contentfilteredtopic; the four
ignore_* and contains_entity take the u64 of an
InstanceHandle.
Tests: crates/py/python/tests/test_participant_ignore.py::test_ignore_participant_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_contains_entity_false_for_unknown_handle,
crates/py/python/tests/test_participant_ignore.py::test_get_discovered_participants_offline_is_empty,
crates/py/python/tests/test_participant_ignore.py::test_assert_liveliness_offline_does_not_raise
Status: done
§1.2.3 BytesTopic (PyClass)
Spec: §1.2 — PyClass BytesTopic with name, type_name.
Repo: crates/py/src/ffi.rs (PyBytesTopic) over
Topic<RawBytes>; type_name reads
<RawBytes as DdsType>::TYPE_NAME and is therefore bound to the Rust
side rather than written a second time.
Tests: crates/py/python/tests/test_smoke.py::test_bytes_topic_and_reader_are_creatable_offline
Status: done
§1.2.4 ShapeTopic (PyClass)
Spec: §1.2 + §2.7 — PyClass ShapeTopic with name, type_name,
type name = "ShapeType".
Repo: crates/py/src/ffi.rs (PyShapeTopic) over
Topic<ShapeType>; type_name reads
<ShapeType as DdsType>::TYPE_NAME from crates/dcps/src/interop.rs.
Tests: crates/py/python/tests/test_smoke.py::test_shape_topic_matches_vendor_interop_type_name,
crates/py/python/tests/test_shapesdemo_interop.py::test_shape_type_name_matches_shapesdemo_convention
Status: done
§1.2.5 Publisher (PyClass)
Spec: §1.2 + §2.3 — PyClass Publisher with
create_bytes_writer(topic), create_shape_writer(topic).
Repo: crates/py/src/ffi.rs (PyPublisher) — plus the
*_with_qos variants from §6.2 and create_keyed_writer.
Tests: crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live,
crates/py/python/tests/test_qos.py::test_create_bytes_writer_with_qos_offline,
crates/py/python/tests/test_qos.py::test_create_shape_writer_with_qos_offline
Status: done
§1.2.6 Subscriber (PyClass)
Spec: §1.2 + §2.3 — PyClass Subscriber with
create_bytes_reader(topic), create_shape_reader(topic).
Repo: crates/py/src/ffi.rs (PySubscriber) — plus the
*_with_qos variants, create_keyed_reader and
create_bytes_reader_cft for the content filter of §2.2.2.5.4.
Tests: crates/py/python/tests/test_smoke.py::test_bytes_topic_and_reader_are_creatable_offline,
crates/py/python/tests/test_qos.py::test_create_bytes_reader_with_qos_offline,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_content_filtered_topic_filters_samples
Status: done
§1.2.7 BytesWriter (PyClass)
Spec: §1.2 + §2.4 — PyClass BytesWriter with write,
wait_for_matched_subscription, matched_subscription_count,
publication_matched_status, liveliness_lost_status,
offered_deadline_missed_status.
Repo: crates/py/src/ffi.rs (PyBytesWriter) — plus
register_instance, lookup_instance, dispose,
unregister_instance and set_listener/clear_listener. write and
the three wait functions release the GIL through py.detach.
Tests: crates/py/python/tests/test_status_getters.py::test_publication_matched_status_shape,
crates/py/python/tests/test_status_getters.py::test_liveliness_lost_status_shape,
crates/py/python/tests/test_status_getters.py::test_offered_deadline_missed_status_shape,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_writer_lifecycle_methods_present
Status: done
§1.2.8 BytesReader (PyClass)
Spec: §1.2 + §2.5 — PyClass BytesReader with take,
wait_for_data, wait_for_matched_publication,
matched_publication_count, subscription_matched_status,
sample_lost_status, requested_deadline_missed_status.
Repo: crates/py/src/ffi.rs (PyBytesReader) — plus
take_with_info / read_with_info, which yield (bytes, SampleInfo)
pairs, liveliness_changed_status and
set_listener/clear_listener.
Tests: crates/py/python/tests/test_status_getters.py::test_subscription_matched_status_shape,
crates/py/python/tests/test_status_getters.py::test_sample_lost_status_shape,
crates/py/python/tests/test_status_getters.py::test_requested_deadline_missed_status_shape,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_take_with_info_returns_sample_info
Status: done
§1.2.9 Shape (PyClass / dataclass)
Spec: §1.2 + §2.7 — PyClass Shape with fields color, x, y,
shapesize (default 30), __repr__, conversions From<&PyShape> and
From<ShapeType> in both directions.
Repo: crates/py/src/ffi.rs (PyShape) with
#[pyo3(signature = (color, x, y, shapesize=30))] and the two From
implementations against zerodds_dcps::interop::ShapeType.
Tests: crates/py/python/tests/test_smoke.py::test_shape_constructor_and_repr,
crates/py/python/tests/test_smoke.py::test_shape_default_shapesize_is_30
Status: done
§1.2.10 ShapeWriter (PyClass)
Spec: §1.2 + §2.4 — PyClass ShapeWriter with write(shape),
register_instance(shape), dispose(shape),
unregister_instance(shape), wait_for_matched_subscription.
Repo: crates/py/src/ffi.rs (PyShapeWriter) — dispose and
unregister_instance register the instance when lookup_instance
returns nil, so a valid handle is always at hand (spec §2.2.2.4.2.4).
Tests: crates/py/python/tests/test_writer_lifecycle.py::test_register_instance_returns_nonzero_handle,
crates/py/python/tests/test_writer_lifecycle.py::test_register_instance_is_idempotent_for_same_key,
crates/py/python/tests/test_writer_lifecycle.py::test_lifecycle_full_sequence
Status: done
§1.2.11 ShapeReader (PyClass)
Spec: §1.2 + §2.5 — PyClass ShapeReader with take,
wait_for_data, wait_for_matched_publication.
Repo: crates/py/src/ffi.rs (PyShapeReader) — plus
take_with_info / read_with_info and
liveliness_changed_status.
Tests: crates/py/python/tests/test_status_getters.py::test_matched_publication_count_zero_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_shape_reader_offline
Status: done
§1.2.12 GuardCondition (PyClass)
Spec: §1.2 + §2.6 — PyClass GuardCondition with constructor,
set_trigger_value(bool), get_trigger_value() -> bool.
Repo: crates/py/src/ffi.rs (PyGuardCondition) over
zerodds_dcps::condition::GuardCondition.
Tests: crates/py/python/tests/test_conditions.py::test_guard_condition_default_trigger_is_false,
crates/py/python/tests/test_conditions.py::test_guard_condition_set_trigger_roundtrip,
crates/py/python/tests/test_conditions.py::test_guard_condition_via_outer_namespace
Status: done
§1.2.13 WaitSet (PyClass)
Spec: §1.2 + §2.6 — PyClass WaitSet with constructor,
attach_guard_condition(gc), wait(timeout_secs) -> int.
Repo: crates/py/src/ffi.rs (PyWaitSet) over
zerodds_dcps::condition::WaitSet; wait releases the GIL and raises
TimeoutError when nothing triggers. attach_read_condition and
attach_query_condition come with §6.6.
Tests: crates/py/python/tests/test_conditions.py::test_waitset_attach_and_wait_raises_timeout,
crates/py/python/tests/test_conditions.py::test_waitset_wakes_when_guard_condition_fires,
crates/py/python/tests/test_conditions.py::test_waitset_via_outer_namespace
Status: done
§1.3 Python wrapper python/zerodds/
§1.3.1 __init__.py — re-export
Spec: §1.3 — zerodds.__init__ re-exports the _core PyClasses
and IdlTopic/IdlWriter/IdlReader from topic.py.
Repo: crates/py/python/zerodds/__init__.py — re-exports the
PyClasses along with __version__, the modules sample_state,
view_state, instance_state and IdlTopic/IdlWriter/IdlReader
from topic.py. When
the extension module is absent, _CORE_AVAILABLE is False and the
PyClasses become placeholders that raise an ImportError naming the
maturin line on instantiation — the pure-Python modules stay usable
with no Rust build.
Tests: crates/py/python/tests/test_smoke.py::test_version_exposed,
crates/py/python/tests/test_conditions.py::test_guard_condition_via_outer_namespace,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_extension_class
Status: done
§1.3.2 topic.py — IdlTopic over a generated TopicDescriptor
Spec: §1.3 + §6.1 — IdlTopic(participant, name, descriptor)
takes only a zerodds_wire.topic_descriptor.TopicDescriptor and creates
the topic with the type’s CompleteTypeObject, key and extensibility;
IdlWriter/IdlReader encode and decode with the generated code.
Repo: crates/py/python/zerodds/topic.py (IdlTopic, IdlWriter,
IdlReader) over DomainParticipant.create_descriptor_topic and the
DescriptorTopic/DescriptorWriter/DescriptorReader PyClasses in
crates/py/src/ffi.rs.
Tests: crates/py/python/tests/test_idl_topic.py::test_idl_topic_rejects_a_non_topic_descriptor,
::test_idl_topic_exposes_type_name_from_the_descriptor,
::test_idl_writer_encodes_and_reader_decodes_roundtrip,
::test_idl_writer_rejects_wrong_type,
::test_idl_writer_passthrough_status
Status: done
§1.3.3 runtime/python — zerodds_wire
Spec: §1 + §3 — the wire runtime the generated code emits onto.
Repo: runtime/python/zerodds_wire/ (Writer, Reader,
TopicDescriptor, OutOfRange, encapsulation and endianness) —
alignment at min(N, MAXALIGN) with MAXALIGN four under XCDR2 and eight
under XCDR1, DHEADER and EMHEADER framing, PL_CDR1 parameters.
Tests: runtime/python/tests/test_wire.py,
crates/py/python/tests/test_idl.py::test_wire_primitive_roundtrip,
::test_wire_string_alignment_padding,
::test_wire_reader_rejects_truncated_string,
::test_xcdr2_max_align_caps_double_at_four
Status: done
§1.3.4 loader.py — pure ctypes loader
Spec: §1.3 + §1.4 — pure ctypes loader against
libzerodds.{so,dylib,dll} from crates/zerodds-c-api/. Follows
zerodds-ffi-loader-1.0 §3.1. Signatures from
crates/zerodds-c-api/include/zerodds.h.
Repo: crates/py/python/zerodds/loader.py (load_library,
Runtime, Writer, Reader, DomainParticipantFactory,
ZeroDdsError) — resolution over ZERODDS_LIB, the wheel-internal
_lib/, target/{debug,release} in a development tree, and finally
the system linker.
Tests: crates/py/python/tests/test_loader_smoke.py::test_loader_resolves_library,
crates/py/python/tests/test_loader_smoke.py::test_loader_runtime_create_participant_offline,
crates/py/python/tests/test_loader_smoke.py::test_loader_zerodds_error_is_runtime_error_subclass
The four tests skip when no libzerodds is found
(cargo build -p zerodds-c-api or ZERODDS_LIB).
Status: done
§1.4 Two-path choice
Spec: §1.4 — two independent paths: PyO3 (performance, RTPS-native)
and pure ctypes (distro package, zero build).
Repo: crates/py/src/ffi.rs for the PyO3 path,
crates/py/python/zerodds/loader.py for the ctypes path. The two
share only the wire format and the domain id: loader.py does not
import _core and runs without a Rust toolchain, and ffi.rs knows
nothing of libzerodds.
Tests: crates/py/python/tests/test_loader_smoke.py::test_loader_factory_singleton,
crates/py/python/tests/test_smoke.py::test_factory_singleton_and_offline_participant
Status: done
Non-goal — RTPS directly in pure Python
Spec: Non-goals — “Direct RTPS wire in pure Python: the pure Python path goes via the ctypes loader bridge to the Rust library. There is no pure-Python RTPS datagram in v1.0 or v2.0.”
Repo: —
Tests: —
Status: open — the ctypes path needs libzerodds; runtime/python/zerodds_wire encodes CDR but sends no RTPS.
§1.5 Layer position
Spec: §1.5 — layer 6 (PSMs / bindings). Direct dependencies:
zerodds-dcps (layer 4), zerodds-c-api (layer 6 C-FFI).
Repo: crates/py/Cargo.toml — the Rust dependencies are
zerodds-dcps, zerodds-qos and zerodds-wire;
crates/zerodds-c-api/ is reached at runtime by the ctypes path
through the loader, with no manifest edge. crates/py/src/lib.rs
names the layer in the crate doc.
Tests: crates/py/src/lib.rs::dcps_re_export_compiles
Status: done
§2 OMG API coverage (DDS 1.4 §2.2.2)
§2.1 DomainParticipantFactory (DDS 1.4 §2.2.2.2.1)
Spec: §2.1 — mapping table: get_instance(), create_participant,
create_participant_offline, implicit delete_participant via the
Python GC.
Repo: crates/py/src/ffi.rs (PyFactory) —
DomainParticipantFactory.instance() as a #[staticmethod],
create_participant and create_participant_offline each onto the
Rust operation of the same name. Deletion hangs off the Python GC:
PyParticipant holds the DomainParticipant, whose Drop tears the
Rust side down.
Tests: crates/py/python/tests/test_smoke.py::test_factory_singleton_and_offline_participant,
crates/py/python/tests/test_multiproc.py::test_multiproc_python_python_roundtrip
Status: done
§2.2 DomainParticipant (DDS 1.4 §2.2.2.2.2)
Spec: §2.2 — twelve operations: get_domain_id, create_topic,
create_publisher, create_subscriber, assert_liveliness,
ignore_{participant,topic,publication,subscription},
contains_entity, get_discovered_{topics,participants}.
Repo: crates/py/src/ffi.rs (PyParticipant) — all twelve.
get_domain_id is the domain_id property, and create_topic is the
three typed constructors create_bytes_topic, create_shape_topic
and create_keyed_topic.
Tests: crates/py/python/tests/test_participant_ignore.py::test_ignore_topic_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_ignore_publication_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_ignore_subscription_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_get_discovered_topics_offline_is_empty
Status: done
§2.3 Publisher / Subscriber (DDS 1.4 §2.2.2.4.1 / §2.2.2.5.1)
Spec: §2.3 — Publisher.create_datawriter →
pub.create_{bytes,shape}_writer(topic),
Subscriber.create_datareader →
sub.create_{bytes,shape}_reader(topic).
Repo: crates/py/src/ffi.rs (PyPublisher, PySubscriber) — one
constructor per topic type, each with a *_with_qos variant.
Tests: crates/py/python/tests/test_qos.py::test_create_shape_reader_with_qos_offline,
crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live
Status: done
§2.4 DataWriter (DDS 1.4 §2.2.2.4.2)
Spec: §2.4 — mapping table: write, register_instance,
unregister_instance, dispose, get_matched_subscriptions,
get_publication_matched_status, get_liveliness_lost_status,
get_offered_deadline_missed_status, plus the
wait_for_matched_subscription sync helper.
Repo: crates/py/src/ffi.rs (PyBytesWriter, PyShapeWriter,
PyKeyedWriter) — get_matched_subscriptions is
matched_subscription_count, and the three status getters return the
tuples of the spec’s structures.
Tests: crates/py/python/tests/test_writer_lifecycle.py::test_dispose_does_not_raise,
crates/py/python/tests/test_writer_lifecycle.py::test_unregister_instance_does_not_raise,
crates/py/python/tests/test_status_getters.py::test_matched_subscription_count_zero_offline,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_keyed_register_instance_returns_real_handle
Status: done
§2.5 DataReader (DDS 1.4 §2.2.2.5.3)
Spec: §2.5 — take, wait_for_data,
wait_for_matched_publication, matched_publication_count,
subscription_matched_status, sample_lost_status,
requested_deadline_missed_status. read and
wait_for_historical_data deliberately omitted.
Repo: crates/py/src/ffi.rs (PyBytesReader, PyShapeReader,
PyKeyedReader). Beyond the spec’s list there are read_with_info
and take_with_info, which carry the SampleInfo per sample — read
without a SampleInfo stays out, as the spec says.
Tests: crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_take_with_info_returns_sample_info,
crates/py/python/tests/test_status_getters.py::test_subscription_matched_status_shape,
crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live
Status: done
§2.6 WaitSet / conditions (DDS 1.4 §2.2.2.6)
Spec: §2.6 — WaitSet, GuardCondition with
attach_guard_condition, wait, set/get_trigger_value.
ReadCondition/QueryCondition are available through §6.6.
Repo: crates/py/src/ffi.rs (PyGuardCondition, PyWaitSet),
crates/py/src/conditions.rs for the two reader conditions.
The spec says in §2.6 that the outer zerodds namespace does not
re-export WaitSet and GuardCondition yet, and lists both in §1.3’s
re-export block. crates/py/python/zerodds/__init__.py follows §1.3:
both are reachable through zerodds. and through zerodds._core.,
which includes the §2.6 access.
Tests: crates/py/python/tests/test_conditions.py::test_waitset_wakes_when_guard_condition_fires,
crates/py/python/tests/test_conditions.py::test_waitset_via_outer_namespace,
crates/py/python/tests/test_read_conditions.py::test_read_condition_attaches_to_waitset
Status: done
§2.7 ShapeType — cross-vendor interop type
Spec: §2.7 — the Shape PyClass is octet-identical to ShapeType
in crates/dcps/src/interop.rs. Fields color: String, x: i32, y: i32, shapesize: i32 (default 30). Type name on the wire =
"ShapeType".
Repo: crates/py/src/ffi.rs (PyShape, shape_reference_octets)
— the conversion goes through zerodds_dcps::interop::ShapeType
itself, so there is no second codec on the Rust side.
shape_reference_octets returns that codec’s octets to Python, so the
generated Python code can be measured against it.
Tests: crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet,
crates/py/python/tests/test_smoke.py::test_shape_topic_matches_vendor_interop_type_name,
crates/py/python/tests/test_shapesdemo_interop.py::test_shape_type_name_matches_shapesdemo_convention
Status: done
§3 IDL mapping — generated code over zerodds_wire
§3.1 Mapping
Spec: §3.1 — zerodds-idlc generate --lang python emits one
dataclass per type, its encode/decode functions and
ZD_TOPIC_DESCRIPTORS; the mapping table (integers, floats, strings,
sequence, T[N], map, @optional, enum, union, struct); a
bounded value over its bound is refused with OutOfRange before a byte
is written; decode applies @try_construct (XTypes 1.3
§7.2.2.4.4.4.9).
Repo: crates/lang-rules/src/adapter/python.rs (the Python
mapping), crates/lang-rules/src/emit/encode.rs (the shared codec walk,
including the bound checks), tools/idlc (generate --lang python),
runtime/python/zerodds_wire/.
Tests: crates/py/python/tests/test_idl.py::test_sensor_mixed_fields_roundtrip,
::test_optional_present_and_absent, ::test_enum_roundtrip,
::test_enum_unknown_value_raises, ::test_union_case_int_roundtrip,
::test_union_default_branch_used_for_unknown_disc,
::test_nested_struct_roundtrip, ::test_fixed_array_roundtrip,
::test_generated_bounds_roundtrip_and_enforced
Status: partial — the element count of a fixed array is not checked
on encode: too many elements are cut, too few raise IndexError
(test_fixed_array_count_is_refused_on_encode, xfail(strict=True)).
§3.2 Example
Spec: §3.2 — IDL, generated module, IdlTopic over its descriptor.
Repo: crates/py/examples/03_idl_topic.idl,
crates/py/examples/03_idl_topic_gen.py (the zerodds-idlc output),
crates/py/examples/03_idl_topic.py.
Tests: crates/py/python/tests/test_docs.py::test_examples_parse,
crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet
Status: done
§4 Test obligation
§4.1 Test inventory
Spec: §4 — pytest over crates/py/python/tests/, fixtures in
tests/_generated/ as IDL plus their unchanged zerodds-idlc output,
the runtime’s own tests in runtime/python/tests/.
Repo: seventeen files in crates/py/python/tests/ — among them
test_smoke.py (seven tests), test_idl.py (forty-four test
functions over the generated fixtures), test_idl_topic.py,
test_qos.py, test_qos_lifecycle_surface.py, test_status_getters.py,
test_writer_lifecycle.py, test_participant_ignore.py,
test_conditions.py, test_read_conditions.py, test_listener.py,
test_aio.py, test_loader_smoke.py,
test_loader_declares_extensibility.py, test_multiproc.py,
test_shapesdemo_interop.py, test_docs.py — plus the ROS-2 tree of
§6.4 and runtime/python/tests/test_wire.py.
Tests: crates/py/python/tests/test_smoke.py::test_version_exposed,
::test_factory_singleton_and_offline_participant,
::test_bytes_topic_and_reader_are_creatable_offline,
::test_shape_topic_matches_vendor_interop_type_name,
::test_shape_constructor_and_repr, ::test_shape_default_shapesize_is_30,
::test_pub_sub_roundtrip_live; the test functions of test_idl.py.
Status: done
§4.2 Rust-side test inventory
Spec: §4 — no Rust test inventory, because without
--features extension-module the crate is a placeholder lib with no
API; spec compliance is verified on the pytest side.
Repo: crates/py/src/lib.rs — the four modules ffi, qos,
listener and conditions hang off
#[cfg(feature = "extension-module")]. There is no tests/
directory in the crate; the only Rust test is
dcps_re_export_compiles, which checks that zerodds-dcps is
reachable as a dependency. Every statement this coverage makes about
behaviour stands on a pytest case.
Tests: crates/py/src/lib.rs::dcps_re_export_compiles
Status: done
§5 Multi-process / cross-vendor
§5.1 Single-process Python ↔︎ Python
Spec: §5 table row 1 — single-process Python ↔︎ Python over
zerodds._core and crates/dcps.
Repo: crates/py/src/ffi.rs (PyFactory::create_participant_fast)
— two participants on the same domain in one process; the matching
runs over the in-process discovery fastpath from crates/dcps.
Tests: crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_take_with_info_returns_sample_info,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_content_filtered_topic_filters_samples
Status: done
§5.2 Multi-process Python ↔︎ Python
Spec: §5 table row 2 — multi-process locally over RTPS/UDP + SPDP/SEDP.
Repo: crates/py/python/tests/test_multiproc.py — two python -c
subprocesses, one BytesTopic, one payload; the subscriber exits 0
with OK as soon as it has it.
Tests: crates/py/python/tests/test_multiproc.py::test_multiproc_python_python_roundtrip
Status: done
§5.3 Cross-vendor Python ↔︎ other languages
Spec: §5 table row 3 — the ShapeType wire is octet-identical to
C++/Rust/Java/C# peers over the shared topic type "ShapeType".
Repo: crates/py/python/tests/_generated/shape.idl and its
generated module shape_gen.py +
crates/py/src/ffi.rs::shape_reference_octets (returns what
zerodds_dcps::interop::ShapeType::encode writes) — the reference the
generated Python codec is held against octet for octet.
Tests: crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet
(7 parameter sets; each case compares the octets written with the Rust
reference from the same process),
crates/py/python/tests/test_idl.py::test_shapetype_byte_roundtrip,
crates/py/python/tests/test_shapesdemo_interop.py::test_shape_type_name_matches_shapesdemo_convention
(the topic type name "ShapeType"). No gate; all three run in the default
pytest run.
Status: partial — the Python ↔︎ Rust octet identity is measured in the tree. For C++, Java and C# there is no measurement in the repo: the interop matrix that carries those columns lives on the bench host and is not part of this tree. Tracked as §5.3b.
§5.3b Octet identity Python ↔︎ C++/Java/C# in the tree
Spec: §5 table row 3 — the ShapeType wire is octet-identical to C++/Java/C# peers.
Repo: —
Tests: —
Status: open — no test case in the repo holds the ShapeType octets the
generated Python codec writes against those of the C++, Java or C# binding.
shape_reference_octets supplies that reference for Rust only. Plan:
produce a golden vector per binding the way
endpoints/python/test_byte_identity.py does for SensorReading, for
ShapeType too, and hold all four codecs against the same vector.
§5.4 Cross-vendor Python ↔︎ Cyclone / Fast DDS ShapesDemo
Spec: §5 table row 4 — same XCDR2-LE convention, same type name, same RTPS 2.5 wire.
Repo: matrix/zerodds-python/cyclone-* and matrix/zerodds-python/fastdds-* — the same measurement against Cyclone and Fast DDS
Tests: the cells of those option sets; plus
crates/py/python/tests/test_shapesdemo_interop.py::test_cross_vendor_participant_discovery_against_ddsperf,
which measures SPDP discovery against Cyclone’s ddsperf and skips
where ddsperf is not on the path
Status: done
§5.5 Generated type over IdlTopic ↔︎ Rust codegen
Spec: §5 table row 5 — octets measured against the Rust reference.
Repo: crates/py/src/ffi.rs (shape_reference_octets) hands the
octets of zerodds_dcps::interop::ShapeType::encode to Python; the
module zerodds-idlc generates from tests/_generated/shape.idl
produces them on the Python side. The comparison is against the codec
compiled into the same module, not against a literal copied out of a
Rust test. wireprobe-python-matrix measures the whole Python column
against the Rust one.
Tests: crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet
(seven shapes, among them an empty and a one-character colour string
and i32::MAX/i32::MIN),
crates/py/python/tests/test_idl.py::test_fixed_member_struct_byte_identical_to_rust_cpp,
crates/py/python/tests/test_idl.py::test_sensor_mixed_fields_roundtrip
Status: done
§6 IDL topic, QoS, async + extended API
§6.1 IdlTopic / IdlWriter / IdlReader
Spec: §6.1 — IdlTopic(participant, name, descriptor) over a
generated TopicDescriptor, yielding IdlWriter[T]/IdlReader[T],
which encode and decode with the generated code.
Repo: crates/py/python/zerodds/topic.py (IdlTopic,
IdlWriter, IdlReader) over DescriptorTopic/DescriptorWriter/
DescriptorReader; the constructor rejects anything but a
TopicDescriptor, and IdlTopic.type_name comes from the descriptor.
Tests: crates/py/python/tests/test_idl_topic.py::test_idl_topic_rejects_a_non_topic_descriptor,
::test_idl_topic_exposes_type_name_from_the_descriptor,
::test_idl_writer_encodes_and_reader_decodes_roundtrip,
::test_idl_writer_rejects_wrong_type, ::test_idl_writer_passthrough_status
Status: done
§6.2 QoS builder (all 22 policies)
Spec: §6.2 — DataWriterQos/DataReaderQos PyClasses with setters
for all twenty-two policies of DDS 1.4 §2.2.3, plus
Publisher.create_*_writer_with_qos /
Subscriber.create_*_reader_with_qos.
Repo: crates/py/src/qos.rs (PyDataWriterQos,
PyDataReaderQos) — setters for Reliability, Durability,
DurabilityService, Deadline, LatencyBudget, Liveliness,
DestinationOrder, Lifespan, Ownership, OwnershipStrength, Partition,
Presentation, History, ResourceLimits, TransportPriority,
WriterDataLifecycle, TimeBasedFilter, ReaderDataLifecycle, UserData,
TopicData, GroupData, each on the side DDS 1.4 §2.2.3 carries it. An
unknown kind string is rejected as a ValueError rather than falling
silently onto a default. The create_*_with_qos constructors live in
crates/py/src/ffi.rs.
Tests: crates/py/python/tests/test_qos.py::test_writer_qos_defaults,
crates/py/python/tests/test_qos.py::test_writer_qos_setters_with_durations,
crates/py/python/tests/test_qos.py::test_reader_qos_full_setter_chain,
crates/py/python/tests/test_qos.py::test_writer_qos_rejects_unknown_reliability_kind,
crates/py/python/tests/test_qos.py::test_create_bytes_writer_with_qos_offline
Status: done
§6.3 AsyncIO API
Spec: §6.3 — zerodds.aio with AsyncBytesWriter,
AsyncBytesReader, AsyncShapeWriter, AsyncShapeReader,
AsyncWaitSet; blocking calls over an asyncio.to_thread bridge.
Repo: crates/py/python/zerodds/aio.py — the five wrappers; every
blocking operation runs through asyncio.to_thread, and the
non-blocking getters pass straight through.
Tests: crates/py/python/tests/test_aio.py::test_async_wrappers_are_constructible,
crates/py/python/tests/test_aio.py::test_async_wait_for_data_uses_event_loop,
crates/py/python/tests/test_aio.py::test_async_take_returns_list_passthrough,
crates/py/python/tests/test_aio.py::test_async_waitset_wait_raises_timeout
Status: done
§6.4 ROS-2 pytest integration
Spec: §6.4 — test tree crates/py/python/tests/ros2/ with a
conftest.py (skip when ROS_DISTRO + rclpy +
RMW_IMPLEMENTATION are missing) and test_rmw_zerodds_interop.py
for standard rclpy publish/subscribe over the ZeroDDS rmw.
Repo: crates/py/python/tests/ros2/conftest.py — marks only the
cases under ros2/ as skipped when ROS_DISTRO, ros2 on the path
or rclpy are missing, and separately when RMW_IMPLEMENTATION does
not name the ZeroDDS rmw;
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py — six cases
over rclpy; crates/rmw-zerodds-shim/rmw_c/run_ros2_pytest.sh — the
reproducible run that builds the ABI layer, registers it into a ROS 2
installation and runs this tree against it.
The spec names the rmw rmw_zerodds_shim; the implementation
identifier per REP-2007 is rmw_zerodds_cpp
(crates/rmw-zerodds-shim/ament/package.xml), and that is what the
conftest.py checks for.
Tests: crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_init_succeeds_with_zerodds_rmw,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_publish_subscribe_string_roundtrip,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_service_call_roundtrip,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_topic_graph_introspection,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_node_names_graph,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_endpoint_info_by_topic
On a host without ROS 2 all six skip with the reason from the
conftest.py; they are measured through run_ros2_pytest.sh on a
ROS 2 installation, see docs/spec-coverage/ros2-rmw.en.md.
Status: done
§6.5 Status listener callbacks
Spec: §6.5 — PyClasses DataWriterListener (3 slots) and
DataReaderListener (6 slots), registered through
writer.set_listener(listener, mask) /
reader.set_listener(listener, mask).
Repo: crates/py/src/listener.rs (PyDataWriterListener,
PyDataReaderListener, PyDataWriterListenerBridge,
PyDataReaderListenerBridge) — three writer slots
(on_offered_deadline_missed, on_liveliness_lost,
on_publication_matched) and six reader slots (on_data_available,
on_sample_lost, on_sample_rejected,
on_requested_deadline_missed, on_liveliness_changed,
on_subscription_matched). The bridge takes the GIL before each call;
an exception inside a callback is printed and not propagated into
Rust. set_listener/clear_listener sit on the four writer and
reader PyClasses in crates/py/src/ffi.rs.
Tests: crates/py/python/tests/test_listener.py::test_writer_listener_constructible,
crates/py/python/tests/test_listener.py::test_reader_listener_constructible,
crates/py/python/tests/test_listener.py::test_set_listener_on_bytes_writer_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_bytes_reader_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_shape_writer_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_shape_reader_offline
Status: done
§6.6 ReadCondition / QueryCondition
Spec: §6.6 — PyClasses ReadCondition and QueryCondition over
the Rust conditions, bitmask constants as
zerodds.{sample_state,view_state,instance_state}, an SQL92 filter
validated in the constructor, WaitSet.attach_read_condition /
attach_query_condition.
Repo: crates/py/src/conditions.rs (PyReadCondition,
PyQueryCondition) over zerodds_dcps::condition;
crates/py/python/zerodds/sample_state.py,
crates/py/python/zerodds/view_state.py,
crates/py/python/zerodds/instance_state.py for the bitmasks that
crates/py/src/ffi.rs sets as module attributes out of
zerodds_dcps::sample_info. An invalid SQL expression is rejected in
the constructor as a RuntimeError.
Tests: crates/py/python/tests/test_read_conditions.py::test_sample_state_constants,
crates/py/python/tests/test_read_conditions.py::test_read_condition_modes,
crates/py/python/tests/test_read_conditions.py::test_query_condition_with_parameters,
crates/py/python/tests/test_read_conditions.py::test_query_condition_invalid_sql_raises,
crates/py/python/tests/test_read_conditions.py::test_query_condition_attaches_to_waitset
Status: done
§6.7 sphinx doc path
Spec: §6.7 — crates/py/docs/conf.py with autodoc/napoleon/
viewcode/intersphinx, autodoc_mock_imports for the build without
maturin, and crates/py/docs/api.rst listing the PyClasses and the
modules loader, topic, aio, sample_state, view_state,
instance_state.
Repo: crates/py/docs/conf.py — the four extensions,
autodoc_mock_imports = ["zerodds._core"] and the version read from
the workspace manifest rather than repeated as a literal;
crates/py/docs/api.rst — every class the extension module exports and
the modules loader, topic, aio and the three state modules;
crates/py/docs/index.rst, crates/py/docs/quickstart.rst,
crates/py/docs/examples.rst.
Tests: crates/py/python/tests/test_docs.py::test_conf_py_declares_the_four_extensions_the_spec_names,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_spec_pyclass,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_pure_python_module,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_extension_class,
crates/py/python/tests/test_docs.py::test_toctree_entries_exist,
crates/py/python/tests/test_docs.py::test_conf_py_is_importable_and_names_the_workspace_version
The tests check the tree’s inventory, not the sphinx-build run.
Status: done
§7 Stability
Spec: §7 — semver policy, v1.0 stable, major bump for breaking changes.
Repo: —
Tests: —
Status: n/a (informative) — versioning commitment, not a property of the binding.
§8 License
Spec: §8 — Apache-2.0 (workspace default).
Repo: —
Tests: —
Status: open — crates/py/pyproject.toml declares license = { text = "Apache-2.0 OR MIT" }, the Cargo workspace Apache-2.0; the spec names Apache-2.0. No test checks the declaration.
§9 References
Spec: §9 — a list of external specs (OMG DDS 1.4, XTypes 1.3,
PSM-Cxx 1.0) and internal vendor specs (zerodds-c-api-1.0,
zerodds-ffi-loader-1.0, zerodds-java-omgdds-1.0) plus PyO3 and
PEP 384.
Repo: —
Tests: —
Status: n/a (informative) — list of references.
Audit status
41 done / 2 partial / 3 open / 0 blocked / 2 n/a (informative).
Test run: PYTHONPATH=runtime/python:crates/py/python python3 -m pytest crates/py/python/tests without test_qos_lifecycle_surface.py (it starts
participants on domains 41–46) — 161 green, 0 failed, 1 xfailed
(test_fixed_array_count_is_refused_on_encode, §3.1), 17 conditionally
skipped (ROS-2 without ROS_DISTRO/rclpy, ShapesDemo without
ddsperf, loader without libzerodds).
cd runtime/python && python3 -m unittest discover -s tests — 125
tests green, 0 failed.
Open items: zerodds-py-1.0.open.en.md.
zerodds-py v1.0 — Spec-Coverage
Audit der Vendor-Spec docs/specs/zerodds-py-1.0.md gegen den heutigen
Baum.
Source: docs/specs/zerodds-py-1.0.md (Vendor-Spec).
crates/py/ ist eine PyO3-Brücke: die DCPS-Entitäten aus crates/dcps/
als Erweiterungsmodul zerodds._core, mit einem Python-Package darüber,
das IdlTopic über generiertem Code und den ctypes-Loader gegen
libzerodds mitbringt. Topic-Typen haben einen Codec: den Code, den
zerodds-idlc generate --lang python erzeugt (das Python-Backend von
crates/lang-rules), über der Wire-Laufzeit in
runtime/python/zerodds_wire/. Das Crate trägt ohne --features extension-module nur die Platzhalter-Lib; das Modul baut maturin.
Daneben steht das XRCE-Endpoint-SDK in
endpoints/python/zerodds_endpoint.py, das diese Spec nicht beschreibt.
Implementation:
crates/py/· docs.rs — PyO3-Brücke (src/) + Python-Package (python/zerodds/)- Test-Baum (
python/tests/) + Beispiele + sphinx-Baum.
- Test-Baum (
§1 Architektur
§1.1 Module-Layout
Spec: §1.1 — Crate-Layout mit Cargo.toml (crate-type cdylib+rlib),
pyproject.toml für maturin, src/lib.rs+ffi.rs,
python/zerodds/{__init__,topic,aio,loader}.py, python/tests/ mit
_generated/, examples/, docs/ sphinx und runtime/python/.
Repo: crates/py/Cargo.toml (crate-type = ["cdylib", "rlib"],
Feature extension-module), crates/py/pyproject.toml
(build-backend = "maturin", python-source = "python",
module-name = "zerodds._core"), crates/py/build.rs
(-undefined dynamic_lookup auf Darwin), crates/py/src/lib.rs,
crates/py/src/ffi.rs, crates/py/python/zerodds/__init__.py,
crates/py/python/zerodds/topic.py, crates/py/python/zerodds/aio.py,
crates/py/python/zerodds/loader.py, crates/py/python/tests/
(Fixtures: tests/_generated/*.idl und ihr zerodds-idlc-Output),
crates/py/examples/, crates/py/docs/conf.py,
runtime/python/zerodds_wire/.
maturin build baut daraus ein abi3-Wheel mit _core.abi3.so und den
Python-Modulen.
Tests: crates/py/python/tests/test_docs.py::test_examples_parse,
crates/py/python/tests/test_docs.py::test_literalincludes_point_at_existing_examples,
crates/py/python/tests/test_smoke.py::test_version_exposed
Status: done
§1.2 PyO3-Modul zerodds._core — PyClasses
Pro PyClass ein Item. Mapping aus DDS 1.4 §2.2.2.
§1.2.1 DomainParticipantFactory (PyClass)
Spec: §1.2 + §2.1 — PyClass DomainParticipantFactory mit
instance(), create_participant(domain_id),
create_participant_offline(domain_id),
create_participant_fast(domain_id).
Repo: crates/py/src/ffi.rs (PyFactory) — instance als
#[staticmethod], die drei Konstruktoren über
zerodds_dcps::DomainParticipantFactory; create_participant_fast
setzt RuntimeConfig { tick_period: 20 ms, spdp_period: 100 ms }.
Tests: crates/py/python/tests/test_smoke.py::test_factory_singleton_and_offline_participant,
crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live,
crates/py/python/tests/test_loader_smoke.py::test_loader_factory_singleton
Status: done
§1.2.2 DomainParticipant (PyClass)
Spec: §1.2 + §2.2 — PyClass DomainParticipant mit domain_id,
topics_len, discovered_participants_count,
create_{bytes,shape}_topic, create_{publisher,subscriber},
assert_liveliness, ignore_{participant,topic,publication,subscription},
contains_entity, get_discovered_{topics,participants}.
Repo: crates/py/src/ffi.rs (PyParticipant) — zusätzlich
create_keyed_topic und create_bytes_contentfilteredtopic;
die vier ignore_* und contains_entity nehmen den u64 einer
InstanceHandle.
Tests: crates/py/python/tests/test_participant_ignore.py::test_ignore_participant_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_contains_entity_false_for_unknown_handle,
crates/py/python/tests/test_participant_ignore.py::test_get_discovered_participants_offline_is_empty,
crates/py/python/tests/test_participant_ignore.py::test_assert_liveliness_offline_does_not_raise
Status: done
§1.2.3 BytesTopic (PyClass)
Spec: §1.2 — PyClass BytesTopic mit name, type_name.
Repo: crates/py/src/ffi.rs (PyBytesTopic) über
Topic<RawBytes>; type_name liest
<RawBytes as DdsType>::TYPE_NAME und ist damit an die Rust-Seite
gebunden statt neu geschrieben.
Tests: crates/py/python/tests/test_smoke.py::test_bytes_topic_and_reader_are_creatable_offline
Status: done
§1.2.4 ShapeTopic (PyClass)
Spec: §1.2 + §2.7 — PyClass ShapeTopic mit name, type_name,
Type-Name = "ShapeType".
Repo: crates/py/src/ffi.rs (PyShapeTopic) über
Topic<ShapeType>; type_name liest
<ShapeType as DdsType>::TYPE_NAME aus crates/dcps/src/interop.rs.
Tests: crates/py/python/tests/test_smoke.py::test_shape_topic_matches_vendor_interop_type_name,
crates/py/python/tests/test_shapesdemo_interop.py::test_shape_type_name_matches_shapesdemo_convention
Status: done
§1.2.5 Publisher (PyClass)
Spec: §1.2 + §2.3 — PyClass Publisher mit
create_bytes_writer(topic), create_shape_writer(topic).
Repo: crates/py/src/ffi.rs (PyPublisher) — dazu die
*_with_qos-Varianten aus §6.2 und create_keyed_writer.
Tests: crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live,
crates/py/python/tests/test_qos.py::test_create_bytes_writer_with_qos_offline,
crates/py/python/tests/test_qos.py::test_create_shape_writer_with_qos_offline
Status: done
§1.2.6 Subscriber (PyClass)
Spec: §1.2 + §2.3 — PyClass Subscriber mit
create_bytes_reader(topic), create_shape_reader(topic).
Repo: crates/py/src/ffi.rs (PySubscriber) — dazu die
*_with_qos-Varianten, create_keyed_reader und
create_bytes_reader_cft für den Content-Filter aus §2.2.2.5.4.
Tests: crates/py/python/tests/test_smoke.py::test_bytes_topic_and_reader_are_creatable_offline,
crates/py/python/tests/test_qos.py::test_create_bytes_reader_with_qos_offline,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_content_filtered_topic_filters_samples
Status: done
§1.2.7 BytesWriter (PyClass)
Spec: §1.2 + §2.4 — PyClass BytesWriter mit write,
wait_for_matched_subscription, matched_subscription_count,
publication_matched_status, liveliness_lost_status,
offered_deadline_missed_status.
Repo: crates/py/src/ffi.rs (PyBytesWriter) — dazu
register_instance, lookup_instance, dispose,
unregister_instance und set_listener/clear_listener. write und
die drei Wartefunktionen geben den GIL über py.detach frei.
Tests: crates/py/python/tests/test_status_getters.py::test_publication_matched_status_shape,
crates/py/python/tests/test_status_getters.py::test_liveliness_lost_status_shape,
crates/py/python/tests/test_status_getters.py::test_offered_deadline_missed_status_shape,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_writer_lifecycle_methods_present
Status: done
§1.2.8 BytesReader (PyClass)
Spec: §1.2 + §2.5 — PyClass BytesReader mit take,
wait_for_data, wait_for_matched_publication,
matched_publication_count, subscription_matched_status,
sample_lost_status, requested_deadline_missed_status.
Repo: crates/py/src/ffi.rs (PyBytesReader) — dazu
take_with_info / read_with_info, die (bytes, SampleInfo)-Paare
liefern, liveliness_changed_status und
set_listener/clear_listener.
Tests: crates/py/python/tests/test_status_getters.py::test_subscription_matched_status_shape,
crates/py/python/tests/test_status_getters.py::test_sample_lost_status_shape,
crates/py/python/tests/test_status_getters.py::test_requested_deadline_missed_status_shape,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_take_with_info_returns_sample_info
Status: done
§1.2.9 Shape (PyClass / Dataclass)
Spec: §1.2 + §2.7 — PyClass Shape mit Feldern color, x, y,
shapesize (default 30), __repr__, Konvertierungen From<&PyShape>
und From<ShapeType> in beide Richtungen.
Repo: crates/py/src/ffi.rs (PyShape) mit
#[pyo3(signature = (color, x, y, shapesize=30))] und den beiden
From-Implementierungen gegen zerodds_dcps::interop::ShapeType.
Tests: crates/py/python/tests/test_smoke.py::test_shape_constructor_and_repr,
crates/py/python/tests/test_smoke.py::test_shape_default_shapesize_is_30
Status: done
§1.2.10 ShapeWriter (PyClass)
Spec: §1.2 + §2.4 — PyClass ShapeWriter mit write(shape),
register_instance(shape), dispose(shape),
unregister_instance(shape), wait_for_matched_subscription.
Repo: crates/py/src/ffi.rs (PyShapeWriter) — dispose und
unregister_instance registrieren die Instanz nach, wenn
lookup_instance nil liefert, damit immer ein gültiges Handle
vorliegt (Spec §2.2.2.4.2.4).
Tests: crates/py/python/tests/test_writer_lifecycle.py::test_register_instance_returns_nonzero_handle,
crates/py/python/tests/test_writer_lifecycle.py::test_register_instance_is_idempotent_for_same_key,
crates/py/python/tests/test_writer_lifecycle.py::test_lifecycle_full_sequence
Status: done
§1.2.11 ShapeReader (PyClass)
Spec: §1.2 + §2.5 — PyClass ShapeReader mit take,
wait_for_data, wait_for_matched_publication.
Repo: crates/py/src/ffi.rs (PyShapeReader) — dazu
take_with_info / read_with_info und
liveliness_changed_status.
Tests: crates/py/python/tests/test_status_getters.py::test_matched_publication_count_zero_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_shape_reader_offline
Status: done
§1.2.12 GuardCondition (PyClass)
Spec: §1.2 + §2.6 — PyClass GuardCondition mit Konstruktor,
set_trigger_value(bool), get_trigger_value() -> bool.
Repo: crates/py/src/ffi.rs (PyGuardCondition) über
zerodds_dcps::condition::GuardCondition.
Tests: crates/py/python/tests/test_conditions.py::test_guard_condition_default_trigger_is_false,
crates/py/python/tests/test_conditions.py::test_guard_condition_set_trigger_roundtrip,
crates/py/python/tests/test_conditions.py::test_guard_condition_via_outer_namespace
Status: done
§1.2.13 WaitSet (PyClass)
Spec: §1.2 + §2.6 — PyClass WaitSet mit Konstruktor,
attach_guard_condition(gc), wait(timeout_secs) -> int.
Repo: crates/py/src/ffi.rs (PyWaitSet) über
zerodds_dcps::condition::WaitSet; wait gibt den GIL frei und wirft
TimeoutError, wenn nichts triggert. attach_read_condition und
attach_query_condition kommen aus §6.6 dazu.
Tests: crates/py/python/tests/test_conditions.py::test_waitset_attach_and_wait_raises_timeout,
crates/py/python/tests/test_conditions.py::test_waitset_wakes_when_guard_condition_fires,
crates/py/python/tests/test_conditions.py::test_waitset_via_outer_namespace
Status: done
§1.3 Python-Wrapper python/zerodds/
§1.3.1 __init__.py — Re-Export
Spec: §1.3 — zerodds.__init__ re-exportiert die _core-PyClasses
und IdlTopic/IdlWriter/IdlReader aus topic.py.
Repo: crates/py/python/zerodds/__init__.py — re-exportiert die
PyClasses samt __version__, die Module sample_state, view_state,
instance_state und IdlTopic/IdlWriter/IdlReader aus topic.py.
Fehlt
das Erweiterungsmodul, setzt _CORE_AVAILABLE = False und die
PyClasses werden zu Platzhaltern, die beim Instanziieren einen
ImportError mit der maturin-Zeile werfen — die pure-Python-Module
bleiben ohne Rust-Build benutzbar.
Tests: crates/py/python/tests/test_smoke.py::test_version_exposed,
crates/py/python/tests/test_conditions.py::test_guard_condition_via_outer_namespace,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_extension_class
Status: done
§1.3.2 topic.py — IdlTopic über einem generierten TopicDescriptor
Spec: §1.3 + §6.1 — IdlTopic(participant, name, descriptor) nimmt
nur einen zerodds_wire.topic_descriptor.TopicDescriptor an und legt das
Topic mit CompleteTypeObject, Schlüssel und Extensibility des Typs an;
IdlWriter/IdlReader kodieren und dekodieren mit dem generierten Code.
Repo: crates/py/python/zerodds/topic.py (IdlTopic, IdlWriter,
IdlReader) über DomainParticipant.create_descriptor_topic und die
PyClasses DescriptorTopic/DescriptorWriter/DescriptorReader in
crates/py/src/ffi.rs.
Tests: crates/py/python/tests/test_idl_topic.py::test_idl_topic_rejects_a_non_topic_descriptor,
::test_idl_topic_exposes_type_name_from_the_descriptor,
::test_idl_writer_encodes_and_reader_decodes_roundtrip,
::test_idl_writer_rejects_wrong_type,
::test_idl_writer_passthrough_status
Status: done
§1.3.3 runtime/python — zerodds_wire
Spec: §1 + §3 — die Wire-Laufzeit, auf die der generierte Code aufsetzt.
Repo: runtime/python/zerodds_wire/ (Writer, Reader,
TopicDescriptor, OutOfRange, Encapsulation und Byte-Ordnung) —
Alignment auf min(N, MAXALIGN) mit MAXALIGN vier unter XCDR2 und acht
unter XCDR1, DHEADER- und EMHEADER-Framing, PL_CDR1-Parameter.
Tests: runtime/python/tests/test_wire.py,
crates/py/python/tests/test_idl.py::test_wire_primitive_roundtrip,
::test_wire_string_alignment_padding,
::test_wire_reader_rejects_truncated_string,
::test_xcdr2_max_align_caps_double_at_four
Status: done
§1.3.4 loader.py — pure-ctypes-Loader
Spec: §1.3 + §1.4 — Pure-ctypes-Loader gegen
libzerodds.{so,dylib,dll} aus crates/zerodds-c-api/. Folgt
zerodds-ffi-loader-1.0 §3.1. Signaturen aus
crates/zerodds-c-api/include/zerodds.h.
Repo: crates/py/python/zerodds/loader.py (load_library,
Runtime, Writer, Reader, DomainParticipantFactory,
ZeroDdsError) — Auflösung über ZERODDS_LIB, das
Wheel-interne _lib/, target/{debug,release} im Entwicklungsbaum
und zuletzt den System-Linker.
Tests: crates/py/python/tests/test_loader_smoke.py::test_loader_resolves_library,
crates/py/python/tests/test_loader_smoke.py::test_loader_runtime_create_participant_offline,
crates/py/python/tests/test_loader_smoke.py::test_loader_zerodds_error_is_runtime_error_subclass
Die vier Tests überspringen, wenn keine libzerodds gefunden wird
(cargo build -p zerodds-c-api oder ZERODDS_LIB).
Status: done
§1.4 Zwei-Pfad-Wahl
Spec: §1.4 — Zwei unabhängige Pfade: PyO3 (Performance,
RTPS-native) und pure-ctypes (Distro-Package, Zero-Build).
Repo: crates/py/src/ffi.rs für den PyO3-Pfad,
crates/py/python/zerodds/loader.py für den ctypes-Pfad. Die beiden
teilen nur Wire-Format und Domain-ID: loader.py importiert
_core nicht und läuft ohne Rust-Toolchain, ffi.rs kennt
libzerodds nicht.
Tests: crates/py/python/tests/test_loader_smoke.py::test_loader_factory_singleton,
crates/py/python/tests/test_smoke.py::test_factory_singleton_and_offline_participant
Status: done
Nicht-Ziel — RTPS direkt in reinem Python
Spec: Nicht-Ziele — „Direct RTPS wire in pure Python: the pure Python path goes via the ctypes loader bridge to the Rust library. There is no pure-Python RTPS datagram in v1.0 or v2.0.“
Repo: —
Tests: —
Status: open — der ctypes-Pfad braucht libzerodds; runtime/python/zerodds_wire kodiert CDR, sendet aber kein RTPS.
§1.5 Schichten-Position
Spec: §1.5 — Layer 6 (PSMs / Bindings). Direkte Abhängigkeiten:
zerodds-dcps (Layer 4), zerodds-c-api (Layer 6 C-FFI).
Repo: crates/py/Cargo.toml — die Rust-Abhängigkeiten sind
zerodds-dcps, zerodds-qos und zerodds-wire; auf
crates/zerodds-c-api/ greift der ctypes-Pfad zur Laufzeit über den
Loader zu, ohne Manifest-Kante. crates/py/src/lib.rs nennt die
Schicht im Crate-Doc.
Tests: crates/py/src/lib.rs::dcps_re_export_compiles
Status: done
§2 OMG-API-Coverage (DDS 1.4 §2.2.2)
§2.1 DomainParticipantFactory (DDS 1.4 §2.2.2.2.1)
Spec: §2.1 — Mapping-Tabelle: get_instance(),
create_participant, create_participant_offline, implizites
delete_participant via Python-GC.
Repo: crates/py/src/ffi.rs (PyFactory) —
DomainParticipantFactory.instance() als classmethod-artige
#[staticmethod], create_participant und
create_participant_offline je auf die gleichnamige Rust-Operation.
Das Löschen hängt am Python-GC: PyParticipant hält den
DomainParticipant, dessen Drop die Rust-Seite abbaut.
Tests: crates/py/python/tests/test_smoke.py::test_factory_singleton_and_offline_participant,
crates/py/python/tests/test_multiproc.py::test_multiproc_python_python_roundtrip
Status: done
§2.2 DomainParticipant (DDS 1.4 §2.2.2.2.2)
Spec: §2.2 — 12 Operations: get_domain_id, create_topic,
create_publisher, create_subscriber, assert_liveliness,
ignore_{participant,topic,publication,subscription},
contains_entity, get_discovered_{topics,participants}.
Repo: crates/py/src/ffi.rs (PyParticipant) — alle zwölf.
get_domain_id ist das Property domain_id, create_topic sind die
drei getypten Konstruktoren create_bytes_topic,
create_shape_topic und create_keyed_topic.
Tests: crates/py/python/tests/test_participant_ignore.py::test_ignore_topic_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_ignore_publication_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_ignore_subscription_unknown_handle_is_silent,
crates/py/python/tests/test_participant_ignore.py::test_get_discovered_topics_offline_is_empty
Status: done
§2.3 Publisher / Subscriber (DDS 1.4 §2.2.2.4.1 / §2.2.2.5.1)
Spec: §2.3 — Publisher.create_datawriter →
pub.create_{bytes,shape}_writer(topic),
Subscriber.create_datareader →
sub.create_{bytes,shape}_reader(topic).
Repo: crates/py/src/ffi.rs (PyPublisher, PySubscriber) —
je Topic-Typ ein Konstruktor, jeweils mit *_with_qos-Variante.
Tests: crates/py/python/tests/test_qos.py::test_create_shape_reader_with_qos_offline,
crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live
Status: done
§2.4 DataWriter (DDS 1.4 §2.2.2.4.2)
Spec: §2.4 — Mapping-Tabelle: write, register_instance,
unregister_instance, dispose, get_matched_subscriptions,
get_publication_matched_status, get_liveliness_lost_status,
get_offered_deadline_missed_status plus
wait_for_matched_subscription sync-helper.
Repo: crates/py/src/ffi.rs (PyBytesWriter, PyShapeWriter,
PyKeyedWriter) — get_matched_subscriptions ist
matched_subscription_count, die drei Status-Getter liefern die
Tupel der Spec-Struktur.
Tests: crates/py/python/tests/test_writer_lifecycle.py::test_dispose_does_not_raise,
crates/py/python/tests/test_writer_lifecycle.py::test_unregister_instance_does_not_raise,
crates/py/python/tests/test_status_getters.py::test_matched_subscription_count_zero_offline,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_keyed_register_instance_returns_real_handle
Status: done
§2.5 DataReader (DDS 1.4 §2.2.2.5.3)
Spec: §2.5 — take, wait_for_data, wait_for_matched_publication,
matched_publication_count, subscription_matched_status,
sample_lost_status, requested_deadline_missed_status. read
und wait_for_historical_data bewusst weggelassen.
Repo: crates/py/src/ffi.rs (PyBytesReader, PyShapeReader,
PyKeyedReader). Über die Spec-Liste hinaus liegen
read_with_info und take_with_info an, die je Sample die
SampleInfo mitliefern — read ohne SampleInfo bleibt weg, wie die
Spec es sagt.
Tests: crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_take_with_info_returns_sample_info,
crates/py/python/tests/test_status_getters.py::test_subscription_matched_status_shape,
crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live
Status: done
§2.6 WaitSet / Conditions (DDS 1.4 §2.2.2.6)
Spec: §2.6 — WaitSet, GuardCondition mit
attach_guard_condition, wait, set/get_trigger_value.
ReadCondition/QueryCondition sind über §6.6 verfügbar.
Repo: crates/py/src/ffi.rs (PyGuardCondition, PyWaitSet),
crates/py/src/conditions.rs für die beiden Reader-Conditions.
Die Spec sagt in §2.6, das äussere zerodds-Namespace re-exportiere
WaitSet und GuardCondition noch nicht, und listet beide in §1.3 im
Re-Export-Block. crates/py/python/zerodds/__init__.py folgt §1.3:
beide sind über zerodds. und über zerodds._core. erreichbar, was
den §2.6-Zugang einschliesst.
Tests: crates/py/python/tests/test_conditions.py::test_waitset_wakes_when_guard_condition_fires,
crates/py/python/tests/test_conditions.py::test_waitset_via_outer_namespace,
crates/py/python/tests/test_read_conditions.py::test_read_condition_attaches_to_waitset
Status: done
§2.7 ShapeType — Cross-Vendor-Interop-Type
Spec: §2.7 — Shape-PyClass byte-identisch zu ShapeType in
crates/dcps/src/interop.rs. Felder color: String, x: i32, y: i32, shapesize: i32 (default 30). Type-Name auf dem Wire = "ShapeType".
Repo: crates/py/src/ffi.rs (PyShape, shape_reference_octets)
— die Konvertierung geht über zerodds_dcps::interop::ShapeType
selbst, es gibt keinen zweiten Codec auf der Rust-Seite.
shape_reference_octets gibt die Oktette dieses Codecs nach Python
zurück, damit der generierte Python-Code gegen ihn gemessen werden kann.
Tests: crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet,
crates/py/python/tests/test_smoke.py::test_shape_topic_matches_vendor_interop_type_name,
crates/py/python/tests/test_shapesdemo_interop.py::test_shape_type_name_matches_shapesdemo_convention
Status: done
§3 IDL-Mapping — generierter Code über zerodds_wire
§3.1 Mapping
Spec: §3.1 — zerodds-idlc generate --lang python erzeugt je Typ
eine Dataclass, ihre Kodierfunktionen und ZD_TOPIC_DESCRIPTORS; die
Mapping-Tabelle (Ganzzahlen, Gleitkomma, Strings, sequence, T[N],
map, @optional, enum, union, struct); ein beschränkter Wert
über seiner Schranke wird mit OutOfRange abgewiesen, bevor ein Byte
geschrieben ist; der Decoder wendet @try_construct an (XTypes 1.3
§7.2.2.4.4.4.9).
Repo: crates/lang-rules/src/adapter/python.rs (das Python-Mapping),
crates/lang-rules/src/emit/encode.rs (der gemeinsame Codec-Walk samt
Schrankenprüfung), tools/idlc (generate --lang python),
runtime/python/zerodds_wire/.
Tests: crates/py/python/tests/test_idl.py::test_sensor_mixed_fields_roundtrip,
::test_optional_present_and_absent, ::test_enum_roundtrip,
::test_enum_unknown_value_raises, ::test_union_case_int_roundtrip,
::test_union_default_branch_used_for_unknown_disc,
::test_nested_struct_roundtrip, ::test_fixed_array_roundtrip,
::test_generated_bounds_roundtrip_and_enforced
Status: partial — die Elementzahl eines festen Arrays prüft der
Encoder nicht: zu viele Elemente werden gekürzt, zu wenige werfen
IndexError (test_fixed_array_count_is_refused_on_encode,
xfail(strict=True)).
§3.2 Beispiel
Spec: §3.2 — IDL, generiertes Modul, IdlTopic über seinem
Deskriptor.
Repo: crates/py/examples/03_idl_topic.idl,
crates/py/examples/03_idl_topic_gen.py (der zerodds-idlc-Output),
crates/py/examples/03_idl_topic.py.
Tests: crates/py/python/tests/test_docs.py::test_examples_parse,
crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet
Status: done
§4 Test-Pflicht
§4.1 Test-Inventar
Spec: §4 — pytest über crates/py/python/tests/, Fixtures in
tests/_generated/ als IDL plus ihr unveränderter zerodds-idlc-Output,
die eigenen Tests der Laufzeit in runtime/python/tests/.
Repo: siebzehn Dateien in crates/py/python/tests/ — darunter
test_smoke.py (sieben Tests), test_idl.py (vierundvierzig
Testfunktionen über den generierten Fixtures), test_idl_topic.py,
test_qos.py, test_qos_lifecycle_surface.py, test_status_getters.py,
test_writer_lifecycle.py, test_participant_ignore.py,
test_conditions.py, test_read_conditions.py, test_listener.py,
test_aio.py, test_loader_smoke.py,
test_loader_declares_extensibility.py, test_multiproc.py,
test_shapesdemo_interop.py, test_docs.py — dazu der ROS-2-Baum aus
§6.4 und runtime/python/tests/test_wire.py.
Tests: crates/py/python/tests/test_smoke.py::test_version_exposed,
::test_factory_singleton_and_offline_participant,
::test_bytes_topic_and_reader_are_creatable_offline,
::test_shape_topic_matches_vendor_interop_type_name,
::test_shape_constructor_and_repr, ::test_shape_default_shapesize_is_30,
::test_pub_sub_roundtrip_live; die Testfunktionen von test_idl.py.
Status: done
§4.2 Rust-seitiges Test-Inventar
Spec: §4 — kein Rust-Test-Inventar, weil das Crate ohne
--features extension-module eine Platzhalter-Lib ohne API ist; die
Spec-Compliance wird pytest-seitig verifiziert.
Repo: crates/py/src/lib.rs — die vier Module ffi, qos,
listener und conditions hängen an #[cfg(feature = "extension-module")]. Es gibt kein tests/-Verzeichnis im Crate; der
einzige Rust-Test ist dcps_re_export_compiles, der prüft, dass
zerodds-dcps als Abhängigkeit erreichbar ist. Jede Aussage dieser
Coverage über Verhalten steht auf einem pytest-Fall.
Tests: crates/py/src/lib.rs::dcps_re_export_compiles
Status: done
§5 Multi-Process / Cross-Vendor
§5.1 Single-Process Python ↔︎ Python
Spec: §5 Tabelle Row 1 — Single-Process Python ↔︎ Python über
zerodds._core und crates/dcps.
Repo: crates/py/src/ffi.rs (PyFactory::create_participant_fast)
— zwei Participants derselben Domain in einem Prozess; das Matching
läuft über den In-Prozess-Discovery-Fastpath aus crates/dcps.
Tests: crates/py/python/tests/test_smoke.py::test_pub_sub_roundtrip_live,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_bytes_take_with_info_returns_sample_info,
crates/py/python/tests/test_qos_lifecycle_surface.py::test_content_filtered_topic_filters_samples
Status: done
§5.2 Multi-Process Python ↔︎ Python
Spec: §5 Tabelle Row 2 — Multi-Process lokal über RTPS/UDP + SPDP/SEDP.
Repo: crates/py/python/tests/test_multiproc.py — zwei
python -c-Subprozesse, ein BytesTopic, eine Nutzlast; der
Subscriber beendet sich mit 0 und OK, sobald er sie hat.
Tests: crates/py/python/tests/test_multiproc.py::test_multiproc_python_python_roundtrip
Status: done
§5.3 Cross-Vendor Python ↔︎ andere Sprachen
Spec: §5 Tabelle Row 3 — ShapeType-Wire byte-identisch zu
C++/Rust/Java/C#-Peers über gemeinsamen Topic-Type "ShapeType".
Repo: crates/py/python/tests/_generated/shape.idl und das daraus
generierte Modul shape_gen.py +
crates/py/src/ffi.rs::shape_reference_octets (gibt zurück, was
zerodds_dcps::interop::ShapeType::encode schreibt) — die Referenz, gegen
die der generierte Python-Codec Oktett für Oktett gehalten wird.
Tests: crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet
(7 Parameter-Sätze, jeder Fall vergleicht die geschriebenen Oktetten mit
der Rust-Referenz aus demselben Prozess),
crates/py/python/tests/test_idl.py::test_shapetype_byte_roundtrip,
crates/py/python/tests/test_shapesdemo_interop.py::test_shape_type_name_matches_shapesdemo_convention
(Topic-Type-Name "ShapeType"). Kein Gate; alle drei laufen im
Default-Lauf von pytest.
Status: partial — die Byte-Identität Python ↔︎ Rust ist im Baum gemessen. Für C++, Java und C# gibt es keine Messung im Repo: die Interop-Matrix, die diese Spalten trägt, liegt auf dem Bench-Host und ist nicht Teil dieses Baums. Rest als §5.3b geführt.
§5.3b Byte-Identität Python ↔︎ C++/Java/C# im Baum
Spec: §5 Tabelle Row 3 — ShapeType-Wire byte-identisch zu C++/Java/C#-Peers.
Repo: —
Tests: —
Status: open — es gibt keinen Testfall im Repo, der die vom
Python-Codec geschriebenen ShapeType-Oktetten gegen die der C++-, Java-
oder C#-Bindung hält. shape_reference_octets liefert diese Referenz
bisher nur für Rust. Plan: einen Golden-Vektor je Bindung wie in
endpoints/python/test_byte_identity.py (dort für SensorReading) auch
für ShapeType erzeugen und die vier Codecs gegen denselben Vektor
halten.
§5.4 Cross-Vendor Python ↔︎ Cyclone-/Fast-DDS-ShapesDemo
Spec: §5 Tabelle Row 4 — gleiche XCDR2-LE-Konvention + gleicher Type-Name + RTPS-2.5-Wire.
Repo: matrix/zerodds-python/cyclone-* und matrix/zerodds-python/fastdds-* — dieselbe Messung gegen Cyclone und Fast DDS
Tests: die Zellen dieser Optionssätze; dazu
crates/py/python/tests/test_shapesdemo_interop.py::test_cross_vendor_participant_discovery_against_ddsperf,
das die SPDP-Entdeckung gegen Cyclones ddsperf misst und
überspringt, wo ddsperf nicht im Pfad liegt
Status: done
§5.5 Generierter Typ über IdlTopic ↔︎ Rust-Codegen
Spec: §5 Tabelle Row 5 — Oktetten gegen die Rust-Referenz gemessen.
Repo: crates/py/src/ffi.rs (shape_reference_octets) reicht die
Oktetten von zerodds_dcps::interop::ShapeType::encode an Python; das
Modul, das zerodds-idlc aus tests/_generated/shape.idl erzeugt,
schreibt sie auf der Python-Seite. Verglichen wird mit dem Codec, der im
selben Modul kompiliert ist, nicht mit einem aus einem Rust-Test
kopierten Literal. wireprobe-python-matrix misst die ganze
Python-Spalte gegen die Rust-Spalte.
Tests: crates/py/python/tests/test_idl.py::test_shapetype_matches_the_rust_codec_octet_for_octet
(sieben Shapes, darunter eine leere und eine einbuchstabige Farbe sowie
i32::MAX/i32::MIN),
crates/py/python/tests/test_idl.py::test_fixed_member_struct_byte_identical_to_rust_cpp,
crates/py/python/tests/test_idl.py::test_sensor_mixed_fields_roundtrip
Status: done
§6 IDL-Topic, QoS, Async + erweiterte API
§6.1 IdlTopic / IdlWriter / IdlReader
Spec: §6.1 — IdlTopic(participant, name, descriptor) über einem
generierten TopicDescriptor, liefert IdlWriter[T]/IdlReader[T], die
mit dem generierten Code kodieren und dekodieren.
Repo: crates/py/python/zerodds/topic.py (IdlTopic, IdlWriter,
IdlReader) über DescriptorTopic/DescriptorWriter/DescriptorReader;
der Konstruktor weist alles ausser einem TopicDescriptor ab, und
IdlTopic.type_name kommt aus dem Deskriptor.
Tests: crates/py/python/tests/test_idl_topic.py::test_idl_topic_rejects_a_non_topic_descriptor,
::test_idl_topic_exposes_type_name_from_the_descriptor,
::test_idl_writer_encodes_and_reader_decodes_roundtrip,
::test_idl_writer_rejects_wrong_type, ::test_idl_writer_passthrough_status
Status: done
§6.2 QoS-Builder (alle 22 Policies)
Spec: §6.2 — DataWriterQos/DataReaderQos-PyClass mit Settern
für alle 22 Policies aus DDS 1.4 §2.2.3 plus
Publisher.create_*_writer_with_qos /
Subscriber.create_*_reader_with_qos.
Repo: crates/py/src/qos.rs (PyDataWriterQos,
PyDataReaderQos) — Setter für Reliability, Durability,
DurabilityService, Deadline, LatencyBudget, Liveliness,
DestinationOrder, Lifespan, Ownership, OwnershipStrength, Partition,
Presentation, History, ResourceLimits, TransportPriority,
WriterDataLifecycle, TimeBasedFilter, ReaderDataLifecycle, UserData,
TopicData, GroupData, je auf der Seite, auf der DDS 1.4 §2.2.3 sie
führt. Ein unbekannter Kind-String wird als ValueError abgewiesen
statt still auf einen Default zu fallen. Die
create_*_with_qos-Konstruktoren liegen in crates/py/src/ffi.rs.
Tests: crates/py/python/tests/test_qos.py::test_writer_qos_defaults,
crates/py/python/tests/test_qos.py::test_writer_qos_setters_with_durations,
crates/py/python/tests/test_qos.py::test_reader_qos_full_setter_chain,
crates/py/python/tests/test_qos.py::test_writer_qos_rejects_unknown_reliability_kind,
crates/py/python/tests/test_qos.py::test_create_bytes_writer_with_qos_offline
Status: done
§6.3 AsyncIO-API
Spec: §6.3 — zerodds.aio mit AsyncBytesWriter,
AsyncBytesReader, AsyncShapeWriter, AsyncShapeReader,
AsyncWaitSet; blockierende Aufrufe über eine
asyncio.to_thread-Brücke.
Repo: crates/py/python/zerodds/aio.py — die fünf Wrapper; jede
blockierende Operation läuft über asyncio.to_thread, die
nicht-blockierenden Getter gehen direkt durch.
Tests: crates/py/python/tests/test_aio.py::test_async_wrappers_are_constructible,
crates/py/python/tests/test_aio.py::test_async_wait_for_data_uses_event_loop,
crates/py/python/tests/test_aio.py::test_async_take_returns_list_passthrough,
crates/py/python/tests/test_aio.py::test_async_waitset_wait_raises_timeout
Status: done
§6.4 ROS-2-pytest-Integration
Spec: §6.4 — Test-Baum crates/py/python/tests/ros2/ mit
conftest.py (Skip, wenn ROS_DISTRO + rclpy +
RMW_IMPLEMENTATION fehlen) und test_rmw_zerodds_interop.py für
Standard-rclpy-Publish/Subscribe über das ZeroDDS-rmw.
Repo: crates/py/python/tests/ros2/conftest.py — markiert
ausschließlich die Fälle unterhalb ros2/ als übersprungen, wenn
ROS_DISTRO, ros2 im Pfad oder rclpy fehlen, und getrennt davon,
wenn RMW_IMPLEMENTATION nicht auf dem ZeroDDS-rmw steht;
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py — sechs
Fälle über rclpy;
crates/rmw-zerodds-shim/rmw_c/run_ros2_pytest.sh — der
reproduzierbare Lauf, der die ABI-Schicht baut, sie in eine
ROS-2-Installation registriert und diesen Baum darauf ausführt.
Die Spec nennt den RMW-Namen rmw_zerodds_shim; der
Implementierungs-Identifier nach REP-2007 ist
rmw_zerodds_cpp (crates/rmw-zerodds-shim/ament/package.xml), und
danach prüft die conftest.py.
Tests: crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_init_succeeds_with_zerodds_rmw,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_publish_subscribe_string_roundtrip,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_service_call_roundtrip,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_topic_graph_introspection,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_node_names_graph,
crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_endpoint_info_by_topic
Auf einem Host ohne ROS 2 überspringen alle sechs mit der Begründung
aus der conftest.py; gemessen werden sie über
run_ros2_pytest.sh auf einer ROS-2-Installation, siehe
docs/spec-coverage/ros2-rmw.md.
Status: done
§6.5 Status-Listener-Callbacks
Spec: §6.5 — PyClasses DataWriterListener (3 Slots) und
DataReaderListener (6 Slots), registriert über
writer.set_listener(listener, mask) /
reader.set_listener(listener, mask).
Repo: crates/py/src/listener.rs (PyDataWriterListener,
PyDataReaderListener, PyDataWriterListenerBridge,
PyDataReaderListenerBridge) — drei Writer-Slots
(on_offered_deadline_missed, on_liveliness_lost,
on_publication_matched) und sechs Reader-Slots
(on_data_available, on_sample_lost, on_sample_rejected,
on_requested_deadline_missed, on_liveliness_changed,
on_subscription_matched). Die Brücke nimmt den GIL vor jedem
Aufruf; eine Exception im Callback wird gedruckt und nicht nach Rust
propagiert. set_listener/clear_listener liegen an den vier
Writer-/Reader-PyClasses in crates/py/src/ffi.rs.
Tests: crates/py/python/tests/test_listener.py::test_writer_listener_constructible,
crates/py/python/tests/test_listener.py::test_reader_listener_constructible,
crates/py/python/tests/test_listener.py::test_set_listener_on_bytes_writer_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_bytes_reader_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_shape_writer_offline,
crates/py/python/tests/test_listener.py::test_set_listener_on_shape_reader_offline
Status: done
§6.6 ReadCondition / QueryCondition
Spec: §6.6 — PyClasses ReadCondition und QueryCondition über
die Rust-Conditions, Bitmask-Konstanten als
zerodds.{sample_state,view_state,instance_state}, SQL92-Filter im
Konstruktor validiert, WaitSet.attach_read_condition /
attach_query_condition.
Repo: crates/py/src/conditions.rs (PyReadCondition,
PyQueryCondition) über zerodds_dcps::condition;
crates/py/python/zerodds/sample_state.py,
crates/py/python/zerodds/view_state.py,
crates/py/python/zerodds/instance_state.py für die Bitmasken, die
crates/py/src/ffi.rs als Modul-Attribute aus
zerodds_dcps::sample_info setzt. Ein ungültiger SQL-Ausdruck wird im
Konstruktor als RuntimeError abgewiesen.
Tests: crates/py/python/tests/test_read_conditions.py::test_sample_state_constants,
crates/py/python/tests/test_read_conditions.py::test_read_condition_modes,
crates/py/python/tests/test_read_conditions.py::test_query_condition_with_parameters,
crates/py/python/tests/test_read_conditions.py::test_query_condition_invalid_sql_raises,
crates/py/python/tests/test_read_conditions.py::test_query_condition_attaches_to_waitset
Status: done
§6.7 sphinx-Doc-Pfad
Spec: §6.7 — crates/py/docs/conf.py mit autodoc/napoleon/
viewcode/intersphinx, autodoc_mock_imports für den Build ohne
maturin, crates/py/docs/api.rst listet die PyClasses und die Module
loader, topic, aio, sample_state, view_state,
instance_state.
Repo: crates/py/docs/conf.py — die vier Extensions,
autodoc_mock_imports = ["zerodds._core"] und die Version aus dem
Workspace-Manifest statt als Literal; crates/py/docs/api.rst — jede
Klasse, die das Erweiterungsmodul exportiert, und die Module loader,
topic, aio und die drei State-Module;
crates/py/docs/index.rst, crates/py/docs/quickstart.rst,
crates/py/docs/examples.rst.
Tests: crates/py/python/tests/test_docs.py::test_conf_py_declares_the_four_extensions_the_spec_names,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_spec_pyclass,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_pure_python_module,
crates/py/python/tests/test_docs.py::test_api_rst_documents_every_extension_class,
crates/py/python/tests/test_docs.py::test_toctree_entries_exist,
crates/py/python/tests/test_docs.py::test_conf_py_is_importable_and_names_the_workspace_version
Die Tests prüfen den Bestand des Baums, nicht den sphinx-build-Lauf.
Status: done
§7 Stabilität
Spec: §7 — semver-Politik, v1.0 stabil, Major-Bump für Breaking-Changes.
Repo: —
Tests: —
Status: n/a (informative) — Versionierungszusage, keine Eigenschaft der Bindung.
§8 Lizenz
Spec: §8 — Apache-2.0 (Workspace-Default).
Repo: —
Tests: —
Status: open — crates/py/pyproject.toml deklariert license = { text = "Apache-2.0 OR MIT" }, der Cargo-Workspace Apache-2.0; die Spec nennt Apache-2.0. Kein Test prüft die Angabe.
§9 Referenzen
Spec: §9 — Liste von externen Specs (OMG DDS 1.4, XTypes 1.3,
PSM-Cxx 1.0) und internen Vendor-Specs (zerodds-c-api-1.0,
zerodds-ffi-loader-1.0, zerodds-java-omgdds-1.0) plus PyO3 und
PEP 384.
Repo: —
Tests: —
Status: n/a (informative) — Verweisliste.
Audit-Status
41 done / 2 partial / 3 open / 0 blocked / 2 n/a (informative).
Test-Lauf: PYTHONPATH=runtime/python:crates/py/python python3 -m pytest crates/py/python/tests ohne test_qos_lifecycle_surface.py (startet
Participants auf den Domains 41–46) — 161 grün, 0 failed, 1 xfailed
(test_fixed_array_count_is_refused_on_encode, §3.1), 17 bedingt
übersprungen (ROS-2 ohne ROS_DISTRO/rclpy, ShapesDemo ohne ddsperf,
Loader ohne libzerodds).
cd runtime/python && python3 -m unittest discover -s tests — 125
Tests grün, 0 failed.
Offene Punkte: zerodds-py-1.0.open.md.