From 1dc2735b7261693b51b22d8c459deb18ca1a4cbe Mon Sep 17 00:00:00 2001 From: Carlos Adir Date: Sun, 17 Aug 2025 20:18:48 +0200 Subject: [PATCH] feat: add loggers to easier debug and tracking errors --- .coveragerc | 3 + src/shapepy/__init__.py | 22 ++-- src/shapepy/analytic/bezier.py | 6 + src/shapepy/analytic/polynomial.py | 10 +- src/shapepy/analytic/tools.py | 4 + src/shapepy/bool2d/base.py | 7 ++ src/shapepy/bool2d/boolean.py | 3 + src/shapepy/bool2d/primitive.py | 6 + src/shapepy/bool2d/shape.py | 5 +- src/shapepy/geometry/factory.py | 3 + src/shapepy/geometry/integral.py | 2 + src/shapepy/geometry/jordancurve.py | 17 ++- src/shapepy/geometry/piecewise.py | 2 + src/shapepy/geometry/point.py | 3 + src/shapepy/geometry/segment.py | 4 + src/shapepy/loggers.py | 167 ++++++++++++++++++++++++++++ src/shapepy/scalar/quadrature.py | 2 + src/shapepy/tools.py | 2 + 18 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 src/shapepy/loggers.py diff --git a/.coveragerc b/.coveragerc index 9528f849..b319329a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -11,3 +11,6 @@ exclude_lines = raise NotExpectedError raise ValueError raise TypeError +omit = + loggers.py + __init__.py diff --git a/src/shapepy/__init__.py b/src/shapepy/__init__.py index 17097cfd..caf56fd6 100644 --- a/src/shapepy/__init__.py +++ b/src/shapepy/__init__.py @@ -7,17 +7,21 @@ import importlib -from shapepy.bool2d.base import EmptyShape, WholeShape -from shapepy.bool2d.primitive import Primitive -from shapepy.bool2d.shape import ConnectedShape, DisjointShape, SimpleShape -from shapepy.common import move, rotate, scale -from shapepy.geometry.integral import IntegrateJordan -from shapepy.geometry.jordancurve import JordanCurve -from shapepy.geometry.point import Point2D -from shapepy.geometry.segment import Segment -from shapepy.plot.plot import ShapePloter +from .bool2d.base import EmptyShape, WholeShape +from .bool2d.primitive import Primitive +from .bool2d.shape import ConnectedShape, DisjointShape, SimpleShape +from .common import move, rotate, scale +from .geometry.integral import IntegrateJordan +from .geometry.jordancurve import JordanCurve +from .geometry.point import Point2D +from .geometry.segment import Segment +from .loggers import set_level +from .plot.plot import ShapePloter __version__ = importlib.metadata.version("shapepy") +set_level("shapepy", level="INFO") + + if __name__ == "__main__": pass diff --git a/src/shapepy/analytic/bezier.py b/src/shapepy/analytic/bezier.py index 0216bd4d..d6587624 100644 --- a/src/shapepy/analytic/bezier.py +++ b/src/shapepy/analytic/bezier.py @@ -9,6 +9,7 @@ from rbool import SubSetR1, Whole +from ..loggers import debug from ..scalar.quadrature import inner from ..scalar.reals import Math, Rational, Real from ..tools import Is, NotExpectedError, To @@ -123,12 +124,14 @@ def __call__(self, node: Real, derivate: int = 0) -> Real: def __str__(self): return str(self.__polynomial) + @debug("shapepy.analytic.bezier") def clean(self) -> Bezier: """ Decreases the degree of the bezier curve if possible """ return polynomial2bezier(bezier2polynomial(self).clean()) + @debug("shapepy.analytic.bezier") def scale(self, amount: Real) -> Bezier: """ Transforms the polynomial p(t) into p(A*t) by @@ -149,6 +152,7 @@ def scale(self, amount: Real) -> Bezier: """ return polynomial2bezier(bezier2polynomial(self).scale(amount)) + @debug("shapepy.analytic.bezier") def shift(self, amount: Real) -> Bezier: """ Transforms the bezier p(t) into p(t-d) by @@ -156,6 +160,7 @@ def shift(self, amount: Real) -> Bezier: """ return polynomial2bezier(bezier2polynomial(self).shift(amount)) + @debug("shapepy.analytic.bezier") def integrate(self, times: int = 1) -> Bezier: """ Integrates the bezier analytic @@ -171,6 +176,7 @@ def integrate(self, times: int = 1) -> Bezier: """ return polynomial2bezier(bezier2polynomial(self).integrate(times)) + @debug("shapepy.analytic.bezier") def derivate(self, times: int = 1) -> Bezier: """ Derivate the bezier curve, giving a new one diff --git a/src/shapepy/analytic/polynomial.py b/src/shapepy/analytic/polynomial.py index 09e0e6f6..1e4f4e9c 100644 --- a/src/shapepy/analytic/polynomial.py +++ b/src/shapepy/analytic/polynomial.py @@ -9,6 +9,7 @@ from rbool import move, scale +from ..loggers import debug from ..scalar.reals import Math from ..tools import Is, To, vectorize from .base import BaseAnalytic, IAnalytic @@ -103,9 +104,9 @@ def __str__(self): msgs: List[str] = [] flag = False for i, coef in enumerate(self): - if coef == 0: + if coef * coef == 0: continue - msg = "- " if coef < 0 else "+ " if flag else "" + msg = "- " if Is.real(coef) and coef < 0 else "+ " if flag else "" flag = True coef = abs(coef) if coef != 1 or i == 0: @@ -119,6 +120,7 @@ def __str__(self): msgs.append(msg) return " ".join(msgs) + @debug("shapepy.analytic.polynomial") def clean(self) -> Polynomial: """ Decreases the degree of the bezier curve if possible @@ -126,6 +128,7 @@ def clean(self) -> Polynomial: degree = max((i for i, v in enumerate(self) if v * v > 0), default=0) return Polynomial(self[: degree + 1], self.domain) + @debug("shapepy.analytic.polynomial") def scale(self, amount: Real) -> Polynomial: """ Transforms the polynomial p(t) into p(A*t) by @@ -148,6 +151,7 @@ def scale(self, amount: Real) -> Polynomial: coefs = tuple(coef * inv**i for i, coef in enumerate(self)) return Polynomial(coefs, scale(self.domain, amount)) + @debug("shapepy.analytic.polynomial") def shift(self, amount: Real) -> Polynomial: """ Transforms the polynomial p(t) into p(t-d) by @@ -175,6 +179,7 @@ def shift(self, amount: Real) -> Polynomial: newcoefs[j] += coef * value return Polynomial(newcoefs, move(self.domain, amount)) + @debug("shapepy.analytic.polynomial") def integrate(self, times: int = 1) -> Polynomial: """ Integrates the polynomial curve @@ -197,6 +202,7 @@ def integrate(self, times: int = 1) -> Polynomial: polynomial = Polynomial(newcoefs, self.domain) return polynomial + @debug("shapepy.analytic.polynomial") def derivate(self, times: int = 1) -> Polynomial: """ Derivate the polynomial curve, giving a new one diff --git a/src/shapepy/analytic/tools.py b/src/shapepy/analytic/tools.py index 92769053..4797c7d1 100644 --- a/src/shapepy/analytic/tools.py +++ b/src/shapepy/analytic/tools.py @@ -15,6 +15,7 @@ unite, ) +from ..loggers import debug from ..scalar.reals import Math, Real from ..tools import Is, NotExpectedError, To from .base import IAnalytic, derivate_analytic @@ -101,6 +102,7 @@ def find_minimum_polynomial( ) +@debug("shapepy.analytic.tools") def find_roots(analytic: IAnalytic, domain: SubSetR1 = Whole()) -> SubSetR1: """ Finds the values of roots of the Analytic function @@ -114,6 +116,7 @@ def find_roots(analytic: IAnalytic, domain: SubSetR1 = Whole()) -> SubSetR1: raise NotExpectedError +@debug("shapepy.analytic.tools") def where_minimum(analytic: IAnalytic, domain: SubSetR1 = Whole()) -> SubSetR1: """ Finds the parameters (t*) such the analytic function is minimum @@ -127,6 +130,7 @@ def where_minimum(analytic: IAnalytic, domain: SubSetR1 = Whole()) -> SubSetR1: raise NotExpectedError +@debug("shapepy.analytic.tools") def find_minimum(analytic: IAnalytic, domain: SubSetR1 = Whole()) -> SubSetR1: """ Finds the minimal value for the given analytic in the given domain diff --git a/src/shapepy/bool2d/base.py b/src/shapepy/bool2d/base.py index 24e30439..bdb9fa05 100644 --- a/src/shapepy/bool2d/base.py +++ b/src/shapepy/bool2d/base.py @@ -13,6 +13,7 @@ from typing import Iterable, Tuple, Union from ..geometry.point import Point2D +from ..loggers import debug from ..scalar.angle import Angle from ..scalar.reals import Real @@ -29,10 +30,12 @@ def __init__(self): def __invert__(self) -> SubSetR2: """Invert shape""" + @debug("shapepy.bool2d.base") def __or__(self, other: SubSetR2) -> SubSetR2: """Union shapes""" return Future.unite((self, other)) + @debug("shapepy.bool2d.base") def __and__(self, other: SubSetR2) -> SubSetR2: """Intersection shapes""" return Future.intersect((self, other)) @@ -49,18 +52,22 @@ def __neg__(self) -> SubSetR2: """Invert the SubSetR2""" return ~self + @debug("shapepy.bool2d.base") def __add__(self, other: SubSetR2): """Union of SubSetR2""" return self | other + @debug("shapepy.bool2d.base") def __mul__(self, value: SubSetR2): """Intersection of SubSetR2""" return self & value + @debug("shapepy.bool2d.base") def __sub__(self, value: SubSetR2): """Subtraction of SubSetR2""" return self & (~value) + @debug("shapepy.bool2d.base") def __xor__(self, other: SubSetR2): """XOR of SubSetR2""" return (self - other) | (other - self) diff --git a/src/shapepy/bool2d/boolean.py b/src/shapepy/bool2d/boolean.py index ca105440..c72efbd1 100644 --- a/src/shapepy/bool2d/boolean.py +++ b/src/shapepy/bool2d/boolean.py @@ -13,6 +13,7 @@ from ..geometry.intersection import GeometricIntersectionCurves from ..geometry.unparam import USegment +from ..loggers import debug from ..tools import CyclicContainer, Is from .base import EmptyShape, SubSetR2, WholeShape from .shape import ( @@ -23,6 +24,7 @@ ) +@debug("shapepy.bool2d.boolean") def unite(subsets: Iterable[SubSetR2]) -> SubSetR2: """ Computes the union of given subsets @@ -55,6 +57,7 @@ def unite(subsets: Iterable[SubSetR2]) -> SubSetR2: return shape_from_jordans(new_jordans) +@debug("shapepy.bool2d.boolean") def intersect(subsets: Iterable[SubSetR2]) -> SubSetR2: """ Computes the intersection of given subsets diff --git a/src/shapepy/bool2d/primitive.py b/src/shapepy/bool2d/primitive.py index 076da1b2..bc4c9eef 100644 --- a/src/shapepy/bool2d/primitive.py +++ b/src/shapepy/bool2d/primitive.py @@ -17,6 +17,7 @@ from ..geometry.point import Point2D, cartesian from ..geometry.segment import Segment from ..geometry.unparam import USegment +from ..loggers import debug from ..tools import Is, To from .base import EmptyShape, WholeShape from .shape import SimpleShape @@ -36,6 +37,7 @@ class Primitive: whole = WholeShape() @staticmethod + @debug("shapepy.bool2d.primitive") def regular_polygon( nsides: int, radius: float = 1, center: Point2D = (0, 0) ) -> SimpleShape: @@ -86,6 +88,7 @@ def regular_polygon( return Primitive.polygon(vertices) @staticmethod + @debug("shapepy.bool2d.primitive") def polygon(vertices: Tuple[Point2D]) -> SimpleShape: """ Creates a generic polygon @@ -112,6 +115,7 @@ def polygon(vertices: Tuple[Point2D]) -> SimpleShape: return SimpleShape(jordan_curve) @staticmethod + @debug("shapepy.bool2d.primitive") def triangle(side: float = 1, center: Point2D = (0, 0)) -> SimpleShape: """ Create a right triangle @@ -142,6 +146,7 @@ def triangle(side: float = 1, center: Point2D = (0, 0)) -> SimpleShape: return Primitive.polygon(vertices) @staticmethod + @debug("shapepy.bool2d.primitive") def square(side: float = 1, center: Point2D = (0, 0)) -> SimpleShape: """ Creates a square with sides aligned with axis @@ -179,6 +184,7 @@ def square(side: float = 1, center: Point2D = (0, 0)) -> SimpleShape: return Primitive.polygon(vertices) @staticmethod + @debug("shapepy.bool2d.primitive") def circle( radius: float = 1, center: Point2D = (0, 0), ndivangle: int = 16 ) -> SimpleShape: diff --git a/src/shapepy/bool2d/shape.py b/src/shapepy/bool2d/shape.py index f4b12e8e..7a875ea2 100644 --- a/src/shapepy/bool2d/shape.py +++ b/src/shapepy/bool2d/shape.py @@ -12,8 +12,6 @@ from copy import copy from typing import Iterable, Set, Tuple, Union -import numpy as np - from ..geometry.box import Box from ..geometry.integral import winding_number from ..geometry.jordancurve import JordanCurve @@ -48,8 +46,7 @@ def __deepcopy__(self, memo) -> SimpleShape: def __str__(self) -> str: # pragma: no cover # For debug area = float(self.area) vertices = tuple(map(tuple, self.jordan.vertices)) - vertices = np.array(vertices, dtype="float64") - return f"Simple Shape of area {area:.2f} with vertices:\n{vertices}" + return f"SimpleShape[{area:.2f}]:[{vertices}]" def __eq__(self, other: SubSetR2) -> bool: """Compare two shapes diff --git a/src/shapepy/geometry/factory.py b/src/shapepy/geometry/factory.py index 97fa44d0..500340c3 100644 --- a/src/shapepy/geometry/factory.py +++ b/src/shapepy/geometry/factory.py @@ -4,6 +4,7 @@ from typing import Tuple +from ..loggers import debug from ..tools import To from .jordancurve import JordanCurve from .point import Point2D @@ -17,6 +18,7 @@ class FactoryJordan: """ @staticmethod + @debug("shapepy.geometry.factory") def polygon(vertices: Tuple[Point2D, ...]) -> JordanCurve: """Initialize a polygonal JordanCurve from a list of vertices, @@ -46,6 +48,7 @@ def polygon(vertices: Tuple[Point2D, ...]) -> JordanCurve: return JordanCurve(beziers) @staticmethod + @debug("shapepy.geometry.factory") def spline_curve(spline_curve) -> JordanCurve: """Initialize a JordanCurve from a spline curve, diff --git a/src/shapepy/geometry/integral.py b/src/shapepy/geometry/integral.py index d8c9b018..adb30f77 100644 --- a/src/shapepy/geometry/integral.py +++ b/src/shapepy/geometry/integral.py @@ -10,6 +10,7 @@ from ..analytic.base import IAnalytic from ..analytic.tools import find_minimum from ..common import derivate +from ..loggers import debug from ..scalar.angle import Angle from ..scalar.quadrature import AdaptativeIntegrator, IntegratorFactory from ..scalar.reals import Math @@ -64,6 +65,7 @@ def polynomial(jordan: JordanCurve, expx: int, expy: int): # pylint: disable=too-many-locals +@debug("shapepy.geometry.integral") def winding_number( jordan: JordanCurve, center: Optional[Point2D] = (0.0, 0.0) ) -> Union[int, float]: diff --git a/src/shapepy/geometry/jordancurve.py b/src/shapepy/geometry/jordancurve.py index 821301de..a201924c 100644 --- a/src/shapepy/geometry/jordancurve.py +++ b/src/shapepy/geometry/jordancurve.py @@ -10,6 +10,7 @@ from typing import Iterable, Tuple, Union from ..common import clean +from ..loggers import debug from ..scalar.angle import Angle from ..scalar.reals import Real from ..tools import CyclicContainer, Is, pairs, reverse @@ -37,14 +38,17 @@ def __deepcopy__(self, memo) -> JordanCurve: """Returns a deep copy of the jordan curve""" return self.__class__(map(copy, self.usegments)) + @debug("shapepy.geometry.jordancurve") def move(self, vector: Point2D) -> JordanCurve: self.__piecewise = self.piecewise.move(vector) return self + @debug("shapepy.geometry.jordancurve") def scale(self, amount: Union[Real, Tuple[Real, Real]]) -> JordanCurve: self.__piecewise = self.piecewise.scale(amount) return self + @debug("shapepy.geometry.jordancurve") def rotate(self, angle: Angle) -> JordanCurve: self.__piecewise = self.piecewise.rotate(angle) return self @@ -183,6 +187,7 @@ def __eq__(self, other: JordanCurve) -> bool: return False return clean(self).usegments == clean(other).usegments + @debug("shapepy.geometry.jordancurve") def invert(self) -> JordanCurve: """Invert the current curve's orientation, doesn't create a copy @@ -207,6 +212,7 @@ def invert(self) -> JordanCurve: self.usegments = reverse(useg.invert() for useg in self.usegments) return self + @debug("shapepy.geometry.jordancurve") def clean(self) -> JordanCurve: """Cleans the jordan curve""" usegments = list(map(clean_usegment, self.usegments)) @@ -235,6 +241,7 @@ def __contains__(self, point: Point2D) -> bool: return point in self.piecewise +@debug("shapepy.geometry.jordancurve") def compute_area(jordan: JordanCurve) -> Real: """ Computes the area inside of the jordan curve @@ -253,16 +260,6 @@ def compute_area(jordan: JordanCurve) -> Real: return total / 2 -def get_ctrlpoints(jordan: JordanCurve) -> Iterable[Point2D]: - """Gets the control points of the jordan curve""" - vertices = {} - for usegment in jordan.usegments: - segment = usegment.parametrize() - for ctrlpt in segment.ctrlpoints: - vertices[id(ctrlpt)] = ctrlpt - return vertices.values() - - def clean_jordan(jordan: JordanCurve) -> JordanCurve: """Cleans the jordan curve diff --git a/src/shapepy/geometry/piecewise.py b/src/shapepy/geometry/piecewise.py index 2b1d2ef8..89587031 100644 --- a/src/shapepy/geometry/piecewise.py +++ b/src/shapepy/geometry/piecewise.py @@ -7,6 +7,7 @@ from collections import defaultdict from typing import Iterable, Tuple, Union +from ..loggers import debug from ..scalar.angle import Angle from ..scalar.reals import Real from ..tools import Is, To, vectorize @@ -76,6 +77,7 @@ def __len__(self) -> int: """ return len(self.__segments) + @debug("shapepy.geometry.piecewise") def span(self, node: Real) -> Union[int, None]: """ Finds the index of the node diff --git a/src/shapepy/geometry/point.py b/src/shapepy/geometry/point.py index 7603e4a2..1e66ecdf 100644 --- a/src/shapepy/geometry/point.py +++ b/src/shapepy/geometry/point.py @@ -8,6 +8,7 @@ from typing import Tuple, Union +from ..loggers import debug from ..scalar.angle import Angle from ..scalar.reals import Math, Real from ..tools import Is, To @@ -15,6 +16,7 @@ TOLERANCE = 1e-9 +@debug("shapepy.geometry.point", maxdepth=0) def cartesian(xcoord: Real, ycoord: Real) -> Point2D: """ Creates a Point with cartesian coordinates @@ -24,6 +26,7 @@ def cartesian(xcoord: Real, ycoord: Real) -> Point2D: return Point2D(xcoord, ycoord, None, None) +@debug("shapepy.geometry.point", maxdepth=0) def polar(radius: Real, angle: Angle) -> Point2D: """ Creates a Point with polar coordinates diff --git a/src/shapepy/geometry/segment.py b/src/shapepy/geometry/segment.py index f52ade2a..879b6f04 100644 --- a/src/shapepy/geometry/segment.py +++ b/src/shapepy/geometry/segment.py @@ -18,6 +18,7 @@ from ..analytic.base import IAnalytic from ..analytic.bezier import Bezier from ..analytic.tools import find_minimum +from ..loggers import debug from ..scalar.angle import Angle from ..scalar.quadrature import AdaptativeIntegrator, IntegratorFactory from ..scalar.reals import Math, Real @@ -56,6 +57,7 @@ def __eq__(self, other: Segment) -> bool: return False return True + @debug("shapepy.geometry.segment") def __contains__(self, point: Point2D) -> bool: point = To.point(point) if point not in self.box(): @@ -191,6 +193,7 @@ def rotate(self, angle: Angle) -> Segment: return self +@debug("shapepy.geometry.segment") def compute_length(segment: Segment) -> Real: """ Computes the length of the jordan curve @@ -211,6 +214,7 @@ def function(node): return adaptative.integrate(function, domain) +@debug("shapepy.geometry.segment") def clean_segment(segment: Segment) -> Segment: """Reduces at maximum the degree of the bezier curve""" newplanar = To.bezier(segment.ctrlpoints) diff --git a/src/shapepy/loggers.py b/src/shapepy/loggers.py new file mode 100644 index 00000000..f1317c7e --- /dev/null +++ b/src/shapepy/loggers.py @@ -0,0 +1,167 @@ +""" +Definition of custom logger of :mod:`shapepy` module. +""" + +from __future__ import annotations + +import logging +import sys +from contextlib import contextmanager +from functools import wraps +from typing import Dict, Optional + + +# pylint: disable=too-few-public-methods +class LogConfiguration: + """Contains the configuration values for the loggers""" + + indent_size = 4 + log_enabled = False + + +class IndentingLoggerAdapter(logging.LoggerAdapter): + """ + An indenting logger that insert spaces depending on the + value of `indent_level`. + It is used to keep track of the stack of function calls + """ + + instances: Dict[str, IndentingLoggerAdapter] = {} + indent_level = 0 + + def __init__(self, logger, extra=None): + super().__init__(logger, extra) + self.instances[logger.name] = self + + def process(self, msg, kwargs): + """ + Inserts spaces proportional to `indent_level` before the message + """ + indent_str = " " * LogConfiguration.indent_size * self.indent_level + return f"{indent_str}{msg}", kwargs + + +def set_level(base: str, /, *, level: logging._Level): + """ + Sets the level of all the shapepy loggers into given level + + Parameters + ---------- + base: str + The base name to filter the + level: logging._Level + One from 'DEBUG', 'INFO', 'WARNING', 'ERROR' and 'CRITICAL' + + Example + ------- + >>> set_level("INFO") + >>> set_level("ERROR") + """ + for name, logger in IndentingLoggerAdapter.instances.items(): + if base in name: + logger.setLevel(level) + + +def get_logger( + name: Optional[str] = None, /, *, level: Optional[logging._Level] = None +) -> IndentingLoggerAdapter: + """ + Equivalent to `logging.getLogger`, but gives the standard + `shapepy` logger if no name is given + """ + if name is None: + name = "shapepy" + if name not in IndentingLoggerAdapter.instances: + setup_logger(name) + logger = IndentingLoggerAdapter.instances[name] + if level is not None: + logger.setLevel(level) + return logger + + +def setup_logger(name, level=logging.INFO): + """ + Setups the indenting logger with given level + and adds the file handler 'shapepy.log' to store + """ + logger = logging.getLogger(name) + adapter = IndentingLoggerAdapter(logger) + adapter.logger.setLevel(logging.DEBUG) + + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + # formatter = logging.Formatter("%(asctime)s - %(message)s") + # formatter = logging.Formatter("%(message)s") + + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setLevel(level) + stdout_handler.setFormatter(formatter) + adapter.logger.addHandler(stdout_handler) + + file_handler = logging.FileHandler( + "shapepy.log", "w" if (name == "shapepy") else "a" + ) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + adapter.logger.addHandler(file_handler) + + +@contextmanager +def indent(): + """ + An indent context manager, that increases the indentation level when + entering the block with `with" command and decreases when leaving it + """ + IndentingLoggerAdapter.indent_level += 1 + try: + yield + finally: + IndentingLoggerAdapter.indent_level -= 1 + + +# Create decorator to use in functions +def debug(name: Optional[str] = None, /, *, maxdepth: Optional[int] = None): + """ + Decorator to automatically log in debug mode the input and outputs + of the given function in a indentation mode. + + Parameters + ---------- + name : str + The name of the logger, something like "shapepy.module.submodule" + maxdepth : Optional[int], default = None + The maximal depth to log the function. It's used as a method to + clean the logger when the functions stack of calls becomes too big + """ + logger = get_logger(name) + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + if not LogConfiguration.log_enabled or ( + maxdepth is not None and logger.indent_level > maxdepth + ): + return func(*args, **kwargs) + tipos = [repr(arg) for arg in args] + tipos += [ + str(key) + "=" + repr(val) for key, val in kwargs.items() + ] + if maxdepth is None or logger.indent_level < maxdepth: + logger.debug( + f"Compute {func.__qualname__}({', '.join(tipos)})" + ) + try: + with indent(): + result = func(*args, **kwargs) + if maxdepth is None or logger.indent_level < maxdepth: + logger.debug("Return = " + repr(result)) + return result + except Exception as e: + logger.debug("Error = " + repr(e)) + raise e + + return wrapper + + return decorator diff --git a/src/shapepy/scalar/quadrature.py b/src/shapepy/scalar/quadrature.py index ff67de7e..afb116c6 100644 --- a/src/shapepy/scalar/quadrature.py +++ b/src/shapepy/scalar/quadrature.py @@ -7,6 +7,7 @@ import numpy as np +from ..loggers import debug from ..tools import Is, To from .nodes_sample import NodeSampleFactory from .reals import Math, Real @@ -388,6 +389,7 @@ def maxdepth(self, value: int): raise ValueError(f"Invalid maxdepth: {value}") self.__maxdepth = value + @debug("shapepy.scalar.quadrature") def integrate( self, function: Callable[[Real], Real], interval: Tuple[Real, Real] ) -> Real: diff --git a/src/shapepy/tools.py b/src/shapepy/tools.py index d17e6ffe..ab50a1ea 100644 --- a/src/shapepy/tools.py +++ b/src/shapepy/tools.py @@ -72,6 +72,8 @@ def vectorize(position: int = 0, dimension: int = 0): def decorator(func): conversion = { + tuple: tuple, + list: list, types.GeneratorType: tuple, # No conversion range: tuple, # No conversion }