From e94b93d5afbc8638890c2eb8a2bcf0e01345e96d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 22:14:29 -0700 Subject: [PATCH 01/15] replace flake8 with ruff --- .flake8 | 30 ------------------------------ pyproject.toml | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 30 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 1bdbec5..0000000 --- a/.flake8 +++ /dev/null @@ -1,30 +0,0 @@ -[flake8] -ignore = - # E501 line too long - E501, - # W391 newlines at EOF - W391, - # E241 multiple spaces after comma - E241, - # E302 expected 2 newlines - E302, - # W503 line break before binary operator (to be deprecated) - W503, - # E265 block comment should start with '# ' - E265, - # E123 closing bracket does not match indentation of opening bracket's line - E123, - # E124 closing bracket does not match visual indentation - E124, - # E221 multiple spaces before operator - E221, - # E201 whitespace after '[' - E201, -# # E741 ambiguous variable name 'I' -# E741, - - -per-file-ignores = - # F401 import without use - */__init__.py: F401, - __init__.py: F401, diff --git a/pyproject.toml b/pyproject.toml index 11d7918..4044b24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,3 +56,37 @@ dependencies = [ [tool.hatch.version] path = "klamath/__init__.py" + + +[tool.ruff] +exclude = [ + ".git", + "dist", + ] +line-length = 145 +indent-width = 4 +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +lint.select = [ + "NPY", "E", "F", "W", "B", "ANN", "UP", "SLOT", "SIM", "LOG", + "C4", "ISC", "PIE", "PT", "RET", "TCH", "PTH", "INT", + "ARG", "PL", "R", "TRY", + "G010", "G101", "G201", "G202", + "Q002", "Q003", "Q004", + ] +lint.ignore = [ + #"ANN001", # No annotation + "ANN002", # *args + "ANN003", # **kwargs + "ANN401", # Any + "ANN101", # self: Self + "SIM108", # single-line if / else assignment + "RET504", # x=y+z; return x + "PIE790", # unnecessary pass + "ISC003", # non-implicit string concatenation + "C408", # dict(x=y) instead of {'x': y} + "PLR09", # Too many xxx + "PLR2004", # magic number + "PLC0414", # import x as x + "TRY003", # Long exception message + ] + From dc58159cdf67ffcc083b4b36245beae2140fbd1a Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 22:15:26 -0700 Subject: [PATCH 02/15] use redundant imports for re-exported names --- klamath/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/klamath/__init__.py b/klamath/__init__.py index 4aef521..60ce57d 100644 --- a/klamath/__init__.py +++ b/klamath/__init__.py @@ -27,11 +27,13 @@ The goal is to keep this library simple: tools for working with hierarchical design data and supports multiple file formats. """ -from . import basic -from . import record -from . import records -from . import elements -from . import library +from . import ( + basic as basic, + record as record, + records as records, + elements as elements, + library as library, + ) __author__ = 'Jan Petykiewicz' __version__ = '1.3' From e7e42a2ef85f25e2ab6e0d18d5bffe46e88243a1 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:04:12 -0700 Subject: [PATCH 03/15] Allow NDArray inputs to pack_* and avoid unnecesary copies --- klamath/basic.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/klamath/basic.py b/klamath/basic.py index 9d7bdcb..6d4d924 100644 --- a/klamath/basic.py +++ b/klamath/basic.py @@ -92,15 +92,15 @@ def pack_bitarray(data: int) -> bytes: return struct.pack('>H', data) -def pack_int2(data: Sequence[int]) -> bytes: - arr = numpy.array(data) +def pack_int2(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: + arr = numpy.array(data, copy=False) if (arr > 32767).any() or (arr < -32768).any(): raise KlamathError(f'int2 data out of range: {arr}') return arr.astype('>i2').tobytes() -def pack_int4(data: Sequence[int]) -> bytes: - arr = numpy.array(data) +def pack_int4(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: + arr = numpy.array(data, copy=False) if (arr > 2147483647).any() or (arr < -2147483648).any(): raise KlamathError(f'int4 data out of range: {arr}') return arr.astype('>i4').tobytes() @@ -164,8 +164,8 @@ def encode_real8(fnums: NDArray[numpy.float64]) -> NDArray[numpy.uint64]: return real8.astype(numpy.uint64, copy=False) -def pack_real8(data: Sequence[float]) -> bytes: - return encode_real8(numpy.array(data)).astype('>u8').tobytes() +def pack_real8(data: NDArray[numpy.floating] | Sequence[float] | float) -> bytes: + return encode_real8(numpy.array(data, copy=False)).astype('>u8').tobytes() def pack_ascii(data: bytes) -> bytes: From 59c94f7c1798f9c5677a57522587b5f13d107954 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:15:11 -0700 Subject: [PATCH 04/15] improve type annotations --- klamath/record.py | 70 +++++++++++++++++++++---------------------- klamath/records.py | 10 +++---- klamath/test_basic.py | 24 +++++++-------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/klamath/record.py b/klamath/record.py index c0b4a6c..674e55c 100644 --- a/klamath/record.py +++ b/klamath/record.py @@ -1,7 +1,8 @@ """ Generic record-level read/write functionality. """ -from typing import Sequence, IO, TypeVar, ClassVar, Type +from typing import IO, ClassVar, Self, Generic, TypeVar +from collections.abc import Sequence import struct import io from datetime import datetime @@ -17,6 +18,8 @@ from .basic import parse_ascii, pack_ascii, read _RECORD_HEADER_FMT = struct.Struct('>HH') +II = TypeVar('II') # Input type +OO = TypeVar('OO') # Output type def write_record_header(stream: IO[bytes], data_size: int, tag: int) -> int: @@ -53,30 +56,27 @@ def expect_record(stream: IO[bytes], tag: int) -> int: return data_size -R = TypeVar('R', bound='Record') - - -class Record(metaclass=ABCMeta): +class Record(Generic[II, OO], metaclass=ABCMeta): tag: ClassVar[int] = -1 expected_size: ClassVar[int | None] = None @classmethod - def check_size(cls, size: int): + def check_size(cls: type[Self], size: int) -> None: if cls.expected_size is not None and size != cls.expected_size: raise KlamathError(f'Expected size {cls.expected_size}, got {size}') @classmethod - def check_data(cls, data): + def check_data(cls: type[Self], data: II) -> None: pass @classmethod @abstractmethod - def read_data(cls, stream: IO[bytes], size: int): + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> OO: pass @classmethod @abstractmethod - def pack_data(cls, data) -> bytes: + def pack_data(cls: type[Self], data: II) -> bytes: pass @staticmethod @@ -84,11 +84,11 @@ class Record(metaclass=ABCMeta): return read_record_header(stream) @classmethod - def write_header(cls, stream: IO[bytes], data_size: int) -> int: + def write_header(cls: type[Self], stream: IO[bytes], data_size: int) -> int: return write_record_header(stream, data_size, cls.tag) @classmethod - def skip_past(cls, stream: IO[bytes]) -> bool: + def skip_past(cls: type[Self], stream: IO[bytes]) -> bool: """ Skip to the end of the next occurence of this record. @@ -110,7 +110,7 @@ class Record(metaclass=ABCMeta): return True @classmethod - def skip_and_read(cls, stream: IO[bytes]): + def skip_and_read(cls: type[Self], stream: IO[bytes]) -> OO: size, tag = Record.read_header(stream) while tag != cls.tag: stream.seek(size, io.SEEK_CUR) @@ -119,90 +119,90 @@ class Record(metaclass=ABCMeta): return data @classmethod - def read(cls: Type[R], stream: IO[bytes]): + def read(cls: type[Self], stream: IO[bytes]) -> OO: size = expect_record(stream, cls.tag) data = cls.read_data(stream, size) return data @classmethod - def write(cls, stream: IO[bytes], data) -> int: + def write(cls: type[Self], stream: IO[bytes], data: II) -> int: data_bytes = cls.pack_data(data) b = cls.write_header(stream, len(data_bytes)) b += stream.write(data_bytes) return b -class NoDataRecord(Record): +class NoDataRecord(Record[None, None]): expected_size: ClassVar[int | None] = 0 @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> None: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> None: stream.read(size) @classmethod - def pack_data(cls, data: None) -> bytes: + def pack_data(cls: type[Self], data: None) -> bytes: if data is not None: raise KlamathError('?? Packing {data} into NoDataRecord??') return b'' -class BitArrayRecord(Record): +class BitArrayRecord(Record[int, int]): expected_size: ClassVar[int | None] = 2 @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> int: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> int: # noqa: ARG003 size unused return parse_bitarray(read(stream, 2)) @classmethod - def pack_data(cls, data: int) -> bytes: + def pack_data(cls: type[Self], data: int) -> bytes: return pack_bitarray(data) -class Int2Record(Record): +class Int2Record(Record[NDArray[numpy.integer] | Sequence[int] | int, NDArray[numpy.int16]]): @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.int16]: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> NDArray[numpy.int16]: return parse_int2(read(stream, size)) @classmethod - def pack_data(cls, data: Sequence[int]) -> bytes: + def pack_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: return pack_int2(data) -class Int4Record(Record): +class Int4Record(Record[NDArray[numpy.integer] | Sequence[int] | int, NDArray[numpy.int32]]): @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.int32]: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> NDArray[numpy.int32]: return parse_int4(read(stream, size)) @classmethod - def pack_data(cls, data: Sequence[int]) -> bytes: + def pack_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: return pack_int4(data) -class Real8Record(Record): +class Real8Record(Record[Sequence[float] | float, NDArray[numpy.float64]]): @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.float64]: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> NDArray[numpy.float64]: return parse_real8(read(stream, size)) @classmethod - def pack_data(cls, data: Sequence[int]) -> bytes: + def pack_data(cls: type[Self], data: Sequence[float] | float) -> bytes: return pack_real8(data) -class ASCIIRecord(Record): +class ASCIIRecord(Record[bytes, bytes]): @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> bytes: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> bytes: return parse_ascii(read(stream, size)) @classmethod - def pack_data(cls, data: bytes) -> bytes: + def pack_data(cls: type[Self], data: bytes) -> bytes: return pack_ascii(data) -class DateTimeRecord(Record): +class DateTimeRecord(Record[Sequence[datetime], list[datetime]]): @classmethod - def read_data(cls, stream: IO[bytes], size: int) -> list[datetime]: + def read_data(cls: type[Self], stream: IO[bytes], size: int) -> list[datetime]: return parse_datetime(read(stream, size)) @classmethod - def pack_data(cls, data: Sequence[datetime]) -> bytes: + def pack_data(cls: type[Self], data: Sequence[datetime]) -> bytes: return pack_datetime(data) diff --git a/klamath/records.py b/klamath/records.py index 96fbc7f..9fe296d 100644 --- a/klamath/records.py +++ b/klamath/records.py @@ -144,7 +144,7 @@ class REFLIBS(ASCIIRecord): tag = 0x1f06 @classmethod - def check_size(cls, size: int): + def check_size(cls: type[Self], size: int) -> None: if size != 0 and size % 44 != 0: raise Exception(f'Expected size to be multiple of 44, got {size}') @@ -153,7 +153,7 @@ class FONTS(ASCIIRecord): tag = 0x2006 @classmethod - def check_size(cls, size: int): + def check_size(cls: type[Self], size: int) -> None: if size != 0 and size % 44 != 0: raise Exception(f'Expected size to be multiple of 44, got {size}') @@ -168,7 +168,7 @@ class GENERATIONS(Int2Record): expected_size = 2 @classmethod - def check_data(cls, data: Sequence[int]): + def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: if len(data) != 1: raise Exception(f'Expected exactly one integer, got {data}') @@ -177,7 +177,7 @@ class ATTRTABLE(ASCIIRecord): tag = 0x2306 @classmethod - def check_size(cls, size: int): + def check_size(cls: type[Self], size: int) -> None: if size > 44: raise Exception(f'Expected size <= 44, got {size}') @@ -266,7 +266,7 @@ class FORMAT(Int2Record): expected_size = 2 @classmethod - def check_data(cls, data: Sequence[int]): + def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: if len(data) != 1: raise Exception(f'Expected exactly one integer, got {data}') diff --git a/klamath/test_basic.py b/klamath/test_basic.py index b511cc6..4d686d9 100644 --- a/klamath/test_basic.py +++ b/klamath/test_basic.py @@ -12,7 +12,7 @@ from .basic import decode_real8, encode_real8, parse_datetime from .basic import KlamathError -def test_parse_bitarray(): +def test_parse_bitarray() -> None: assert parse_bitarray(b'59') == 13625 assert parse_bitarray(b'\0\0') == 0 assert parse_bitarray(b'\xff\xff') == 65535 @@ -26,7 +26,7 @@ def test_parse_bitarray(): parse_bitarray(b'') -def test_parse_int2(): +def test_parse_int2() -> None: assert_array_equal(parse_int2(b'59\xff\xff\0\0'), (13625, -1, 0)) # odd length @@ -38,7 +38,7 @@ def test_parse_int2(): parse_int2(b'') -def test_parse_int4(): +def test_parse_int4() -> None: assert_array_equal(parse_int4(b'4321'), (875770417,)) # length % 4 != 0 @@ -50,7 +50,7 @@ def test_parse_int4(): parse_int4(b'') -def test_decode_real8(): +def test_decode_real8() -> None: # zeroes assert decode_real8(numpy.array([0x0])) == 0 assert decode_real8(numpy.array([1 << 63])) == 0 # negative @@ -60,7 +60,7 @@ def test_decode_real8(): assert decode_real8(numpy.array([0xC120 << 48])) == -2.0 -def test_parse_real8(): +def test_parse_real8() -> None: packed = struct.pack('>3Q', 0x0, 0x4110_0000_0000_0000, 0xC120_0000_0000_0000) assert_array_equal(parse_real8(packed), (0.0, 1.0, -2.0)) @@ -73,7 +73,7 @@ def test_parse_real8(): parse_real8(b'') -def test_parse_ascii(): +def test_parse_ascii() -> None: # # empty data Now allowed! # with pytest.raises(KlamathError): # parse_ascii(b'') @@ -82,40 +82,40 @@ def test_parse_ascii(): assert parse_ascii(b'12345\0') == b'12345' # strips trailing null byte -def test_pack_bitarray(): +def test_pack_bitarray() -> None: packed = pack_bitarray(321) assert len(packed) == 2 assert packed == struct.pack('>H', 321) -def test_pack_int2(): +def test_pack_int2() -> None: packed = pack_int2((3, 2, 1)) assert len(packed) == 3 * 2 assert packed == struct.pack('>3h', 3, 2, 1) assert pack_int2([-3, 2, -1]) == struct.pack('>3h', -3, 2, -1) -def test_pack_int4(): +def test_pack_int4() -> None: packed = pack_int4((3, 2, 1)) assert len(packed) == 3 * 4 assert packed == struct.pack('>3l', 3, 2, 1) assert pack_int4([-3, 2, -1]) == struct.pack('>3l', -3, 2, -1) -def test_encode_real8(): +def test_encode_real8() -> None: assert encode_real8(numpy.array([0.0])) == 0 arr = numpy.array((1.0, -2.0, 1e-9, 1e-3, 1e-12)) assert_array_equal(decode_real8(encode_real8(arr)), arr) -def test_pack_real8(): +def test_pack_real8() -> None: reals = (0, 1, -1, 0.5, 1e-9, 1e-3, 1e-12) packed = pack_real8(reals) assert len(packed) == len(reals) * 8 assert_array_equal(parse_real8(packed), reals) -def test_pack_ascii(): +def test_pack_ascii() -> None: assert pack_ascii(b'4321') == b'4321' assert pack_ascii(b'321') == b'321\0' From 438cde513ebfa401f94b36482f5aa37687efe03e Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:15:23 -0700 Subject: [PATCH 05/15] whitespace --- klamath/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klamath/elements.py b/klamath/elements.py index ba4098f..fe509b8 100644 --- a/klamath/elements.py +++ b/klamath/elements.py @@ -51,7 +51,7 @@ def read_properties(stream: IO[bytes]) -> dict[int, bytes]: value = PROPVALUE.read(stream) if key in properties: raise KlamathError(f'Duplicate property key: {key!r}') - properties[key] = value + properties[key] = value size, tag = Record.read_header(stream) return properties From f12a1c642169fc982e916530cd32205581eca349 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:15:38 -0700 Subject: [PATCH 06/15] ignore a lint --- klamath/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klamath/elements.py b/klamath/elements.py index fe509b8..a1a10d7 100644 --- a/klamath/elements.py +++ b/klamath/elements.py @@ -211,7 +211,7 @@ class Reference(Element): if self.colrow is not None: if self.xy.size != 6: raise KlamathError(f'colrow is not None, so expected size-6 xy. Got {self.xy}') - else: + else: # noqa: PLR5501 if self.xy.size != 2: raise KlamathError(f'Expected size-2 xy. Got {self.xy}') From 15af9078f0aa99f9e935d7ca7740ddcda54b6144 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:20:14 -0700 Subject: [PATCH 07/15] modernize type annotations and improve handling of int scalars --- klamath/basic.py | 3 ++- klamath/elements.py | 16 ++++++++-------- klamath/library.py | 8 ++++---- klamath/records.py | 9 ++++++--- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/klamath/basic.py b/klamath/basic.py index 6d4d924..95f5551 100644 --- a/klamath/basic.py +++ b/klamath/basic.py @@ -1,7 +1,8 @@ """ Functionality for encoding/decoding basic datatypes """ -from typing import Sequence, IO +from typing import IO +from collections.abc import Sequence import struct import logging from datetime import datetime diff --git a/klamath/elements.py b/klamath/elements.py index a1a10d7..302921c 100644 --- a/klamath/elements.py +++ b/klamath/elements.py @@ -2,7 +2,7 @@ Functionality for reading/writing elements (geometry, text labels, structure references) and associated properties. """ -from typing import Optional, IO, TypeVar, Type, Union +from typing import IO, TypeVar from abc import ABCMeta, abstractmethod from dataclasses import dataclass @@ -78,7 +78,7 @@ class Element(metaclass=ABCMeta): """ @classmethod @abstractmethod - def read(cls: Type[E], stream: IO[bytes]) -> E: + def read(cls: type[E], stream: IO[bytes]) -> E: """ Read from a stream to construct this object. Consumes up to (and including) the ENDEL record. @@ -151,7 +151,7 @@ class Reference(Element): """ Properties associated with this reference. """ @classmethod - def read(cls: Type[R], stream: IO[bytes]) -> R: + def read(cls: type[R], stream: IO[bytes]) -> R: invert_y = False mag = 1 angle_deg = 0 @@ -233,7 +233,7 @@ class Boundary(Element): """ Properties for the element. """ @classmethod - def read(cls: Type[B], stream: IO[bytes]) -> B: + def read(cls: type[B], stream: IO[bytes]) -> B: layer = LAYER.skip_and_read(stream)[0] dtype = DATATYPE.read(stream)[0] xy = XY.read(stream).reshape(-1, 2) @@ -279,7 +279,7 @@ class Path(Element): """ Properties for the element. """ @classmethod - def read(cls: Type[P], stream: IO[bytes]) -> P: + def read(cls: type[P], stream: IO[bytes]) -> P: path_type = 0 width = 0 bgn_ext = 0 @@ -344,7 +344,7 @@ class Box(Element): """ Properties for the element. """ @classmethod - def read(cls: Type[X], stream: IO[bytes]) -> X: + def read(cls: type[X], stream: IO[bytes]) -> X: layer = LAYER.skip_and_read(stream)[0] dtype = BOXTYPE.read(stream)[0] xy = XY.read(stream).reshape(-1, 2) @@ -378,7 +378,7 @@ class Node(Element): """ Properties for the element. """ @classmethod - def read(cls: Type[N], stream: IO[bytes]) -> N: + def read(cls: type[N], stream: IO[bytes]) -> N: layer = LAYER.skip_and_read(stream)[0] dtype = NODETYPE.read(stream)[0] xy = XY.read(stream).reshape(-1, 2) @@ -438,7 +438,7 @@ class Text(Element): """ Properties for the element. """ @classmethod - def read(cls: Type[T], stream: IO[bytes]) -> T: + def read(cls: type[T], stream: IO[bytes]) -> T: path_type = 0 presentation = 0 invert_y = False diff --git a/klamath/library.py b/klamath/library.py index 239609d..4021677 100644 --- a/klamath/library.py +++ b/klamath/library.py @@ -1,7 +1,7 @@ """ File-level read/write functionality. """ -from typing import IO, TypeVar, Type, MutableMapping +from typing import IO, Self, TYPE_CHECKING import io from datetime import datetime from dataclasses import dataclass @@ -15,8 +15,8 @@ from .records import BGNSTR, STRNAME, ENDSTR, SNAME, COLROW, ENDEL from .records import BOX, BOUNDARY, NODE, PATH, TEXT, SREF, AREF from .elements import Element, Reference, Text, Box, Boundary, Path, Node - -FH = TypeVar('FH', bound='FileHeader') +if TYPE_CHECKING: + from collections.abc import MutableMapping @dataclass @@ -45,7 +45,7 @@ class FileHeader: """ Last-accessed time """ @classmethod - def read(cls: Type[FH], stream: IO[bytes]) -> FH: + def read(cls: type[Self], stream: IO[bytes]) -> Self: """ Read and construct a header from the provided stream. diff --git a/klamath/records.py b/klamath/records.py index 9fe296d..31db805 100644 --- a/klamath/records.py +++ b/klamath/records.py @@ -1,7 +1,10 @@ """ Record type and tag definitions """ -from typing import Sequence +from typing import Self +from collections.abc import Sequence, Sized +import numpy +from numpy.typing import NDArray from .record import NoDataRecord, BitArrayRecord, Int2Record, Int4Record, Real8Record from .record import ASCIIRecord, DateTimeRecord @@ -169,7 +172,7 @@ class GENERATIONS(Int2Record): @classmethod def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: - if len(data) != 1: + if not isinstance(data, Sized) or len(data) != 1: raise Exception(f'Expected exactly one integer, got {data}') @@ -267,7 +270,7 @@ class FORMAT(Int2Record): @classmethod def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: - if len(data) != 1: + if not isinstance(data, Sized) or len(data) != 1: raise Exception(f'Expected exactly one integer, got {data}') From 2ea9d32984591826033c81ada654666143aa7ed6 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:20:25 -0700 Subject: [PATCH 08/15] use KlamathError everywhere --- klamath/records.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/klamath/records.py b/klamath/records.py index 31db805..0fbcb4d 100644 --- a/klamath/records.py +++ b/klamath/records.py @@ -6,6 +6,7 @@ from collections.abc import Sequence, Sized import numpy from numpy.typing import NDArray +from .basic import KlamathError from .record import NoDataRecord, BitArrayRecord, Int2Record, Int4Record, Real8Record from .record import ASCIIRecord, DateTimeRecord @@ -149,7 +150,7 @@ class REFLIBS(ASCIIRecord): @classmethod def check_size(cls: type[Self], size: int) -> None: if size != 0 and size % 44 != 0: - raise Exception(f'Expected size to be multiple of 44, got {size}') + raise KlamathError(f'Expected size to be multiple of 44, got {size}') class FONTS(ASCIIRecord): @@ -158,7 +159,7 @@ class FONTS(ASCIIRecord): @classmethod def check_size(cls: type[Self], size: int) -> None: if size != 0 and size % 44 != 0: - raise Exception(f'Expected size to be multiple of 44, got {size}') + raise KlamathError(f'Expected size to be multiple of 44, got {size}') class PATHTYPE(Int2Record): @@ -173,7 +174,7 @@ class GENERATIONS(Int2Record): @classmethod def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: if not isinstance(data, Sized) or len(data) != 1: - raise Exception(f'Expected exactly one integer, got {data}') + raise KlamathError(f'Expected exactly one integer, got {data}') class ATTRTABLE(ASCIIRecord): @@ -182,7 +183,7 @@ class ATTRTABLE(ASCIIRecord): @classmethod def check_size(cls: type[Self], size: int) -> None: if size > 44: - raise Exception(f'Expected size <= 44, got {size}') + raise KlamathError(f'Expected size <= 44, got {size}') class STYPTABLE(ASCIIRecord): @@ -271,7 +272,7 @@ class FORMAT(Int2Record): @classmethod def check_data(cls: type[Self], data: NDArray[numpy.integer] | Sequence[int] | int) -> None: if not isinstance(data, Sized) or len(data) != 1: - raise Exception(f'Expected exactly one integer, got {data}') + raise KlamathError(f'Expected exactly one integer, got {data}') class MASK(ASCIIRecord): From cae970e65c908f042e03d344b15cd4d8bdd37536 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:20:32 -0700 Subject: [PATCH 09/15] simplify comparisons --- klamath/library.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/klamath/library.py b/klamath/library.py index 4021677..f7b68bd 100644 --- a/klamath/library.py +++ b/klamath/library.py @@ -176,9 +176,7 @@ def read_elements(stream: IO[bytes]) -> list[Element]: data.append(Box.read(stream)) elif tag == TEXT.tag: data.append(Text.read(stream)) - elif tag == SREF.tag: - data.append(Reference.read(stream)) - elif tag == AREF.tag: + elif tag in (SREF.tag, AREF.tag): data.append(Reference.read(stream)) else: # don't care, skip From 8061d6cd378931e2dd5c42c4db22ea4ac80a135b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:20:47 -0700 Subject: [PATCH 10/15] note intentionally non-abstract method --- klamath/record.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klamath/record.py b/klamath/record.py index 674e55c..48bfe7c 100644 --- a/klamath/record.py +++ b/klamath/record.py @@ -65,7 +65,7 @@ class Record(Generic[II, OO], metaclass=ABCMeta): if cls.expected_size is not None and size != cls.expected_size: raise KlamathError(f'Expected size {cls.expected_size}, got {size}') - @classmethod + @classmethod # noqa: B027 Intentionally non-abstract def check_data(cls: type[Self], data: II) -> None: pass From 95976cd6373e3c1b0bf444dd2b883cb5d7dd9f43 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 23:20:57 -0700 Subject: [PATCH 11/15] increase min python version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4044b24..22b6fc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ classifiers = [ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", ] -requires-python = ">=3.8" +requires-python = ">=3.11" include = [ "LICENSE.md" ] From 7d6cea1c4a1d890bea938045c886e35c113f5e3e Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 03:19:13 -0700 Subject: [PATCH 12/15] numpy.array(..., copy=False) -> numpy.asarray(...) for numpy 2.0 compatibility --- klamath/basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/klamath/basic.py b/klamath/basic.py index 95f5551..86c9d59 100644 --- a/klamath/basic.py +++ b/klamath/basic.py @@ -94,14 +94,14 @@ def pack_bitarray(data: int) -> bytes: def pack_int2(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: - arr = numpy.array(data, copy=False) + arr = numpy.asarray(data) if (arr > 32767).any() or (arr < -32768).any(): raise KlamathError(f'int2 data out of range: {arr}') return arr.astype('>i2').tobytes() def pack_int4(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: - arr = numpy.array(data, copy=False) + arr = numpy.asarray(data) if (arr > 2147483647).any() or (arr < -2147483648).any(): raise KlamathError(f'int4 data out of range: {arr}') return arr.astype('>i4').tobytes() @@ -166,7 +166,7 @@ def encode_real8(fnums: NDArray[numpy.float64]) -> NDArray[numpy.uint64]: def pack_real8(data: NDArray[numpy.floating] | Sequence[float] | float) -> bytes: - return encode_real8(numpy.array(data, copy=False)).astype('>u8').tobytes() + return encode_real8(numpy.asarray(data)).astype('>u8').tobytes() def pack_ascii(data: bytes) -> bytes: From 65a33d2eca47258195b634d70fb0c1bac90da8ec Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 03:19:56 -0700 Subject: [PATCH 13/15] allow numpy v2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 22b6fc5..c7d7a0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ include = [ ] dynamic = ["version"] dependencies = [ - "numpy~=1.21", + "numpy>=1.26", ] [tool.hatch.version] From 6ad3358665d61655da1c8bf595784085da98aad2 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 03:20:27 -0700 Subject: [PATCH 14/15] update reqs in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd4a4ea..3d838ec 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The goal is to keep this library simple: ## Installation Requirements: -* python >= 3.10 (written and tested with 3.11) +* python >= 3.11 * numpy From a50d53b5085c388457f0b33ef98f07689221d53a Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 20:53:45 -0700 Subject: [PATCH 15/15] bump version to v1.4 Main change is numpy 2.0 compatibility --- klamath/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klamath/__init__.py b/klamath/__init__.py index 60ce57d..8de1f87 100644 --- a/klamath/__init__.py +++ b/klamath/__init__.py @@ -36,5 +36,5 @@ from . import ( ) __author__ = 'Jan Petykiewicz' -__version__ = '1.3' +__version__ = '1.4'