Update type annotations and some formatting

This commit is contained in:
jan 2023-04-14 11:57:31 -07:00
parent 4d362f8e09
commit 9d14bf27c6
6 changed files with 77 additions and 67 deletions

View File

@ -49,7 +49,7 @@ The goal is to keep this library simple:
## Installation ## Installation
Requirements: Requirements:
* python >= 3.7 (written and tested with 3.8) * python >= 3.10 (written and tested with 3.11)
* numpy * numpy

View File

@ -1,20 +1,21 @@
""" """
Functionality for encoding/decoding basic datatypes Functionality for encoding/decoding basic datatypes
""" """
from typing import Sequence, IO, List from typing import Sequence, IO
import struct import struct
from datetime import datetime from datetime import datetime
import numpy # type: ignore import numpy
from numpy.typing import NDArray
class KlamathError(Exception): class KlamathError(Exception):
pass pass
""" #
Parse functions # Parse functions
""" #
def parse_bitarray(data: bytes) -> int: def parse_bitarray(data: bytes) -> int:
if len(data) != 2: if len(data) != 2:
raise KlamathError(f'Incorrect bitarray size ({len(data)}). Data is {data!r}.') raise KlamathError(f'Incorrect bitarray size ({len(data)}). Data is {data!r}.')
@ -22,21 +23,21 @@ def parse_bitarray(data: bytes) -> int:
return val return val
def parse_int2(data: bytes) -> numpy.ndarray: def parse_int2(data: bytes) -> NDArray[numpy.int16]:
data_len = len(data) data_len = len(data)
if data_len == 0 or (data_len % 2) != 0: if data_len == 0 or (data_len % 2) != 0:
raise KlamathError(f'Incorrect int2 size ({len(data)}). Data is {data!r}.') raise KlamathError(f'Incorrect int2 size ({len(data)}). Data is {data!r}.')
return numpy.frombuffer(data, dtype='>i2', count=data_len // 2) return numpy.frombuffer(data, dtype='>i2', count=data_len // 2)
def parse_int4(data: bytes) -> numpy.ndarray: def parse_int4(data: bytes) -> NDArray[numpy.int32]:
data_len = len(data) data_len = len(data)
if data_len == 0 or (data_len % 4) != 0: if data_len == 0 or (data_len % 4) != 0:
raise KlamathError(f'Incorrect int4 size ({len(data)}). Data is {data!r}.') raise KlamathError(f'Incorrect int4 size ({len(data)}). Data is {data!r}.')
return numpy.frombuffer(data, dtype='>i4', count=data_len // 4) return numpy.frombuffer(data, dtype='>i4', count=data_len // 4)
def decode_real8(nums: numpy.ndarray) -> numpy.ndarray: def decode_real8(nums: NDArray[numpy.uint64]) -> NDArray[numpy.float64]:
""" Convert GDS REAL8 data to IEEE float64. """ """ Convert GDS REAL8 data to IEEE float64. """
nums = nums.astype(numpy.uint64) nums = nums.astype(numpy.uint64)
neg = nums & 0x8000_0000_0000_0000 neg = nums & 0x8000_0000_0000_0000
@ -46,7 +47,7 @@ def decode_real8(nums: numpy.ndarray) -> numpy.ndarray:
return numpy.ldexp(mant, (4 * (exp - 64) - 56).astype(numpy.int64)) return numpy.ldexp(mant, (4 * (exp - 64) - 56).astype(numpy.int64))
def parse_real8(data: bytes) -> numpy.ndarray: def parse_real8(data: bytes) -> NDArray[numpy.float64]:
data_len = len(data) data_len = len(data)
if data_len == 0 or (data_len % 8) != 0: if data_len == 0 or (data_len % 8) != 0:
raise KlamathError(f'Incorrect real8 size ({len(data)}). Data is {data!r}.') raise KlamathError(f'Incorrect real8 size ({len(data)}). Data is {data!r}.')
@ -62,7 +63,7 @@ def parse_ascii(data: bytes) -> bytes:
return data return data
def parse_datetime(data: bytes) -> List[datetime]: def parse_datetime(data: bytes) -> list[datetime]:
""" Parse date/time data (12 byte blocks) """ """ Parse date/time data (12 byte blocks) """
if len(data) == 0 or len(data) % 12 != 0: if len(data) == 0 or len(data) % 12 != 0:
raise KlamathError(f'Incorrect datetime size ({len(data)}). Data is {data!r}.') raise KlamathError(f'Incorrect datetime size ({len(data)}). Data is {data!r}.')
@ -73,9 +74,9 @@ def parse_datetime(data: bytes) -> List[datetime]:
return dts return dts
""" #
Pack functions # Pack functions
""" #
def pack_bitarray(data: int) -> bytes: def pack_bitarray(data: int) -> bytes:
if data > 65535 or data < 0: if data > 65535 or data < 0:
raise KlamathError(f'bitarray data out of range: {data}') raise KlamathError(f'bitarray data out of range: {data}')
@ -96,7 +97,7 @@ def pack_int4(data: Sequence[int]) -> bytes:
return arr.astype('>i4').tobytes() return arr.astype('>i4').tobytes()
def encode_real8(fnums: numpy.ndarray) -> numpy.ndarray: def encode_real8(fnums: NDArray[numpy.float64]) -> NDArray[numpy.uint64]:
""" Convert from float64 to GDS REAL8 representation. """ """ Convert from float64 to GDS REAL8 representation. """
# Split the ieee float bitfields # Split the ieee float bitfields
ieee = numpy.atleast_1d(fnums.astype(numpy.float64).view(numpy.uint64)) ieee = numpy.atleast_1d(fnums.astype(numpy.float64).view(numpy.uint64))
@ -151,7 +152,7 @@ def encode_real8(fnums: numpy.ndarray) -> numpy.ndarray:
real8[zero] = 0 real8[zero] = 0
real8[gds_exp < -14] = 0 # number is too small real8[gds_exp < -14] = 0 # number is too small
return real8 return real8.astype(numpy.uint64, copy=False)
def pack_real8(data: Sequence[float]) -> bytes: def pack_real8(data: Sequence[float]) -> bytes:

View File

@ -2,11 +2,12 @@
Functionality for reading/writing elements (geometry, text labels, Functionality for reading/writing elements (geometry, text labels,
structure references) and associated properties. structure references) and associated properties.
""" """
from typing import Dict, Tuple, Optional, IO, TypeVar, Type, Union from typing import Optional, IO, TypeVar, Type, Union
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
import numpy # type: ignore import numpy
from numpy.typing import NDArray
from .basic import KlamathError from .basic import KlamathError
from .record import Record from .record import Record
@ -28,7 +29,7 @@ T = TypeVar('T', bound='Text')
X = TypeVar('X', bound='Box') X = TypeVar('X', bound='Box')
def read_properties(stream: IO[bytes]) -> Dict[int, bytes]: def read_properties(stream: IO[bytes]) -> dict[int, bytes]:
""" """
Read element properties. Read element properties.
@ -55,7 +56,7 @@ def read_properties(stream: IO[bytes]) -> Dict[int, bytes]:
return properties return properties
def write_properties(stream: IO[bytes], properties: Dict[int, bytes]) -> int: def write_properties(stream: IO[bytes], properties: dict[int, bytes]) -> int:
""" """
Write element properties. Write element properties.
@ -130,7 +131,7 @@ class Reference(Element):
angle_deg: float angle_deg: float
""" Rotation (degrees counterclockwise) """ """ Rotation (degrees counterclockwise) """
xy: numpy.ndarray xy: NDArray[numpy.int32]
""" """
(For SREF) Location in the parent structure corresponding to the instance's origin (0, 0). (For SREF) Location in the parent structure corresponding to the instance's origin (0, 0).
(For AREF) 3 locations: (For AREF) 3 locations:
@ -143,10 +144,10 @@ class Reference(Element):
basis vectors to match it. basis vectors to match it.
""" """
colrow: Optional[Union[Tuple[int, int], numpy.ndarray]] colrow: tuple[int, int] | NDArray[numpy.int16] | None
""" Number of columns and rows (AREF) or None (SREF) """ """ Number of columns and rows (AREF) or None (SREF) """
properties: Dict[int, bytes] properties: dict[int, bytes]
""" Properties associated with this reference. """ """ Properties associated with this reference. """
@classmethod @classmethod
@ -173,8 +174,15 @@ class Reference(Element):
size, tag = Record.read_header(stream) size, tag = Record.read_header(stream)
xy = XY.read_data(stream, size).reshape(-1, 2) xy = XY.read_data(stream, size).reshape(-1, 2)
properties = read_properties(stream) properties = read_properties(stream)
return cls(struct_name=struct_name, xy=xy, properties=properties, colrow=colrow, return cls(
invert_y=invert_y, mag=mag, angle_deg=angle_deg) struct_name=struct_name,
xy=xy,
properties=properties,
colrow=colrow,
invert_y=invert_y,
mag=mag,
angle_deg=angle_deg,
)
def write(self, stream: IO[bytes]) -> int: def write(self, stream: IO[bytes]) -> int:
b = 0 b = 0
@ -215,13 +223,13 @@ class Boundary(Element):
""" """
__slots__ = ('layer', 'xy', 'properties') __slots__ = ('layer', 'xy', 'properties')
layer: Tuple[int, int] layer: tuple[int, int]
""" (layer, data_type) tuple """ """ (layer, data_type) tuple """
xy: numpy.ndarray xy: NDArray[numpy.int32]
""" Ordered vertices of the shape. First and last points should be identical. """ """ Ordered vertices of the shape. First and last points should be identical. """
properties: Dict[int, bytes] properties: dict[int, bytes]
""" Properties for the element. """ """ Properties for the element. """
@classmethod @classmethod
@ -252,7 +260,7 @@ class Path(Element):
""" """
__slots__ = ('layer', 'xy', 'properties', 'path_type', 'width', 'extension') __slots__ = ('layer', 'xy', 'properties', 'path_type', 'width', 'extension')
layer: Tuple[int, int] layer: tuple[int, int]
""" (layer, data_type) tuple """ """ (layer, data_type) tuple """
path_type: int path_type: int
@ -261,13 +269,13 @@ class Path(Element):
width: int width: int
""" Path width """ """ Path width """
extension: Tuple[int, int] extension: tuple[int, int]
""" Extension when using path_type=4. Ignored otherwise. """ """ Extension when using path_type=4. Ignored otherwise. """
xy: numpy.ndarray xy: NDArray[numpy.int32]
""" Path centerline coordinates """ """ Path centerline coordinates """
properties: Dict[int, bytes] properties: dict[int, bytes]
""" Properties for the element. """ """ Properties for the element. """
@classmethod @classmethod
@ -326,13 +334,13 @@ class Box(Element):
""" """
__slots__ = ('layer', 'xy', 'properties') __slots__ = ('layer', 'xy', 'properties')
layer: Tuple[int, int] layer: tuple[int, int]
""" (layer, box_type) tuple """ """ (layer, box_type) tuple """
xy: numpy.ndarray xy: NDArray[numpy.int32]
""" Box coordinates (5 pairs) """ """ Box coordinates (5 pairs) """
properties: Dict[int, bytes] properties: dict[int, bytes]
""" Properties for the element. """ """ Properties for the element. """
@classmethod @classmethod
@ -360,13 +368,13 @@ class Node(Element):
""" """
__slots__ = ('layer', 'xy', 'properties') __slots__ = ('layer', 'xy', 'properties')
layer: Tuple[int, int] layer: tuple[int, int]
""" (layer, node_type) tuple """ """ (layer, node_type) tuple """
xy: numpy.ndarray xy: NDArray[numpy.int32]
""" 1-50 pairs of coordinates. """ """ 1-50 pairs of coordinates. """
properties: Dict[int, bytes] properties: dict[int, bytes]
""" Properties for the element. """ """ Properties for the element. """
@classmethod @classmethod
@ -395,7 +403,7 @@ class Text(Element):
__slots__ = ('layer', 'xy', 'properties', 'presentation', 'path_type', __slots__ = ('layer', 'xy', 'properties', 'presentation', 'path_type',
'width', 'invert_y', 'mag', 'angle_deg', 'string') 'width', 'invert_y', 'mag', 'angle_deg', 'string')
layer: Tuple[int, int] layer: tuple[int, int]
""" (layer, node_type) tuple """ """ (layer, node_type) tuple """
presentation: int presentation: int
@ -420,13 +428,13 @@ class Text(Element):
angle_deg: float angle_deg: float
""" Rotation (ccw). Default 0. """ """ Rotation (ccw). Default 0. """
xy: numpy.ndarray xy: NDArray[numpy.int32]
""" Position (1 pair only) """ """ Position (1 pair only) """
string: bytes string: bytes
""" Text content """ """ Text content """
properties: Dict[int, bytes] properties: dict[int, bytes]
""" Properties for the element. """ """ Properties for the element. """
@classmethod @classmethod

View File

@ -1,7 +1,7 @@
""" """
File-level read/write functionality. File-level read/write functionality.
""" """
from typing import List, Dict, Tuple, Optional, IO, TypeVar, Type, MutableMapping from typing import IO, TypeVar, Type, MutableMapping
import io import io
from datetime import datetime from datetime import datetime
from dataclasses import dataclass from dataclasses import dataclass
@ -80,7 +80,7 @@ class FileHeader:
return b return b
def scan_structs(stream: IO[bytes]) -> Dict[bytes, int]: def scan_structs(stream: IO[bytes]) -> dict[bytes, int]:
""" """
Scan through a GDS file, building a table of Scan through a GDS file, building a table of
{b'structure_name': byte_offset}. {b'structure_name': byte_offset}.
@ -107,7 +107,7 @@ def scan_structs(stream: IO[bytes]) -> Dict[bytes, int]:
return positions return positions
def try_read_struct(stream: IO[bytes]) -> Optional[Tuple[bytes, List[Element]]]: def try_read_struct(stream: IO[bytes]) -> tuple[bytes, list[Element]] | None:
""" """
Skip to the next structure and attempt to read it. Skip to the next structure and attempt to read it.
@ -125,12 +125,13 @@ def try_read_struct(stream: IO[bytes]) -> Optional[Tuple[bytes, List[Element]]]:
return name, elements return name, elements
def write_struct(stream: IO[bytes], def write_struct(
name: bytes, stream: IO[bytes],
elements: List[Element], name: bytes,
cre_time: datetime = datetime(1900, 1, 1), elements: list[Element],
mod_time: datetime = datetime(1900, 1, 1), cre_time: datetime = datetime(1900, 1, 1),
) -> int: mod_time: datetime = datetime(1900, 1, 1),
) -> int:
""" """
Write a structure to the provided stream. Write a structure to the provided stream.
@ -150,7 +151,7 @@ def write_struct(stream: IO[bytes],
return b return b
def read_elements(stream: IO[bytes]) -> List[Element]: def read_elements(stream: IO[bytes]) -> list[Element]:
""" """
Read elements from the stream until an ENDSTR Read elements from the stream until an ENDSTR
record is encountered. The ENDSTR record is also record is encountered. The ENDSTR record is also
@ -162,7 +163,7 @@ def read_elements(stream: IO[bytes]) -> List[Element]:
Returns: Returns:
List of element objects. List of element objects.
""" """
data: List[Element] = [] data: list[Element] = []
size, tag = Record.read_header(stream) size, tag = Record.read_header(stream)
while tag != ENDSTR.tag: while tag != ENDSTR.tag:
if tag == BOUNDARY.tag: if tag == BOUNDARY.tag:
@ -186,7 +187,7 @@ def read_elements(stream: IO[bytes]) -> List[Element]:
return data return data
def scan_hierarchy(stream: IO[bytes]) -> Dict[bytes, Dict[bytes, int]]: def scan_hierarchy(stream: IO[bytes]) -> dict[bytes, dict[bytes, int]]:
""" """
Scan through a GDS file, building a table of instance counts Scan through a GDS file, building a table of instance counts
`{b'structure_name': {b'ref_name': count}}`. `{b'structure_name': {b'ref_name': count}}`.

View File

@ -1,14 +1,14 @@
""" """
Generic record-level read/write functionality. Generic record-level read/write functionality.
""" """
from typing import Optional, Sequence, IO from typing import Sequence, IO, TypeVar, ClassVar, Type
from typing import TypeVar, List, Tuple, ClassVar, Type
import struct import struct
import io import io
from datetime import datetime from datetime import datetime
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import numpy # type: ignore import numpy
from numpy.typing import NDArray
from .basic import KlamathError from .basic import KlamathError
from .basic import parse_int2, parse_int4, parse_real8, parse_datetime, parse_bitarray from .basic import parse_int2, parse_int4, parse_real8, parse_datetime, parse_bitarray
@ -27,7 +27,7 @@ def write_record_header(stream: IO[bytes], data_size: int, tag: int) -> int:
return stream.write(header) return stream.write(header)
def read_record_header(stream: IO[bytes]) -> Tuple[int, int]: def read_record_header(stream: IO[bytes]) -> tuple[int, int]:
""" """
Read a record's header (size and tag). Read a record's header (size and tag).
Args: Args:
@ -58,7 +58,7 @@ R = TypeVar('R', bound='Record')
class Record(metaclass=ABCMeta): class Record(metaclass=ABCMeta):
tag: ClassVar[int] = -1 tag: ClassVar[int] = -1
expected_size: ClassVar[Optional[int]] = None expected_size: ClassVar[int | None] = None
@classmethod @classmethod
def check_size(cls, size: int): def check_size(cls, size: int):
@ -80,7 +80,7 @@ class Record(metaclass=ABCMeta):
pass pass
@staticmethod @staticmethod
def read_header(stream: IO[bytes]) -> Tuple[int, int]: def read_header(stream: IO[bytes]) -> tuple[int, int]:
return read_record_header(stream) return read_record_header(stream)
@classmethod @classmethod
@ -133,7 +133,7 @@ class Record(metaclass=ABCMeta):
class NoDataRecord(Record): class NoDataRecord(Record):
expected_size: ClassVar[Optional[int]] = 0 expected_size: ClassVar[int | None] = 0
@classmethod @classmethod
def read_data(cls, stream: IO[bytes], size: int) -> None: def read_data(cls, stream: IO[bytes], size: int) -> None:
@ -147,7 +147,7 @@ class NoDataRecord(Record):
class BitArrayRecord(Record): class BitArrayRecord(Record):
expected_size: ClassVar[Optional[int]] = 2 expected_size: ClassVar[int | None] = 2
@classmethod @classmethod
def read_data(cls, stream: IO[bytes], size: int) -> int: def read_data(cls, stream: IO[bytes], size: int) -> int:
@ -160,7 +160,7 @@ class BitArrayRecord(Record):
class Int2Record(Record): class Int2Record(Record):
@classmethod @classmethod
def read_data(cls, stream: IO[bytes], size: int) -> numpy.ndarray: def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.int16]:
return parse_int2(read(stream, size)) return parse_int2(read(stream, size))
@classmethod @classmethod
@ -170,7 +170,7 @@ class Int2Record(Record):
class Int4Record(Record): class Int4Record(Record):
@classmethod @classmethod
def read_data(cls, stream: IO[bytes], size: int) -> numpy.ndarray: def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.int32]:
return parse_int4(read(stream, size)) return parse_int4(read(stream, size))
@classmethod @classmethod
@ -180,7 +180,7 @@ class Int4Record(Record):
class Real8Record(Record): class Real8Record(Record):
@classmethod @classmethod
def read_data(cls, stream: IO[bytes], size: int) -> numpy.ndarray: def read_data(cls, stream: IO[bytes], size: int) -> NDArray[numpy.float64]:
return parse_real8(read(stream, size)) return parse_real8(read(stream, size))
@classmethod @classmethod
@ -200,7 +200,7 @@ class ASCIIRecord(Record):
class DateTimeRecord(Record): class DateTimeRecord(Record):
@classmethod @classmethod
def read_data(cls, stream: IO[bytes], size: int) -> List[datetime]: def read_data(cls, stream: IO[bytes], size: int) -> list[datetime]:
return parse_datetime(read(stream, size)) return parse_datetime(read(stream, size))
@classmethod @classmethod

View File

@ -1,8 +1,8 @@
import struct import struct
import pytest # type: ignore import pytest # type: ignore
import numpy # type: ignore import numpy
from numpy.testing import assert_array_equal # type: ignore from numpy.testing import assert_array_equal
from .basic import parse_bitarray, parse_int2, parse_int4, parse_real8, parse_ascii from .basic import parse_bitarray, parse_int2, parse_int4, parse_real8, parse_ascii
from .basic import pack_bitarray, pack_int2, pack_int4, pack_real8, pack_ascii from .basic import pack_bitarray, pack_int2, pack_int4, pack_real8, pack_ascii