From 7f0c46525e2bcdea270fa2b5c7399605d2c35e62 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2020 15:38:52 -0700 Subject: [PATCH] improve type annotations --- fatamorgana/basic.py | 32 +++++++++++---------- fatamorgana/main.py | 9 +++--- fatamorgana/records.py | 63 ++++++++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 49 deletions(-) diff --git a/fatamorgana/basic.py b/fatamorgana/basic.py index a650203..24c266f 100644 --- a/fatamorgana/basic.py +++ b/fatamorgana/basic.py @@ -140,7 +140,7 @@ if _USE_NUMPY: byte_arr = _read(stream, 1) return numpy.unpackbits(numpy.frombuffer(byte_arr, dtype=numpy.uint8)) - def write_bool_byte(stream: io.BufferedIOBase, bits: Tuple[bool]) -> int: + def write_bool_byte(stream: io.BufferedIOBase, bits: Tuple[Union[bool, int], ...]) -> int: """ Pack 8 booleans into a byte, and write it to the stream. @@ -173,7 +173,7 @@ else: bits = [bool((byte >> i) & 0x01) for i in reversed(range(8))] return bits - def write_bool_byte(stream: io.BufferedIOBase, bits: Tuple[bool]) -> int: + def write_bool_byte(stream: io.BufferedIOBase, bits: Tuple[Union[bool, int], ...]) -> int: """ Pack 8 booleans into a byte, and write it to the stream. @@ -1611,6 +1611,7 @@ def write_point_list(stream: io.BufferedIOBase, return size # Try writing a bunch of Manhattan or Octangular deltas + deltas: Union[List[ManhattanDelta], List[OctangularDelta], List[Delta]] list_type = None try: deltas = [ManhattanDelta(x, y) for x, y in points] @@ -1721,6 +1722,7 @@ def read_property_value(stream: io.BufferedIOBase) -> property_value_t: Raises: InvalidDataError: if an invalid type is read. """ + ref_type: Type prop_type = read_uint(stream) if 0 <= prop_type <= 7: return read_real(stream, prop_type) @@ -1964,20 +1966,20 @@ class OffsetTable: layernames (OffsetEntry): Offset for LayerNames xnames (OffsetEntry): Offset for XNames """ - cellnames: OffsetEntry = None - textstrings: OffsetEntry = None - propnames: OffsetEntry = None - propstrings: OffsetEntry = None - layernames: OffsetEntry = None - xnames: OffsetEntry = None + cellnames: OffsetEntry + textstrings: OffsetEntry + propnames: OffsetEntry + propstrings: OffsetEntry + layernames: OffsetEntry + xnames: OffsetEntry def __init__(self, - cellnames: OffsetEntry = None, - textstrings: OffsetEntry = None, - propnames: OffsetEntry = None, - propstrings: OffsetEntry = None, - layernames: OffsetEntry = None, - xnames: OffsetEntry = None): + cellnames: Optional[OffsetEntry] = None, + textstrings: Optional[OffsetEntry] = None, + propnames: Optional[OffsetEntry] = None, + propstrings: Optional[OffsetEntry] = None, + layernames: Optional[OffsetEntry] = None, + xnames: Optional[OffsetEntry] = None): """ All parameters default to a non-strict entry with offset `0`. @@ -2204,5 +2206,5 @@ def read_magic_bytes(stream: io.BufferedIOBase): magic = _read(stream, len(MAGIC_BYTES)) if magic != MAGIC_BYTES: raise InvalidDataError('Could not read magic bytes, ' - 'found {} : {}'.format(magic, magic.decode())) + 'found {!r} : {}'.format(magic, magic.decode())) diff --git a/fatamorgana/main.py b/fatamorgana/main.py index 8fca84e..2529ec4 100644 --- a/fatamorgana/main.py +++ b/fatamorgana/main.py @@ -3,12 +3,12 @@ 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 +from typing import List, Dict, Union, Optional, Type import io import logging from . import records -from .records import Modals +from .records import Modals, Record from .basic import OffsetEntry, OffsetTable, NString, AString, real_t, Validation, \ read_magic_bytes, write_magic_bytes, read_uint, EOFError, \ InvalidDataError, InvalidRecordError @@ -29,7 +29,6 @@ class FileModals: xname_implicit: Optional[bool] = None textstring_implicit: Optional[bool] = None propstring_implicit: Optional[bool] = None - cellname_implicit: Optional[bool] = None within_cell: bool = False within_cblock: bool = False @@ -158,6 +157,8 @@ class OasisLayout: logger.info('read_record of type {} at position 0x{:x}'.format(record_id, stream.tell())) + record: Record + # CBlock if record_id == 34: if file_state.within_cblock: @@ -451,7 +452,7 @@ class XName: # Mapping from record id to record class. -_GEOMETRY = { +_GEOMETRY: Dict[int, Type] = { 19: records.Text, 20: records.Rectangle, 21: records.Polygon, diff --git a/fatamorgana/records.py b/fatamorgana/records.py index 5d8e613..c223822 100644 --- a/fatamorgana/records.py +++ b/fatamorgana/records.py @@ -11,7 +11,7 @@ Higher-level code (e.g. monitoring for combinations of records with in main.py instead. """ from abc import ABCMeta, abstractmethod -from typing import List, Dict, Tuple, Union, Optional, Sequence +from typing import List, Dict, Tuple, Union, Optional, Sequence, Any import copy import math import zlib @@ -337,17 +337,17 @@ class Start(Record): Attributes: version (AString): "1.0" unit (real_t): positive real number, grid steps per micron - offset_table (OffsetTable or None): If `None` then table must be + offset_table (Optional[OffsetTable]): If `None` then table must be placed in the `End` record) """ version: AString unit: real_t - offset_table: OffsetTable = None + offset_table: Optional[OffsetTable] = None def __init__(self, unit: real_t, version: Union[AString, str] = None, - offset_table: OffsetTable = None): + offset_table: Optional[OffsetTable] = None): """ Args unit: Grid steps per micron (positive real number) @@ -390,6 +390,7 @@ class Start(Record): version = AString.read(stream) unit = read_real(stream) has_offset_table = read_uint(stream) == 0 + offset_table: Optional[OffsetTable] if has_offset_table: offset_table = OffsetTable.read(stream) else: @@ -448,7 +449,7 @@ class End(Record): if record_id != 2: raise InvalidDataError('Invalid record id for End {}'.format(record_id)) if has_offset_table: - offset_table = OffsetTable.read(stream) + offset_table: Optional[OffsetTable] = OffsetTable.read(stream) else: offset_table = None _padding_string = read_bstring(stream) @@ -630,7 +631,7 @@ class CellName(Record): '{}'.format(record_id)) nstring = NString.read(stream) if record_id == 4: - reference_number = read_uint(stream) + reference_number: Optional[int] = read_uint(stream) else: reference_number = None record = CellName(nstring, reference_number) @@ -684,7 +685,7 @@ class PropName(Record): '{}'.format(record_id)) nstring = NString.read(stream) if record_id == 8: - reference_number = read_uint(stream) + reference_number: Optional[int] = read_uint(stream) else: reference_number = None record = PropName(nstring, reference_number) @@ -739,7 +740,7 @@ class TextString(Record): '{}'.format(record_id)) astring = AString.read(stream) if record_id == 6: - reference_number = read_uint(stream) + reference_number: Optional[int] = read_uint(stream) else: reference_number = None record = TextString(astring, reference_number) @@ -794,7 +795,7 @@ class PropString(Record): '{}'.format(record_id)) astring = AString.read(stream) if record_id == 10: - reference_number = read_uint(stream) + reference_number: Optional[int] = read_uint(stream) else: reference_number = None record = PropString(astring, reference_number) @@ -936,6 +937,7 @@ class Property(Record): s = 0x01 & (byte >> 0) name = read_refname(stream, c, n) + values: Optional[List[property_value_t]] if v == 0: if u < 0x0f: value_count = u @@ -1028,7 +1030,7 @@ class XName(Record): attribute = read_uint(stream) bstring = read_bstring(stream) if record_id == 31: - reference_number = read_uint(stream) + reference_number: Optional[int] = read_uint(stream) else: reference_number = None record = XName(attribute, bstring, reference_number) @@ -1113,11 +1115,11 @@ class XGeometry(Record): def __init__(self, attribute: int, bstring: bytes, - layer: int = None, - datatype: int = None, - x: int = None, - y: int = None, - repetition: repetition_t = None): + layer: Optional[int] = None, + datatype: Optional[int] = None, + x: Optional[int] = None, + y: Optional[int] = None, + repetition: Optional[repetition_t] = None): """ Args: attribute: Attribute number for this XGeometry. @@ -1158,7 +1160,7 @@ class XGeometry(Record): if z0 or z1 or z2: raise InvalidDataError('Malformed XGeometry header') attribute = read_uint(stream) - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -1223,6 +1225,7 @@ class Cell(Record): @staticmethod def read(stream: io.BufferedIOBase, record_id: int) -> 'Cell': + name: Union[int, NString] if record_id == 13: name = read_uint(stream) elif record_id == 14: @@ -1317,7 +1320,7 @@ class Placement(Record): #CNXYRAAF (17) or CNXYRMAF (18) c, n, x, y, r, ma0, ma1, flip = read_bool_byte(stream) - optional = {} + optional: Dict[str, Any] = {} name = read_refname(stream, c, n) if record_id == 17: aa = (ma0 << 1) | ma1 @@ -1451,7 +1454,7 @@ class Text(Record): if z0: raise InvalidDataError('Malformed Text header') - optional = {} + optional: Dict[str, Any] = {} string = read_refstring(stream, c, n) if l: optional['layer'] = read_uint(stream) @@ -1584,7 +1587,7 @@ class Rectangle(Record): '{}'.format(record_id)) is_square, w, h, x, y, r, d, l = read_bool_byte(stream) - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -1706,7 +1709,7 @@ class Polygon(Record): if z0 or z1: raise InvalidDataError('Invalid polygon header') - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -1844,7 +1847,7 @@ class Path(Record): '{}'.format(record_id)) e, w, p, x, y, r, d, l = read_bool_byte(stream) - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -2035,7 +2038,7 @@ class Trapezoid(Record): '{}'.format(record_id)) is_vertical, w, h, x, y, r, d, l = read_bool_byte(stream) - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -2227,7 +2230,7 @@ class CTrapezoid(Record): '{}'.format(record_id)) t, w, h, x, y, r, d, l = read_bool_byte(stream) - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -2348,7 +2351,7 @@ class Circle(Record): if z0 or z1: raise InvalidDataError('Malformed circle header') - optional = {} + optional: Dict[str, Any] = {} if l: optional['layer'] = read_uint(stream) if d: @@ -2390,7 +2393,7 @@ class Circle(Record): return size -def adjust_repetition(record: Record, modals: Modals): +def adjust_repetition(record, modals: Modals): """ Merge the record's repetition entry with the one in the modals @@ -2412,7 +2415,7 @@ def adjust_repetition(record: Record, modals: Modals): modals.repetition = copy.copy(record.repetition) -def adjust_field(record: Record, r_field: str, modals: Modals, m_field: str): +def adjust_field(record, r_field: str, modals: Modals, m_field: str): """ Merge `record.r_field` with `modals.m_field` @@ -2436,7 +2439,7 @@ def adjust_field(record: Record, r_field: str, modals: Modals, m_field: str): raise InvalidDataError('Unfillable field: {}'.format(m_field)) -def adjust_coordinates(record: Record, modals: Modals, mx_field: str, my_field: str): +def adjust_coordinates(record, modals: Modals, mx_field: str, my_field: str): """ Merge `record.x` and `record.y` with `modals.mx_field` and `modals.my_field`, taking into account the value of `modals.xy_relative`. @@ -2472,7 +2475,7 @@ def adjust_coordinates(record: Record, modals: Modals, mx_field: str, my_field: # TODO: Clarify the docs on the dedup_* functions -def dedup_repetition(record: Record, modals: Modals): +def dedup_repetition(record, modals: Modals): """ Deduplicate the record's repetition entry with the one in the modals. Update the one in the modals if they are different. @@ -2499,7 +2502,7 @@ def dedup_repetition(record: Record, modals: Modals): modals.repetition = record.repetition -def dedup_field(record: Record, r_field: str, modals: Modals, m_field: str): +def dedup_field(record, r_field: str, modals: Modals, m_field: str): """ Deduplicate `record.r_field` using `modals.m_field` Update the `modals.m_field` if they are different. @@ -2529,7 +2532,7 @@ def dedup_field(record: Record, r_field: str, modals: Modals, m_field: str): raise InvalidDataError('Unfillable field') -def dedup_coordinates(record: Record, modals: Modals, mx_field: str, my_field: str): +def dedup_coordinates(record, modals: Modals, mx_field: str, my_field: str): """ Deduplicate `record.x` and `record.y` using `modals.mx_field` and `modals.my_field`, taking into account the value of `modals.xy_relative`.