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.
**Homepage:** https://mpxd.net/code/jan/fatamorgana
* [PyPI](https://pypi.org/project/fatamorgana)
* [Github mirror](https://github.com/anewusername/fatamorgana)
**Capabilities:**
* This package is a work-in-progress and is largely untested -- it works for
@ -20,7 +22,7 @@
## Installation
**Dependencies:**
* python 3.5 or newer
* python >=3.10
* (optional) numpy

View File

@ -2,7 +2,7 @@
This module contains all datatypes and parsing/writing functions for
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 enum import Enum
import math
@ -132,7 +132,7 @@ def write_byte(stream: IO[bytes], n: int) -> int:
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
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))]
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.
@ -184,7 +184,7 @@ if _USE_NUMPY:
byte_arr = _read(stream, 1)
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.
@ -457,7 +457,7 @@ def write_float64(stream: IO[bytes], f: float) -> int:
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.
@ -783,13 +783,12 @@ def write_astring(stream: IO[bytes], string: str) -> int:
class ManhattanDelta:
"""
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
"""`True` if aligned along y-axis"""
value: int
"""signed length of the vector"""
def __init__(self, x: int, y: int) -> None:
"""
@ -810,7 +809,7 @@ class ManhattanDelta:
self.vertical = True
self.value = y
def as_list(self) -> List[int]:
def as_list(self) -> list[int]:
"""
Return a list representation of this vector.
@ -892,10 +891,13 @@ class ManhattanDelta:
class OctangularDelta:
"""
Class representing an axis-aligned or 45-degree ("Octangular") vector.
"""
proj_mag: int
"""projection of the vector onto the x or y axis (non-zero)"""
Attributes:
proj_mag (int): projection of the vector onto the x or y axis (non-zero)
octangle (int): bitfield:
octangle: int
"""
bitfield:
bit 2: 1 if non-axis-aligned (non-Manhattan)
if Manhattan:
bit 1: 1 if direction is negative
@ -909,8 +911,6 @@ class OctangularDelta:
4: +x+y, 5: -x+y,
6: +x-y, 7: -x-y
"""
proj_mag: int
octangle: int
def __init__(self, x: int, y: int) -> None:
"""
@ -936,7 +936,7 @@ class OctangularDelta:
else:
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.
@ -1028,13 +1028,12 @@ class OctangularDelta:
class Delta:
"""
Class representing an arbitrary vector
Attributes
x (int): x-displacement
y (int): y-displacement
"""
x: int
"""x-displacement"""
y: int
"""y-displacement"""
def __init__(self, x: int, y: int) -> None:
"""
@ -1047,7 +1046,7 @@ class Delta:
self.x = x
self.y = y
def as_list(self) -> List[int]:
def as_list(self) -> list[int]:
"""
Return a list representation of this vector.
@ -1168,32 +1167,35 @@ class ReuseRepetition:
class GridRepetition:
"""
Class representing a repetition entry denoting a 1D or 2D array
of regularly-spaced elements. The spacings are stored as one or
two lattice vectors, and the extent of the grid is stored as the
number of elements along each lattice vector.
A repetition entry denoting a 1D or 2D array of regularly-spaced elements. The
spacings are stored as one or two lattice vectors, and the extent of the grid
is stored as the number of elements along each lattice vector.
"""
Attributes:
a_vector (Tuple[int, int]): `(xa, ya)` vector specifying a center-to-center
a_vector: list[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,
"""
b_vector: list[int] | None = None
"""`(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
"""
a_count: int
"""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`.
"""
a_vector: List[int]
b_vector: Optional[List[int]] = None
a_count: int
b_count: Optional[int] = None
def __init__(
self,
a_vector: Sequence[int],
a_count: int,
b_vector: Optional[Sequence[int]] = None,
b_count: Optional[int] = None):
b_vector: Sequence[int] | None = None,
b_count: int | None = None):
"""
Args:
a_vector: First lattice vector, of the form `[x, y]`.
@ -1245,8 +1247,8 @@ class GridRepetition:
Raises:
InvalidDataError: if `repetition_type` is invalid.
"""
nb: Optional[int]
b_vector: Optional[List[int]]
nb: int | None
b_vector: list[int] | None
if repetition_type == 1:
na = 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
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]
y_displacements: List[int]
x_displacements: list[int]
"""x-displacements between consecutive elements"""
y_displacements: list[int]
"""y-displacements between consecutive elements"""
def __init__(
self,
@ -1443,7 +1444,7 @@ class ArbitraryRepetition:
Returns:
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.
"""
@ -1506,7 +1507,7 @@ class ArbitraryRepetition:
def read_point_list(
stream: IO[bytes],
implicit_closed: bool,
) -> List[List[int]]:
) -> Sequence[Sequence[int]]:
"""
Read a point list from a stream.
@ -1594,7 +1595,7 @@ def read_point_list(
def write_point_list(
stream: IO[bytes],
points: List[Sequence[int]],
points: list[Sequence[int]],
fast: bool = False,
implicit_closed: bool = True
) -> int:
@ -1655,7 +1656,7 @@ def write_point_list(
return size
# 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
try:
deltas = [ManhattanDelta(x, y) for x, y in points]
@ -1717,13 +1718,12 @@ def write_point_list(
class PropStringReference:
"""
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
"""ID of the target"""
reference_type: Type
"""Type of the target: `bytes`, `NString`, or `AString`"""
def __init__(self, ref: int, ref_type: Type) -> None:
"""
@ -1854,7 +1854,7 @@ def write_property_value(
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.
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(
stream: IO[bytes],
min_bound: Optional[int] = None,
max_bound: Optional[int] = None,
min_bound: int | None = None,
max_bound: int | None = None,
) -> int:
"""
Write an interval to a stream.
@ -1931,23 +1931,24 @@ def write_interval(
class OffsetEntry:
"""
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
"""
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 from the start of the file; 0 for records that are not present."""
def __init__(self, strict: bool = False, offset: int = 0) -> None:
"""
@ -2006,14 +2007,6 @@ class OffsetTable:
XName
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
textstrings: OffsetEntry
@ -2024,12 +2017,12 @@ class OffsetTable:
def __init__(
self,
cellnames: Optional[OffsetEntry] = None,
textstrings: Optional[OffsetEntry] = None,
propnames: Optional[OffsetEntry] = None,
propstrings: Optional[OffsetEntry] = None,
layernames: Optional[OffsetEntry] = None,
xnames: Optional[OffsetEntry] = None,
cellnames: OffsetEntry | None = None,
textstrings: OffsetEntry | None = None,
propnames: OffsetEntry | None = None,
propstrings: OffsetEntry | None = None,
layernames: OffsetEntry | None = None,
xnames: OffsetEntry | None = None,
) -> None:
"""
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 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: 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:
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
abstractions for the data contained inside them.
"""
from typing import List, Dict, Union, Optional, Type, IO
from typing import Type, IO
import io
import logging
@ -27,20 +27,20 @@ class FileModals:
"""
File-scoped modal variables
"""
cellname_implicit: Optional[bool] = None
propname_implicit: Optional[bool] = None
xname_implicit: Optional[bool] = None
textstring_implicit: Optional[bool] = None
propstring_implicit: Optional[bool] = None
cellname_implicit: bool | None = None
propname_implicit: bool | None = None
xname_implicit: bool | None = None
textstring_implicit: bool | None = None
propstring_implicit: bool | None = None
property_target: List[records.Property]
property_target: list[records.Property]
within_cell: bool = False
within_cblock: bool = False
end_has_offset_table: 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
@ -53,46 +53,48 @@ class OasisLayout:
record objects.
Cells are stored using `Cell` objects (different from `records.Cell`
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
"""File format version string ('1.0')"""
unit: real_t
"""grid steps per micron"""
validation: Validation
"""checksum data"""
properties: List[records.Property]
cells: List['Cell']
# Data
properties: list[records.Property]
"""Property values"""
cellnames: Dict[int, 'CellName']
propnames: Dict[int, NString]
xnames: Dict[int, 'XName']
cells: list['Cell']
"""Layout cells"""
textstrings: Dict[int, AString]
propstrings: Dict[int, AString]
layers: List[records.LayerName]
layers: list[records.LayerName]
"""Layer definitions"""
# 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__(
self,
unit: real_t,
validation: Optional[Validation] = None,
validation: Validation | None = None,
) -> None:
"""
Args:
@ -404,26 +406,21 @@ class OasisLayout:
class 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]
properties: List[records.Property]
placements: List[records.Placement]
geometry: List[records.geometry_t]
name: NString | int
"""name or "CellName reference" number"""
properties: list[records.Property]
placements: list[records.Placement]
geometry: list[records.geometry_t]
def __init__(
self,
name: Union[NString, str, int],
name: NString | str | int,
*,
properties: Optional[List[records.Property]] = None,
placements: Optional[List[records.Placement]] = None,
geometry: Optional[List[records.geometry_t]] = None,
properties: list[records.Property] | None = None,
placements: list[records.Placement] | None = None,
geometry: list[records.geometry_t] | None = None,
) -> None:
self.name = name if isinstance(name, (NString, int)) else NString(name)
self.properties = [] if properties is None else properties
@ -464,12 +461,12 @@ class CellName:
with the reference data stripped out.
"""
nstring: NString
properties: List[records.Property]
properties: list[records.Property]
def __init__(
self,
nstring: Union[NString, str],
properties: Optional[List[records.Property]] = None,
nstring: NString | str,
properties: list[records.Property] | None = None,
) -> None:
"""
Args:
@ -531,7 +528,7 @@ class XName:
# Mapping from record id to record class.
_GEOMETRY: Dict[int, Type[records.geometry_t]] = {
_GEOMETRY: dict[int, Type[records.geometry_t]] = {
19: records.Text,
20: records.Rectangle,
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 :: Electronic Design Automation (EDA)",
]
requires-python = ">=3.8"
requires-python = ">=3.10"
dynamic = ["version"]
dependencies = [
]