stdfast 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stdfast-0.1.2 → stdfast-0.2.0}/CHANGELOG.md +5 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/Cargo.lock +1 -1
- {stdfast-0.1.2 → stdfast-0.2.0}/Cargo.toml +1 -1
- {stdfast-0.1.2 → stdfast-0.2.0}/PKG-INFO +8 -2
- {stdfast-0.1.2 → stdfast-0.2.0}/README.md +7 -1
- {stdfast-0.1.2 → stdfast-0.2.0}/src/write_py.rs +4 -2
- {stdfast-0.1.2 → stdfast-0.2.0}/stdfast/stdfast.pyi +9 -2
- {stdfast-0.1.2 → stdfast-0.2.0}/stdfast/tests/test_roundtrip.py +212 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/.github/workflows/CI.yml +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/.gitignore +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/LICENSE +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/pyproject.toml +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/data.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/data_py.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/lib.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/main.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/record_types.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/records/record_impl.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/records.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/test_information.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/src/util.rs +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/stdfast/__init__.py +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/stdfast/py.typed +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/stdfast/records.py +0 -0
- {stdfast-0.1.2 → stdfast-0.2.0}/tests/records.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stdfast
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Requires-Dist: polars
|
|
5
5
|
Requires-Dist: pydantic>=2.12.5
|
|
6
6
|
License-File: LICENSE
|
|
@@ -107,7 +107,8 @@ sf.write_stdf("output.stdf", records)
|
|
|
107
107
|
import stdfast as sf
|
|
108
108
|
from stdfast.records import FAR, MIR, MRR, PIR, PTR, PRR
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
# Creates new file or overwrites content if it exists
|
|
111
|
+
with sf.StdfWriter("output.stdf", append=False) as w:
|
|
111
112
|
w.write_record(FAR(cpu_type=2, stdf_ver=4))
|
|
112
113
|
w.write_record(MIR(lot_id="LOT001", part_typ="MYPART", job_nam="MYJOB"))
|
|
113
114
|
for i, result in enumerate(my_results):
|
|
@@ -115,6 +116,11 @@ with sf.StdfWriter("output.stdf") as w:
|
|
|
115
116
|
w.write_record(PTR(test_num=1000 + i, head_num=1, site_num=1, result=result, test_txt=f"test_{i}"))
|
|
116
117
|
w.write_record(PRR(head_num=1, site_num=1, hard_bin=1, soft_bin=1, num_test=1))
|
|
117
118
|
w.write_record(MRR())
|
|
119
|
+
|
|
120
|
+
# Creates new file or appends to content if it exists
|
|
121
|
+
with sf.StdfWriter("output.stdf", append=True) as w:
|
|
122
|
+
...
|
|
123
|
+
|
|
118
124
|
```
|
|
119
125
|
|
|
120
126
|
`StdfWriter` is preferable when the number of records is large or unknown upfront, since it never holds more than one record in memory at a time.
|
|
@@ -92,7 +92,8 @@ sf.write_stdf("output.stdf", records)
|
|
|
92
92
|
import stdfast as sf
|
|
93
93
|
from stdfast.records import FAR, MIR, MRR, PIR, PTR, PRR
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
# Creates new file or overwrites content if it exists
|
|
96
|
+
with sf.StdfWriter("output.stdf", append=False) as w:
|
|
96
97
|
w.write_record(FAR(cpu_type=2, stdf_ver=4))
|
|
97
98
|
w.write_record(MIR(lot_id="LOT001", part_typ="MYPART", job_nam="MYJOB"))
|
|
98
99
|
for i, result in enumerate(my_results):
|
|
@@ -100,6 +101,11 @@ with sf.StdfWriter("output.stdf") as w:
|
|
|
100
101
|
w.write_record(PTR(test_num=1000 + i, head_num=1, site_num=1, result=result, test_txt=f"test_{i}"))
|
|
101
102
|
w.write_record(PRR(head_num=1, site_num=1, hard_bin=1, soft_bin=1, num_test=1))
|
|
102
103
|
w.write_record(MRR())
|
|
104
|
+
|
|
105
|
+
# Creates new file or appends to content if it exists
|
|
106
|
+
with sf.StdfWriter("output.stdf", append=True) as w:
|
|
107
|
+
...
|
|
108
|
+
|
|
103
109
|
```
|
|
104
110
|
|
|
105
111
|
`StdfWriter` is preferable when the number of records is large or unknown upfront, since it never holds more than one record in memory at a time.
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
//! ```
|
|
21
21
|
|
|
22
22
|
use std::fs::File;
|
|
23
|
+
use std::fs::OpenOptions;
|
|
23
24
|
use std::io::{BufWriter, Write};
|
|
24
25
|
use std::sync::Mutex;
|
|
25
26
|
|
|
@@ -576,8 +577,9 @@ pub struct StdfWriter {
|
|
|
576
577
|
#[pymethods]
|
|
577
578
|
impl StdfWriter {
|
|
578
579
|
#[new]
|
|
579
|
-
|
|
580
|
-
|
|
580
|
+
#[pyo3(signature = (fname, append = false))]
|
|
581
|
+
pub fn open(fname: &str, append: bool) -> PyResult<Self> {
|
|
582
|
+
let file = OpenOptions::new().write(true).create(true).truncate(!append).append(append).open(fname)
|
|
581
583
|
.map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?;
|
|
582
584
|
Ok(Self {
|
|
583
585
|
inner: Mutex::new(Some(BufWriter::new(file))),
|
|
@@ -114,12 +114,19 @@ class StdfWriter:
|
|
|
114
114
|
|
|
115
115
|
Can be used as a context manager::
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
# Create file or overwrites content if it exists
|
|
118
|
+
with sf.StdfWriter("out.stdf", append=False) as w:
|
|
118
119
|
w.write_record(FAR(cpu_type=2, stdf_ver=4))
|
|
119
120
|
w.write_record(MRR())
|
|
121
|
+
|
|
122
|
+
# Create file or appends to content if it exists
|
|
123
|
+
with sf.StdfWriter("out.stdf", append=True) as w:
|
|
124
|
+
w.write_record(FAR(cpu_type=2, stdf_ver=4))
|
|
125
|
+
w.write_record(MRR())
|
|
126
|
+
|
|
120
127
|
"""
|
|
121
128
|
|
|
122
|
-
def __init__(self, fname: str) -> None: ...
|
|
129
|
+
def __init__(self, fname: str, append: bool = False) -> None: ...
|
|
123
130
|
def write_record(self, record) -> None: ...
|
|
124
131
|
def close(self) -> None: ...
|
|
125
132
|
def __enter__(self) -> "StdfWriter": ...
|
|
@@ -3,6 +3,7 @@ Roundtrip tests: Python records → write_stdf → binary file → parse_stdf
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
+
import os
|
|
6
7
|
|
|
7
8
|
import pytest
|
|
8
9
|
|
|
@@ -248,6 +249,217 @@ class TestStdfWriterRoundtrip:
|
|
|
248
249
|
assert row["units"][0] == "mA"
|
|
249
250
|
|
|
250
251
|
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
# StdfWriter append mode
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _make_first_part():
|
|
258
|
+
"""FAR + MIR + TSR + first PIR/PTR/PRR block (no MRR yet).
|
|
259
|
+
|
|
260
|
+
The TSR must precede PIR so that test_num 1000 is registered before the
|
|
261
|
+
second parse pass processes PTRs.
|
|
262
|
+
"""
|
|
263
|
+
return [
|
|
264
|
+
FAR(cpu_type=2, stdf_ver=4),
|
|
265
|
+
MIR(lot_id="LOT_APPEND", part_typ="APPEND_PART", job_nam="APPEND_JOB"),
|
|
266
|
+
TSR(
|
|
267
|
+
test_num=1000,
|
|
268
|
+
head_num=1,
|
|
269
|
+
site_num=1,
|
|
270
|
+
test_typ="P",
|
|
271
|
+
test_nam="vdd",
|
|
272
|
+
exec_cnt=2,
|
|
273
|
+
),
|
|
274
|
+
PIR(head_num=1, site_num=1),
|
|
275
|
+
PTR(
|
|
276
|
+
test_num=1000,
|
|
277
|
+
head_num=1,
|
|
278
|
+
site_num=1,
|
|
279
|
+
result=1.0,
|
|
280
|
+
test_txt="vdd",
|
|
281
|
+
lo_limit=0.9,
|
|
282
|
+
hi_limit=1.2,
|
|
283
|
+
units="V",
|
|
284
|
+
),
|
|
285
|
+
PRR(
|
|
286
|
+
head_num=1,
|
|
287
|
+
site_num=1,
|
|
288
|
+
hard_bin=1,
|
|
289
|
+
soft_bin=1,
|
|
290
|
+
num_test=1,
|
|
291
|
+
part_id="PART_001",
|
|
292
|
+
),
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _make_second_part():
|
|
297
|
+
"""Second PIR/PTR/PRR block + MRR to close the file."""
|
|
298
|
+
return [
|
|
299
|
+
PIR(head_num=1, site_num=1),
|
|
300
|
+
PTR(
|
|
301
|
+
test_num=1000,
|
|
302
|
+
head_num=1,
|
|
303
|
+
site_num=1,
|
|
304
|
+
result=1.1,
|
|
305
|
+
test_txt="vdd",
|
|
306
|
+
lo_limit=0.9,
|
|
307
|
+
hi_limit=1.2,
|
|
308
|
+
units="V",
|
|
309
|
+
),
|
|
310
|
+
PRR(
|
|
311
|
+
head_num=1,
|
|
312
|
+
site_num=1,
|
|
313
|
+
hard_bin=1,
|
|
314
|
+
soft_bin=1,
|
|
315
|
+
num_test=1,
|
|
316
|
+
part_id="PART_002",
|
|
317
|
+
),
|
|
318
|
+
MRR(),
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class TestStdfWriterAppend:
|
|
323
|
+
"""Tests for StdfWriter append=True mode."""
|
|
324
|
+
|
|
325
|
+
def test_append_extends_raw_record_count(self, tmp_path):
|
|
326
|
+
"""Appending records to an existing file increases the total record count."""
|
|
327
|
+
path = str(tmp_path / "append_count.stdf")
|
|
328
|
+
first = _make_first_part()
|
|
329
|
+
second = _make_second_part()
|
|
330
|
+
|
|
331
|
+
with sf.StdfWriter(path) as w:
|
|
332
|
+
for r in first:
|
|
333
|
+
w.write_record(r)
|
|
334
|
+
|
|
335
|
+
with sf.StdfWriter(path, append=True) as w:
|
|
336
|
+
for r in second:
|
|
337
|
+
w.write_record(r)
|
|
338
|
+
|
|
339
|
+
raw = sf.get_raw_records(path)
|
|
340
|
+
assert len(raw) == len(first) + len(second)
|
|
341
|
+
|
|
342
|
+
def test_append_record_order(self, tmp_path):
|
|
343
|
+
"""Records appear in the file in exactly the order they were written."""
|
|
344
|
+
path = str(tmp_path / "append_order.stdf")
|
|
345
|
+
first = _make_first_part() # FAR, MIR, PIR, PTR, PRR
|
|
346
|
+
second = _make_second_part() # PIR, PTR, PRR, MRR
|
|
347
|
+
|
|
348
|
+
with sf.StdfWriter(path) as w:
|
|
349
|
+
for r in first:
|
|
350
|
+
w.write_record(r)
|
|
351
|
+
|
|
352
|
+
with sf.StdfWriter(path, append=True) as w:
|
|
353
|
+
for r in second:
|
|
354
|
+
w.write_record(r)
|
|
355
|
+
|
|
356
|
+
types = [r["record_type"] for r in sf.get_raw_records(path)]
|
|
357
|
+
assert types == ["FAR", "MIR", "PIR", "PTR", "PRR", "PIR", "PTR", "PRR", "MRR"]
|
|
358
|
+
|
|
359
|
+
def test_append_parses_both_parts(self, tmp_path):
|
|
360
|
+
"""parse_stdf on a split-written STDF yields both parts as data rows."""
|
|
361
|
+
path = str(tmp_path / "append_parse.stdf")
|
|
362
|
+
|
|
363
|
+
with sf.StdfWriter(path) as w:
|
|
364
|
+
for r in _make_first_part():
|
|
365
|
+
w.write_record(r)
|
|
366
|
+
|
|
367
|
+
with sf.StdfWriter(path, append=True) as w:
|
|
368
|
+
for r in _make_second_part():
|
|
369
|
+
w.write_record(r)
|
|
370
|
+
|
|
371
|
+
result = sf.parse_stdf(path)
|
|
372
|
+
assert len(result["data"]) == 2
|
|
373
|
+
|
|
374
|
+
def test_append_part_ids(self, tmp_path):
|
|
375
|
+
"""Both part IDs survive the split-write roundtrip."""
|
|
376
|
+
path = str(tmp_path / "append_part_ids.stdf")
|
|
377
|
+
|
|
378
|
+
with sf.StdfWriter(path) as w:
|
|
379
|
+
for r in _make_first_part():
|
|
380
|
+
w.write_record(r)
|
|
381
|
+
|
|
382
|
+
with sf.StdfWriter(path, append=True) as w:
|
|
383
|
+
for r in _make_second_part():
|
|
384
|
+
w.write_record(r)
|
|
385
|
+
|
|
386
|
+
part_ids = sf.parse_stdf(path)["data"]["part_id"].to_list()
|
|
387
|
+
assert "PART_001" in part_ids
|
|
388
|
+
assert "PART_002" in part_ids
|
|
389
|
+
|
|
390
|
+
def test_no_append_truncates_existing_file(self, tmp_path):
|
|
391
|
+
"""Opening StdfWriter without append=True discards the original content."""
|
|
392
|
+
path = str(tmp_path / "no_append.stdf")
|
|
393
|
+
|
|
394
|
+
with sf.StdfWriter(path) as w:
|
|
395
|
+
for r in _make_first_part() + _make_second_part():
|
|
396
|
+
w.write_record(r)
|
|
397
|
+
|
|
398
|
+
# Overwrite with just a single FAR record
|
|
399
|
+
with sf.StdfWriter(path) as w:
|
|
400
|
+
w.write_record(FAR(cpu_type=2, stdf_ver=4))
|
|
401
|
+
|
|
402
|
+
raw = sf.get_raw_records(path)
|
|
403
|
+
assert len(raw) == 1
|
|
404
|
+
assert raw[0]["record_type"] == "FAR"
|
|
405
|
+
|
|
406
|
+
def test_append_preserves_original_bytes(self, tmp_path):
|
|
407
|
+
"""Bytes written in the first session are not modified by an append."""
|
|
408
|
+
path = str(tmp_path / "bytes_preserved.stdf")
|
|
409
|
+
|
|
410
|
+
with sf.StdfWriter(path) as w:
|
|
411
|
+
for r in _make_first_part():
|
|
412
|
+
w.write_record(r)
|
|
413
|
+
|
|
414
|
+
original_bytes = open(path, "rb").read()
|
|
415
|
+
|
|
416
|
+
with sf.StdfWriter(path, append=True) as w:
|
|
417
|
+
for r in _make_second_part():
|
|
418
|
+
w.write_record(r)
|
|
419
|
+
|
|
420
|
+
all_bytes = open(path, "rb").read()
|
|
421
|
+
assert all_bytes[: len(original_bytes)] == original_bytes
|
|
422
|
+
|
|
423
|
+
def test_append_bytes_equal_manual_concat(self, tmp_path):
|
|
424
|
+
"""Content of the appended file equals the manual concatenation of both parts."""
|
|
425
|
+
file_a = str(tmp_path / "part_a.stdf")
|
|
426
|
+
file_b = str(tmp_path / "part_b.stdf")
|
|
427
|
+
file_ab = str(tmp_path / "appended.stdf")
|
|
428
|
+
|
|
429
|
+
with sf.StdfWriter(file_a) as w:
|
|
430
|
+
for r in _make_first_part():
|
|
431
|
+
w.write_record(r)
|
|
432
|
+
|
|
433
|
+
with sf.StdfWriter(file_b) as w:
|
|
434
|
+
for r in _make_second_part():
|
|
435
|
+
w.write_record(r)
|
|
436
|
+
|
|
437
|
+
with sf.StdfWriter(file_ab) as w:
|
|
438
|
+
for r in _make_first_part():
|
|
439
|
+
w.write_record(r)
|
|
440
|
+
|
|
441
|
+
with sf.StdfWriter(file_ab, append=True) as w:
|
|
442
|
+
for r in _make_second_part():
|
|
443
|
+
w.write_record(r)
|
|
444
|
+
|
|
445
|
+
concat = open(file_a, "rb").read() + open(file_b, "rb").read()
|
|
446
|
+
appended = open(file_ab, "rb").read()
|
|
447
|
+
assert appended == concat
|
|
448
|
+
|
|
449
|
+
def test_append_creates_new_file(self, tmp_path):
|
|
450
|
+
"""append=True on a nonexistent path creates the file."""
|
|
451
|
+
path = str(tmp_path / "new_via_append.stdf")
|
|
452
|
+
assert not os.path.exists(path)
|
|
453
|
+
|
|
454
|
+
records = _make_first_part() + _make_second_part()
|
|
455
|
+
with sf.StdfWriter(path, append=True) as w:
|
|
456
|
+
for r in records:
|
|
457
|
+
w.write_record(r)
|
|
458
|
+
|
|
459
|
+
assert os.path.exists(path)
|
|
460
|
+
assert len(sf.get_raw_records(path)) == len(records)
|
|
461
|
+
|
|
462
|
+
|
|
251
463
|
class TestStdfWriterBytesMatchBatch:
|
|
252
464
|
"""StdfWriter must produce bit-for-bit identical output to write_stdf."""
|
|
253
465
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|