Compare commits

..

No commits in common. "3e88ed94382c8b11f781c87b31743ee972f7b973" and "ee4147ef99bcdba02e2234c0a7c66e2b86e71363" have entirely different histories.

4 changed files with 24 additions and 84 deletions

View File

@ -1,10 +1,3 @@
import traceback
import pathlib
MASQUE_DIR = str(pathlib.Path(__file__).parent)
class MasqueError(Exception): class MasqueError(Exception):
""" """
Parent exception for all Masque-related Exceptions Parent exception for all Masque-related Exceptions
@ -32,64 +25,15 @@ class BuildError(MasqueError):
""" """
pass pass
class PortError(MasqueError): class PortError(MasqueError):
""" """
Exception raised by port-related functions Exception raised by builder-related functions
""" """
pass pass
class OneShotError(MasqueError): class OneShotError(MasqueError):
""" """
Exception raised when a function decorated with `@oneshot` is called more than once Exception raised when a function decorated with `@oneshot` is called more than once
""" """
def __init__(self, func_name: str) -> None: def __init__(self, func_name: str) -> None:
Exception.__init__(self, f'Function "{func_name}" with @oneshot was called more than once') Exception.__init__(self, f'Function "{func_name}" with @oneshot was called more than once')
def format_stacktrace(
stacklevel: int = 1,
*,
skip_file_prefixes: tuple[str, ...] = (MASQUE_DIR,),
low_file_prefixes: tuple[str, ...] = ('<runpy', '<string>'),
low_file_suffixes: tuple[str, ...] = ('IPython/utils/py3compat.py', ),
) -> str:
"""
Utility function for making nicer stack traces (e.g. excluding <frozen runpy> and similar)
Args:
stacklevel: Number of frames to remove from near this function (default is to
show caller but not ourselves). Similar to `warnings.warn` and `logging.warning`.
skip_file_prefixes: Indicates frames to ignore after counting stack levels; similar
to `warnings.warn` *TODO check if this is actually the same effect re:stacklevel*.
Forces stacklevel to max(2, stacklevel).
Default is to exclude anything within `masque`.
low_file_prefixes: Indicates frames to ignore on the other (entry-point) end of the stack,
based on prefixes on their filenames.
low_file_suffixes: Indicates frames to ignore on the other (entry-point) end of the stack,
based on suffixes on their filenames.
Returns:
Formatted trimmed stack trace
"""
if skip_file_prefixes:
stacklevel = max(2, stacklevel)
stack = traceback.extract_stack()
bad_inds = [ii + 1 for ii, frame in enumerate(stack)
if frame.filename.startswith(low_file_prefixes) or frame.filename.endswith(low_file_suffixes)]
first_ok = max([0] + bad_inds)
last_ok = -stacklevel - 1
while last_ok >= -len(stack) and stack[last_ok].filename.startswith(skip_file_prefixes):
last_ok -= 1
if selected := stack[first_ok:last_ok + 1]:
pass
elif selected := stack[:-stacklevel]:
pass
else:
selected = stack
return ''.join(traceback.format_list(selected))

View File

@ -2,7 +2,7 @@
SVG file format readers and writers SVG file format readers and writers
""" """
from collections.abc import Mapping from collections.abc import Mapping
import logging import warnings
import numpy import numpy
from numpy.typing import ArrayLike from numpy.typing import ArrayLike
@ -12,9 +12,6 @@ from .utils import mangle_name
from .. import Pattern from .. import Pattern
logger = logging.getLogger(__name__)
def writefile( def writefile(
library: Mapping[str, Pattern], library: Mapping[str, Pattern],
top: str, top: str,
@ -53,7 +50,7 @@ def writefile(
bounds = pattern.get_bounds(library=library) bounds = pattern.get_bounds(library=library)
if bounds is None: if bounds is None:
bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]]) bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]])
logger.warning('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1) warnings.warn('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1)
else: else:
bounds_min, bounds_max = bounds bounds_min, bounds_max = bounds
@ -120,7 +117,7 @@ def writefile_inverted(
bounds = pattern.get_bounds(library=library) bounds = pattern.get_bounds(library=library)
if bounds is None: if bounds is None:
bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]]) bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]])
logger.warning('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1) warnings.warn('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1)
else: else:
bounds_min, bounds_max = bounds bounds_min, bounds_max = bounds

View File

