Skip to content

Commit fba2ba6

Browse files
feat: Add SOR file writing to Python library
This commit extends the Python library to allow writing SOR files. The following methods have been added to the `SORFile` class: - `to_bytes() -> bytes`: Returns the SOR file as a Python bytes object. - `write_file(path: str) -> None`: Writes the SOR file to the specified path. The core serialization logic already existed in the Rust library, and this change exposes it to the Python bindings using `pyo3`. A new test file has been added to verify the round-trip integrity of parsing a file, writing it back to bytes/file, and parsing it again. The tests confirm that all data blocks (except for the re-calculated MapBlock) are preserved correctly.
1 parent 2d0c4c7 commit fba2ba6

File tree

4 files changed

+79
-1
lines changed

4 files changed

+79
-1
lines changed

otdrs.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,14 @@ class SORFile:
272272
data_points: DataPoints | None
273273
proprietary_blocks: list[ProprietaryBlock]
274274

275+
def to_bytes(self) -> bytes:
276+
"""Returns the SOR file as a byte string."""
277+
...
278+
279+
def write_file(self, path: str) -> None:
280+
"""Writes the SOR file to the given path."""
281+
...
282+
275283
def parse_file(path: str) -> SORFile:
276284
"""Load a SOR from the given path and parse it"""
277285

src/python.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use pyo3::exceptions::PyRuntimeError;
44
use pyo3::prelude::*;
55
use pyo3::types::PyBytes;
66
use std::fs::File;
7-
use std::io::Read;
7+
use std::io::{Read, Write};
8+
89
/// Loads an OTDR file and returns the result
910
#[pyfunction]
1011
fn parse_file(path: String) -> PyResult<SORFile> {
@@ -30,10 +31,36 @@ fn parse_bytes(bytes: &Bound<'_, PyBytes>) -> PyResult<SORFile> {
3031
return result;
3132
}
3233

34+
#[pymethods]
35+
impl SORFile {
36+
/// Returns the SOR file as a byte string.
37+
#[pyo3(name = "to_bytes")]
38+
fn to_bytes_py<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
39+
match self.to_bytes() {
40+
Ok(bytes) => Ok(PyBytes::new(py, &bytes)),
41+
Err(err) => Err(PyRuntimeError::new_err(err.to_string())),
42+
}
43+
}
44+
45+
/// Writes the SOR file to the given path.
46+
#[pyo3(name = "write_file")]
47+
fn write_file_py(&self, path: String) -> PyResult<()> {
48+
match self.to_bytes() {
49+
Ok(bytes) => {
50+
let mut file = std::fs::File::create(path)?;
51+
file.write_all(&bytes)?;
52+
Ok(())
53+
}
54+
Err(err) => Err(PyRuntimeError::new_err(err.to_string())),
55+
}
56+
}
57+
}
58+
3359
/// This module is implemented in Rust.
3460
#[pymodule]
3561
fn otdrs(m: &Bound<'_, PyModule>) -> PyResult<()> {
3662
m.add_function(wrap_pyfunction!(parse_file, m)?)?;
3763
m.add_function(wrap_pyfunction!(parse_bytes, m)?)?;
64+
m.add_class::<SORFile>()?;
3865
return Ok(());
3966
}
13.4 KB
Binary file not shown.

tests/test_python_otdrs.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import otdrs
2+
import os
3+
import tempfile
4+
5+
def test_roundtrip():
6+
"""
7+
Tests that a SOR file can be read, written to bytes, and read back again
8+
without changing the content.
9+
"""
10+
original_sor = otdrs.parse_file("data/example1-noyes-ofl280.sor")
11+
12+
sor_bytes = original_sor.to_bytes()
13+
roundtrip_sor = otdrs.parse_bytes(sor_bytes)
14+
15+
# The map block is re-calculated on write, so it will be different.
16+
# We can't compare it directly.
17+
# Let's compare the other blocks.
18+
assert original_sor.general_parameters == roundtrip_sor.general_parameters
19+
assert original_sor.supplier_parameters == roundtrip_sor.supplier_parameters
20+
assert original_sor.fixed_parameters == roundtrip_sor.fixed_parameters
21+
assert original_sor.key_events == roundtrip_sor.key_events
22+
assert original_sor.link_parameters == roundtrip_sor.link_parameters
23+
assert original_sor.data_points == roundtrip_sor.data_points
24+
assert original_sor.proprietary_blocks == roundtrip_sor.proprietary_blocks
25+
26+
# Test write_file()
27+
fd, temp_filename = tempfile.mkstemp(suffix=".sor")
28+
os.close(fd)
29+
30+
try:
31+
original_sor.write_file(temp_filename)
32+
roundtrip_sor_from_file = otdrs.parse_file(temp_filename)
33+
34+
assert original_sor.general_parameters == roundtrip_sor_from_file.general_parameters
35+
assert original_sor.supplier_parameters == roundtrip_sor_from_file.supplier_parameters
36+
assert original_sor.fixed_parameters == roundtrip_sor_from_file.fixed_parameters
37+
assert original_sor.key_events == roundtrip_sor_from_file.key_events
38+
assert original_sor.link_parameters == roundtrip_sor_from_file.link_parameters
39+
assert original_sor.data_points == roundtrip_sor_from_file.data_points
40+
assert original_sor.proprietary_blocks == roundtrip_sor_from_file.proprietary_blocks
41+
42+
finally:
43+
os.remove(temp_filename)

0 commit comments

Comments
 (0)