diff --git a/README.md b/README.md index 4351171..3d838ec 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ header = klamath.library.FileHeader.read(stream) struct_positions = klamath.library.scan_structs(stream) stream.seek(struct_positions[b'my_struct']) -elements_A = klamath.library.read_elements(stream) +elements_A = klamath.library.try_read_struct(stream) stream.close() diff --git a/klamath/__init__.py b/klamath/__init__.py index 5a315b4..8de1f87 100644 --- a/klamath/__init__.py +++ b/klamath/__init__.py @@ -36,5 +36,5 @@ from . import ( ) __author__ = 'Jan Petykiewicz' -__version__ = '1.5' +__version__ = '1.4' diff --git a/klamath/basic.py b/klamath/basic.py index e880393..86c9d59 100644 --- a/klamath/basic.py +++ b/klamath/basic.py @@ -49,7 +49,7 @@ def decode_real8(nums: NDArray[numpy.uint64]) -> NDArray[numpy.float64]: exp = (nums >> 56) & 0x7f mant = (nums & 0x00ff_ffff_ffff_ffff).astype(numpy.float64) mant[neg != 0] *= -1 - return numpy.ldexp(mant, 4 * (exp.astype(numpy.int64) - 64) - 56) + return numpy.ldexp(mant, 4 * (exp - 64) - 56, signature=(float, int, float)) def parse_real8(data: bytes) -> NDArray[numpy.float64]: @@ -77,7 +77,7 @@ def parse_datetime(data: bytes) -> list[datetime]: year, *date_parts = parse_int2(data[ii:ii + 12]) try: dt = datetime(year + 1900, *date_parts) - except ValueError: + except ValueError as err: dt = datetime(1900, 1, 1, 0, 0, 0) logger.info(f'Invalid date {[year] + date_parts}, setting {dt} instead') dts.append(dt) @@ -149,7 +149,7 @@ def encode_real8(fnums: NDArray[numpy.float64]) -> NDArray[numpy.uint64]: gds_exp = exp16 + 64 neg_biased = (gds_exp < 0) - gds_mant[neg_biased] >>= (-gds_exp[neg_biased] * 4).astype(numpy.uint16) + gds_mant[neg_biased] >>= (gds_exp[neg_biased] * 4).astype(numpy.uint16) gds_exp[neg_biased] = 0 too_big = (gds_exp > 0x7f) & ~(zero | subnorm) @@ -160,6 +160,7 @@ def encode_real8(fnums: NDArray[numpy.float64]) -> NDArray[numpy.uint64]: real8 = sign | gds_exp_bits | gds_mant real8[zero] = 0 + real8[gds_exp < -14] = 0 # number is too small return real8.astype(numpy.uint64, copy=False) diff --git a/klamath/elements.py b/klamath/elements.py index c0d5567..302921c 100644 --- a/klamath/elements.py +++ b/klamath/elements.py @@ -2,9 +2,7 @@ Functionality for reading/writing elements (geometry, text labels, structure references) and associated properties. """ -import io from typing import IO, TypeVar -from collections.abc import Mapping from abc import ABCMeta, abstractmethod from dataclasses import dataclass @@ -54,13 +52,11 @@ def read_properties(stream: IO[bytes]) -> dict[int, bytes]: if key in properties: raise KlamathError(f'Duplicate property key: {key!r}') properties[key] = value - else: - stream.seek(size, io.SEEK_CUR) size, tag = Record.read_header(stream) return properties -def write_properties(stream: IO[bytes], properties: Mapping[int, bytes]) -> int: +def write_properties(stream: IO[bytes], properties: dict[int, bytes]) -> int: """ Write element properties. @@ -151,7 +147,7 @@ class Reference(Element): colrow: tuple[int, int] | NDArray[numpy.int16] | None """ Number of columns and rows (AREF) or None (SREF) """ - properties: Mapping[int, bytes] + properties: dict[int, bytes] """ Properties associated with this reference. """ @classmethod @@ -233,7 +229,7 @@ class Boundary(Element): xy: NDArray[numpy.int32] """ Ordered vertices of the shape. First and last points should be identical. """ - properties: Mapping[int, bytes] + properties: dict[int, bytes] """ Properties for the element. """ @classmethod @@ -279,7 +275,7 @@ class Path(Element): xy: NDArray[numpy.int32] """ Path centerline coordinates """ - properties: Mapping[int, bytes] + properties: dict[int, bytes] """ Properties for the element. """ @classmethod @@ -319,12 +315,12 @@ class Path(Element): if self.width != 0: b += WIDTH.write(stream, self.width) - if self.path_type == 4: + if self.path_type < 4: bgn_ext, end_ext = self.extension if bgn_ext != 0: - b += BGNEXTN.write(stream, int(bgn_ext)) + b += BGNEXTN.write(stream, bgn_ext) if end_ext != 0: - b += ENDEXTN.write(stream, int(end_ext)) + b += ENDEXTN.write(stream, end_ext) b += XY.write(stream, self.xy) b += write_properties(stream, self.properties) b += ENDEL.write(stream, None) @@ -344,7 +340,7 @@ class Box(Element): xy: NDArray[numpy.int32] """ Box coordinates (5 pairs) """ - properties: Mapping[int, bytes] + properties: dict[int, bytes] """ Properties for the element. """ @classmethod @@ -378,7 +374,7 @@ class Node(Element): xy: NDArray[numpy.int32] """ 1-50 pairs of coordinates. """ - properties: Mapping[int, bytes] + properties: dict[int, bytes] """ Properties for the element. """ @classmethod @@ -438,7 +434,7 @@ class Text(Element): string: bytes """ Text content """ - properties: Mapping[int, bytes] + properties: dict[int, bytes] """ Properties for the element. """ @classmethod diff --git a/klamath/library.py b/klamath/library.py index 8b3ab20..f7b68bd 100644 --- a/klamath/library.py +++ b/klamath/library.py @@ -220,15 +220,10 @@ def scan_hierarchy(stream: IO[bytes]) -> dict[bytes, dict[bytes, int]]: colrow = COLROW.read_data(stream, size) ref_count = colrow[0] * colrow[1] elif tag == ENDEL.tag: - if ref_name is not None: - if ref_count is None: - ref_count = 1 - cur_structure[ref_name] += ref_count - ref_name = None - ref_count = None - elif tag in (SREF.tag, AREF.tag): - ref_name = None - ref_count = None + if ref_count is None: + ref_count = 1 + assert ref_name is not None + cur_structure[ref_name] += ref_count else: stream.seek(size, io.SEEK_CUR) size, tag = Record.read_header(stream) diff --git a/klamath/record.py b/klamath/record.py index f5fce86..48bfe7c 100644 --- a/klamath/record.py +++ b/klamath/record.py @@ -142,7 +142,7 @@ class NoDataRecord(Record[None, None]): @classmethod def pack_data(cls: type[Self], data: None) -> bytes: if data is not None: - raise KlamathError('?? Packing {data!r} into NoDataRecord??') + raise KlamathError('?? Packing {data} into NoDataRecord??') return b'' diff --git a/klamath/records.py b/klamath/records.py index b05bb04..0fbcb4d 100644 --- a/klamath/records.py +++ b/klamath/records.py @@ -18,7 +18,7 @@ class HEADER(Int2Record): class BGNLIB(DateTimeRecord): tag = 0x0102 - expected_size = 2 * 6 * 2 + expected_size = 6 * 2 class LIBNAME(ASCIIRecord): @@ -37,7 +37,7 @@ class ENDLIB(NoDataRecord): class BGNSTR(DateTimeRecord): tag = 0x0502 - expected_size = 2 * 6 * 2 + expected_size = 6 * 2 class STRNAME(ASCIIRecord): @@ -173,8 +173,6 @@ class GENERATIONS(Int2Record): @classmethod def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: - if isinstance(data, (int, numpy.integer)): - return if not isinstance(data, Sized) or len(data) != 1: raise KlamathError(f'Expected exactly one integer, got {data}') @@ -224,6 +222,7 @@ class PROPATTR(Int2Record): class PROPVALUE(ASCIIRecord): tag = 0x2c06 + expected_size = 2 class BOX(NoDataRecord): @@ -272,8 +271,6 @@ class FORMAT(Int2Record): @classmethod def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: - if isinstance(data, (int, numpy.integer)): - return if not isinstance(data, Sized) or len(data) != 1: raise KlamathError(f'Expected exactly one integer, got {data}') @@ -309,7 +306,7 @@ class SOFTFENCE(NoDataRecord): class HARDFENCE(NoDataRecord): - tag = 0x3e00 + tag = 0x3f00 class SOFTWIRE(NoDataRecord): diff --git a/klamath/test_basic.py b/klamath/test_basic.py index 287f7a9..4d686d9 100644 --- a/klamath/test_basic.py +++ b/klamath/test_basic.py @@ -120,7 +120,7 @@ def test_pack_ascii() -> None: assert pack_ascii(b'321') == b'321\0' -def test_invalid_date() -> None: +def test_invalid_date(): default = [datetime(1900, 1, 1, 0, 0, 0)] assert parse_datetime(pack_int2((0, 0, 0, 0, 0, 0))) == default assert parse_datetime(pack_int2((0, 1, 32, 0, 0, 0))) == default diff --git a/klamath/test_elements.py b/klamath/test_elements.py deleted file mode 100644 index ae3df18..0000000 --- a/klamath/test_elements.py +++ /dev/null @@ -1,156 +0,0 @@ -import io -import numpy -from numpy.testing import assert_array_equal -from klamath.elements import Boundary, Path, Text, Reference, Box, Node - -def test_boundary_roundtrip() -> None: - xy = numpy.array([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], dtype=numpy.int32) - b = Boundary(layer=(4, 5), xy=xy, properties={1: b'prop1'}) - - stream = io.BytesIO() - b.write(stream) - stream.seek(0) - - b2 = Boundary.read(stream) - assert b2.layer == b.layer - assert_array_equal(b2.xy, b.xy) - assert b2.properties == b.properties - -def test_path_roundtrip() -> None: - xy = numpy.array([[0, 0], [100, 0], [100, 100]], dtype=numpy.int32) - p = Path(layer=(10, 20), xy=xy, properties={2: b'pathprop'}, - path_type=4, width=50, extension=(10, 20)) - - stream = io.BytesIO() - p.write(stream) - stream.seek(0) - - p2 = Path.read(stream) - assert p2.layer == p.layer - assert_array_equal(p2.xy, p.xy) - assert p2.properties == p.properties - assert p2.path_type == p.path_type - assert p2.width == p.width - assert p2.extension == p.extension - -def test_text_roundtrip() -> None: - xy = numpy.array([[50, 50]], dtype=numpy.int32) - t = Text(layer=(1, 1), xy=xy, string=b"HELLO WORLD", properties={}, - presentation=5, path_type=0, width=0, invert_y=True, - mag=2.5, angle_deg=45.0) - - stream = io.BytesIO() - t.write(stream) - stream.seek(0) - - t2 = Text.read(stream) - assert t2.layer == t.layer - assert_array_equal(t2.xy, t.xy) - assert t2.string == t.string - assert t2.presentation == t.presentation - assert t2.invert_y == t.invert_y - assert t2.mag == t.mag - assert t2.angle_deg == t.angle_deg - -def test_reference_sref_roundtrip() -> None: - xy = numpy.array([[100, 200]], dtype=numpy.int32) - r = Reference(struct_name=b"MY_CELL", xy=xy, colrow=None, - properties={5: b'sref'}, invert_y=False, mag=1.0, angle_deg=90.0) - - stream = io.BytesIO() - r.write(stream) - stream.seek(0) - - r2 = Reference.read(stream) - assert r2.struct_name == r.struct_name - assert_array_equal(r2.xy, r.xy) - assert r2.colrow is None - assert r2.properties == r.properties - assert r2.angle_deg == r.angle_deg - -def test_reference_aref_roundtrip() -> None: - xy = numpy.array([[0, 0], [1000, 0], [0, 500]], dtype=numpy.int32) - colrow = (5, 2) - r = Reference(struct_name=b"ARRAY_CELL", xy=xy, colrow=colrow, - properties={}, invert_y=False, mag=1.0, angle_deg=0.0) - - stream = io.BytesIO() - r.write(stream) - stream.seek(0) - - r2 = Reference.read(stream) - assert r2.struct_name == r.struct_name - assert_array_equal(r2.xy, r.xy) - assert r2.colrow is not None - assert list(r2.colrow) == list(colrow) - assert r2.properties == r.properties - -def test_box_roundtrip() -> None: - xy = numpy.array([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], dtype=numpy.int32) - b = Box(layer=(30, 40), xy=xy, properties={}) - - stream = io.BytesIO() - b.write(stream) - stream.seek(0) - - b2 = Box.read(stream) - assert b2.layer == b.layer - assert_array_equal(b2.xy, b.xy) - -def test_node_roundtrip() -> None: - xy = numpy.array([[0, 0], [10, 10]], dtype=numpy.int32) - n = Node(layer=(50, 60), xy=xy, properties={}) - - stream = io.BytesIO() - n.write(stream) - stream.seek(0) - - n2 = Node.read(stream) - assert n2.layer == n.layer - assert_array_equal(n2.xy, n.xy) - -def test_reference_check() -> None: - import pytest - from klamath.basic import KlamathError - # SREF with too many points - xy = numpy.array([[0, 0], [10, 10]], dtype=numpy.int32) - r = Reference(struct_name=b"CELL", xy=xy, colrow=None, properties={}, invert_y=False, mag=1.0, angle_deg=0.0) - with pytest.raises(KlamathError, match="Expected size-2 xy"): - r.check() - - # AREF with too few points - xy = numpy.array([[0, 0]], dtype=numpy.int32) - r = Reference(struct_name=b"CELL", xy=xy, colrow=(2, 2), properties={}, invert_y=False, mag=1.0, angle_deg=0.0) - with pytest.raises(KlamathError, match="colrow is not None, so expected size-6 xy"): - r.check() - -def test_read_properties_duplicate() -> None: - import pytest - from klamath.basic import KlamathError - from klamath.records import PROPATTR, PROPVALUE, ENDEL - stream = io.BytesIO() - PROPATTR.write(stream, 1) - PROPVALUE.write(stream, b"val1") - PROPATTR.write(stream, 1) # DUPLICATE - PROPVALUE.write(stream, b"val2") - ENDEL.write(stream, None) - stream.seek(0) - - from klamath.elements import read_properties - with pytest.raises(KlamathError, match="Duplicate property key"): - read_properties(stream) - -def test_element_read_unexpected_tag() -> None: - import pytest - from klamath.basic import KlamathError - from klamath.records import SREF, SNAME, HEADER, XY, ENDEL - stream = io.BytesIO() - SREF.write(stream, None) - SNAME.write(stream, b"CELL") - HEADER.write(stream, 123) # UNEXPECTED TAG for Reference.read - XY.write(stream, [0, 0]) - ENDEL.write(stream, None) - stream.seek(0) - - with pytest.raises(KlamathError, match="Unexpected tag"): - Reference.read(stream) diff --git a/klamath/test_library.py b/klamath/test_library.py deleted file mode 100644 index a27c65c..0000000 --- a/klamath/test_library.py +++ /dev/null @@ -1,102 +0,0 @@ -import io -import numpy -from datetime import datetime -from klamath.library import FileHeader, write_struct, try_read_struct, scan_structs, scan_hierarchy, read_elements -from klamath.elements import Boundary -from klamath.records import ENDLIB - -def test_file_header_roundtrip() -> None: - h = FileHeader(name=b"MY_LIB", user_units_per_db_unit=0.001, meters_per_db_unit=1e-9, - mod_time=datetime(2023, 1, 1, 0, 0, 0), acc_time=datetime(2023, 1, 1, 0, 0, 0)) - - stream = io.BytesIO() - h.write(stream) - stream.seek(0) - - h2 = FileHeader.read(stream) - assert h2.name == h.name - assert h2.user_units_per_db_unit == h.user_units_per_db_unit - assert h2.meters_per_db_unit == h.meters_per_db_unit - assert h2.mod_time == h.mod_time - -def test_write_read_struct() -> None: - xy = numpy.array([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], dtype=numpy.int32) - b = Boundary(layer=(1, 1), xy=xy, properties={}) - - stream = io.BytesIO() - # Need a header for some operations, but write_struct works standalone - write_struct(stream, name=b"CELL_A", elements=[b]) - ENDLIB.write(stream, None) - stream.seek(0) - - res = try_read_struct(stream) - assert res is not None - name, elements = res - assert name == b"CELL_A" - assert len(elements) == 1 - assert isinstance(elements[0], Boundary) - -def test_scan_structs() -> None: - stream = io.BytesIO() - write_struct(stream, name=b"CELL_A", elements=[]) - write_struct(stream, name=b"CELL_B", elements=[]) - ENDLIB.write(stream, None) - stream.seek(0) - - positions = scan_structs(stream) - assert b"CELL_A" in positions - assert b"CELL_B" in positions - - # Verify we can seek and read - stream.seek(positions[b"CELL_B"]) - elements = read_elements(stream) - assert len(elements) == 0 - -def test_scan_hierarchy() -> None: - from klamath.elements import Reference - - stream = io.BytesIO() - # Struct A has 2 refs to Struct B - ref_b1 = Reference(struct_name=b"B", xy=numpy.array([[0, 0]], dtype=numpy.int32), colrow=None, properties={}, - invert_y=False, mag=1.0, angle_deg=0.0) - ref_b2 = Reference(struct_name=b"B", xy=numpy.array([[10, 10]], dtype=numpy.int32), colrow=None, properties={}, - invert_y=False, mag=1.0, angle_deg=0.0) - write_struct(stream, name=b"A", elements=[ref_b1, ref_b2]) - - # Struct B has a 3x2 AREF of Struct C - ref_c = Reference(struct_name=b"C", xy=numpy.array([[0, 0], [10, 0], [0, 10]], dtype=numpy.int32), - colrow=(3, 2), properties={}, invert_y=False, mag=1.0, angle_deg=0.0) - write_struct(stream, name=b"B", elements=[ref_c]) - - write_struct(stream, name=b"C", elements=[]) - ENDLIB.write(stream, None) - stream.seek(0) - - hierarchy = scan_hierarchy(stream) - assert hierarchy[b"A"] == {b"B": 2} - assert hierarchy[b"B"] == {b"C": 6} - assert hierarchy[b"C"] == {} - -def test_scan_structs_duplicate() -> None: - import pytest - from klamath.basic import KlamathError - stream = io.BytesIO() - write_struct(stream, name=b"CELL_A", elements=[]) - write_struct(stream, name=b"CELL_A", elements=[]) - ENDLIB.write(stream, None) - stream.seek(0) - - with pytest.raises(KlamathError, match="Duplicate structure name"): - scan_structs(stream) - -def test_scan_hierarchy_duplicate() -> None: - import pytest - from klamath.basic import KlamathError - stream = io.BytesIO() - write_struct(stream, name=b"CELL_A", elements=[]) - write_struct(stream, name=b"CELL_A", elements=[]) - ENDLIB.write(stream, None) - stream.seek(0) - - with pytest.raises(KlamathError, match="Duplicate structure name"): - scan_hierarchy(stream) diff --git a/klamath/test_record.py b/klamath/test_record.py deleted file mode 100644 index 99a5a1f..0000000 --- a/klamath/test_record.py +++ /dev/null @@ -1,134 +0,0 @@ -import io -import pytest -import struct -from datetime import datetime -from klamath.basic import KlamathError -from klamath.record import ( - write_record_header, read_record_header, expect_record, - BitArrayRecord, Int2Record, ASCIIRecord, DateTimeRecord, NoDataRecord -) -from klamath.records import ENDLIB, HEADER - -def test_write_read_record_header() -> None: - stream = io.BytesIO() - tag = 0x1234 - data_size = 8 - - write_record_header(stream, data_size, tag) - stream.seek(0) - - read_size, read_tag = read_record_header(stream) - assert read_size == data_size - assert read_tag == tag - assert stream.tell() == 4 - -def test_write_record_header_too_big() -> None: - stream = io.BytesIO() - with pytest.raises(KlamathError, match="Record size is too big"): - write_record_header(stream, 0x10000, 0x1234) - -def test_read_record_header_errors() -> None: - # Too small - stream = io.BytesIO(struct.pack('>HH', 2, 0x1234)) - with pytest.raises(KlamathError, match="Record size is too small"): - read_record_header(stream) - - # Odd size - stream = io.BytesIO(struct.pack('>HH', 5, 0x1234)) - with pytest.raises(KlamathError, match="Record size is odd"): - read_record_header(stream) - -def test_expect_record() -> None: - stream = io.BytesIO() - write_record_header(stream, 4, 0x1111) - stream.seek(0) - - # Correct tag - size = expect_record(stream, 0x1111) - assert size == 4 - - # Incorrect tag - stream.seek(0) - with pytest.raises(KlamathError, match="Unexpected record"): - expect_record(stream, 0x2222) - -def test_bitarray_record() -> None: - class TestBit(BitArrayRecord): - tag = 0x9999 - - stream = io.BytesIO() - TestBit.write(stream, 0x8000) - stream.seek(0) - - val = TestBit.read(stream) - assert val == 0x8000 - -def test_int2_record() -> None: - class TestInt2(Int2Record): - tag = 0x8888 - - stream = io.BytesIO() - TestInt2.write(stream, [1, -2, 3]) - stream.seek(0) - - val = TestInt2.read(stream) - assert list(val) == [1, -2, 3] - -def test_ascii_record() -> None: - class TestASCII(ASCIIRecord): - tag = 0x7777 - - stream = io.BytesIO() - TestASCII.write(stream, b"HELLO") - stream.seek(0) - - val = TestASCII.read(stream) - assert val == b"HELLO" - -def test_datetime_record() -> None: - class TestDT(DateTimeRecord): - tag = 0x6666 - - now = datetime(2023, 10, 27, 12, 30, 45) - stream = io.BytesIO() - TestDT.write(stream, [now, now]) - stream.seek(0) - - vals = TestDT.read(stream) - assert vals == [now, now] - -def test_nodata_record() -> None: - class TestNoData(NoDataRecord): - tag = 0x5555 - - stream = io.BytesIO() - TestNoData.write(stream, None) - stream.seek(0) - - # Verify header: 4 bytes total (size=4, tag=0x5555), data_size=0 - header = stream.read(4) - assert header == struct.pack('>HH', 4, 0x5555) - - stream.seek(0) - assert TestNoData.read(stream) is None - -def test_record_skip_past() -> None: - stream = io.BytesIO() - HEADER.write(stream, 600) - ENDLIB.write(stream, None) - - stream.seek(0) - # Skip past HEADER - found = HEADER.skip_past(stream) - assert found is True - assert stream.tell() == 6 # 4 header + 2 data - - # Try to skip past something that doesn't exist before ENDLIB - class NONEXISTENT(NoDataRecord): - tag = 0xFFFF - - stream.seek(0) - found = NONEXISTENT.skip_past(stream) - assert found is False - # Should be at the end of ENDLIB record header/tag read - assert stream.tell() == 10 # 6 (HEADER) + 4 (ENDLIB) diff --git a/klamath/test_records.py b/klamath/test_records.py deleted file mode 100644 index fd07b44..0000000 --- a/klamath/test_records.py +++ /dev/null @@ -1,110 +0,0 @@ -import io -import pytest -import numpy -from datetime import datetime -from klamath.basic import KlamathError -from klamath import records - -def test_record_tags() -> None: - assert records.HEADER.tag == 0x0002 - assert records.BGNLIB.tag == 0x0102 - assert records.LIBNAME.tag == 0x0206 - assert records.UNITS.tag == 0x0305 - assert records.ENDLIB.tag == 0x0400 - assert records.BGNSTR.tag == 0x0502 - assert records.STRNAME.tag == 0x0606 - assert records.ENDSTR.tag == 0x0700 - assert records.BOUNDARY.tag == 0x0800 - assert records.PATH.tag == 0x0900 - assert records.SREF.tag == 0x0a00 - assert records.AREF.tag == 0x0b00 - assert records.TEXT.tag == 0x0c00 - assert records.LAYER.tag == 0x0d02 - assert records.DATATYPE.tag == 0x0e02 - assert records.WIDTH.tag == 0x0f03 - assert records.XY.tag == 0x1003 - assert records.ENDEL.tag == 0x1100 - assert records.SNAME.tag == 0x1206 - assert records.COLROW.tag == 0x1302 - assert records.NODE.tag == 0x1500 - assert records.TEXTTYPE.tag == 0x1602 - assert records.PRESENTATION.tag == 0x1701 - assert records.STRING.tag == 0x1906 - assert records.STRANS.tag == 0x1a01 - assert records.MAG.tag == 0x1b05 - assert records.ANGLE.tag == 0x1c05 - assert records.REFLIBS.tag == 0x1f06 - assert records.FONTS.tag == 0x2006 - assert records.PATHTYPE.tag == 0x2102 - assert records.GENERATIONS.tag == 0x2202 - assert records.ATTRTABLE.tag == 0x2306 - assert records.ELFLAGS.tag == 0x2601 - assert records.NODETYPE.tag == 0x2a02 - assert records.PROPATTR.tag == 0x2b02 - assert records.PROPVALUE.tag == 0x2c06 - assert records.BOX.tag == 0x2d00 - assert records.BOXTYPE.tag == 0x2e02 - assert records.PLEX.tag == 0x2f03 - assert records.BGNEXTN.tag == 0x3003 - assert records.ENDEXTN.tag == 0x3103 - assert records.TAPENUM.tag == 0x3202 - assert records.TAPECODE.tag == 0x3302 - assert records.FORMAT.tag == 0x3602 - assert records.MASK.tag == 0x3706 - assert records.ENDMASKS.tag == 0x3800 - assert records.LIBDIRSIZE.tag == 0x3902 - assert records.SRFNAME.tag == 0x3a06 - assert records.LIBSECUR.tag == 0x3b02 - -def test_header_validation() -> None: - # Correct size - records.HEADER.check_size(2) - - # Incorrect size - with pytest.raises(KlamathError, match="Expected size 2, got 4"): - records.HEADER.check_size(4) - -def test_bgnlib_validation() -> None: - now = datetime(2023, 10, 27, 12, 30, 45) - # Correct size (2 datetimes = 24 bytes) - records.BGNLIB.check_size(24) - - # Incorrect size - with pytest.raises(KlamathError, match="Expected size 24, got 12"): - records.BGNLIB.check_size(12) - -def test_reflibs_fonts_validation() -> None: - # REFLIBS must be multiple of 44 - records.REFLIBS.check_size(44) - records.REFLIBS.check_size(88) - records.REFLIBS.check_size(0) - - with pytest.raises(KlamathError, match="Expected size to be multiple of 44"): - records.REFLIBS.check_size(10) - -def test_generations_format_validation() -> None: - # GENERATIONS expects exactly one integer - records.GENERATIONS.check_data(3) - records.GENERATIONS.check_data([1]) - - with pytest.raises(KlamathError, match="Expected exactly one integer"): - records.GENERATIONS.check_data([1, 2]) - -def test_attrtable_validation() -> None: - # ATTRTABLE size <= 44 - records.ATTRTABLE.check_size(44) - records.ATTRTABLE.check_size(10) - - with pytest.raises(KlamathError, match="Expected size <= 44"): - records.ATTRTABLE.check_size(45) - -def test_nodata_records() -> None: - stream = io.BytesIO() - records.ENDLIB.write(stream, None) - stream.seek(0) - assert records.ENDLIB.read(stream) is None - - stream = io.BytesIO() - records.BOUNDARY.write(stream, None) - stream.seek(0) - assert records.BOUNDARY.read(stream) is None diff --git a/pyproject.toml b/pyproject.toml index 5ca097d..c7d7a0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] name = "klamath" description = "GDSII format reader/writer" @@ -40,7 +44,6 @@ classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", - "Topic :: File Formats", ] requires-python = ">=3.11" include = [ @@ -51,11 +54,6 @@ dependencies = [ "numpy>=1.26", ] -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - - [tool.hatch.version] path = "klamath/__init__.py" @@ -80,6 +78,7 @@ lint.ignore = [ "ANN002", # *args "ANN003", # **kwargs "ANN401", # Any + "ANN101", # self: Self "SIM108", # single-line if / else assignment "RET504", # x=y+z; return x "PIE790", # unnecessary pass