@ -1,5 +1,7 @@
from typing import overload, Self, NoReturn, Any from typing import overload, Self, NoReturn, Any
from collections.abc import Iterable, KeysView, ValuesView, Mapping from collections.abc import Iterable, KeysView, ValuesView, Mapping
import warnings
import traceback
import logging import logging
import functools import functools
from collections import Counter from collections import Counter
@ -12,7 +14,7 @@ from numpy.typing import ArrayLike, NDArray
from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
from .utils import rotate_offsets_around from .utils import rotate_offsets_around
from .error import PortError, format_stacktrace from .error import PortError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -303,11 +305,11 @@ class PortList(metaclass=ABCMeta):
if type_conflicts.any(): if type_conflicts.any():
msg = 'Ports have conflicting types:\n' msg = 'Ports have conflicting types:\n'
for nn, (kk, vv) in enumerate(connections.items()): for nn, (k, v) in enumerate(connections.items()):
if type_conflicts[nn]: if type_conflicts[nn]:
msg += f'{kk} | {a_types[nn]}:{b_types[nn]} | {vv}\n' msg += f'{k} | {a_types[nn]}:{b_types[nn]} | {v}\n'
msg += '\nStack trace:\n' + format_stacktrace() msg = ''.join(traceback.format_stack()) + '\n' + msg
logger.warning(msg) warnings.warn(msg, stacklevel=2)
a_offsets = numpy.array([pp.offset for pp in a_ports]) a_offsets = numpy.array([pp.offset for pp in a_ports])
b_offsets = numpy.array([pp.offset for pp in b_ports]) b_offsets = numpy.array([pp.offset for pp in b_ports])
@ -324,17 +326,17 @@ class PortList(metaclass=ABCMeta):
if not numpy.allclose(rotations, 0): if not numpy.allclose(rotations, 0):
rot_deg = numpy.rad2deg(rotations) rot_deg = numpy.rad2deg(rotations)
msg = 'Port orientations do not match:\n' msg = 'Port orientations do not match:\n'
for nn, (kk, vv) in enumerate(connections.items()): for nn, (k, v) in enumerate(connections.items()):
if not numpy.isclose(rot_deg[nn], 0): if not numpy.isclose(rot_deg[nn], 0):
msg += f'{kk} | {rot_deg[nn]:g} | {vv}\n' msg += f'{k} | {rot_deg[nn]:g} | {v}\n'
raise PortError(msg) raise PortError(msg)
translations = a_offsets - b_offsets translations = a_offsets - b_offsets
if not numpy.allclose(translations, 0): if not numpy.allclose(translations, 0):
msg = 'Port translations do not match:\n' msg = 'Port translations do not match:\n'
for nn, (kk, vv) in enumerate(connections.items()): for nn, (k, v) in enumerate(connections.items()):
if not numpy.allclose(translations[nn], 0): if not numpy.allclose(translations[nn], 0):
msg += f'{kk} | {translations[nn]} | {vv}\n' msg += f'{k} | {translations[nn]} | {v}\n'
raise PortError(msg) raise PortError(msg)
for pp in chain(a_names, b_names): for pp in chain(a_names, b_names):
@ -404,7 +406,7 @@ class PortList(metaclass=ABCMeta):
map_out_counts = Counter(map_out.values()) map_out_counts = Counter(map_out.values())
map_out_counts[None] = 0 map_out_counts[None] = 0
conflicts_out = {kk for kk, vv in map_out_counts.items() if vv > 1} conflicts_out = {k for k, v in map_out_counts.items() if v > 1}
if conflicts_out: if conflicts_out:
raise PortError(f'Duplicate targets in `map_out`: {conflicts_out}') raise PortError(f'Duplicate targets in `map_out`: {conflicts_out}')
@ -436,7 +438,7 @@ class PortList(metaclass=ABCMeta):
`set_rotation` must remain `None`. `set_rotation` must remain `None`.
ok_connections: Set of "allowed" ptype combinations. Identical ok_connections: Set of "allowed" ptype combinations. Identical
ptypes are always allowed to connect, as is `'unk'` with ptypes are always allowed to connect, as is `'unk'` with
any other ptypte. Non-allowed ptype connections will log a any other ptypte. Non-allowed ptype connections will emit a
warning. Order is ignored, i.e. `(a, b)` is equivalent to warning. Order is ignored, i.e. `(a, b)` is equivalent to
`(b, a)`. `(b, a)`.
@ -487,7 +489,7 @@ class PortList(metaclass=ABCMeta):
`set_rotation` must remain `None`. `set_rotation` must remain `None`.
ok_connections: Set of "allowed" ptype combinations. Identical ok_connections: Set of "allowed" ptype combinations. Identical
ptypes are always allowed to connect, as is `'unk'` with ptypes are always allowed to connect, as is `'unk'` with
any other ptypte. Non-allowed ptype connections will log a any other ptypte. Non-allowed ptype connections will emit a
warning. Order is ignored, i.e. `(a, b)` is equivalent to warning. Order is ignored, i.e. `(a, b)` is equivalent to
`(b, a)`. `(b, a)`.
@ -518,11 +520,11 @@ class PortList(metaclass=ABCMeta):
for st, ot in zip(s_types, o_types, strict=True)]) for st, ot in zip(s_types, o_types, strict=True)])
if type_conflicts.any(): if type_conflicts.any():
msg = 'Ports have conflicting types:\n' msg = 'Ports have conflicting types:\n'
for nn, (kk, vv) in enumerate(map_in.items()): for nn, (k, v) in enumerate(map_in.items()):
if type_conflicts[nn]: if type_conflicts[nn]:
msg += f'{kk} | {s_types[nn]}:{o_types[nn]} | {vv}\n' msg += f'{k} | {s_types[nn]}:{o_types[nn]} | {v}\n'
msg += '\nStack trace:\n' + format_stacktrace() msg = ''.join(traceback.format_stack()) + '\n' + msg
logger.warning(msg) warnings.warn(msg, stacklevel=2)
rotations = numpy.mod(s_rotations - o_rotations - pi, 2 * pi) rotations = numpy.mod(s_rotations - o_rotations - pi, 2 * pi)
if not has_rot.any(): if not has_rot.any():
@ -544,11 +546,8 @@ class PortList(metaclass=ABCMeta):
translations = s_offsets - o_offsets translations = s_offsets - o_offsets
if not numpy.allclose(translations[:1], translations): if not numpy.allclose(translations[:1], translations):
msg = 'Port translations do not match:\n' msg = 'Port translations do not match:\n'
common_translation = numpy.min(translations, axis=0)
msg += f'Common: {common_translation} \n'
msg += 'Deltas:\n'
for nn, (kk, vv) in enumerate(map_in.items()): for nn, (kk, vv) in enumerate(map_in.items()):
msg += f'{kk} | {translations[nn] - common_translation} | {vv}\n' msg += f'{kk} | {translations[nn]} | {vv}\n'
raise PortError(msg) raise PortError(msg)
return translations[0], rotations[0], o_offsets[0] return translations[0], rotations[0], o_offsets[0]

View File

@ -1,4 +1,4 @@
from typing import Self from typing import Self, Any
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import numpy import numpy