Common Workflows

Constructing a message from caller data

import ccsds_ndm
from ccsds_ndm import OdmHeader, Opm, OpmData, OpmMetadata, OpmSegment, StateVector

header = OdmHeader("2023-01-01T00:00:00", "TEST")
meta = OpmMetadata(
    object_name="SAT1",
    object_id="2023-001A",
    center_name="EARTH",
    ref_frame="GCRF",
    time_system="UTC",
)
state = StateVector(
    epoch="2023-01-01T00:00:00",
    x=7000.0, y=0.0, z=0.0, x_dot=0.0, y_dot=7.5, z_dot=0.0,
    comment=None,
)
opm = Opm(
    header=header,
    segment=OpmSegment(metadata=meta, data=OpmData(state_vector=state, comment=[])),
)
opm.to_file("output.opm", "kvn")

Generation validates the complete message before writing any caller-visible bytes. For repeated-record shapes (OEM state vectors, AEM states), prefer the documented from_numpy entry points at scale. just bench-python runs the OemData.from_numpy benchmark at 10,000 records; it is tracked by CodSpeed alongside the Rust benchmarks.

Editing nested Python models

Nested model objects and record collections are live. Change them directly:

import ccsds_ndm

message = ccsds_ndm.from_file("example.opm")
message.segment.metadata.object_name = "UPDATED"
message.to_file("updated.opm", "kvn")

Retained child references preserve their identity, and structural changes to repeated model fields such as OEM segment, state-vector, and covariance lists affect the owning message. Generation always validates the complete message. There is no commit step and no unchecked validate=False mode.

Lists of plain values are copies, not live views: comment returns a new list of strings each time, so appending to it changes nothing. Assign the whole list instead:

data = message.segment.data
data.comment = [*data.comment, "Reprocessed"]

NumPy properties such as state_vector_numpy are likewise copies; assign an array to write it back.

Model objects cannot be copied with copy or serialized with pickle. To duplicate a message, generate and parse it again, for example Oem.from_str(message.to_str("xml"), "xml"). That round trip validates and normalizes, so it only duplicates a complete, valid message, and it keeps only what the chosen notation represents.

Converting notation

Use the same generic API for every recognized message family:

xml = ccsds_ndm.convert(kvn, "xml")
ccsds_ndm.convert_file("input.opm", "output.xml", "xml")

The input notation is detected automatically.

OPM, OEM, and OMM support edition-correct ODM 2.0 and 3.0 output. The message’s version attribute selects the edition, for example message.version = "2.0" before to_file. The 2.0 checks use the official SANA NDM/XML schema archive. OPM and OEM ODM 1.0 remain parse-only because they do not have an audited schema-backed serializer; attempted relabeling is rejected.

CCSDS units

The parser checks supplied units against each field’s CCSDS unit enum. Required XML unit attributes must be present, incompatible KVN units are rejected, and dimensionless fields reject spurious units. Values are never silently reinterpreted under a different unit.

Parsing and generation guarantees

Callers that accept untrusted input should enforce size limits at their system boundary. XML nesting retains a fixed internal safety limit. Parsing materializes complete messages; streaming parsing is intentionally absent.

Generation always validates the complete message before writing any caller-visible bytes and preserves the edition stored on the message. In Python, use to_str(format) for a string and to_file(path, format) for atomic file replacement. Rust exposes to_kvn, to_xml, and the write_*_to streaming methods; streaming can leave partial output if the caller’s sink fails mid-write. convert_file also replaces its destination atomically. Finite XML values are rounded when necessary to the 16-digit KVN representation required by CCSDS.

Errors expose stable code(), field_path(), and diagnostic() accessors; diagnostic wording may improve before 1.0 while codes, enum meanings, and canonical paths are the machine interface. Python NDM exceptions additionally expose operation, notation, message_kind, source edition, and available source location/token fields.

Migrating metadata construction

Metadata constructors now require CCSDS-significant interpretation fields such as time_system, reference frames, centers, and required interval epochs instead of silently supplying defaults. Pass those values explicitly. interpolation_degree=0 is now rejected; omit it with None when no interpolation degree is present.