From c9402500e20841df9497656b2a675114dab49f44 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Thu, 23 Feb 2023 13:15:32 -0800 Subject: [PATCH] modernize type annotations --- examples/tutorial/basic_shapes.py | 2 +- examples/tutorial/devices.py | 4 +- examples/tutorial/library.py | 6 +- examples/tutorial/pcgen.py | 2 +- masque/abstract.py | 14 ++- masque/builder/builder.py | 91 ++++++++++--------- masque/builder/flatbuilder.py | 53 ++++++----- masque/builder/tools.py | 8 +- masque/builder/utils.py | 13 ++- masque/file/dxf.py | 29 +++---- masque/file/gdsii.py | 49 +++++------ masque/file/oasis.py | 87 +++++++++---------- masque/file/utils.py | 6 +- masque/label.py | 8 +- masque/library.py | 140 +++++++++++++----------------- masque/pattern.py | 53 ++++++----- masque/ports.py | 43 +++++---- masque/ref.py | 28 +++--- masque/repetition.py | 18 ++-- masque/shapes/arc.py | 14 +-- masque/shapes/circle.py | 15 ++-- masque/shapes/ellipse.py | 14 +-- masque/shapes/path.py | 26 +++--- masque/shapes/polygon.py | 42 ++++----- masque/shapes/shape.py | 18 ++-- masque/shapes/text.py | 18 ++-- masque/traits/mirrorable.py | 4 +- masque/traits/positionable.py | 4 +- masque/traits/repeatable.py | 16 ++-- masque/utils/pack2d.py | 10 +-- masque/utils/ports2data.py | 6 +- masque/utils/transform.py | 4 +- masque/utils/types.py | 6 +- masque/utils/vertices.py | 2 - 34 files changed, 409 insertions(+), 444 deletions(-) diff --git a/examples/tutorial/basic_shapes.py b/examples/tutorial/basic_shapes.py index 4d896fc..8c9550b 100644 --- a/examples/tutorial/basic_shapes.py +++ b/examples/tutorial/basic_shapes.py @@ -1,4 +1,4 @@ -from typing import Tuple, Sequence +from typing import Sequence import numpy from numpy import pi diff --git a/examples/tutorial/devices.py b/examples/tutorial/devices.py index 45b4a36..c4e0896 100644 --- a/examples/tutorial/devices.py +++ b/examples/tutorial/devices.py @@ -1,5 +1,5 @@ # TODO update tutorials -from typing import Tuple, Sequence, Dict, Mapping +from typing import Sequence, Mapping import numpy from numpy import pi @@ -43,7 +43,7 @@ def perturbed_l3( trench_layer: layer_t = (1, 0), shifts_a: Sequence[float] = (0.15, 0, 0.075), shifts_r: Sequence[float] = (1.0, 1.0, 1.0), - xy_size: Tuple[int, int] = (10, 10), + xy_size: tuple[int, int] = (10, 10), perturbed_radius: float = 1.1, trench_width: float = 1200, ) -> Pattern: diff --git a/examples/tutorial/library.py b/examples/tutorial/library.py index b86a81c..f23cf14 100644 --- a/examples/tutorial/library.py +++ b/examples/tutorial/library.py @@ -1,4 +1,4 @@ -from typing import Tuple, Sequence, Callable +from typing import Sequence, Callable from pprint import pformat import numpy @@ -116,12 +116,12 @@ if __name__ == '__main__': # other: Pattern, # label_layer: layer_t = 'WATLAYER', # *, -# port_map: Optional[Dict[str, Optional[str]]] = None, +# port_map: Dict[str, str | None] | None = None, # **kwargs, # ) -> 'prout': # # Pattern.place(self, other, port_map=port_map, **kwargs) -# name: Optional[str] +# name: str | None # for name in other.ports: # if port_map: # assert(name is not None) diff --git a/examples/tutorial/pcgen.py b/examples/tutorial/pcgen.py index 0856ff9..17243e4 100644 --- a/examples/tutorial/pcgen.py +++ b/examples/tutorial/pcgen.py @@ -2,7 +2,7 @@ Routines for creating normalized 2D lattices and common photonic crystal cavity designs. """ -from typing import Sequence, Tuple +from typing import Sequence import numpy from numpy.typing import ArrayLike, NDArray diff --git a/masque/abstract.py b/masque/abstract.py index e1202bf..d43a3df 100644 --- a/masque/abstract.py +++ b/masque/abstract.py @@ -1,12 +1,10 @@ -from typing import Dict, TypeVar -#from typing import Union, Optional, MutableMapping, TYPE_CHECKING +from typing import TypeVar import copy import logging import numpy from numpy.typing import ArrayLike -#from .pattern import Pattern from .ref import Ref from .ports import PortList, Port from .utils import rotation_matrix_2d, normalize_mirror @@ -28,21 +26,21 @@ class Abstract(PortList): name: str """ Name of the pattern this device references """ - _ports: Dict[str, Port] + _ports: dict[str, Port] """ Uniquely-named ports which can be used to instances together""" @property - def ports(self) -> Dict[str, Port]: + def ports(self) -> dict[str, Port]: return self._ports @ports.setter - def ports(self, value: Dict[str, Port]) -> None: + def ports(self, value: dict[str, Port]) -> None: self._ports = value def __init__( self, name: str, - ports: Dict[str, Port], + ports: dict[str, Port], ) -> None: self.name = name self.ports = copy.deepcopy(ports) @@ -50,7 +48,7 @@ class Abstract(PortList): # def build( # self, # library: 'MutableLibrary', -# tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None, +# tools: None | 'Tool' | MutableMapping[str | None, 'Tool'] = None, # ) -> 'Builder': # """ # Begin building a new device around an instance of the current device diff --git a/masque/builder/builder.py b/masque/builder/builder.py index 52aafaa..9d7b978 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -1,5 +1,4 @@ -from typing import Dict, Tuple, Union, TypeVar, Optional, Sequence -from typing import MutableMapping, Mapping +from typing import TypeVar, Sequence, MutableMapping, Mapping import copy import logging @@ -84,7 +83,7 @@ class Builder(PortList): pattern: Pattern """ Layout of this device """ - library: Optional[MutableLibrary] + library: MutableLibrary | None """ Library from which existing patterns should be referenced, and to which new ones should be added @@ -94,20 +93,20 @@ class Builder(PortList): """ If True, plug()/place() are skipped (for debugging)""" @property - def ports(self) -> Dict[str, Port]: + def ports(self) -> dict[str, Port]: return self.pattern.ports @ports.setter - def ports(self, value: Dict[str, Port]) -> None: + def ports(self, value: dict[str, Port]) -> None: self.pattern.ports = value def __init__( self, - library: Optional[MutableLibrary] = None, + library: MutableLibrary | None = None, *, - pattern: Optional[Pattern] = None, - ports: Union[None, str, Mapping[str, Port]] = None, - name: Optional[str] = None, + pattern: Pattern | None = None, + ports: str | Mapping[str, Port] | None = None, + name: str | None = None, ) -> None: """ # TODO documentation for Builder() constructor @@ -143,13 +142,13 @@ class Builder(PortList): @classmethod def interface( cls, - source: Union[PortList, Mapping[str, Port], str], + source: PortList | Mapping[str, Port] | str, *, - library: Optional[MutableLibrary] = None, + library: MutableLibrary | None = None, in_prefix: str = 'in_', out_prefix: str = '', - port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None, - name: Optional[str] = None, + port_map: dict[str, str] | Sequence[str] | None = None, + name: str | None = None, ) -> 'Builder': """ Begin building a new device based on all or some of the ports in the @@ -241,13 +240,13 @@ class Builder(PortList): def plug( self: BB, - other: Union[Abstract, str, NamedPattern], - map_in: Dict[str, str], - map_out: Optional[Dict[str, Optional[str]]] = None, + other: Abstract | str | NamedPattern, + map_in: dict[str, str], + map_out: dict[str, str | None] | None = None, *, - mirrored: Tuple[bool, bool] = (False, False), + mirrored: tuple[bool, bool] = (False, False), inherit_name: bool = True, - set_rotation: Optional[bool] = None, + set_rotation: bool | None = None, ) -> BB: """ Instantiate a device `library[name]` into the current device, connecting @@ -272,9 +271,9 @@ class Builder(PortList): Args: other: An `Abstract` describing the device to be instatiated. - map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying + map_in: dict of `{'self_port': 'other_port'}` mappings, specifying port connections between the two devices. - map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying + map_out: dict of `{'old_name': 'new_name'}` mappings, specifying new names for ports in `other`. mirrored: Enables mirroring `other` across the x or y axes prior to connecting any ports. @@ -342,13 +341,13 @@ class Builder(PortList): def place( self: BB, - other: Union[Abstract, str, NamedPattern], + other: Abstract | str | NamedPattern, *, offset: ArrayLike = (0, 0), rotation: float = 0, pivot: ArrayLike = (0, 0), - mirrored: Tuple[bool, bool] = (False, False), - port_map: Optional[Dict[str, Optional[str]]] = None, + mirrored: tuple[bool, bool] = (False, False), + port_map: dict[str, str | None] | None = None, skip_port_check: bool = False, ) -> BB: """ @@ -373,7 +372,7 @@ class Builder(PortList): Rotation is applied prior to translation (`offset`). mirrored: Whether theinstance should be mirrored across the x and y axes. Mirroring is applied before translation and rotation. - port_map: Dict of `{'old_name': 'new_name'}` mappings, specifying + port_map: dict of `{'old_name': 'new_name'}` mappings, specifying new names for ports in the instantiated device. New names can be `None`, which will delete those ports. skip_port_check: Can be used to skip the internal call to `check_ports`, @@ -554,7 +553,7 @@ class Pather(Builder): new ones should be added """ - tools: Dict[Optional[str], Tool] + tools: dict[str | None, Tool] """ Tool objects are used to dynamically generate new single-use Devices (e.g wires or waveguides) to be plugged into this device. @@ -564,10 +563,10 @@ class Pather(Builder): self, library: MutableLibrary, *, - pattern: Optional[Pattern] = None, - ports: Union[None, str, Mapping[str, Port]] = None, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, - name: Optional[str] = None, + pattern: Pattern | None = None, + ports: str | Mapping[str, Port] | None = None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, + name: str | None = None, ) -> None: """ # TODO documentation for Builder() constructor @@ -609,9 +608,9 @@ class Pather(Builder): library: MutableLibrary, base_name: str, *, - ports: Union[None, str, Mapping[str, Port]] = None, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, - ) -> Tuple['Pather', str]: + ports: str | Mapping[str, Port] | None= None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, + ) -> tuple['Pather', str]: """ Name-and-make combination """ pat = library.create(base_name) pather = Pather(library, pattern=pat, ports=ports, tools=tools) @@ -622,8 +621,8 @@ class Pather(Builder): cls, builder: Builder, *, - library: Optional[MutableLibrary] = None, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, + library: MutableLibrary | None = None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, ) -> 'Pather': """TODO from_builder docs""" library = library if library is not None else builder.library @@ -635,14 +634,14 @@ class Pather(Builder): @classmethod def interface( cls, - source: Union[PortList, Mapping[str, Port], str], + source: PortList | Mapping[str, Port] | str, *, - library: Optional[MutableLibrary] = None, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, + library: MutableLibrary | None = None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, in_prefix: str = 'in_', out_prefix: str = '', - port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None, - name: Optional[str] = None, + port_map: dict[str, str] | Sequence[str] | None = None, + name: str | None = None, ) -> 'Pather': """ TODO doc pather.interface @@ -676,7 +675,7 @@ class Pather(Builder): def retool( self: PP, tool: Tool, - keys: Union[Optional[str], Sequence[Optional[str]]] = None, + keys: str | Sequence[str | None] | None = None, ) -> PP: if keys is None or isinstance(keys, str): self.tools[keys] = tool @@ -688,7 +687,7 @@ class Pather(Builder): def path( self: PP, portspec: str, - ccw: Optional[SupportsBool], + ccw: SupportsBool | None, length: float, *, tool_port_names: Sequence[str] = ('A', 'B'), @@ -709,7 +708,7 @@ class Pather(Builder): def path_to( self: PP, portspec: str, - ccw: Optional[SupportsBool], + ccw: SupportsBool | None, position: float, *, tool_port_names: Sequence[str] = ('A', 'B'), @@ -742,11 +741,11 @@ class Pather(Builder): def mpath( self: PP, - portspec: Union[str, Sequence[str]], - ccw: Optional[SupportsBool], + portspec: str | Sequence[str], + ccw: SupportsBool | None, *, - spacing: Optional[Union[float, ArrayLike]] = None, - set_rotation: Optional[float] = None, + spacing: float | ArrayLike | None = None, + set_rotation: float | None = None, tool_port_names: Sequence[str] = ('A', 'B'), force_container: bool = False, base_name: str = '_mpath', diff --git a/masque/builder/flatbuilder.py b/masque/builder/flatbuilder.py index 29e882b..6263689 100644 --- a/masque/builder/flatbuilder.py +++ b/masque/builder/flatbuilder.py @@ -1,5 +1,4 @@ -from typing import Dict, Tuple, Union, TypeVar, Optional, Sequence -from typing import MutableMapping, Mapping +from typing import TypeVar, Sequence, MutableMapping, Mapping import copy import logging @@ -30,7 +29,7 @@ class FlatBuilder(PortList): pattern: Pattern """ Layout of this device """ - tools: Dict[Optional[str], Tool] + tools: dict[str | None, Tool] """ Tool objects are used to dynamically generate new single-use Devices (e.g wires or waveguides) to be plugged into this device. @@ -40,19 +39,19 @@ class FlatBuilder(PortList): """ If True, plug()/place() are skipped (for debugging)""" @property - def ports(self) -> Dict[str, Port]: + def ports(self) -> dict[str, Port]: return self.pattern.ports @ports.setter - def ports(self, value: Dict[str, Port]) -> None: + def ports(self, value: dict[str, Port]) -> None: self.pattern.ports = value def __init__( self, *, - pattern: Optional[Pattern] = None, - ports: Union[None, Mapping[str, Port]] = None, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, + pattern: Pattern | None = None, + ports: Mapping[str, Port] | None = None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, ) -> None: """ # TODO documentation for FlatBuilder() constructor @@ -79,12 +78,12 @@ class FlatBuilder(PortList): @classmethod def interface( cls, - source: Union[PortList, Mapping[str, Port]], + source: PortList | Mapping[str, Port], *, - tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, in_prefix: str = 'in_', out_prefix: str = '', - port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None, + port_map: dict[str, str] | Sequence[str] | None = None, ) -> 'FlatBuilder': """ Begin building a new device based on all or some of the ports in the @@ -176,12 +175,12 @@ class FlatBuilder(PortList): def plug( self: BB, other: Pattern, - map_in: Dict[str, str], - map_out: Optional[Dict[str, Optional[str]]] = None, + map_in: dict[str, str], + map_out: dict[str, str | None] | None = None, *, - mirrored: Tuple[bool, bool] = (False, False), + mirrored: tuple[bool, bool] = (False, False), inherit_name: bool = True, - set_rotation: Optional[bool] = None, + set_rotation: bool | None = None, ) -> BB: """ Instantiate another pattern into the current device, connecting @@ -206,9 +205,9 @@ class FlatBuilder(PortList): Args: other: An `Abstract` describing the device to be instatiated. - map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying + map_in: dict of `{'self_port': 'other_port'}` mappings, specifying port connections between the two devices. - map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying + map_out: dict of `{'old_name': 'new_name'}` mappings, specifying new names for ports in `other`. mirrored: Enables mirroring `other` across the x or y axes prior to connecting any ports. @@ -276,8 +275,8 @@ class FlatBuilder(PortList): offset: ArrayLike = (0, 0), rotation: float = 0, pivot: ArrayLike = (0, 0), - mirrored: Tuple[bool, bool] = (False, False), - port_map: Optional[Dict[str, Optional[str]]] = None, + mirrored: tuple[bool, bool] = (False, False), + port_map: dict[str, str | None] | None = None, skip_port_check: bool = False, ) -> BB: """ @@ -302,7 +301,7 @@ class FlatBuilder(PortList): Rotation is applied prior to translation (`offset`). mirrored: Whether theinstance should be mirrored across the x and y axes. Mirroring is applied before translation and rotation. - port_map: Dict of `{'old_name': 'new_name'}` mappings, specifying + port_map: dict of `{'old_name': 'new_name'}` mappings, specifying new names for ports in the instantiated device. New names can be `None`, which will delete those ports. skip_port_check: Can be used to skip the internal call to `check_ports`, @@ -420,7 +419,7 @@ class FlatBuilder(PortList): def retool( self: BB, tool: Tool, - keys: Union[Optional[str], Sequence[Optional[str]]] = None, + keys: str | Sequence[str | None] | None = None, ) -> BB: if keys is None or isinstance(keys, str): self.tools[keys] = tool @@ -432,7 +431,7 @@ class FlatBuilder(PortList): def path( self: BB, portspec: str, - ccw: Optional[SupportsBool], + ccw: SupportsBool | None, length: float, *, tool_port_names: Sequence[str] = ('A', 'B'), @@ -451,7 +450,7 @@ class FlatBuilder(PortList): def path_to( self: BB, portspec: str, - ccw: Optional[SupportsBool], + ccw: SupportsBool | None, position: float, *, tool_port_names: Sequence[str] = ('A', 'B'), @@ -484,11 +483,11 @@ class FlatBuilder(PortList): def mpath( self: BB, - portspec: Union[str, Sequence[str]], - ccw: Optional[SupportsBool], + portspec: str | Sequence[str], + ccw: SupportsBool | None, *, - spacing: Optional[Union[float, ArrayLike]] = None, - set_rotation: Optional[float] = None, + spacing: float | ArrayLike | None = None, + set_rotation: float | None = None, tool_port_names: Sequence[str] = ('A', 'B'), force_container: bool = False, base_name: str = '_mpath', diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 71f1062..43af974 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -1,7 +1,7 @@ """ Tools are objects which dynamically generate simple single-use devices (e.g. wires or waveguides) """ -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Sequence from ..utils import SupportsBool @@ -12,11 +12,11 @@ if TYPE_CHECKING: class Tool: def path( self, - ccw: Optional[SupportsBool], + ccw: SupportsBool | None, length: float, *, - in_ptype: Optional[str] = None, - out_ptype: Optional[str] = None, + in_ptype: str | None = None, + out_ptype: str | None = None, port_names: Sequence[str] = ('A', 'B'), **kwargs, ) -> 'Pattern': diff --git a/masque/builder/utils.py b/masque/builder/utils.py index 67c999a..6cc02d8 100644 --- a/masque/builder/utils.py +++ b/masque/builder/utils.py @@ -1,5 +1,4 @@ -from typing import Dict, Mapping, Sequence, SupportsFloat -from typing import Optional, Union, cast, TYPE_CHECKING +from typing import Mapping, Sequence, SupportsFloat, cast, TYPE_CHECKING from pprint import pformat import numpy @@ -15,13 +14,13 @@ if TYPE_CHECKING: def ell( ports: Mapping[str, 'Port'], - ccw: Optional[SupportsBool], + ccw: SupportsBool | None, bound_type: str, - bound: Union[float, ArrayLike], + bound: float | ArrayLike, *, - spacing: Optional[Union[float, ArrayLike]] = None, - set_rotation: Optional[float] = None, - ) -> Dict[str, float]: + spacing: float | ArrayLike | None = None, + set_rotation: float | None = None, + ) -> dict[str, float]: """ Calculate extension for each port in order to build a 90-degree bend with the provided channel spacing: diff --git a/masque/file/dxf.py b/masque/file/dxf.py index 24bc6a0..0a5e90c 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -6,8 +6,7 @@ Notes: * ezdxf sets creation time, write time, $VERSIONGUID, and $FINGERPRINTGUID to unique values, so byte-for-byte reproducibility is not achievable for now """ -from typing import List, Any, Dict, Tuple, Callable, Union, Mapping -from typing import cast, TextIO, IO +from typing import Any, Callable, Mapping, cast, TextIO, IO import io import logging import pathlib @@ -107,7 +106,7 @@ def write( def writefile( library: Mapping[str, Pattern], top_name: str, - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *args, **kwargs, ) -> None: @@ -128,7 +127,7 @@ def writefile( gz_stream: IO[bytes] with tmpfile(path) as base_stream: - streams: Tuple[Any, ...] = (base_stream,) + streams: tuple[Any, ...] = (base_stream,) if path.suffix == '.gz': gz_stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb')) streams = (gz_stream,) + streams @@ -145,10 +144,10 @@ def writefile( def readfile( - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *args, **kwargs, - ) -> Tuple[WrapLibrary, Dict[str, Any]]: + ) -> tuple[WrapLibrary, dict[str, Any]]: """ Wrapper for `dxf.read()` that takes a filename or path instead of a stream. @@ -172,7 +171,7 @@ def readfile( def read( stream: TextIO, - ) -> Tuple[WrapLibrary, Dict[str, Any]]: + ) -> tuple[WrapLibrary, dict[str, Any]]: """ Read a dxf file and translate it into a dict of `Pattern` objects. DXF `Block`s are translated into `Pattern` objects; `LWPolyline`s are translated into polygons, and `Insert`s @@ -204,7 +203,7 @@ def read( return mlib, library_info -def _read_block(block) -> Tuple[str, Pattern]: +def _read_block(block) -> tuple[str, Pattern]: name = block.name pat = Pattern() for element in block: @@ -230,7 +229,7 @@ def _read_block(block) -> Tuple[str, Pattern]: if width == 0: width = attr.get('const_width', 0) - shape: Union[Path, Polygon] + shape: Path | Polygon if width == 0 and len(points) > 2 and numpy.array_equal(points[0], points[-1]): shape = Polygon(layer=layer, vertices=points[:-1, :2]) else: @@ -285,8 +284,8 @@ def _read_block(block) -> Tuple[str, Pattern]: def _mrefs_to_drefs( - block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace], - refs: List[Ref], + block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace, + refs: list[Ref], ) -> None: for ref in refs: if ref.target is None: @@ -332,8 +331,8 @@ def _mrefs_to_drefs( def _shapes_to_elements( - block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace], - shapes: List[Shape], + block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace, + shapes: list[Shape], polygonize_paths: bool = False, ) -> None: # Add `LWPolyline`s for each shape. @@ -353,8 +352,8 @@ def _shapes_to_elements( def _labels_to_texts( - block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace], - labels: List[Label], + block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace, + labels: list[Label], ) -> None: for label in labels: attribs = dict(layer=_mlayer2dxf(label.layer)) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 966f797..621c9b4 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -19,8 +19,7 @@ Notes: * GDS creation/modification/access times are set to 1900-01-01 for reproducibility. * Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01) """ -from typing import List, Dict, Tuple, Callable, Union, Iterable, Mapping -from typing import IO, cast, Optional, Any +from typing import Callable, Iterable, Mapping, IO, cast, Any import io import mmap import logging @@ -114,7 +113,7 @@ def write( # Now create a structure for each pattern, and add in any Boundary and SREF elements for name, pat in library.items(): - elements: List[klamath.elements.Element] = [] + elements: list[klamath.elements.Element] = [] elements += _shapes_to_elements(pat.shapes) elements += _labels_to_texts(pat.labels) elements += _mrefs_to_grefs(pat.refs) @@ -125,7 +124,7 @@ def write( def writefile( library: Mapping[str, Pattern], - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *args, **kwargs, ) -> None: @@ -143,7 +142,7 @@ def writefile( path = pathlib.Path(filename) with tmpfile(path) as base_stream: - streams: Tuple[Any, ...] = (base_stream,) + streams: tuple[Any, ...] = (base_stream,) if path.suffix == '.gz': stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb')) streams = (stream,) + streams @@ -158,10 +157,10 @@ def writefile( def readfile( - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *args, **kwargs, - ) -> Tuple[WrapLibrary, Dict[str, Any]]: + ) -> tuple[WrapLibrary, dict[str, Any]]: """ Wrapper for `read()` that takes a filename or path instead of a stream. @@ -186,7 +185,7 @@ def readfile( def read( stream: IO[bytes], raw_mode: bool = True, - ) -> Tuple[WrapLibrary, Dict[str, Any]]: + ) -> tuple[WrapLibrary, dict[str, Any]]: """ # TODO check GDSII file for cycles! Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are @@ -204,8 +203,8 @@ def read( raw_mode: If True, constructs shapes in raw mode, bypassing most data validation, Default True. Returns: - - Dict of pattern_name:Patterns generated from GDSII structures - - Dict of GDSII library info + - dict of pattern_name:Patterns generated from GDSII structures + - dict of GDSII library info """ library_info = _read_header(stream) @@ -220,7 +219,7 @@ def read( return mlib, library_info -def _read_header(stream: IO[bytes]) -> Dict[str, Any]: +def _read_header(stream: IO[bytes]) -> dict[str, Any]: """ Read the file header and create the library_info dict. """ @@ -272,7 +271,7 @@ def read_elements( return pat -def _mlayer2gds(mlayer: layer_t) -> Tuple[int, int]: +def _mlayer2gds(mlayer: layer_t) -> tuple[int, int]: """ Helper to turn a layer tuple-or-int into a layer and datatype""" if isinstance(mlayer, int): layer = mlayer @@ -344,7 +343,7 @@ def _boundary_to_polygon(boundary: klamath.library.Boundary, raw_mode: bool) -> ) -def _mrefs_to_grefs(refs: List[Ref]) -> List[klamath.library.Reference]: +def _mrefs_to_grefs(refs: list[Ref]) -> list[klamath.library.Reference]: grefs = [] for ref in refs: if ref.target is None: @@ -402,11 +401,11 @@ def _mrefs_to_grefs(refs: List[Ref]) -> List[klamath.library.Reference]: return grefs -def _properties_to_annotations(properties: Dict[int, bytes]) -> annotations_t: +def _properties_to_annotations(properties: dict[int, bytes]) -> annotations_t: return {str(k): [v.decode()] for k, v in properties.items()} -def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -> Dict[int, bytes]: +def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -> dict[int, bytes]: cum_len = 0 props = {} for key, vals in annotations.items(): @@ -429,10 +428,10 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) - def _shapes_to_elements( - shapes: List[Shape], + shapes: list[Shape], polygonize_paths: bool = False, - ) -> List[klamath.elements.Element]: - elements: List[klamath.elements.Element] = [] + ) -> list[klamath.elements.Element]: + elements: list[klamath.elements.Element] = [] # Add a Boundary element for each shape, and Path elements if necessary for shape in shapes: if shape.repetition is not None: @@ -446,7 +445,7 @@ def _shapes_to_elements( width = rint_cast(shape.width) path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup - extension: Tuple[int, int] + extension: tuple[int, int] if shape.cap == Path.Cap.SquareCustom and shape.cap_extensions is not None: extension = tuple(shape.cap_extensions) # type: ignore else: @@ -486,7 +485,7 @@ def _shapes_to_elements( return elements -def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]: +def _labels_to_texts(labels: list[Label]) -> list[klamath.elements.Text]: texts = [] for label in labels: properties = _annotations_to_properties(label.annotations, 128) @@ -512,8 +511,8 @@ def load_library( stream: IO[bytes], *, full_load: bool = False, - postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None - ) -> Tuple[LazyLibrary, Dict[str, Any]]: + postprocess: Callable[[Library, str, Pattern], Pattern] | None = None + ) -> tuple[LazyLibrary, dict[str, Any]]: """ Scan a GDSII stream to determine what structures are present, and create a library from them. This enables deferred reading of structures @@ -568,12 +567,12 @@ def load_library( def load_libraryfile( - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *, use_mmap: bool = True, full_load: bool = False, - postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None - ) -> Tuple[LazyLibrary, Dict[str, Any]]: + postprocess: Callable[[Library, str, Pattern], Pattern] | None = None + ) -> tuple[LazyLibrary, dict[str, Any]]: """ Wrapper for `load_library()` that takes a filename or path instead of a stream. diff --git a/masque/file/oasis.py b/masque/file/oasis.py index 392c338..714af3f 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -14,8 +14,7 @@ Note that OASIS references follow the same convention as `masque`, Notes: * Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01) """ -from typing import List, Any, Dict, Tuple, Callable, Union, Iterable -from typing import IO, Mapping, Optional, cast, Sequence +from typing import Any, Callable, Iterable, IO, Mapping, cast, Sequence import logging import pathlib import gzip @@ -57,9 +56,9 @@ def rint_cast(val: ArrayLike) -> NDArray[numpy.int64]: def build( library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable! units_per_micron: int, - layer_map: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None, + layer_map: dict[str, int | tuple[int, int]] | None = None, *, - annotations: Optional[annotations_t] = None, + annotations: annotations_t | None = None, ) -> fatamorgana.OasisLayout: """ Convert a collection of {name: Pattern} pairs to an OASIS stream, writing patterns @@ -86,7 +85,7 @@ def build( library: A {name: Pattern} mapping of patterns to write. units_per_micron: Written into the OASIS file, number of grid steps per micrometer. All distances are assumed to be an integer multiple of the grid step, and are stored as such. - layer_map: Dictionary which translates layer names into layer numbers. If this argument is + layer_map: dictionary which translates layer names into layer numbers. If this argument is provided, input shapes and labels are allowed to have layer names instead of numbers. It is assumed that geometry and text share the same layer names, and each name is assigned only to a single layer (not a range). @@ -127,7 +126,7 @@ def build( ) for tt in (True, False)] - def layer2oas(mlayer: layer_t) -> Tuple[int, int]: + def layer2oas(mlayer: layer_t) -> tuple[int, int]: assert layer_map is not None layer_num = layer_map[mlayer] if isinstance(mlayer, str) else mlayer return _mlayer2oas(layer_num) @@ -170,7 +169,7 @@ def write( def writefile( library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable! - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *args, **kwargs, ) -> None: @@ -188,7 +187,7 @@ def writefile( path = pathlib.Path(filename) with tmpfile(path) as base_stream: - streams: Tuple[Any, ...] = (base_stream,) + streams: tuple[Any, ...] = (base_stream,) if path.suffix == '.gz': stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb')) streams += (stream,) @@ -203,10 +202,10 @@ def writefile( def readfile( - filename: Union[str, pathlib.Path], + filename: str | pathlib.Path, *args, **kwargs, - ) -> Tuple[WrapLibrary, Dict[str, Any]]: + ) -> tuple[WrapLibrary, dict[str, Any]]: """ Wrapper for `oasis.read()` that takes a filename or path instead of a stream. @@ -230,7 +229,7 @@ def readfile( def read( stream: IO[bytes], - ) -> Tuple[WrapLibrary, Dict[str, Any]]: + ) -> tuple[WrapLibrary, dict[str, Any]]: """ Read a OASIS file and translate it into a dict of Pattern objects. OASIS cells are translated into Pattern objects; Polygons are translated into polygons, and Placements @@ -245,13 +244,13 @@ def read( stream: Stream to read from. Returns: - - Dict of `pattern_name`:`Pattern`s generated from OASIS cells - - Dict of OASIS library info + - dict of `pattern_name`:`Pattern`s generated from OASIS cells + - dict of OASIS library info """ lib = fatamorgana.OasisLayout.read(stream) - library_info: Dict[str, Any] = { + library_info: dict[str, Any] = { 'units_per_micrometer': lib.unit, 'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings), } @@ -304,7 +303,7 @@ def read( raise Exception('masque does not support multiple cap types on a single path.') # TODO handle multiple cap types cap = cap_start - path_args: Dict[str, Any] = {} + path_args: dict[str, Any] = {} if cap == Path.Cap.SquareCustom: path_args['cap_extensions'] = numpy.array(( element.get_extension_start()[1], @@ -468,7 +467,7 @@ def read( return mlib, library_info -def _mlayer2oas(mlayer: layer_t) -> Tuple[int, int]: +def _mlayer2oas(mlayer: layer_t) -> tuple[int, int]: """ Helper to turn a layer tuple-or-int into a layer and datatype""" if isinstance(mlayer, int): layer = mlayer @@ -494,7 +493,7 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout) mag = placement.magnification if placement.magnification is not None else 1 pname = placement.get_name() - name: Union[int, str] = pname if isinstance(pname, int) else pname.string # TODO deal with referenced names + name: int | str = pname if isinstance(pname, int) else pname.string # TODO deal with referenced names annotations = properties_to_annotations(placement.properties, lib.propnames, lib.propstrings) if placement.angle is None: @@ -514,8 +513,8 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout) def _refs_to_placements( - refs: List[Ref], - ) -> List[fatrec.Placement]: + refs: list[Ref], + ) -> list[fatrec.Placement]: placements = [] for ref in refs: if ref.target is None: @@ -543,11 +542,11 @@ def _refs_to_placements( def _shapes_to_elements( - shapes: List[Shape], - layer2oas: Callable[[layer_t], Tuple[int, int]], - ) -> List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]]: + shapes: list[Shape], + layer2oas: Callable[[layer_t], tuple[int, int]], + ) -> list[fatrec.Polygon | fatrec.Path | fatrec.Circle]: # Add a Polygon record for each shape, and Path elements if necessary - elements: List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]] = [] + elements: list[fatrec.Polygon | fatrec.Path | fatrec.Circle] = [] for shape in shapes: layer, datatype = layer2oas(shape.layer) repetition, rep_offset = repetition_masq2fata(shape.repetition) @@ -594,7 +593,7 @@ def _shapes_to_elements( datatype=datatype, x=xy[0], y=xy[1], - point_list=cast(List[List[int]], points), + point_list=cast(list[list[int]], points), properties=properties, repetition=repetition, )) @@ -602,9 +601,9 @@ def _shapes_to_elements( def _labels_to_texts( - labels: List[Label], - layer2oas: Callable[[layer_t], Tuple[int, int]], - ) -> List[fatrec.Text]: + labels: list[Label], + layer2oas: Callable[[layer_t], tuple[int, int]], + ) -> list[fatrec.Text]: texts = [] for label in labels: layer, datatype = layer2oas(label.layer) @@ -624,9 +623,9 @@ def _labels_to_texts( def repetition_fata2masq( - rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None], - ) -> Optional[Repetition]: - mrep: Optional[Repetition] + rep: fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None, + ) -> Repetition | None: + mrep: Repetition | None if isinstance(rep, fatamorgana.GridRepetition): mrep = Grid(a_vector=rep.a_vector, b_vector=rep.b_vector, @@ -645,22 +644,22 @@ def repetition_fata2masq( def repetition_masq2fata( - rep: Optional[Repetition], - ) -> Tuple[Union[fatamorgana.GridRepetition, - fatamorgana.ArbitraryRepetition, - None], - Tuple[int, int]]: - frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None] + rep: Repetition | None, + ) -> tuple[ + fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None, + tuple[int, int] + ]: + frep: fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None if isinstance(rep, Grid): a_vector = rint_cast(rep.a_vector) b_vector = rint_cast(rep.b_vector) if rep.b_vector is not None else None a_count = rint_cast(rep.a_count) b_count = rint_cast(rep.b_count) if rep.b_count is not None else None frep = fatamorgana.GridRepetition( - a_vector=cast(List[int], a_vector), - b_vector=cast(Optional[List[int]], b_vector), + a_vector=cast(list[int], a_vector), + b_vector=cast(list[int] | None, b_vector), a_count=cast(int, a_count), - b_count=cast(Optional[int], b_count), + b_count=cast(int | None, b_count), ) offset = (0, 0) elif isinstance(rep, Arbitrary): @@ -675,7 +674,7 @@ def repetition_masq2fata( return frep, offset -def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Property]: +def annotations_to_properties(annotations: annotations_t) -> list[fatrec.Property]: #TODO determine is_standard based on key? properties = [] for key, values in annotations.items(): @@ -686,9 +685,9 @@ def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Propert def properties_to_annotations( - properties: List[fatrec.Property], - propnames: Dict[int, NString], - propstrings: Dict[int, AString], + properties: list[fatrec.Property], + propnames: dict[int, NString], + propstrings: dict[int, AString], ) -> annotations_t: annotations = {} for proprec in properties: @@ -697,7 +696,7 @@ def properties_to_annotations( key = propnames[proprec.name].string else: key = proprec.name.string - values: List[Union[str, float, int]] = [] + values: list[str | float | int] = [] assert proprec.values is not None for value in proprec.values: diff --git a/masque/file/utils.py b/masque/file/utils.py index c0335f1..9d25682 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -1,7 +1,7 @@ """ Helper functions for file reading and writing """ -from typing import Union, IO, Iterator +from typing import IO, Iterator import re import pathlib import logging @@ -62,7 +62,7 @@ def is_gzipped(path: pathlib.Path) -> bool: @contextmanager -def tmpfile(path: Union[str, pathlib.Path]) -> Iterator[IO[bytes]]: +def tmpfile(path: str | pathlib.Path) -> Iterator[IO[bytes]]: """ Context manager which allows you to write to a temporary file, and move that file into its final location only after the write @@ -77,5 +77,3 @@ def tmpfile(path: Union[str, pathlib.Path]) -> Iterator[IO[bytes]]: shutil.move(tmp_stream.name, path) finally: pathlib.Path(tmp_stream.name).unlink(missing_ok=True) - - diff --git a/masque/label.py b/masque/label.py index d0b1998..fe34f9b 100644 --- a/masque/label.py +++ b/masque/label.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, TypeVar +from typing import TypeVar import copy import numpy @@ -44,8 +44,8 @@ class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl, *, offset: ArrayLike = (0.0, 0.0), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, ) -> None: self.string = string self.offset = numpy.array(offset, dtype=float, copy=True) @@ -61,7 +61,7 @@ class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl, repetition=self.repetition, ) - def __deepcopy__(self: L, memo: Optional[Dict] = None) -> L: + def __deepcopy__(self: L, memo: dict | None = None) -> L: memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() diff --git a/masque/library.py b/masque/library.py index 3586ba8..1165140 100644 --- a/masque/library.py +++ b/masque/library.py @@ -5,8 +5,8 @@ Library classes for managing unique name->pattern mappings and # TODO documentn all library classes # TODO toplevel documentation of library, classes, and abstracts """ -from typing import List, Dict, Callable, TypeVar, Type, TYPE_CHECKING, cast -from typing import Tuple, Union, Iterator, Mapping, MutableMapping, Set, Optional, Sequence +from typing import Callable, TypeVar, Type, TYPE_CHECKING, cast +from typing import Iterator, Mapping, MutableMapping, Sequence import logging import base64 import struct @@ -75,8 +75,8 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): def dangling_refs( self, - tops: Union[None, str, Sequence[str]] = None, - ) -> Set[Optional[str]]: + tops: str | Sequence[str] | None = None, + ) -> set[str | None]: """ Get the set of all pattern names not present in the library but referenced by `tops`, recursively traversing any refs. @@ -99,9 +99,9 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): def referenced_patterns( self, - tops: Union[None, str, Sequence[str]] = None, - skip: Optional[Set[Optional[str]]] = None, - ) -> Set[Optional[str]]: + tops: str | Sequence[str] | None = None, + skip: set[str | None] | None = None, + ) -> set[str | None]: """ Get the set of all pattern names referenced by `tops`. Recursively traverses into any refs. @@ -140,7 +140,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): def subtree( self, - tops: Union[str, Sequence[str]], + tops: str | Sequence[str], ) -> 'Library': """ Return a new `Library`, containing only the specified patterns and the patterns they @@ -155,7 +155,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): if isinstance(tops, str): tops = (tops,) - keep: Set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore + keep: set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore keep |= set(tops) filtered = {kk: vv for kk, vv in self.items() if kk in keep} @@ -164,25 +164,25 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): def polygonize( self: L, - poly_num_points: Optional[int] = None, - poly_max_arclen: Optional[float] = None, + num_vertices: int | None = None, + max_arclen: float | None = None, ) -> L: """ Calls `.polygonize(...)` on each pattern in this library. Arguments are passed on to `shape.to_polygons(...)`. Args: - poly_num_points: Number of points to use for each polygon. Can be overridden by - `poly_max_arclen` if that results in more points. Optional, defaults to shapes' + num_vertices: Number of points to use for each polygon. Can be overridden by + `max_arclen` if that results in more points. Optional, defaults to shapes' internal defaults. - poly_max_arclen: Maximum arclength which can be approximated by a single line + max_arclen: Maximum arclength which can be approximated by a single line segment. Optional, defaults to shapes' internal defaults. Returns: self """ for pat in self.values(): - pat.polygonize(poly_num_points, poly_max_arclen) + pat.polygonize(num_vertices, max_arclen) return self def manhattanize( @@ -206,9 +206,9 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): def flatten( self, - tops: Union[str, Sequence[str]], + tops: str | Sequence[str], flatten_ports: bool = False, # TODO document - ) -> Dict[str, 'Pattern']: + ) -> dict[str, 'Pattern']: """ Removes all refs and adds equivalent shapes. Also flattens all referenced patterns. @@ -222,7 +222,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): if isinstance(tops, str): tops = (tops,) - flattened: Dict[str, Optional['Pattern']] = {} + flattened: dict[str, 'Pattern' | None] = {} def flatten_single(name) -> None: flattened[name] = None @@ -261,7 +261,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): name: str = '__', sanitize: bool = True, max_length: int = 32, - quiet: Optional[bool] = None, + quiet: bool | None = None, ) -> str: """ Find a unique name for the pattern. @@ -308,7 +308,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): return cropped_name - def find_toplevel(self) -> List[str]: + def find_toplevel(self) -> list[str]: """ Return the list of all patterns that are not referenced by any other pattern in the library. @@ -316,7 +316,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): A list of pattern names in which no pattern is referenced by any other pattern. """ names = set(self.keys()) - not_toplevel: Set[Optional[str]] = set() + not_toplevel: set[str | None] = set() for name in names: not_toplevel |= set(sp.target for sp in self[name].refs) @@ -326,12 +326,12 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): def dfs( self: L, pattern: 'Pattern', - visit_before: Optional[visitor_function_t] = None, - visit_after: Optional[visitor_function_t] = None, + visit_before: visitor_function_t | None = None, + visit_after: visitor_function_t | None = None, *, - hierarchy: Tuple[Optional[str], ...] = (None,), - transform: Union[ArrayLike, bool, None] = False, - memo: Optional[Dict] = None, + hierarchy: tuple[str | None, ...] = (None,), + transform: ArrayLike | bool | None = False, + memo: dict | None = None, ) -> L: """ Convenience function. @@ -434,14 +434,14 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) #def __getitem__(self, key: str) -> 'Pattern': #def __iter__(self) -> Iterator[str]: #def __len__(self) -> int: - #def __setitem__(self, key: str, value: Union['Pattern', Callable[[], 'Pattern']]) -> None: + #def __setitem__(self, key: str, value: 'Pattern' | Callable[[], 'Pattern']) -> None: #def __delitem__(self, key: str) -> None: @abstractmethod def __setitem__( self, key: str, - value: Union['Pattern', Callable[[], 'Pattern']], + value: 'Pattern' | Callable[[], 'Pattern'], ) -> None: pass @@ -510,31 +510,11 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) self[name] = npat return npat - def set( - self, - name: str, - value: Union['Pattern', Callable[[], 'Pattern']], - ) -> str: - """ - Convenience method which finds a suitable name for the provided - pattern, adds it with that name, and returns the name. - - Args: - base_name: Prefix used when naming the pattern - value: The pattern (or callable used to generate it) - - Returns: - The name of the pattern. - """ - #name = self.get_name(base_name) - self[name] = value - return name - def add( self, other: Mapping[str, 'Pattern'], rename_theirs: Callable[['Library', str], str] = _rename_patterns, - ) -> Dict[str, str]: + ) -> dict[str, str]: """ Add keys from another library into this one. @@ -581,7 +561,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) def add_tree( self, tree: 'Tree', - name: Optional[str] = None, + name: str | None = None, rename_theirs: Callable[['Library', str], str] = _rename_patterns, ) -> str: """ @@ -623,8 +603,8 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) def dedup( self: ML, norm_value: int = int(1e6), - exclude_types: Tuple[Type] = (Polygon,), - label2name: Optional[Callable[[Tuple], str]] = None, + exclude_types: tuple[Type] = (Polygon,), + label2name: Callable[[tuple], str] | None = None, threshold: int = 2, ) -> ML: """ @@ -664,7 +644,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) return self.get_name('_shape') #label2name = lambda label: self.get_name('_shape') - shape_counts: MutableMapping[Tuple, int] = defaultdict(int) + shape_counts: MutableMapping[tuple, int] = defaultdict(int) shape_funcs = {} # ## First pass ## @@ -692,7 +672,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) # are to be replaced. # The `values` are `(offset, scale, rotation)`. - shape_table: MutableMapping[Tuple, List] = defaultdict(list) + shape_table: MutableMapping[tuple, list] = defaultdict(list) for i, shape in enumerate(pat.shapes): if any(isinstance(shape, t) for t in exclude_types): continue @@ -727,7 +707,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) def wrap_repeated_shapes( self: ML, - name_func: Optional[Callable[['Pattern', Union[Shape, Label]], str]] = None, + name_func: Callable[['Pattern', Shape | Label], str] | None = None, ) -> ML: """ Wraps all shapes and labels with a non-`None` `repetition` attribute @@ -776,7 +756,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) def subtree( self: ML, - tops: Union[str, Sequence[str]], + tops: str | Sequence[str], ) -> ML: """ Return a new `Library`, containing only the specified patterns and the patterns they @@ -791,7 +771,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) if isinstance(tops, str): tops = (tops,) - keep: Set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore + keep: set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore keep |= set(tops) new = type(self)() @@ -802,7 +782,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta) def prune_empty( self, repeat: bool = True, - ) -> Set[str]: + ) -> set[str]: # TODO doc prune_empty trimmed = set() while empty := set(name for name, pat in self.items() if pat.is_empty()): @@ -859,7 +839,7 @@ class WrapLibrary(MutableLibrary): def __init__( self, - mapping: Optional[MutableMapping[str, 'Pattern']] = None, + mapping: MutableMapping[str, 'Pattern'] | None = None, ) -> None: if mapping is None: self.mapping = {} @@ -881,7 +861,7 @@ class WrapLibrary(MutableLibrary): def __setitem__( self, key: str, - value: Union['Pattern', Callable[[], 'Pattern']], + value: 'Pattern' | Callable[[], 'Pattern'], ) -> None: if key in self.mapping: raise LibraryError(f'"{key}" already exists in the library. Overwriting is not allowed!') @@ -909,21 +889,21 @@ class LazyLibrary(MutableLibrary): This class is usually used to create a library of Patterns by mapping names to functions which generate or load the relevant `Pattern` object as-needed. """ - dict: Dict[str, Callable[[], 'Pattern']] - cache: Dict[str, 'Pattern'] - _lookups_in_progress: Set[str] + mapping: dict[str, Callable[[], 'Pattern']] + cache: dict[str, 'Pattern'] + _lookups_in_progress: set[str] def __init__(self) -> None: - self.dict = {} + self.mapping = {} self.cache = {} self._lookups_in_progress = set() def __setitem__( self, key: str, - value: Union['Pattern', Callable[[], 'Pattern']], + value: 'Pattern' | Callable[[], 'Pattern'], ) -> None: - if key in self.dict: + if key in self.mapping: raise LibraryError(f'"{key}" already exists in the library. Overwriting is not allowed!') if callable(value): @@ -931,12 +911,12 @@ class LazyLibrary(MutableLibrary): else: value_func = lambda: cast('Pattern', value) # noqa: E731 - self.dict[key] = value_func + self.mapping[key] = value_func if key in self.cache: del self.cache[key] def __delitem__(self, key: str) -> None: - del self.dict[key] + del self.mapping[key] if key in self.cache: del self.cache[key] @@ -954,24 +934,24 @@ class LazyLibrary(MutableLibrary): ) self._lookups_in_progress.add(key) - func = self.dict[key] + func = self.mapping[key] pat = func() self._lookups_in_progress.remove(key) self.cache[key] = pat return pat def __iter__(self) -> Iterator[str]: - return iter(self.dict) + return iter(self.mapping) def __len__(self) -> int: - return len(self.dict) + return len(self.mapping) def __contains__(self, key: object) -> bool: - return key in self.dict + return key in self.mapping def _merge(self, key_self: str, other: Mapping[str, 'Pattern'], key_other: str) -> None: if isinstance(other, LazyLibrary): - self.dict[key_self] = other.dict[key_other] + self.mapping[key_self] = other.mapping[key_other] if key_other in other.cache: self.cache[key_self] = other.cache[key_other] else: @@ -999,7 +979,7 @@ class LazyLibrary(MutableLibrary): Returns: self """ - self[new_name] = self.dict[old_name] # copy over function + self[new_name] = self.mapping[old_name] # copy over function if old_name in self.cache: self.cache[new_name] = self.cache[old_name] del self[old_name] @@ -1034,11 +1014,11 @@ class LazyLibrary(MutableLibrary): Returns: self """ - for key in self.dict: + for key in self.mapping: _ = self[key] # want to trigger our own __getitem__ return self - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'LazyLibrary': + def __deepcopy__(self, memo: dict | None = None) -> 'LazyLibrary': raise LibraryError('LazyLibrary cannot be deepcopied (deepcopy doesn\'t descend into closures)') @@ -1068,14 +1048,14 @@ class Tree(MutableLibrary): def __init__( self, - top: Union[str, 'NamedPattern'], - library: Optional[MutableLibrary] = None + top: str | 'NamedPattern', + library: MutableLibrary | None = None ) -> None: self.top = top if isinstance(top, str) else top.name self.library = library if library is not None else WrapLibrary() @classmethod - def mk(cls, top: str) -> Tuple['Tree', 'Pattern']: + def mk(cls, top: str) -> tuple['Tree', 'Pattern']: from .pattern import Pattern tree = cls(top=top) pat = Pattern() @@ -1091,7 +1071,7 @@ class Tree(MutableLibrary): def __len__(self) -> int: return len(self.library) - def __setitem__(self, key: str, value: Union['Pattern', Callable[[], 'Pattern']]) -> None: + def __setitem__(self, key: str, value: 'Pattern' | Callable[[], 'Pattern']) -> None: self.library[key] = value def __delitem__(self, key: str) -> None: diff --git a/masque/pattern.py b/masque/pattern.py index 2a72a63..a126bad 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -2,8 +2,7 @@ Base object representing a lithography mask. """ -from typing import List, Callable, Dict, Union, Set, Sequence, Optional, cast -from typing import Mapping, TypeVar, Any +from typing import Callable, Sequence, cast, Mapping, TypeVar, Any import copy from itertools import chain @@ -35,29 +34,29 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): '_offset', '_annotations', ) - shapes: List[Shape] + shapes: list[Shape] """ List of all shapes in this Pattern. Elements in this list are assumed to inherit from Shape or provide equivalent functions. """ - labels: List[Label] + labels: list[Label] """ List of all labels in this Pattern. """ - refs: List[Ref] + refs: list[Ref] """ List of all references to other patterns (`Ref`s) in this `Pattern`. Multiple objects in this list may reference the same Pattern object (i.e. multiple instances of the same object). """ - _ports: Dict[str, Port] + _ports: dict[str, Port] """ Uniquely-named ports which can be used to snap to other Pattern instances""" @property - def ports(self) -> Dict[str, Port]: + def ports(self) -> dict[str, Port]: return self._ports @ports.setter - def ports(self, value: Dict[str, Port]) -> None: + def ports(self, value: dict[str, Port]) -> None: self._ports = value def __init__( @@ -66,8 +65,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): shapes: Sequence[Shape] = (), labels: Sequence[Label] = (), refs: Sequence[Ref] = (), - annotations: Optional[annotations_t] = None, - ports: Optional[Mapping[str, 'Port']] = None + annotations: annotations_t | None = None, + ports: Mapping[str, 'Port'] | None = None ) -> None: """ Basic init; arguments get assigned to member variables. @@ -118,7 +117,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): ports=copy.deepcopy(self.ports), ) - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Pattern': + def __deepcopy__(self, memo: dict | None = None) -> 'Pattern': memo = {} if memo is None else memo new = Pattern( shapes=copy.deepcopy(self.shapes, memo), @@ -158,11 +157,11 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): def subset( self, - shapes: Optional[Callable[[Shape], bool]] = None, - labels: Optional[Callable[[Label], bool]] = None, - refs: Optional[Callable[[Ref], bool]] = None, - annotations: Optional[Callable[[str, List[Union[int, float, str]]], bool]] = None, - ports: Optional[Callable[[str, Port], bool]] = None, + shapes: Callable[[Shape], bool] | None = None, + labels: Callable[[Label], bool] | None = None, + refs: Callable[[Ref], bool] | None = None, + annotations: Callable[[str, list[int | float | str]], bool] | None = None, + ports: Callable[[str, Port], bool] | None = None, default_keep: bool = False ) -> 'Pattern': """ @@ -214,8 +213,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): def polygonize( self: P, - num_points: Optional[int] = None, - max_arclen: Optional[float] = None, + num_points: int | None = None, + max_arclen: float | None = None, ) -> P: """ Calls `.to_polygons(...)` on all the shapes in this Pattern, replacing them with the returned polygons. @@ -260,7 +259,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): (shape.manhattanize(grid_x, grid_y) for shape in old_shapes))) return self - def as_polygons(self, library: Mapping[str, 'Pattern']) -> List[NDArray[numpy.float64]]: + def as_polygons(self, library: Mapping[str, 'Pattern']) -> list[NDArray[numpy.float64]]: """ Represents the pattern as a list of polygons. @@ -274,7 +273,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): pat = self.deepcopy().polygonize().flatten(library=library) return [shape.vertices + shape.offset for shape in pat.shapes] # type: ignore # mypy can't figure out that shapes are all Polygons now - def referenced_patterns(self) -> Set[Optional[str]]: + def referenced_patterns(self) -> set[str | None]: """ Get all pattern namers referenced by this pattern. Non-recursive. @@ -285,9 +284,9 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): def get_bounds( self, - library: Optional[Mapping[str, 'Pattern']] = None, + library: Mapping[str, 'Pattern'] | None = None, recurse: bool = True, - ) -> Optional[NDArray[numpy.float64]]: + ) -> NDArray[numpy.float64] | None: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the extent of the Pattern's contents in each dimension. @@ -330,7 +329,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): def get_bounds_nonempty( self, - library: Optional[Mapping[str, 'Pattern']] = None, + library: Mapping[str, 'Pattern'] | None = None, recurse: bool = True, ) -> NDArray[numpy.float64]: """ @@ -574,10 +573,10 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): Returns: self """ - flattened: Dict[Optional[str], Optional[P]] = {} + flattened: dict[str | None, P | None] = {} # TODO both Library and Pattern have flatten()... pattern is in-place? - def flatten_single(name: Optional[str]) -> None: + def flatten_single(name: str | None) -> None: if name is None: pat = self else: @@ -611,7 +610,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): def visualize( self: P, - library: Optional[Mapping[str, P]] = None, + library: Mapping[str, P] | None = None, offset: ArrayLike = (0., 0.), line_color: str = 'k', fill_color: str = 'none', @@ -710,7 +709,7 @@ class NamedPattern(Pattern): def __copy__(self) -> Pattern: return Pattern.__copy__(self) - def __deepcopy__(self, memo: Optional[Dict] = None) -> Pattern: + def __deepcopy__(self, memo: dict | None = None) -> Pattern: return Pattern.__deepcopy__(self, memo) def as_pattern(self) -> Pattern: diff --git a/masque/ports.py b/masque/ports.py index 3a297ab..85ca750 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -1,5 +1,4 @@ -from typing import Dict, Iterable, List, Tuple, KeysView, ValuesView -from typing import overload, Union, Optional, TypeVar +from typing import Iterable, KeysView, ValuesView, overload, TypeVar import warnings import traceback import logging @@ -43,7 +42,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable): '_offset', ) - _rotation: Optional[float] + _rotation: float | None """ radians counterclockwise from +x, pointing into device body. Can be `None` to signify undirected port """ @@ -53,7 +52,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable): def __init__( self, offset: ArrayLike, - rotation: Optional[float], + rotation: float | None, ptype: str = 'unk', ) -> None: self.offset = offset @@ -61,7 +60,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable): self.ptype = ptype @property - def rotation(self) -> Optional[float]: + def rotation(self) -> float | None: """ Rotation, radians counterclockwise, pointing into device body. Can be None. """ return self._rotation @@ -94,7 +93,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable): self.rotation += rotation return self - def set_rotation(self: P, rotation: Optional[float]) -> P: + def set_rotation(self: P, rotation: float | None) -> P: self.rotation = rotation return self @@ -111,13 +110,13 @@ class PortList(metaclass=ABCMeta): @property @abstractmethod - def ports(self) -> Dict[str, Port]: + def ports(self) -> dict[str, Port]: """ Uniquely-named ports which can be used to snap to other Device instances""" pass @ports.setter @abstractmethod - def ports(self, value: Dict[str, Port]) -> None: + def ports(self, value: dict[str, Port]) -> None: pass @overload @@ -125,10 +124,10 @@ class PortList(metaclass=ABCMeta): pass @overload - def __getitem__(self, key: Union[List[str], Tuple[str, ...], KeysView[str], ValuesView[str]]) -> Dict[str, Port]: + def __getitem__(self, key: list[str] | tuple[str, ...] | KeysView[str] | ValuesView[str]) -> dict[str, Port]: pass - def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]: + def __getitem__(self, key: str | Iterable[str]) -> Port | dict[str, Port]: """ For convenience, ports can be read out using square brackets: - `pattern['A'] == Port((0, 0), 0)` @@ -150,7 +149,7 @@ class PortList(metaclass=ABCMeta): def rename_ports( self: PL, - mapping: Dict[str, Optional[str]], + mapping: dict[str, str | None], overwrite: bool = False, ) -> PL: """ @@ -158,7 +157,7 @@ class PortList(metaclass=ABCMeta): Ports can be explicitly deleted by mapping them to `None`. Args: - mapping: Dict of `{'old_name': 'new_name'}` pairs. Names can be mapped + mapping: dict of `{'old_name': 'new_name'}` pairs. Names can be mapped to `None` to perform an explicit deletion. `'new_name'` can also overwrite an existing non-renamed port to implicitly delete it if `overwrite` is set to `True`. @@ -183,7 +182,7 @@ class PortList(metaclass=ABCMeta): self: PL, offset: ArrayLike = (0, 0), rotation: float = 0.0, - names: Tuple[str, str] = ('A', 'B'), + names: tuple[str, str] = ('A', 'B'), ptype: str = 'unk', ) -> PL: """ @@ -210,8 +209,8 @@ class PortList(metaclass=ABCMeta): def check_ports( self: PL, other_names: Iterable[str], - map_in: Optional[Dict[str, str]] = None, - map_out: Optional[Dict[str, Optional[str]]] = None, + map_in: dict[str, str] | None = None, + map_out: dict[str, str | None] | None = None, ) -> PL: """ Given the provided port mappings, check that: @@ -221,9 +220,9 @@ class PortList(metaclass=ABCMeta): Args: other_names: List of port names being considered for inclusion into `self.ports` (before mapping) - map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying + map_in: dict of `{'self_port': 'other_port'}` mappings, specifying port connections between the two devices. - map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying + map_out: dict of `{'old_name': 'new_name'}` mappings, specifying new names for unconnected `other_names` ports. Returns: @@ -279,18 +278,18 @@ class PortList(metaclass=ABCMeta): def find_transform( self: PL, other: PL2, - map_in: Dict[str, str], + map_in: dict[str, str], *, - mirrored: Tuple[bool, bool] = (False, False), - set_rotation: Optional[bool] = None, - ) -> Tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]: + mirrored: tuple[bool, bool] = (False, False), + set_rotation: bool | None = None, + ) -> tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]: """ Given a device `other` and a mapping `map_in` specifying port connections, find the transform which will correctly align the specified ports. Args: other: a device - map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying + map_in: dict of `{'self_port': 'other_port'}` mappings, specifying port connections between the two devices. mirrored: Mirrors `other` across the x or y axes prior to connecting any ports. diff --git a/masque/ref.py b/masque/ref.py index 7b699cc..ddd2245 100644 --- a/masque/ref.py +++ b/masque/ref.py @@ -4,7 +4,7 @@ """ #TODO more top-level documentation -from typing import Dict, Optional, Sequence, Mapping, Union, TYPE_CHECKING, Any, TypeVar, cast +from typing import Sequence, Mapping, TYPE_CHECKING, Any, TypeVar, cast import copy import numpy @@ -41,7 +41,7 @@ class Ref( '_offset', '_rotation', 'scale', '_repetition', '_annotations', ) - _target: Optional[str] + _target: str | None """ The name of the `Pattern` being instanced """ _mirrored: NDArray[numpy.bool_] @@ -49,14 +49,14 @@ class Ref( def __init__( self, - target: Union[None, str, 'NamedPattern'], + target: str | 'NamedPattern' | None, *, offset: ArrayLike = (0.0, 0.0), rotation: float = 0.0, - mirrored: Optional[Sequence[bool]] = None, + mirrored: Sequence[bool] | None = None, scale: float = 1.0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, ) -> None: """ Args: @@ -91,7 +91,7 @@ class Ref( ) return new - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Ref': + def __deepcopy__(self, memo: dict | None = None) -> 'Ref': memo = {} if memo is None else memo new = copy.copy(self) new.repetition = copy.deepcopy(self.repetition, memo) @@ -100,11 +100,11 @@ class Ref( # target property @property - def target(self) -> Optional[str]: + def target(self) -> str | None: return self._target @target.setter - def target(self, val: Optional[str]) -> None: + def target(self, val: str | None) -> None: if val is not None and not isinstance(val, str): raise PatternError(f'Provided target {val} is not a str or None!') self._target = val @@ -123,8 +123,8 @@ class Ref( def as_pattern( self, *, - pattern: Optional['Pattern'] = None, - library: Optional[Mapping[str, 'Pattern']] = None, + pattern: 'Pattern' | None = None, + library: Mapping[str, 'Pattern'] | None = None, ) -> 'Pattern': """ Args: @@ -180,9 +180,9 @@ class Ref( def get_bounds( self, *, - pattern: Optional['Pattern'] = None, - library: Optional[Mapping[str, 'Pattern']] = None, - ) -> Optional[NDArray[numpy.float64]]: + pattern: 'Pattern' | None = None, + library: Mapping[str, 'Pattern'] | None = None, + ) -> NDArray[numpy.float64] | None: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the extent of the `Ref` in each dimension. diff --git a/masque/repetition.py b/masque/repetition.py index 467783c..5caf9ab 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -3,7 +3,7 @@ instances of an object . """ -from typing import Union, Dict, Optional, Any, Type +from typing import Any, Type import copy from abc import ABCMeta, abstractmethod @@ -52,7 +52,7 @@ class Grid(Repetition): _a_count: int """ Number of instances along the direction specified by the `a_vector` """ - _b_vector: Optional[NDArray[numpy.float64]] + _b_vector: NDArray[numpy.float64] | None """ Vector `[x, y]` specifying a second lattice vector for the grid. Specifies center-to-center spacing between adjacent elements. Can be `None` for a 1D array. @@ -65,8 +65,8 @@ class Grid(Repetition): self, a_vector: ArrayLike, a_count: int, - b_vector: Optional[ArrayLike] = None, - b_count: Optional[int] = 1, + b_vector: ArrayLike | None = None, + b_count: int | None = 1, ) -> None: """ Args: @@ -133,7 +133,7 @@ class Grid(Repetition): ) return new - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Grid': + def __deepcopy__(self, memo: dict | None = None) -> 'Grid': memo = {} if memo is None else memo new = copy.copy(self) return new @@ -154,7 +154,7 @@ class Grid(Repetition): # b_vector property @property - def b_vector(self) -> Optional[NDArray[numpy.float64]]: + def b_vector(self) -> NDArray[numpy.float64] | None: return self._b_vector @b_vector.setter @@ -228,7 +228,7 @@ class Grid(Repetition): self.b_vector[1 - axis] *= -1 return self - def get_bounds(self) -> Optional[NDArray[numpy.float64]]: + def get_bounds(self) -> NDArray[numpy.float64] | None: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the extent of the `Grid` in each dimension. @@ -237,7 +237,7 @@ class Grid(Repetition): `[[x_min, y_min], [x_max, y_max]]` or `None` """ a_extent = self.a_vector * self.a_count - b_extent = self.b_vector * self.b_count if (self.b_vector is not None) else 0 # type: Union[NDArray[numpy.float64], float] + b_extent = self.b_vector * self.b_count if (self.b_vector is not None) else 0 # type: NDArray[numpy.float64] | float corners = numpy.stack(((0, 0), a_extent, b_extent, a_extent + b_extent)) xy_min = numpy.min(corners, axis=0) @@ -350,7 +350,7 @@ class Arbitrary(Repetition): self.displacements[1 - axis] *= -1 return self - def get_bounds(self) -> Optional[NDArray[numpy.float64]]: + def get_bounds(self) -> NDArray[numpy.float64] | None: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the extent of the `displacements` in each dimension. diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index cb07a81..ac60630 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Optional, Sequence, Any +from typing import Sequence, Any import copy import math @@ -157,8 +157,8 @@ class Arc(Shape): rotation: float = 0, mirrored: Sequence[bool] = (False, False), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, raw: bool = False, ) -> None: if raw: @@ -184,7 +184,7 @@ class Arc(Shape): self.layer = layer [self.mirror(a) for a, do in enumerate(mirrored) if do] - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Arc': + def __deepcopy__(self, memo: dict | None = None) -> 'Arc': memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() @@ -195,9 +195,9 @@ class Arc(Shape): def to_polygons( self, - num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, - max_arclen: Optional[float] = None, - ) -> List[Polygon]: + num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES, + max_arclen: float | None = None, + ) -> list[Polygon]: if (num_vertices is None) and (max_arclen is None): raise PatternError('Max number of points and arclength left unspecified' + ' (default was also overridden)') diff --git a/masque/shapes/circle.py b/masque/shapes/circle.py index e74da28..2390ece 100644 --- a/masque/shapes/circle.py +++ b/masque/shapes/circle.py @@ -1,4 +1,3 @@ -from typing import List, Dict, Optional import copy import numpy @@ -46,8 +45,8 @@ class Circle(Shape): *, offset: ArrayLike = (0.0, 0.0), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, raw: bool = False, ) -> None: if raw: @@ -64,7 +63,7 @@ class Circle(Shape): self.annotations = annotations if annotations is not None else {} self.layer = layer - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Circle': + def __deepcopy__(self, memo: dict | None = None) -> 'Circle': memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() @@ -73,14 +72,14 @@ class Circle(Shape): def to_polygons( self, - num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, - max_arclen: Optional[float] = None, - ) -> List[Polygon]: + num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES, + max_arclen: float | None = None, + ) -> list[Polygon]: if (num_vertices is None) and (max_arclen is None): raise PatternError('Number of points and arclength left ' 'unspecified (default was also overridden)') - n: List[float] = [] + n: list[float] = [] if num_vertices is not None: n += [num_vertices] if max_arclen is not None: diff --git a/masque/shapes/ellipse.py b/masque/shapes/ellipse.py index d3de929..f96f86a 100644 --- a/masque/shapes/ellipse.py +++ b/masque/shapes/ellipse.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Sequence, Optional, Any +from typing import Sequence, Any import copy import math @@ -92,8 +92,8 @@ class Ellipse(Shape): rotation: float = 0, mirrored: Sequence[bool] = (False, False), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, raw: bool = False, ) -> None: if raw: @@ -114,7 +114,7 @@ class Ellipse(Shape): self.layer = layer [self.mirror(a) for a, do in enumerate(mirrored) if do] - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Ellipse': + def __deepcopy__(self, memo: dict | None = None) -> 'Ellipse': memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() @@ -124,9 +124,9 @@ class Ellipse(Shape): def to_polygons( self, - num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, - max_arclen: Optional[float] = None, - ) -> List[Polygon]: + num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES, + max_arclen: float | None = None, + ) -> list[Polygon]: if (num_vertices is None) and (max_arclen is None): raise PatternError('Number of points and arclength left unspecified' ' (default was also overridden)') diff --git a/masque/shapes/path.py b/masque/shapes/path.py index 852a8a2..eba3211 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Dict, Optional, Sequence, Any, cast +from typing import Sequence, Any, cast import copy from enum import Enum @@ -36,7 +36,7 @@ class Path(Shape): _vertices: NDArray[numpy.float64] _width: float _cap: PathCap - _cap_extensions: Optional[NDArray[numpy.float64]] + _cap_extensions: NDArray[numpy.float64] | None Cap = PathCap @@ -76,7 +76,7 @@ class Path(Shape): # cap_extensions property @property - def cap_extensions(self) -> Optional[Any]: # TODO mypy#3004 NDArray[numpy.float64]]: + def cap_extensions(self) -> Any | None: # TODO mypy#3004 NDArray[numpy.float64]]: """ Path end-cap extension @@ -86,7 +86,7 @@ class Path(Shape): return self._cap_extensions @cap_extensions.setter - def cap_extensions(self, vals: Optional[ArrayLike]) -> None: + def cap_extensions(self, vals: ArrayLike | None) -> None: custom_caps = (PathCap.SquareCustom,) if self.cap in custom_caps: if vals is None: @@ -150,13 +150,13 @@ class Path(Shape): width: float = 0.0, *, cap: PathCap = PathCap.Flush, - cap_extensions: Optional[ArrayLike] = None, + cap_extensions: ArrayLike | None = None, offset: ArrayLike = (0.0, 0.0), rotation: float = 0, mirrored: Sequence[bool] = (False, False), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, raw: bool = False, ) -> None: self._cap_extensions = None # Since .cap setter might access it @@ -185,7 +185,7 @@ class Path(Shape): self.rotate(rotation) [self.mirror(a) for a, do in enumerate(mirrored) if do] - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Path': + def __deepcopy__(self, memo: dict | None = None) -> 'Path': memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() @@ -197,10 +197,10 @@ class Path(Shape): @staticmethod def travel( - travel_pairs: Sequence[Tuple[float, float]], + travel_pairs: Sequence[tuple[float, float]], width: float = 0.0, cap: PathCap = PathCap.Flush, - cap_extensions: Optional[Tuple[float, float]] = None, + cap_extensions: tuple[float, float] | None = None, offset: ArrayLike = (0.0, 0.0), rotation: float = 0, mirrored: Sequence[bool] = (False, False), @@ -243,9 +243,9 @@ class Path(Shape): def to_polygons( self, - num_vertices: Optional[int] = None, - max_arclen: Optional[float] = None, - ) -> List['Polygon']: + num_vertices: int | None = None, + max_arclen: float | None = None, + ) -> list['Polygon']: extensions = self._calculate_cap_extensions() v = remove_colinear_vertices(self.vertices, closed_path=False) diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index a4b1291..c8f8326 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Optional, Sequence, Any, cast +from typing import Sequence, Any, cast import copy import numpy @@ -83,8 +83,8 @@ class Polygon(Shape): rotation: float = 0.0, mirrored: Sequence[bool] = (False, False), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, raw: bool = False, ) -> None: if raw: @@ -104,7 +104,7 @@ class Polygon(Shape): self.rotate(rotation) [self.mirror(a) for a, do in enumerate(mirrored) if do] - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon': + def __deepcopy__(self, memo: dict | None = None) -> 'Polygon': memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() @@ -119,7 +119,7 @@ class Polygon(Shape): rotation: float = 0.0, offset: ArrayLike = (0.0, 0.0), layer: layer_t = 0, - repetition: Optional[Repetition] = None, + repetition: Repetition | None = None, ) -> 'Polygon': """ Draw a square given side_length, centered on the origin. @@ -151,7 +151,7 @@ class Polygon(Shape): rotation: float = 0, offset: ArrayLike = (0.0, 0.0), layer: layer_t = 0, - repetition: Optional[Repetition] = None, + repetition: Repetition | None = None, ) -> 'Polygon': """ Draw a rectangle with side lengths lx and ly, centered on the origin. @@ -178,16 +178,16 @@ class Polygon(Shape): @staticmethod def rect( *, - xmin: Optional[float] = None, - xctr: Optional[float] = None, - xmax: Optional[float] = None, - lx: Optional[float] = None, - ymin: Optional[float] = None, - yctr: Optional[float] = None, - ymax: Optional[float] = None, - ly: Optional[float] = None, + xmin: float | None = None, + xctr: float | None = None, + xmax: float | None = None, + lx: float | None = None, + ymin: float | None = None, + yctr: float | None = None, + ymax: float | None = None, + ly: float | None = None, layer: layer_t = 0, - repetition: Optional[Repetition] = None, + repetition: Repetition | None = None, ) -> 'Polygon': """ Draw a rectangle by specifying side/center positions. @@ -276,13 +276,13 @@ class Polygon(Shape): @staticmethod def octagon( *, - side_length: Optional[float] = None, - inner_radius: Optional[float] = None, + side_length: float | None = None, + inner_radius: float | None = None, regular: bool = True, center: ArrayLike = (0.0, 0.0), rotation: float = 0.0, layer: layer_t = 0, - repetition: Optional[Repetition] = None, + repetition: Repetition | None = None, ) -> 'Polygon': """ Draw an octagon given one of (side length, inradius, circumradius). @@ -333,9 +333,9 @@ class Polygon(Shape): def to_polygons( self, - num_vertices: Optional[int] = None, # unused - max_arclen: Optional[float] = None, # unused - ) -> List['Polygon']: + num_vertices: int | None = None, # unused + max_arclen: float | None = None, # unused + ) -> list['Polygon']: return [copy.deepcopy(self)] def get_bounds(self) -> NDArray[numpy.float64]: diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index 6784013..201fc2b 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING +from typing import Callable, TypeVar, TYPE_CHECKING from abc import ABCMeta, abstractmethod import numpy @@ -15,9 +15,9 @@ if TYPE_CHECKING: # Type definitions -normalized_shape_tuple = Tuple[ - Tuple, - Tuple[NDArray[numpy.float64], float, float, bool], +normalized_shape_tuple = tuple[ + tuple, + tuple[NDArray[numpy.float64], float, float, bool], Callable[[], 'Shape'], ] @@ -49,9 +49,9 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc @abstractmethod def to_polygons( self, - num_vertices: Optional[int] = None, - max_arclen: Optional[float] = None, - ) -> List['Polygon']: + num_vertices: int | None = None, + max_arclen: float | None = None, + ) -> list['Polygon']: """ Returns a list of polygons which approximate the shape. @@ -98,7 +98,7 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc self, grid_x: ArrayLike, grid_y: ArrayLike, - ) -> List['Polygon']: + ) -> list['Polygon']: """ Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape. @@ -208,7 +208,7 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc self, grid_x: ArrayLike, grid_y: ArrayLike, - ) -> List['Polygon']: + ) -> list['Polygon']: """ Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape. diff --git a/masque/shapes/text.py b/masque/shapes/text.py index 4663f49..940ea15 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Dict, Sequence, Optional, Any +from typing import Sequence, Any import copy import numpy @@ -74,8 +74,8 @@ class Text(RotatableImpl, Shape): rotation: float = 0.0, mirrored: ArrayLike = (False, False), layer: layer_t = 0, - repetition: Optional[Repetition] = None, - annotations: Optional[annotations_t] = None, + repetition: Repetition | None = None, + annotations: annotations_t | None = None, raw: bool = False, ) -> None: if raw: @@ -100,7 +100,7 @@ class Text(RotatableImpl, Shape): self.annotations = annotations if annotations is not None else {} self.font_path = font_path - def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Text': + def __deepcopy__(self, memo: dict | None = None) -> 'Text': memo = {} if memo is None else memo new = copy.copy(self) new._offset = self._offset.copy() @@ -110,9 +110,9 @@ class Text(RotatableImpl, Shape): def to_polygons( self, - num_vertices: Optional[int] = None, # unused - max_arclen: Optional[float] = None, # unused - ) -> List[Polygon]: + num_vertices: int | None = None, # unused + max_arclen: float | None = None, # unused + ) -> list[Polygon]: all_polygons = [] total_advance = 0.0 for char in self.string: @@ -172,7 +172,7 @@ def get_char_as_polygons( font_path: str, char: str, resolution: float = 48 * 64, - ) -> Tuple[List[List[List[float]]], float]: + ) -> tuple[list[list[list[float]]], float]: from freetype import Face # type: ignore from matplotlib.path import Path # type: ignore @@ -209,7 +209,7 @@ def get_char_as_polygons( tags = outline.tags[start:end + 1] tags.append(tags[0]) - segments: List[List[List[float]]] = [] + segments: list[list[list[float]]] = [] for j, point in enumerate(points): # If we already have a segment, add this point to it if j > 0: diff --git a/masque/traits/mirrorable.py b/masque/traits/mirrorable.py index 6990e4c..bc82b83 100644 --- a/masque/traits/mirrorable.py +++ b/masque/traits/mirrorable.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Tuple +from typing import TypeVar from abc import ABCMeta, abstractmethod @@ -28,7 +28,7 @@ class Mirrorable(metaclass=ABCMeta): """ pass - def mirror2d(self: T, axes: Tuple[bool, bool]) -> T: + def mirror2d(self: T, axes: tuple[bool, bool]) -> T: """ Optionally mirror the entity across both axes diff --git a/masque/traits/positionable.py b/masque/traits/positionable.py index 8e25b9c..1f4ba6e 100644 --- a/masque/traits/positionable.py +++ b/masque/traits/positionable.py @@ -1,6 +1,6 @@ # TODO top-level comment about how traits should set __slots__ = (), and how to use AutoSlots -from typing import TypeVar, Any, Optional +from typing import TypeVar, Any from abc import ABCMeta, abstractmethod import numpy @@ -65,7 +65,7 @@ class Positionable(metaclass=ABCMeta): pass @abstractmethod - def get_bounds(self) -> Optional[NDArray[numpy.float64]]: + def get_bounds(self) -> NDArray[numpy.float64] | None: """ Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity. Returns `None` for an empty entity. diff --git a/masque/traits/repeatable.py b/masque/traits/repeatable.py index 4ec9147..f9f6438 100644 --- a/masque/traits/repeatable.py +++ b/masque/traits/repeatable.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Optional, TYPE_CHECKING +from typing import TypeVar, TYPE_CHECKING from abc import ABCMeta, abstractmethod from ..error import MasqueError @@ -26,7 +26,7 @@ class Repeatable(metaclass=ABCMeta): ''' @property @abstractmethod - def repetition(self) -> Optional['Repetition']: + def repetition(self) -> 'Repetition' | None: """ Repetition object, or None (single instance only) """ @@ -34,14 +34,14 @@ class Repeatable(metaclass=ABCMeta): # @repetition.setter # @abstractmethod -# def repetition(self, repetition: Optional['Repetition']): +# def repetition(self, repetition: 'Repetition' | None): # pass ''' ---- Methods ''' @abstractmethod - def set_repetition(self: T, repetition: Optional['Repetition']) -> T: + def set_repetition(self: T, repetition: 'Repetition' | None) -> T: """ Set the repetition @@ -60,18 +60,18 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta): """ __slots__ = _empty_slots - _repetition: Optional['Repetition'] + _repetition: 'Repetition' | None """ Repetition object, or None (single instance only) """ ''' ---- Non-abstract properties ''' @property - def repetition(self) -> Optional['Repetition']: + def repetition(self) -> 'Repetition' | None: return self._repetition @repetition.setter - def repetition(self, repetition: Optional['Repetition']): + def repetition(self, repetition: 'Repetition' | None): from ..repetition import Repetition if repetition is not None and not isinstance(repetition, Repetition): raise MasqueError(f'{repetition} is not a valid Repetition object!') @@ -80,6 +80,6 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta): ''' ---- Non-abstract methods ''' - def set_repetition(self: I, repetition: Optional['Repetition']) -> I: + def set_repetition(self: I, repetition: 'Repetition' | None) -> I: self.repetition = repetition return self diff --git a/masque/utils/pack2d.py b/masque/utils/pack2d.py index 150403c..873f91c 100644 --- a/masque/utils/pack2d.py +++ b/masque/utils/pack2d.py @@ -1,7 +1,7 @@ """ 2D bin-packing """ -from typing import Tuple, List, Set, Sequence, Callable, Mapping +from typing import Sequence, Callable, Mapping import numpy from numpy.typing import NDArray, ArrayLike @@ -16,7 +16,7 @@ def maxrects_bssf( containers: ArrayLike, presort: bool = True, allow_rejects: bool = True, - ) -> Tuple[NDArray[numpy.float64], Set[int]]: + ) -> tuple[NDArray[numpy.float64], set[int]]: """ sizes should be Nx2 regions should be Mx4 (xmin, ymin, xmax, ymax) @@ -88,7 +88,7 @@ def guillotine_bssf_sas(rect_sizes: numpy.ndarray, regions: numpy.ndarray, presort: bool = True, allow_rejects: bool = True, - ) -> Tuple[numpy.ndarray, Set[int]]: + ) -> tuple[numpy.ndarray, set[int]]: """ sizes should be Nx2 regions should be Mx4 (xmin, ymin, xmax, ymax) @@ -146,11 +146,11 @@ def pack_patterns( library: Mapping[str, Pattern], patterns: Sequence[str], regions: numpy.ndarray, - spacing: Tuple[float, float], + spacing: tuple[float, float], presort: bool = True, allow_rejects: bool = True, packer: Callable = maxrects_bssf, - ) -> Tuple[Pattern, List[str]]: + ) -> tuple[Pattern, list[str]]: half_spacing = numpy.array(spacing) / 2 bounds = [library[pp].get_bounds() for pp in patterns] diff --git a/masque/utils/ports2data.py b/masque/utils/ports2data.py index 51f33ec..d7ccf41 100644 --- a/masque/utils/ports2data.py +++ b/masque/utils/ports2data.py @@ -6,7 +6,7 @@ and retrieving it (`data_to_ports`). the port locations. This particular approach is just a sensible default; feel free to to write equivalent functions for your own format or alternate storage methods. """ -from typing import Sequence, Optional, Mapping +from typing import Sequence, Mapping import logging import numpy @@ -56,7 +56,7 @@ def data_to_ports( pattern: Pattern, # Pattern is good since we don't want to do library[name] to avoid infinite recursion. # LazyLibrary protects against library[ref.target] causing a circular lookup. # For others, maybe check for cycles up front? TODO - name: Optional[str] = None, # Note: name optional, but arg order different from read(postprocess=) + name: str | None = None, # Note: name optional, but arg order different from read(postprocess=) max_depth: int = 0, skip_subcells: bool = True, # TODO missing ok? @@ -130,7 +130,7 @@ def data_to_ports( def data_to_ports_flat( layers: Sequence[layer_t], pattern: Pattern, - cell_name: Optional[str] = None, + cell_name: str | None = None, ) -> Pattern: """ Examine `pattern` for labels specifying port info, and use that info diff --git a/masque/utils/transform.py b/masque/utils/transform.py index 6265d26..bc364f9 100644 --- a/masque/utils/transform.py +++ b/masque/utils/transform.py @@ -1,7 +1,7 @@ """ Geometric transforms """ -from typing import Sequence, Tuple +from typing import Sequence import numpy from numpy.typing import NDArray @@ -21,7 +21,7 @@ def rotation_matrix_2d(theta: float) -> NDArray[numpy.float64]: [numpy.sin(theta), +numpy.cos(theta)]]) -def normalize_mirror(mirrored: Sequence[bool]) -> Tuple[bool, float]: +def normalize_mirror(mirrored: Sequence[bool]) -> tuple[bool, float]: """ Converts 0-2 mirror operations `(mirror_across_x_axis, mirror_across_y_axis)` into 0-1 mirror operations and a rotation diff --git a/masque/utils/types.py b/masque/utils/types.py index 3583726..4060ac4 100644 --- a/masque/utils/types.py +++ b/masque/utils/types.py @@ -1,11 +1,11 @@ """ Type definitions """ -from typing import Union, Tuple, Dict, List, Protocol +from typing import Protocol -layer_t = Union[int, Tuple[int, int], str] -annotations_t = Dict[str, List[Union[int, float, str]]] +layer_t = int | tuple[int, int] | str +annotations_t = dict[str, list[int | float | str]] class SupportsBool(Protocol): diff --git a/masque/utils/vertices.py b/masque/utils/vertices.py index dd04b36..0c5f03b 100644 --- a/masque/utils/vertices.py +++ b/masque/utils/vertices.py @@ -113,5 +113,3 @@ def poly_contains_points( inside = nontrivial.copy() inside[nontrivial] = nontrivial_inside return inside - -