Modernize type annotations

This commit is contained in:
Jan Petykiewicz 2024-03-30 19:44:47 -07:00
parent d61bbc530f
commit a4c1e52ff8
5 changed files with 547 additions and 590 deletions

View File

@ -3,6 +3,8 @@
**fatamorgana** is a Python package for reading and writing OASIS format layout files. **fatamorgana** is a Python package for reading and writing OASIS format layout files.
**Homepage:** https://mpxd.net/code/jan/fatamorgana **Homepage:** https://mpxd.net/code/jan/fatamorgana
* [PyPI](https://pypi.org/project/fatamorgana)
* [Github mirror](https://github.com/anewusername/fatamorgana)
**Capabilities:** **Capabilities:**
* This package is a work-in-progress and is largely untested -- it works for * This package is a work-in-progress and is largely untested -- it works for
@ -20,7 +22,7 @@
## Installation ## Installation
**Dependencies:** **Dependencies:**
* python 3.5 or newer * python >=3.10
* (optional) numpy * (optional) numpy

View File

@ -2,7 +2,7 @@
This module contains all datatypes and parsing/writing functions for This module contains all datatypes and parsing/writing functions for
all abstractions below the 'record' or 'block' level. all abstractions below the 'record' or 'block' level.
""" """
from typing import List, Tuple, Type, Union, Optional, Any, Sequence, IO from typing import Type, Union, Any, Sequence, IO
from fractions import Fraction from fractions import Fraction
from enum import Enum from enum import Enum
import math import math
@ -132,7 +132,7 @@ def write_byte(stream: IO[bytes], n: int) -> int:
return stream.write(bytes((n,))) return stream.write(bytes((n,)))
def _py_read_bool_byte(stream: IO[bytes]) -> List[bool]: def _py_read_bool_byte(stream: IO[bytes]) -> list[bool]:
""" """
Read a single byte from the stream, and interpret its bits as Read a single byte from the stream, and interpret its bits as
a list of 8 booleans. a list of 8 booleans.
@ -147,7 +147,7 @@ def _py_read_bool_byte(stream: IO[bytes]) -> List[bool]:
bits = [bool((byte >> i) & 0x01) for i in reversed(range(8))] bits = [bool((byte >> i) & 0x01) for i in reversed(range(8))]
return bits return bits
def _py_write_bool_byte(stream: IO[bytes], bits: Tuple[Union[bool, int], ...]) -> int: def _py_write_bool_byte(stream: IO[bytes], bits: tuple[bool | int, ...]) -> int:
""" """
Pack 8 booleans into a byte, and write it to the stream. Pack 8 booleans into a byte, and write it to the stream.
@ -184,7 +184,7 @@ if _USE_NUMPY:
byte_arr = _read(stream, 1) byte_arr = _read(stream, 1)
return numpy.unpackbits(numpy.frombuffer(byte_arr, dtype=numpy.uint8)) return numpy.unpackbits(numpy.frombuffer(byte_arr, dtype=numpy.uint8))
def _np_write_bool_byte(stream: IO[bytes], bits: Tuple[Union[bool, int], ...]) -> int: def _np_write_bool_byte(stream: IO[bytes], bits: tuple[Union[bool, int], ...]) -> int:
""" """
Pack 8 booleans into a byte, and write it to the stream. Pack 8 booleans into a byte, and write it to the stream.
@ -457,7 +457,7 @@ def write_float64(stream: IO[bytes], f: float) -> int:
return stream.write(b) return stream.write(b)
def read_real(stream: IO[bytes], real_type: Optional[int] = None) -> real_t: def read_real(stream: IO[bytes], real_type: int | None = None) -> real_t:
""" """
Read a real number from the stream. Read a real number from the stream.
@ -783,13 +783,12 @@ def write_astring(stream: IO[bytes], string: str) -> int:
class ManhattanDelta: class ManhattanDelta:
""" """
Class representing an axis-aligned ("Manhattan") vector. Class representing an axis-aligned ("Manhattan") vector.
Attributes:
vertical (bool): `True` if aligned along y-axis
value (int): signed length of the vector
""" """
vertical: bool vertical: bool
"""`True` if aligned along y-axis"""
value: int value: int
"""signed length of the vector"""
def __init__(self, x: int, y: int) -> None: def __init__(self, x: int, y: int) -> None:
""" """
@ -810,7 +809,7 @@ class ManhattanDelta:
self.vertical = True self.vertical = True
self.value = y self.value = y
def as_list(self) -> List[int]: def as_list(self) -> list[int]:
""" """
Return a list representation of this vector. Return a list representation of this vector.
@ -892,25 +891,26 @@ class ManhattanDelta:
class OctangularDelta: class OctangularDelta:
""" """
Class representing an axis-aligned or 45-degree ("Octangular") vector. Class representing an axis-aligned or 45-degree ("Octangular") vector.
Attributes:
proj_mag (int): projection of the vector onto the x or y axis (non-zero)
octangle (int): bitfield:
bit 2: 1 if non-axis-aligned (non-Manhattan)
if Manhattan:
bit 1: 1 if direction is negative
bit 0: 1 if direction is y
if non-Manhattan:
bit 1: 1 if in lower half-plane
bit 0: 1 if x==-y
Resulting directions:
0: +x, 1: +y, 2: -x, 3: -y,
4: +x+y, 5: -x+y,
6: +x-y, 7: -x-y
""" """
proj_mag: int proj_mag: int
"""projection of the vector onto the x or y axis (non-zero)"""
octangle: int octangle: int
"""
bitfield:
bit 2: 1 if non-axis-aligned (non-Manhattan)
if Manhattan:
bit 1: 1 if direction is negative
bit 0: 1 if direction is y
if non-Manhattan:
bit 1: 1 if in lower half-plane
bit 0: 1 if x==-y
Resulting directions:
0: +x, 1: +y, 2: -x, 3: -y,
4: +x+y, 5: -x+y,
6: +x-y, 7: -x-y
"""
def __init__(self, x: int, y: int) -> None: def __init__(self, x: int, y: int) -> None:
""" """
@ -936,7 +936,7 @@ class OctangularDelta:
else: else:
raise InvalidDataError(f'Non-octangular delta! ({x}, {y})') raise InvalidDataError(f'Non-octangular delta! ({x}, {y})')
def as_list(self) -> List[int]: def as_list(self) -> list[int]:
""" """
Return a list representation of this vector. Return a list representation of this vector.
@ -1028,13 +1028,12 @@ class OctangularDelta:
class Delta: class Delta:
""" """
Class representing an arbitrary vector Class representing an arbitrary vector
Attributes
x (int): x-displacement
y (int): y-displacement
""" """
x: int x: int
"""x-displacement"""
y: int y: int
"""y-displacement"""
def __init__(self, x: int, y: int) -> None: def __init__(self, x: int, y: int) -> None:
""" """
@ -1047,7 +1046,7 @@ class Delta:
self.x = x self.x = x
self.y = y self.y = y
def as_list(self) -> List[int]: def as_list(self) -> list[int]:
""" """
Return a list representation of this vector. Return a list representation of this vector.
@ -1168,32 +1167,35 @@ class ReuseRepetition:
class GridRepetition: class GridRepetition:
""" """
Class representing a repetition entry denoting a 1D or 2D array A repetition entry denoting a 1D or 2D array of regularly-spaced elements. The
of regularly-spaced elements. The spacings are stored as one or spacings are stored as one or two lattice vectors, and the extent of the grid
two lattice vectors, and the extent of the grid is stored as the is stored as the number of elements along each lattice vector.
number of elements along each lattice vector.
Attributes:
a_vector (Tuple[int, int]): `(xa, ya)` vector specifying a center-to-center
displacement between adjacent elements in the grid.
b_vector (Optional[Tuple[int, int]]): `(xb, yb)`, a second displacement,
present if a 2D grid is being specified.
a_count (int): number of elements (>=1) along the grid axis specified by
`a_vector`.
b_count (Optional[int]): Number of elements (>=1) along the grid axis
specified by `b_vector`, if `b_vector` is not `None`.
""" """
a_vector: List[int]
b_vector: Optional[List[int]] = None a_vector: list[int]
"""`(xa, ya)` vector specifying a center-to-center
displacement between adjacent elements in the grid.
"""
b_vector: list[int] | None = None
"""`(xb, yb)`, a second displacement,
present if a 2D grid is being specified.
"""
a_count: int a_count: int
b_count: Optional[int] = None """number of elements (>=1) along the grid axis specified by `a_vector`."""
b_count: int | None = None
"""Number of elements (>=1) along the grid axis
specified by `b_vector`, if `b_vector` is not `None`.
"""
def __init__( def __init__(
self, self,
a_vector: Sequence[int], a_vector: Sequence[int],
a_count: int, a_count: int,
b_vector: Optional[Sequence[int]] = None, b_vector: Sequence[int] | None = None,
b_count: Optional[int] = None): b_count: int | None = None):
""" """
Args: Args:
a_vector: First lattice vector, of the form `[x, y]`. a_vector: First lattice vector, of the form `[x, y]`.
@ -1245,8 +1247,8 @@ class GridRepetition:
Raises: Raises:
InvalidDataError: if `repetition_type` is invalid. InvalidDataError: if `repetition_type` is invalid.
""" """
nb: Optional[int] nb: int | None
b_vector: Optional[List[int]] b_vector: list[int] | None
if repetition_type == 1: if repetition_type == 1:
na = read_uint(stream) + 2 na = read_uint(stream) + 2
nb = read_uint(stream) + 2 nb = read_uint(stream) + 2
@ -1352,14 +1354,13 @@ class ArbitraryRepetition:
""" """
Class representing a repetition entry denoting a 1D or 2D array Class representing a repetition entry denoting a 1D or 2D array
of arbitrarily-spaced elements. of arbitrarily-spaced elements.
Attributes:
x_displacements (List[int]): x-displacements between consecutive elements
y_displacements (List[int]): y-displacements between consecutive elements
""" """
x_displacements: List[int] x_displacements: list[int]
y_displacements: List[int] """x-displacements between consecutive elements"""
y_displacements: list[int]
"""y-displacements between consecutive elements"""
def __init__( def __init__(
self, self,
@ -1443,7 +1444,7 @@ class ArbitraryRepetition:
Returns: Returns:
Number of bytes written. Number of bytes written.
""" """
def get_gcd(vals: List[int]) -> int: def get_gcd(vals: list[int]) -> int:
""" """
Get the greatest common denominator of a list of ints. Get the greatest common denominator of a list of ints.
""" """
@ -1506,7 +1507,7 @@ class ArbitraryRepetition:
def read_point_list( def read_point_list(
stream: IO[bytes], stream: IO[bytes],
implicit_closed: bool, implicit_closed: bool,
) -> List[List[int]]: ) -> Sequence[Sequence[int]]:
""" """
Read a point list from a stream. Read a point list from a stream.
@ -1594,7 +1595,7 @@ def read_point_list(
def write_point_list( def write_point_list(
stream: IO[bytes], stream: IO[bytes],
points: List[Sequence[int]], points: list[Sequence[int]],
fast: bool = False, fast: bool = False,
implicit_closed: bool = True implicit_closed: bool = True
) -> int: ) -> int:
@ -1655,7 +1656,7 @@ def write_point_list(
return size return size
# Try writing a bunch of Manhattan or Octangular deltas # Try writing a bunch of Manhattan or Octangular deltas
deltas: Union[List[ManhattanDelta], List[OctangularDelta], List[Delta]] deltas: list[ManhattanDelta] | list[OctangularDelta] | list[Delta]
list_type = None list_type = None
try: try:
deltas = [ManhattanDelta(x, y) for x, y in points] deltas = [ManhattanDelta(x, y) for x, y in points]
@ -1717,13 +1718,12 @@ def write_point_list(
class PropStringReference: class PropStringReference:
""" """
Reference to a property string. Reference to a property string.
Attributes:
ref (int): ID of the target
ref_type (Type): Type of the target: `bytes`, `NString`, or `AString`
""" """
ref: int ref: int
"""ID of the target"""
reference_type: Type reference_type: Type
"""Type of the target: `bytes`, `NString`, or `AString`"""
def __init__(self, ref: int, ref_type: Type) -> None: def __init__(self, ref: int, ref_type: Type) -> None:
""" """
@ -1854,7 +1854,7 @@ def write_property_value(
return size return size
def read_interval(stream: IO[bytes]) -> Tuple[Optional[int], Optional[int]]: def read_interval(stream: IO[bytes]) -> tuple[int | None, int | None]:
""" """
Read an interval from a stream. Read an interval from a stream.
These are used for storing layer info. These are used for storing layer info.
@ -1896,8 +1896,8 @@ def read_interval(stream: IO[bytes]) -> Tuple[Optional[int], Optional[int]]:
def write_interval( def write_interval(
stream: IO[bytes], stream: IO[bytes],
min_bound: Optional[int] = None, min_bound: int | None = None,
max_bound: Optional[int] = None, max_bound: int | None = None,
) -> int: ) -> int:
""" """
Write an interval to a stream. Write an interval to a stream.
@ -1931,23 +1931,24 @@ def write_interval(
class OffsetEntry: class OffsetEntry:
""" """
Entry for the file's offset table. Entry for the file's offset table.
Attributes:
strict (bool): If `False`, the records pointed to by this
offset entry may also appear elsewhere in the file. If `True`, all
records of the type pointed to by this offset entry must be present
in a contiuous block at the specified offset [pad records also allowed].
Additionally:
- All references to strict-mode records must be
explicit (using reference_number).
- The offset may point to an encapsulating CBlock record, if the first
record in that CBlock is of the target record type. A strict modei
table cannot begin in the middle of a CBlock.
offset (int): offset from the start of the file; may be 0
for records that are not present.
""" """
strict: bool = False strict: bool = False
"""
If `False`, the records pointed to by this offset entry may also appear
elsewhere in the file. If `True`, all records of the type pointed to by
this offset entry must be present in a contiuous block at the specified
offset [pad records also allowed].
Additionally:
- All references to strict-mode records must be explicit (using
`reference_number`).
- The offset may point to an encapsulating CBlock record, if the first
record in that CBlock is of the target record type. A strict mode
table cannot begin in the middle of a CBlock.
"""
offset: int = 0 offset: int = 0
"""offset from the start of the file; 0 for records that are not present."""
def __init__(self, strict: bool = False, offset: int = 0) -> None: def __init__(self, strict: bool = False, offset: int = 0) -> None:
""" """
@ -2006,14 +2007,6 @@ class OffsetTable:
XName XName
which are stored in the above order in the file's offset table. which are stored in the above order in the file's offset table.
Attributes:
cellnames (OffsetEntry): Offset for CellNames
textstrings (OffsetEntry): Offset for TextStrings
propnames (OffsetEntry): Offset for PropNames
propstrings (OffsetEntry): Offset for PropStrings
layernames (OffsetEntry): Offset for LayerNames
xnames (OffsetEntry): Offset for XNames
""" """
cellnames: OffsetEntry cellnames: OffsetEntry
textstrings: OffsetEntry textstrings: OffsetEntry
@ -2024,12 +2017,12 @@ class OffsetTable:
def __init__( def __init__(
self, self,
cellnames: Optional[OffsetEntry] = None, cellnames: OffsetEntry | None = None,
textstrings: Optional[OffsetEntry] = None, textstrings: OffsetEntry | None = None,
propnames: Optional[OffsetEntry] = None, propnames: OffsetEntry | None = None,
propstrings: Optional[OffsetEntry] = None, propstrings: OffsetEntry | None = None,
layernames: Optional[OffsetEntry] = None, layernames: OffsetEntry | None = None,
xnames: Optional[OffsetEntry] = None, xnames: OffsetEntry | None = None,
) -> None: ) -> None:
""" """
All parameters default to a non-strict entry with offset `0`. All parameters default to a non-strict entry with offset `0`.
@ -2149,14 +2142,18 @@ class Validation:
The checksum is calculated using the entire file, excluding the final 4 bytes The checksum is calculated using the entire file, excluding the final 4 bytes
(the value of the checksum itself). (the value of the checksum itself).
Attributes:
checksum_type (int): `0` for no checksum, `1` for crc32, `2` for checksum32
checksum (Optional[int]): value of the checksum
""" """
checksum_type: int checksum_type: int
checksum: Optional[int] = None """
`0` for no checksum,
`1` for crc32,
`2` for checksum32,
"""
def __init__(self, checksum_type: int, checksum: Optional[int] = None) -> None: checksum: int | None = None
"""value of the checksum"""
def __init__(self, checksum_type: int, checksum: int | None = None) -> None:
""" """
Args: Args:
checksum_type: 0,1,2 (No checksum, crc32, checksum32) checksum_type: 0,1,2 (No checksum, crc32, checksum32)

View File

@ -3,7 +3,7 @@ This module contains data structures and functions for reading from and
writing to whole OASIS layout files, and provides a few additional writing to whole OASIS layout files, and provides a few additional
abstractions for the data contained inside them. abstractions for the data contained inside them.
""" """
from typing import List, Dict, Union, Optional, Type, IO from typing import Type, IO
import io import io
import logging import logging
@ -27,20 +27,20 @@ class FileModals:
""" """
File-scoped modal variables File-scoped modal variables
""" """
cellname_implicit: Optional[bool] = None cellname_implicit: bool | None = None
propname_implicit: Optional[bool] = None propname_implicit: bool | None = None
xname_implicit: Optional[bool] = None xname_implicit: bool | None = None
textstring_implicit: Optional[bool] = None textstring_implicit: bool | None = None
propstring_implicit: Optional[bool] = None propstring_implicit: bool | None = None
property_target: List[records.Property] property_target: list[records.Property]
within_cell: bool = False within_cell: bool = False
within_cblock: bool = False within_cblock: bool = False
end_has_offset_table: bool = False end_has_offset_table: bool = False
started: bool = False started: bool = False
def __init__(self, property_target: List[records.Property]) -> None: def __init__(self, property_target: list[records.Property]) -> None:
self.property_target = property_target self.property_target = property_target
@ -53,46 +53,48 @@ class OasisLayout:
record objects. record objects.
Cells are stored using `Cell` objects (different from `records.Cell` Cells are stored using `Cell` objects (different from `records.Cell`
record objects). record objects).
Attributes:
(File properties)
version (AString): Version string ('1.0')
unit (real_t): grid steps per micron
validation (Validation): checksum data
(Names)
cellnames (Dict[int, CellName]): Cell names
propnames (Dict[int, NString]): Property names
xnames (Dict[int, XName]): Custom names
(Strings)
textstrings (Dict[int, AString]): Text strings
propstrings (Dict[int, AString]): Property strings
(Data)
layers (List[records.LayerName]): Layer definitions
properties (List[records.Property]): Property values
cells (List[Cell]): Layout cells
""" """
# File properties
version: AString version: AString
"""File format version string ('1.0')"""
unit: real_t unit: real_t
"""grid steps per micron"""
validation: Validation validation: Validation
"""checksum data"""
properties: List[records.Property] # Data
cells: List['Cell'] properties: list[records.Property]
"""Property values"""
cellnames: Dict[int, 'CellName'] cells: list['Cell']
propnames: Dict[int, NString] """Layout cells"""
xnames: Dict[int, 'XName']
textstrings: Dict[int, AString] layers: list[records.LayerName]
propstrings: Dict[int, AString] """Layer definitions"""
layers: List[records.LayerName]
# Names
cellnames: dict[int, 'CellName']
"""Cell names"""
propnames: dict[int, NString]
"""Property names"""
xnames: dict[int, 'XName']
"""Custom names"""
# String storage
textstrings: dict[int, AString]
"""Text strings"""
propstrings: dict[int, AString]
"""Property strings"""
def __init__( def __init__(
self, self,
unit: real_t, unit: real_t,
validation: Optional[Validation] = None, validation: Validation | None = None,
) -> None: ) -> None:
""" """
Args: Args:
@ -404,26 +406,21 @@ class OasisLayout:
class Cell: class Cell:
""" """
Representation of an OASIS cell. Representation of an OASIS cell.
Attributes:
name (Union[NString, int]): name or "CellName reference" number
properties (List[records.Property]): Properties of this cell
placements (List[records.Placement]): Placement record objects
geometry: (List[records.geometry_t]): Geometry record objectes
""" """
name: Union[NString, int] name: NString | int
properties: List[records.Property] """name or "CellName reference" number"""
placements: List[records.Placement]
geometry: List[records.geometry_t] properties: list[records.Property]
placements: list[records.Placement]
geometry: list[records.geometry_t]
def __init__( def __init__(
self, self,
name: Union[NString, str, int], name: NString | str | int,
*, *,
properties: Optional[List[records.Property]] = None, properties: list[records.Property] | None = None,
placements: Optional[List[records.Placement]] = None, placements: list[records.Placement] | None = None,
geometry: Optional[List[records.geometry_t]] = None, geometry: list[records.geometry_t] | None = None,
) -> None: ) -> None:
self.name = name if isinstance(name, (NString, int)) else NString(name) self.name = name if isinstance(name, (NString, int)) else NString(name)
self.properties = [] if properties is None else properties self.properties = [] if properties is None else properties
@ -464,12 +461,12 @@ class CellName:
with the reference data stripped out. with the reference data stripped out.
""" """
nstring: NString nstring: NString
properties: List[records.Property] properties: list[records.Property]
def __init__( def __init__(
self, self,
nstring: Union[NString, str], nstring: NString | str,
properties: Optional[List[records.Property]] = None, properties: list[records.Property] | None = None,
) -> None: ) -> None:
""" """
Args: Args:
@ -531,7 +528,7 @@ class XName:
# Mapping from record id to record class. # Mapping from record id to record class.
_GEOMETRY: Dict[int, Type[records.geometry_t]] = { _GEOMETRY: dict[int, Type[records.geometry_t]] = {
19: records.Text, 19: records.Text,
20: records.Rectangle, 20: records.Rectangle,
21: records.Polygon, 21: records.Polygon,

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ classifiers = [
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
] ]
requires-python = ">=3.8" requires-python = ">=3.10"
dynamic = ["version"] dynamic = ["version"]
dependencies = [ dependencies = [
] ]