From 6333bf75be905d992d927f639c19919139df9f3f Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Thu, 8 Nov 2018 16:05:57 +0100 Subject: [PATCH 1/2] extra optimize decorator (_spec & _logger) copy docstrings to cython code Signed-off-by: Alexey Stepanov (cherry picked from commit 91523727cfa9d4f40311fef3508e2a9ba8ea74b1) Signed-off-by: Alexey Stepanov --- logwrap/class_decorator.py | 4 +- logwrap/class_decorator.pyx | 20 ++- logwrap/log_wrap.pxd | 7 +- logwrap/log_wrap.py | 2 +- logwrap/log_wrap.pyx | 189 ++++++++++++++++++++++++---- logwrap/repr_utils.py | 6 +- logwrap/repr_utils.pyx | 243 ++++++++++++++++++++++++++++++++---- 7 files changed, 411 insertions(+), 60 deletions(-) diff --git a/logwrap/class_decorator.py b/logwrap/class_decorator.py index 1659b24..62fcb02 100644 --- a/logwrap/class_decorator.py +++ b/logwrap/class_decorator.py @@ -26,9 +26,7 @@ class BaseDecorator(metaclass=abc.ABCMeta): Implements wrapping and __call__, wrapper getter is abstract. - Note: - wrapper getter is called only on function call, - if decorator used without braces. + .. note:: wrapper getter is called only on function call, if decorator used without braces. Usage example: diff --git a/logwrap/class_decorator.pyx b/logwrap/class_decorator.pyx index ca0c81e..b65da63 100644 --- a/logwrap/class_decorator.pyx +++ b/logwrap/class_decorator.pyx @@ -19,10 +19,19 @@ import typing cdef class BaseDecorator: - """Base class for decorators.""" + """Base class for decorators. + + Implements wrapping and __call__, wrapper getter is abstract. + + .. note:: wrapper getter is called only on function call, if decorator used without braces. + """ def __init__(self, func: typing.Optional[typing.Callable] = None) -> None: - """Decorator.""" + """Decorator. + + :param func: function to wrap + :type func: typing.Optional[typing.Callable] + """ # noinspection PyArgumentList super(BaseDecorator, self).__init__() # pylint: disable=assigning-non-slot @@ -32,7 +41,12 @@ cdef class BaseDecorator: # pylint: enable=assigning-non-slot def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: - """Here should be constructed and returned real decorator.""" + """Here should be constructed and returned real decorator. + + :param func: Wrapped function + :type func: typing.Callable + :rtype: typing.Callable + """ raise NotImplementedError() # pragma: no cover def __call__(self, *args: typing.Union[typing.Callable, typing.Any], **kwargs: typing.Any) -> typing.Any: diff --git a/logwrap/log_wrap.pxd b/logwrap/log_wrap.pxd index 5308c21..a9157b7 100644 --- a/logwrap/log_wrap.pxd +++ b/logwrap/log_wrap.pxd @@ -44,13 +44,14 @@ cdef class LogWrap(class_decorator.BaseDecorator): cdef public bint log_traceback cdef public bint log_result_obj + cdef readonly object _spec + cdef readonly object _logger + cdef list __blacklisted_names cdef list __blacklisted_exceptions - cdef object __logger - cdef object __spec cdef str _get_func_args_repr( - self, sig: inspect.Signature, args: typing.Tuple, kwargs: typing.Dict[str, typing.Any] + self, sig: inspect.Signature, tuple args, dict kwargs ) cdef void _make_done_record(self, str func_name, result: typing.Any) cdef void _make_calling_record(self, str name, str arguments, str method=?) diff --git a/logwrap/log_wrap.py b/logwrap/log_wrap.py index 21869d2..17ee61a 100644 --- a/logwrap/log_wrap.py +++ b/logwrap/log_wrap.py @@ -712,7 +712,7 @@ def logwrap( # noqa: F811 # pylint: disable=unexpected-keyword-arg, no-value-f log_traceback: bool = True, log_result_obj: bool = True ) -> typing.Union[LogWrap, typing.Callable]: - """Log function calls and return values. Python 3.4+ version. + """Log function calls and return values. :param func: function to wrap :type func: typing.Optional[typing.Callable] diff --git a/logwrap/log_wrap.pyx b/logwrap/log_wrap.pyx index eda0d9f..3041564 100644 --- a/logwrap/log_wrap.pyx +++ b/logwrap/log_wrap.pyx @@ -63,7 +63,14 @@ class BoundParameter: empty = inspect.Parameter.empty def __init__(self, parameter: inspect.Parameter, value: typing.Any = inspect.Parameter.empty) -> None: - """Parameter-like object store BOUND with value parameter.""" + """Parameter-like object store BOUND with value parameter. + + :param parameter: parameter from signature + :type parameter: inspect.Parameter + :param value: parameter real value + :type value: typing.Any + :raises ValueError: No default value and no value + """ self._parameter = parameter if value is self.empty: @@ -105,7 +112,10 @@ class BoundParameter: # noinspection PyTypeChecker def __hash__(self) -> int: - """Block hashing.""" + """Block hashing. + + :raises TypeError: Not hashable. + """ msg = "unhashable type: '{0}'".format(self.__class__.__name__) raise TypeError(msg) @@ -150,7 +160,19 @@ class BoundParameter: def bind_args_kwargs( sig: inspect.Signature, *args: typing.Any, **kwargs: typing.Any ) -> typing.Iterator[BoundParameter]: - """Bind *args and **kwargs to signature and get Bound Parameters.""" + """Bind *args and **kwargs to signature and get Bound Parameters. + + :param sig: source signature + :type sig: inspect.Signature + :param args: not keyworded arguments + :type args: typing.Any + :param kwargs: keyworded arguments + :type kwargs: typing.Any + :return: Iterator for bound parameters with all information about it + :rtype: typing.Iterator[BoundParameter] + + .. versionadded:: 3.3.0 + """ bound = sig.bind(*args, **kwargs).arguments parameters = list(sig.parameters.values()) for param in parameters: @@ -176,7 +198,42 @@ cdef class LogWrap(class_decorator.BaseDecorator): bint log_traceback=True, bint log_result_obj=True ) -> None: - """Log function calls and return values.""" + """Log function calls and return values. + + :param func: function to wrap + :type func: typing.Optional[typing.Callable] + :param log: logger object for decorator, by default used 'logwrap' + :type log: logging.Logger + :param log_level: log level for successful calls + :type log_level: int + :param exc_level: log level for exception cases + :type exc_level: int + :param max_indent: maximum indent before classic `repr()` call. + :type max_indent: int + :param spec: callable object used as spec for arguments bind. + This is designed for the special cases only, + when impossible to change signature of target object, + but processed/redirected signature is accessible. + Note: this object should provide fully compatible + signature with decorated function, or arguments bind + will be failed! + :type spec: typing.Optional[typing.Callable] + :param blacklisted_names: Blacklisted argument names. Arguments with this names will be skipped in log. + :type blacklisted_names: typing.Optional[typing.Iterable[str]] + :param blacklisted_exceptions: list of exception, which should be re-raised without producing log record. + :type blacklisted_exceptions: typing.Optional[typing.Iterable[typing.Type[Exception]]] + :param log_call_args: log call arguments before executing wrapped function. + :type log_call_args: bool + :param log_call_args_on_exc: log call arguments if exception raised. + :type log_call_args_on_exc: bool + :param log_traceback: log traceback on excaption in addition to failure info + :type log_traceback: bool + :param log_result_obj: log result of function call. + :type log_result_obj: bool + + .. versionchanged:: 3.3.0 Extract func from log and do not use Union. + .. versionchanged:: 5.1.0 log_traceback parameter + """ super(LogWrap, self).__init__(func=func) self.log_level = log_level @@ -198,9 +255,8 @@ cdef class LogWrap(class_decorator.BaseDecorator): else: self.__blacklisted_exceptions = list(blacklisted_exceptions) - self.__logger = log - - self.__spec = spec or self._func + self._logger = log + self._spec = spec or self._func # We are not interested to pass any arguments to object @@ -214,16 +270,6 @@ cdef class LogWrap(class_decorator.BaseDecorator): """List of exceptions to re-raise without log.""" return self.__blacklisted_exceptions - @property - def _logger(self) -> logging.Logger: - """Logger instance.""" - return self.__logger - - @property - def _spec(self) -> typing.Optional[typing.Callable]: - """Spec for function arguments.""" - return self.__spec - def __repr__(self) -> str: """Repr for debug purposes.""" return ( @@ -243,15 +289,49 @@ cdef class LogWrap(class_decorator.BaseDecorator): def pre_process_param( self, arg: BoundParameter ) -> typing.Union[BoundParameter, typing.Tuple[BoundParameter, typing.Any], None]: - """Process parameter for the future logging.""" + """Process parameter for the future logging. + + :param arg: bound parameter + :type arg: BoundParameter + :return: value, value override for logging or None if argument should not be logged. + :rtype: typing.Union[BoundParameter, typing.Tuple[BoundParameter, typing.Any], None] + + Override this method if some modifications required for parameter value before logging + + .. versionadded:: 3.3.0 + """ return arg def post_process_param(self, arg: BoundParameter, str arg_repr: str) -> str: - """Process parameter for the future logging.""" + """Process parameter for the future logging. + + :param arg: bound parameter + :type arg: BoundParameter + :param arg_repr: repr for value + :type arg_repr: str + :return: processed repr for value + :rtype: str + + Override this method if some modifications required for result of repr() over parameter + + .. versionadded:: 3.3.0 + """ return arg_repr - cdef str _get_func_args_repr(self, sig: inspect.Signature, args: typing.Tuple, kwargs: typing.Dict[str, typing.Any]): - """Internal helper for reducing complexity of decorator code.""" + cdef str _get_func_args_repr(self, sig: inspect.Signature, tuple args, dict kwargs): + """Internal helper for reducing complexity of decorator code. + + :param sig: function signature + :type sig: inspect.Signature + :param args: not keyworded arguments + :type args: typing.Tuple + :param kwargs: keyworded arguments + :type kwargs: typing.Dict[str, typing.Any] + :return: repr over function arguments + :rtype: str + + .. versionchanged:: 3.3.0 Use pre- and post- processing of params during execution + """ if not (self.log_call_args or self.log_call_args_on_exc): return "" @@ -298,7 +378,11 @@ cdef class LogWrap(class_decorator.BaseDecorator): return param_str cdef void _make_done_record(self, str func_name, result: typing.Any): - """Construct success record.""" + """Construct success record. + + :type func_name: str + :type result: typing.Any + """ cdef str msg = "Done: {name!r}".format(name=func_name) if self.log_result_obj: @@ -315,7 +399,12 @@ cdef class LogWrap(class_decorator.BaseDecorator): self._logger.log(level=self.log_level, msg=msg) # type: ignore cdef void _make_calling_record(self, str name, str arguments, str method="Calling"): - """Make log record before execution.""" + """Make log record before execution. + + :type name: str + :type arguments: str + :type method: str + """ self._logger.log( # type: ignore level=self.log_level, msg="{method}: \n{name!r}({arguments})".format( @@ -324,7 +413,11 @@ cdef class LogWrap(class_decorator.BaseDecorator): ) cdef void _make_exc_record(self, str name, str arguments): - """Make log record if exception raised.""" + """Make log record if exception raised. + + :type name: str + :type arguments: str + """ exc_info = sys.exc_info() stack = traceback.extract_stack() tb = traceback.extract_tb(exc_info[2]) @@ -344,7 +437,13 @@ cdef class LogWrap(class_decorator.BaseDecorator): ) def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: - """Here should be constructed and returned real decorator.""" + """Here should be constructed and returned real decorator. + + :param func: Wrapped function + :type func: typing.Callable + :return: wrapped coroutine or function + :rtype: typing.Callable + """ sig = inspect.signature(self._spec or func) @functools.wraps(func) @@ -402,7 +501,45 @@ def logwrap( bint log_traceback=True, bint log_result_obj=True ) -> typing.Union[LogWrap, typing.Callable]: - """Log function calls and return values.""" + """Log function calls and return values. + + :param func: function to wrap + :type func: typing.Optional[typing.Callable] + :param log: logger object for decorator, by default used 'logwrap' + :type log: logging.Logger + :param log_level: log level for successful calls + :type log_level: int + :param exc_level: log level for exception cases + :type exc_level: int + :param max_indent: maximum indent before classic `repr()` call. + :type max_indent: int + :param spec: callable object used as spec for arguments bind. + This is designed for the special cases only, + when impossible to change signature of target object, + but processed/redirected signature is accessible. + Note: this object should provide fully compatible signature + with decorated function, or arguments bind will be failed! + :type spec: typing.Optional[typing.Callable] + :param blacklisted_names: Blacklisted argument names. Arguments with this names will be skipped in log. + :type blacklisted_names: typing.Optional[typing.Iterable[str]] + :param blacklisted_exceptions: list of exceptions, which should be re-raised without producing log record. + :type blacklisted_exceptions: typing.Optional[typing.Iterable[typing.Type[Exception]]] + :param log_call_args: log call arguments before executing wrapped function. + :type log_call_args: bool + :param log_call_args_on_exc: log call arguments if exception raised. + :type log_call_args_on_exc: bool + :param log_traceback: log traceback on excaption in addition to failure info + :type log_traceback: bool + :param log_result_obj: log result of function call. + :type log_result_obj: bool + :return: built real decorator. + :rtype: _log_wrap_shared.BaseLogWrap + + .. versionchanged:: 3.3.0 Extract func from log and do not use Union. + .. versionchanged:: 3.3.0 Deprecation of *args + .. versionchanged:: 4.0.0 Drop of *args + .. versionchanged:: 5.1.0 log_traceback parameter + """ wrapper = LogWrap( log=log, log_level=log_level, diff --git a/logwrap/repr_utils.py b/logwrap/repr_utils.py index 870401a..a078ee0 100644 --- a/logwrap/repr_utils.py +++ b/logwrap/repr_utils.py @@ -26,6 +26,9 @@ import typing +__all__ = ("PrettyFormat", "PrettyRepr", "PrettyStr", "pretty_repr", "pretty_str") + + def _known_callable(item: typing.Any) -> bool: """Check for possibility to parse callable.""" return isinstance(item, (types.FunctionType, types.MethodType)) @@ -628,6 +631,3 @@ def pretty_str( return PrettyStr(max_indent=max_indent, indent_step=indent_step)( src=src, indent=indent, no_indent_start=no_indent_start ) - - -__all__ = ("PrettyFormat", "PrettyRepr", "PrettyStr", "pretty_repr", "pretty_str") diff --git a/logwrap/repr_utils.pyx b/logwrap/repr_utils.pyx index 1f5c918..d3ff439 100644 --- a/logwrap/repr_utils.pyx +++ b/logwrap/repr_utils.pyx @@ -111,7 +111,12 @@ class ReprParameter: # pylint: disable=no-member def _prepare_repr(func: typing.Union[types.FunctionType, types.MethodType]) -> typing.Iterator[ReprParameter]: - """Get arguments lists with defaults.""" + """Get arguments lists with defaults. + + :param func: Callable object to process + :type func: typing.Union[types.FunctionType, types.MethodType] + :return: repr of callable parameter from signature + :rtype: typing.Iterator[ReprParameter]""" ismethod = isinstance(func, types.MethodType) if not ismethod: real_func = func @@ -140,37 +145,109 @@ cdef class PrettyFormat: """ def __cinit__(self, unsigned int max_indent=20, unsigned int indent_step=4): - """Pretty Formatter.""" + """Pretty Formatter. + + :param max_indent: maximal indent before classic repr() call + :type max_indent: int + :param indent_step: step for the next indentation level + :type indent_step: int + """ self.max_indent = max_indent self.indent_step = indent_step cdef int next_indent(self, unsigned int indent, unsigned int multiplier=1): - """Next indentation value.""" + """Next indentation value. + + :param indent: current indentation value + :type indent: int + :param multiplier: step multiplier + :type multiplier: int + :return: next indentation value + :rtype: int + """ return indent + multiplier * self.indent_step cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): - """Repr callable object (function or method).""" + """Repr callable object (function or method). + + :param src: Callable to process + :type src: typing.Union[types.FunctionType, types.MethodType] + :param indent: start indentation + :type indent: int + :return: Repr of function or method with signature. + :rtype: str + """ raise NotImplementedError() cdef str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): - """Repr object without iteration.""" + """Repr object without iteration. + + :param src: Source object + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: ignore indent + :type no_indent_start: bool + :return: simple repr() over object + :rtype: str + """ raise NotImplementedError() def _repr_dict_items(self, dict src, unsigned int indent=0) -> typing.Iterator[str]: # type - """Repr dict items.""" + """Repr dict items. + + :param src: object to process + :type src: typing.Dict + :param indent: start indentation + :type indent: int + :rtype: typing.Iterator[str] + """ raise NotImplementedError() cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): - """Repr iterable item.""" + """Repr iterable item. + + :param nl: newline before item + :type nl: bool + :param obj_type: Object type + :type obj_type: str + :param prefix: prefix + :type prefix: str + :param indent: start indentation + :type indent: int + :param result: result of pre-formatting + :type result: str + :param suffix: suffix + :type suffix: str + :rtype: str + """ raise NotImplementedError() def _repr_iterable_items(self, src: typing.Iterable, unsigned int indent=0) -> typing.Iterator[str]: - """Repr iterable items (not designed for dicts).""" + """Repr iterable items (not designed for dicts). + + :param src: object to process + :type src: typing.Iterable + :param indent: start indentation + :type indent: int + :return: repr of element in iterable item + :rtype: typing.Iterator[str] + """ for elem in src: yield "\n" + self.process_element(src=elem, indent=self.next_indent(indent)) + "," def process_element(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False) -> str: - """Make human readable representation of object.""" + """Make human readable representation of object. + + :param src: object to process + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: do not indent open bracket and simple parameters + :type no_indent_start: bool + :return: formatted string + :rtype: str + """ cdef str prefix cdef str suffix cdef str result @@ -206,7 +283,17 @@ cdef class PrettyFormat: ) def __call__(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False) -> str: - """Make human readable representation of object. The main entry point.""" + """Make human readable representation of object. The main entry point. + + :param src: object to process + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: do not indent open bracket and simple parameters + :type no_indent_start: bool + :return: formatted string + :rtype: str + """ result = self.process_element(src, indent=indent, no_indent_start=no_indent_start) return result @@ -234,7 +321,17 @@ cdef class PrettyRepr(PrettyFormat): return "{spc:<{indent}}{prefix}'''{string}'''".format(spc="", indent=indent, prefix=prefix, string=val) cdef str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): - """Repr object without iteration.""" + """Repr object without iteration. + + :param src: Source object + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: ignore indent + :type no_indent_start: bool + :return: simple repr() over object, except strings (add prefix) and set (uniform py2/py3) + :rtype: str + """ indent = 0 if no_indent_start else indent if isinstance(src, set): return "{spc:<{indent}}{val}".format(spc="", indent=indent, val="set(" + " ,".join(map(repr, src)) + ")") @@ -243,7 +340,15 @@ cdef class PrettyRepr(PrettyFormat): return "{spc:<{indent}}{val!r}".format(spc="", indent=indent, val=src) def _repr_dict_items(self, dict src, unsigned int indent=0) -> typing.Iterator[str]: - """Repr dict items.""" + """Repr dict items. + + :param src: object to process + :type src: dict + :param indent: start indentation + :type indent: int + :return: repr of key/value pair from dict + :rtype: typing.Iterator[str] + """ cdef unsigned int max_len = max((len(repr(key)) for key in src)) if src else 0 for key, val in src.items(): @@ -256,7 +361,15 @@ cdef class PrettyRepr(PrettyFormat): ) cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): - """Repr callable object (function or method).""" + """Repr callable object (function or method). + + :param src: Callable to process + :type src: typing.Union[types.FunctionType, types.MethodType] + :param indent: start indentation + :type indent: int + :return: Repr of function or method with signature. + :rtype: str + """ cdef str param_str = "" cdef str annotation @@ -284,7 +397,23 @@ cdef class PrettyRepr(PrettyFormat): ) cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): - """Repr iterable item.""" + """Repr iterable item. + + :param nl: newline before item + :type nl: bool + :param obj_type: Object type + :type obj_type: str + :param prefix: prefix + :type prefix: str + :param indent: start indentation + :type indent: int + :param result: result of pre-formatting + :type result: str + :param suffix: suffix + :type suffix: str + :return: formatted repr of "result" with prefix and suffix to explain type. + :rtype: str + """ return ( "{nl}" "{spc:<{indent}}{obj_type:}({prefix}{result}\n" @@ -301,7 +430,10 @@ cdef class PrettyRepr(PrettyFormat): cdef class PrettyStr(PrettyFormat): - """Pretty str.""" + """Pretty str. + + Designed for usage as __str__ replacement on complex objects + """ def __cinit__(self, unsigned int max_indent=20, unsigned int indent_step=4): self._magic_method_name = "__pretty_str__" @@ -313,7 +445,17 @@ cdef class PrettyStr(PrettyFormat): return "{spc:<{indent}}{string}".format(spc="", indent=indent, string=val) cdef str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): - """Repr object without iteration.""" + """Repr object without iteration. + + :param src: Source object + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: ignore indent + :type no_indent_start: bool + :return: simple repr() over object, except strings (decode) and set (uniform py2/py3) + :rtype: str + """ indent = 0 if no_indent_start else indent if isinstance(src, set): return "{spc:<{indent}}{val}".format(spc="", indent=indent, val="set(" + " ,".join(map(str, src)) + ")") @@ -322,7 +464,15 @@ cdef class PrettyStr(PrettyFormat): return "{spc:<{indent}}{val!s}".format(spc="", indent=indent, val=src) def _repr_dict_items(self, dict src, unsigned int indent=0) -> typing.Iterator[str]: - """Repr dict items.""" + """Repr dict items. + + :param src: object to process + :type src: dict + :param indent: start indentation + :type indent: int + :return: repr of key/value pair from dict + :rtype: typing.Iterator[str] + """ cdef unsigned int max_len = max((len(str(key)) for key in src)) if src else 0 for key, val in src.items(): yield "\n{spc:<{indent}}{key!s:{size}}: {val},".format( @@ -334,7 +484,15 @@ cdef class PrettyStr(PrettyFormat): ) cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): - """Repr callable object (function or method).""" + """Repr callable object (function or method). + + :param src: Callable to process + :type src: typing.Union[types.FunctionType, types.MethodType] + :param indent: start indentation + :type indent: int + :return: Repr of function or method with signature. + :rtype: str + """ cdef str param_str = "" cdef str annotation @@ -362,7 +520,23 @@ cdef class PrettyStr(PrettyFormat): ) cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): - """Repr iterable item.""" + """Repr iterable item. + + :param nl: newline before item + :type nl: bool + :param obj_type: Object type + :type obj_type: str + :param prefix: prefix + :type prefix: str + :param indent: start indentation + :type indent: int + :param result: result of pre-formatting + :type result: str + :param suffix: suffix + :type suffix: str + :return: formatted repr of "result" with prefix and suffix to explain type. + :rtype: str + """ return ( "{nl}" "{spc:<{indent}}{prefix}{result}\n" @@ -379,7 +553,21 @@ def pretty_repr( unsigned int max_indent=20, unsigned int indent_step=4 ) -> str: - """Make human readable repr of object.""" + """Make human readable repr of object. + + :param src: object to process + :type src: typing.Any + :param indent: start indentation, all next levels is +indent_step + :type indent: int + :param no_indent_start: do not indent open bracket and simple parameters + :type no_indent_start: bool + :param max_indent: maximal indent before classic repr() call + :type max_indent: int + :param indent_step: step for the next indentation level + :type indent_step: int + :return: formatted string + :rtype: str + """ return PrettyRepr(max_indent=max_indent, indent_step=indent_step)( src=src, indent=indent, no_indent_start=no_indent_start ) @@ -392,7 +580,20 @@ def pretty_str( unsigned int max_indent=20, unsigned int indent_step=4 ) -> str: - """Make human readable str of object.""" + """Make human readable str of object. + + :param src: object to process + :type src: typing.Any + :param indent: start indentation, all next levels is +indent_step + :type indent: int + :param no_indent_start: do not indent open bracket and simple parameters + :type no_indent_start: bool + :param max_indent: maximal indent before classic repr() call + :type max_indent: int + :param indent_step: step for the next indentation level + :type indent_step: int + :return: formatted string + """ return PrettyStr(max_indent=max_indent, indent_step=indent_step)( src=src, indent=indent, no_indent_start=no_indent_start ) From 62a1e30a58a6c5a4683d7b76266e3dcfce14f2ac Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Mon, 12 Nov 2018 10:38:21 +0100 Subject: [PATCH 2/2] Optimize cdef/cpdef/def: group and re-arrange Signed-off-by: Alexey Stepanov (cherry picked from commit 1bd2f2f38b863394d5226cbe3718121b10744147) Signed-off-by: Alexey Stepanov --- logwrap/__init__.pxd | 2 +- logwrap/__init__.pyx | 5 +- logwrap/class_decorator.pxd | 5 +- logwrap/log_wrap.pxd | 51 ++-- logwrap/log_wrap.pyx | 251 +++++++++---------- logwrap/repr_utils.pxd | 53 ++-- logwrap/repr_utils.pyx | 485 ++++++++++++++++++------------------ 7 files changed, 432 insertions(+), 420 deletions(-) diff --git a/logwrap/__init__.pxd b/logwrap/__init__.pxd index 802ae15..aa09ac5 100644 --- a/logwrap/__init__.pxd +++ b/logwrap/__init__.pxd @@ -1 +1 @@ -cdef tuple __all__ +cpdef tuple __all__ diff --git a/logwrap/__init__.pyx b/logwrap/__init__.pyx index e57c6c6..765d677 100644 --- a/logwrap/__init__.pyx +++ b/logwrap/__init__.pyx @@ -1,10 +1,9 @@ -from .repr_utils cimport PrettyFormat, PrettyRepr, PrettyStr -from .repr_utils import pretty_repr, pretty_str +from .repr_utils cimport PrettyFormat, PrettyRepr, PrettyStr, pretty_repr, pretty_str from .log_wrap cimport LogWrap from .log_wrap import logwrap, BoundParameter, bind_args_kwargs -cdef tuple __all__ = ( +cpdef tuple __all__ = ( "LogWrap", "logwrap", "PrettyFormat", diff --git a/logwrap/class_decorator.pxd b/logwrap/class_decorator.pxd index 318b0d5..76e3f77 100644 --- a/logwrap/class_decorator.pxd +++ b/logwrap/class_decorator.pxd @@ -16,5 +16,6 @@ cdef class BaseDecorator: - cdef readonly object _func - cdef dict __dict__ + cdef: + readonly object _func + dict __dict__ diff --git a/logwrap/log_wrap.pxd b/logwrap/log_wrap.pxd index a9157b7..4fa5029 100644 --- a/logwrap/log_wrap.pxd +++ b/logwrap/log_wrap.pxd @@ -20,39 +20,34 @@ import typing from logwrap cimport class_decorator -cdef unsigned int indent +cdef: + unsigned int indent -cdef str pretty_repr( - src: typing.Any, - unsigned int indent=?, - bint no_indent_start=?, - unsigned int max_indent=?, - unsigned int indent_step=? -) + class LogWrap(class_decorator.BaseDecorator): + """Base class for LogWrap implementation.""" + cdef: + public unsigned int log_level + public unsigned int exc_level + public unsigned int max_indent -cdef class LogWrap(class_decorator.BaseDecorator): - """Base class for LogWrap implementation.""" + public bint log_call_args + public bint log_call_args_on_exc + public bint log_traceback + public bint log_result_obj - cdef public unsigned int log_level - cdef public unsigned int exc_level - cdef public unsigned int max_indent + readonly object _spec + readonly object _logger - cdef public bint log_call_args - cdef public bint log_call_args_on_exc - cdef public bint log_traceback - cdef public bint log_result_obj + list __blacklisted_names + list __blacklisted_exceptions - cdef readonly object _spec - cdef readonly object _logger + cpdef object pre_process_param(self, object arg) + cpdef str post_process_param(self, object arg, str arg_repr) - cdef list __blacklisted_names - cdef list __blacklisted_exceptions - - cdef str _get_func_args_repr( - self, sig: inspect.Signature, tuple args, dict kwargs - ) - cdef void _make_done_record(self, str func_name, result: typing.Any) - cdef void _make_calling_record(self, str name, str arguments, str method=?) - cdef void _make_exc_record(self, str name, str arguments) + cdef: + str _get_func_args_repr(self, sig: inspect.Signature, tuple args, dict kwargs) + void _make_done_record(self, str func_name, result: typing.Any) + void _make_calling_record(self, str name, str arguments, str method=?) + void _make_exc_record(self, str name, str arguments) diff --git a/logwrap/log_wrap.pyx b/logwrap/log_wrap.pyx index 3041564..2133266 100644 --- a/logwrap/log_wrap.pyx +++ b/logwrap/log_wrap.pyx @@ -26,7 +26,7 @@ from logwrap cimport repr_utils from logwrap cimport class_decorator -cdef tuple __all__ = ("LogWrap", "logwrap", "BoundParameter", "bind_args_kwargs") +cpdef tuple __all__ = ("LogWrap", "logwrap", "BoundParameter", "bind_args_kwargs") logger = logging.getLogger("logwrap") # type: logging.Logger @@ -36,19 +36,6 @@ fmt = "\n{spc:<{indent}}{{key!r}}={{val}},{{annotation}}".format(spc="", indent= comment = "\n{spc:<{indent}}# {{kind!s}}:".format(spc="", indent=indent).format -cdef str pretty_repr( - src: typing.Any, - unsigned int indent=0, - bint no_indent_start=False, - unsigned int max_indent=20, - unsigned int indent_step=4 -): - """Make human readable repr of object.""" - return repr_utils.PrettyRepr(max_indent=max_indent, indent_step=indent_step)( - src=src, indent=indent, no_indent_start=no_indent_start - ) - - class BoundParameter: """Parameter-like object store BOUND with value parameter.""" @@ -286,9 +273,7 @@ cdef class LogWrap(class_decorator.BaseDecorator): "log_result_obj={self.log_result_obj}, )".format(cls=self.__class__.__name__, self=self, spec=self._spec) ) - def pre_process_param( - self, arg: BoundParameter - ) -> typing.Union[BoundParameter, typing.Tuple[BoundParameter, typing.Any], None]: + cpdef object pre_process_param(self, object arg: BoundParameter): """Process parameter for the future logging. :param arg: bound parameter @@ -302,7 +287,7 @@ cdef class LogWrap(class_decorator.BaseDecorator): """ return arg - def post_process_param(self, arg: BoundParameter, str arg_repr: str) -> str: + cpdef str post_process_param(self, object arg: BoundParameter, str arg_repr): """Process parameter for the future logging. :param arg: bound parameter @@ -318,123 +303,125 @@ cdef class LogWrap(class_decorator.BaseDecorator): """ return arg_repr - cdef str _get_func_args_repr(self, sig: inspect.Signature, tuple args, dict kwargs): - """Internal helper for reducing complexity of decorator code. - - :param sig: function signature - :type sig: inspect.Signature - :param args: not keyworded arguments - :type args: typing.Tuple - :param kwargs: keyworded arguments - :type kwargs: typing.Dict[str, typing.Any] - :return: repr over function arguments - :rtype: str - - .. versionchanged:: 3.3.0 Use pre- and post- processing of params during execution - """ - if not (self.log_call_args or self.log_call_args_on_exc): - return "" - - cdef str param_str = "" - cdef str val - cdef str annotation - - last_kind = None - for param in bind_args_kwargs(sig, *args, **kwargs): - if param.name in self.blacklisted_names: - continue - - preprocessed = self.pre_process_param(param) - if preprocessed is None: - continue - - if isinstance(preprocessed, (tuple, list)): - param, value = preprocessed - else: - value = param.value - - if param.empty is value: - if param.VAR_POSITIONAL == param.kind: - value = () - elif param.VAR_KEYWORD == param.kind: - value = {} - - val = pretty_repr(src=value, indent=indent + 4, no_indent_start=True, max_indent=self.max_indent) - - val = self.post_process_param(param, val) - - if last_kind != param.kind: - param_str += comment(kind=param.kind) - last_kind = param.kind - - if param.empty is param.annotation: - annotation = "" - else: - annotation = " # type: {param.annotation!s}".format(param=param) - - param_str += fmt(key=param.name, annotation=annotation, val=val) - if param_str: - param_str += "\n" - return param_str - - cdef void _make_done_record(self, str func_name, result: typing.Any): - """Construct success record. - - :type func_name: str - :type result: typing.Any - """ - cdef str msg = "Done: {name!r}".format(name=func_name) - - if self.log_result_obj: - msg += " with result:\n{result}".format( - result=pretty_repr( - result, - indent=0, - no_indent_start=False, - max_indent=self.max_indent, - indent_step=4 - + cdef: + str _get_func_args_repr(self, sig: inspect.Signature, tuple args, dict kwargs): + """Internal helper for reducing complexity of decorator code. + + :param sig: function signature + :type sig: inspect.Signature + :param args: not keyworded arguments + :type args: typing.Tuple + :param kwargs: keyworded arguments + :type kwargs: typing.Dict[str, typing.Any] + :return: repr over function arguments + :rtype: str + + .. versionchanged:: 3.3.0 Use pre- and post- processing of params during execution + """ + if not (self.log_call_args or self.log_call_args_on_exc): + return "" + + cdef: + str param_str = "" + str val + str annotation + + last_kind = None + for param in bind_args_kwargs(sig, *args, **kwargs): + if param.name in self.blacklisted_names: + continue + + preprocessed = self.pre_process_param(param) + if preprocessed is None: + continue + + if isinstance(preprocessed, (tuple, list)): + param, value = preprocessed + else: + value = param.value + + if param.empty is value: + if param.VAR_POSITIONAL == param.kind: + value = () + elif param.VAR_KEYWORD == param.kind: + value = {} + + val = repr_utils.pretty_repr(src=value, indent=indent + 4, no_indent_start=True, max_indent=self.max_indent) + + val = self.post_process_param(param, val) + + if last_kind != param.kind: + param_str += comment(kind=param.kind) + last_kind = param.kind + + if param.empty is param.annotation: + annotation = "" + else: + annotation = " # type: {param.annotation!s}".format(param=param) + + param_str += fmt(key=param.name, annotation=annotation, val=val) + if param_str: + param_str += "\n" + return param_str + + void _make_done_record(self, str func_name, result: typing.Any): + """Construct success record. + + :type func_name: str + :type result: typing.Any + """ + cdef str msg = "Done: {name!r}".format(name=func_name) + + if self.log_result_obj: + msg += " with result:\n{result}".format( + result=repr_utils.pretty_repr( + result, + indent=0, + no_indent_start=False, + max_indent=self.max_indent, + indent_step=4 + + ) ) + self._logger.log(level=self.log_level, msg=msg) # type: ignore + + void _make_calling_record(self, str name, str arguments, str method="Calling"): + """Make log record before execution. + + :type name: str + :type arguments: str + :type method: str + """ + self._logger.log( # type: ignore + level=self.log_level, + msg="{method}: \n{name!r}({arguments})".format( + method=method, name=name, arguments=arguments if self.log_call_args else "" + ), ) - self._logger.log(level=self.log_level, msg=msg) # type: ignore - - cdef void _make_calling_record(self, str name, str arguments, str method="Calling"): - """Make log record before execution. - :type name: str - :type arguments: str - :type method: str - """ - self._logger.log( # type: ignore - level=self.log_level, - msg="{method}: \n{name!r}({arguments})".format( - method=method, name=name, arguments=arguments if self.log_call_args else "" - ), - ) - - cdef void _make_exc_record(self, str name, str arguments): - """Make log record if exception raised. - - :type name: str - :type arguments: str - """ - exc_info = sys.exc_info() - stack = traceback.extract_stack() - tb = traceback.extract_tb(exc_info[2]) - full_tb = stack[:2] + tb # cut decorator and build full traceback - exc_line = traceback.format_exception_only(*exc_info[:2]) - # Make standard traceback string - cdef str tb_text = "Traceback (most recent call last):\n" + "".join(traceback.format_list(full_tb)) + "".join(exc_line) - - self._logger.log( # type: ignore - level=self.exc_level, - msg="Failed: \n{name!r}({arguments})\n{tb_text}".format( - name=name, - arguments=arguments if self.log_call_args_on_exc else "", - tb_text=tb_text if self.log_traceback else "", - ), - exc_info=False, - ) + void _make_exc_record(self, str name, str arguments): + """Make log record if exception raised. + + :type name: str + :type arguments: str + """ + exc_info = sys.exc_info() + stack = traceback.extract_stack() + tb = traceback.extract_tb(exc_info[2]) + full_tb = stack[:2] + tb # cut decorator and build full traceback + exc_line = traceback.format_exception_only(*exc_info[:2]) + # Make standard traceback string + cdef str tb_text = "Traceback (most recent call last):\n" + "".join(traceback.format_list(full_tb)) + "".join(exc_line) + + self._logger.log( # type: ignore + level=self.exc_level, + msg="Failed: \n{name!r}({arguments})\n{tb_text}".format( + name=name, + arguments=arguments if self.log_call_args_on_exc else "", + tb_text=tb_text if self.log_traceback else "", + ), + exc_info=False, + ) def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: """Here should be constructed and returned real decorator. diff --git a/logwrap/repr_utils.pxd b/logwrap/repr_utils.pxd index fefd06d..2a9d649 100644 --- a/logwrap/repr_utils.pxd +++ b/logwrap/repr_utils.pxd @@ -22,18 +22,41 @@ import types import typing -cdef class PrettyFormat: - cdef readonly unsigned int max_indent - cdef readonly unsigned int indent_step - cdef readonly str _magic_method_name - - cdef int next_indent(self, unsigned int indent, unsigned int multiplier=?) - cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=?) - cdef str _repr_simple(self, src: typing.Any, unsigned int indent=?, bint no_indent_start=?) - cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix) - -cdef class PrettyRepr(PrettyFormat): - cdef str _strings_repr(self, unsigned int indent, val: typing.Union[bytes, str]) - -cdef class PrettyStr(PrettyFormat): - cdef str _strings_str(self, unsigned int indent, val: typing.Union[bytes, str]) +cdef: + class PrettyFormat: + cdef: + readonly unsigned int max_indent + readonly unsigned int indent_step + readonly str _magic_method_name + + cpdef int next_indent(self, unsigned int indent, unsigned int multiplier=?) + + cdef: + str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=?) + str _repr_simple(self, src: typing.Any, unsigned int indent=?, bint no_indent_start=?) + str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix) + + cpdef str process_element(self, src: typing.Any, unsigned int indent=?, bint no_indent_start=?) + + class PrettyRepr(PrettyFormat): + cdef str _strings_repr(self, unsigned int indent, val: typing.Union[bytes, str]) + + class PrettyStr(PrettyFormat): + cdef str _strings_str(self, unsigned int indent, val: typing.Union[bytes, str]) + + +cpdef str pretty_repr( + src: typing.Any, + unsigned int indent=?, + bint no_indent_start=?, + unsigned int max_indent=?, + unsigned int indent_step=? +) + +cpdef str pretty_str( + src: typing.Any, + unsigned int indent=?, + bint no_indent_start=?, + unsigned int max_indent=?, + unsigned int indent_step=? +) diff --git a/logwrap/repr_utils.pyx b/logwrap/repr_utils.pyx index d3ff439..46d9a10 100644 --- a/logwrap/repr_utils.pyx +++ b/logwrap/repr_utils.pyx @@ -23,17 +23,18 @@ import types import typing -cdef tuple __all__ = ("PrettyFormat", "PrettyRepr", "PrettyStr", "pretty_repr", "pretty_str") +cpdef tuple __all__ = ("PrettyFormat", "PrettyRepr", "PrettyStr", "pretty_repr", "pretty_str") -cdef bint _known_callable(item: typing.Any): - """Check for possibility to parse callable.""" - return isinstance(item, (types.FunctionType, types.MethodType)) +cdef: + bint _known_callable(item: typing.Any): + """Check for possibility to parse callable.""" + return isinstance(item, (types.FunctionType, types.MethodType)) -cdef bint _simple(item: typing.Any): - """Check for nested iterations: True, if not.""" - return not isinstance(item, (list, set, tuple, dict, frozenset)) + bint _simple(item: typing.Any): + """Check for nested iterations: True, if not.""" + return not isinstance(item, (list, set, tuple, dict, frozenset)) class ReprParameter: @@ -117,7 +118,7 @@ def _prepare_repr(func: typing.Union[types.FunctionType, types.MethodType]) -> t :type func: typing.Union[types.FunctionType, types.MethodType] :return: repr of callable parameter from signature :rtype: typing.Iterator[ReprParameter]""" - ismethod = isinstance(func, types.MethodType) + cdef bint ismethod = isinstance(func, types.MethodType) if not ismethod: real_func = func else: @@ -155,7 +156,7 @@ cdef class PrettyFormat: self.max_indent = max_indent self.indent_step = indent_step - cdef int next_indent(self, unsigned int indent, unsigned int multiplier=1): + cpdef int next_indent(self, unsigned int indent, unsigned int multiplier=1): """Next indentation value. :param indent: current indentation value @@ -167,31 +168,51 @@ cdef class PrettyFormat: """ return indent + multiplier * self.indent_step - cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): - """Repr callable object (function or method). - - :param src: Callable to process - :type src: typing.Union[types.FunctionType, types.MethodType] - :param indent: start indentation - :type indent: int - :return: Repr of function or method with signature. - :rtype: str - """ - raise NotImplementedError() - - cdef str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): - """Repr object without iteration. - - :param src: Source object - :type src: typing.Any - :param indent: start indentation - :type indent: int - :param no_indent_start: ignore indent - :type no_indent_start: bool - :return: simple repr() over object - :rtype: str - """ - raise NotImplementedError() + cdef: + str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): + """Repr callable object (function or method). + + :param src: Callable to process + :type src: typing.Union[types.FunctionType, types.MethodType] + :param indent: start indentation + :type indent: int + :return: Repr of function or method with signature. + :rtype: str + """ + raise NotImplementedError() + + str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): + """Repr object without iteration. + + :param src: Source object + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: ignore indent + :type no_indent_start: bool + :return: simple repr() over object + :rtype: str + """ + raise NotImplementedError() + + str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): + """Repr iterable item. + + :param nl: newline before item + :type nl: bool + :param obj_type: Object type + :type obj_type: str + :param prefix: prefix + :type prefix: str + :param indent: start indentation + :type indent: int + :param result: result of pre-formatting + :type result: str + :param suffix: suffix + :type suffix: str + :rtype: str + """ + raise NotImplementedError() def _repr_dict_items(self, dict src, unsigned int indent=0) -> typing.Iterator[str]: # type """Repr dict items. @@ -204,25 +225,6 @@ cdef class PrettyFormat: """ raise NotImplementedError() - cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): - """Repr iterable item. - - :param nl: newline before item - :type nl: bool - :param obj_type: Object type - :type obj_type: str - :param prefix: prefix - :type prefix: str - :param indent: start indentation - :type indent: int - :param result: result of pre-formatting - :type result: str - :param suffix: suffix - :type suffix: str - :rtype: str - """ - raise NotImplementedError() - def _repr_iterable_items(self, src: typing.Iterable, unsigned int indent=0) -> typing.Iterator[str]: """Repr iterable items (not designed for dicts). @@ -236,7 +238,7 @@ cdef class PrettyFormat: for elem in src: yield "\n" + self.process_element(src=elem, indent=self.next_indent(indent)) + "," - def process_element(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False) -> str: + cpdef str process_element(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): """Make human readable representation of object. :param src: object to process @@ -248,9 +250,10 @@ cdef class PrettyFormat: :return: formatted string :rtype: str """ - cdef str prefix - cdef str suffix - cdef str result + cdef: + str prefix + str suffix + str result if hasattr(src, self._magic_method_name): result = getattr(src, self._magic_method_name)(self, indent=indent, no_indent_start=no_indent_start) @@ -309,35 +312,105 @@ cdef class PrettyRepr(PrettyFormat): def __cinit__(self, unsigned int max_indent=20, unsigned int indent_step=4): self._magic_method_name = "__pretty_repr__" - cdef str _strings_repr(self, unsigned int indent, val: typing.Union[bytes, str]): - """Custom repr for strings and binary strings.""" - cdef str prefix + cdef: + str _strings_repr(self, unsigned int indent, val: typing.Union[bytes, str]): + """Custom repr for strings and binary strings.""" + cdef str prefix - if isinstance(val, bytes): - val = val.decode(encoding="utf-8", errors="backslashreplace") - prefix = "b" - else: - prefix = "u" - return "{spc:<{indent}}{prefix}'''{string}'''".format(spc="", indent=indent, prefix=prefix, string=val) + if isinstance(val, bytes): + val = val.decode(encoding="utf-8", errors="backslashreplace") + prefix = "b" + else: + prefix = "u" + return "{spc:<{indent}}{prefix}'''{string}'''".format(spc="", indent=indent, prefix=prefix, string=val) + + str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): + """Repr object without iteration. + + :param src: Source object + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: ignore indent + :type no_indent_start: bool + :return: simple repr() over object, except strings (add prefix) and set (uniform py2/py3) + :rtype: str + """ + indent = 0 if no_indent_start else indent + if isinstance(src, set): + return "{spc:<{indent}}{val}".format(spc="", indent=indent, val="set(" + " ,".join(map(repr, src)) + ")") + if isinstance(src, (bytes, str)): + return self._strings_repr(indent=indent, val=src) + return "{spc:<{indent}}{val!r}".format(spc="", indent=indent, val=src) + + str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): + """Repr callable object (function or method). + + :param src: Callable to process + :type src: typing.Union[types.FunctionType, types.MethodType] + :param indent: start indentation + :type indent: int + :return: Repr of function or method with signature. + :rtype: str + """ + cdef: + str param_str = "" + str annotation + + for param in _prepare_repr(src): + param_str += "\n{spc:<{indent}}{param.name}".format(spc="", indent=self.next_indent(indent), param=param) + if param.annotation is not param.empty: + param_str += ": {param.annotation}".format(param=param) + if param.value is not param.empty: + param_str += "={val}".format( + val=self.process_element(src=param.value, indent=indent, no_indent_start=True) + ) + param_str += "," + + if param_str: + param_str += "\n" + " " * indent + + sig = inspect.signature(src) + if sig.return_annotation is inspect.Parameter.empty: + annotation = "" + else: + annotation = " -> {sig.return_annotation!r}".format(sig=sig) - cdef str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): - """Repr object without iteration. + return "\n{spc:<{indent}}<{obj!r} with interface ({args}){annotation}>".format( + spc="", indent=indent, obj=src, args=param_str, annotation=annotation + ) - :param src: Source object - :type src: typing.Any - :param indent: start indentation - :type indent: int - :param no_indent_start: ignore indent - :type no_indent_start: bool - :return: simple repr() over object, except strings (add prefix) and set (uniform py2/py3) - :rtype: str - """ - indent = 0 if no_indent_start else indent - if isinstance(src, set): - return "{spc:<{indent}}{val}".format(spc="", indent=indent, val="set(" + " ,".join(map(repr, src)) + ")") - if isinstance(src, (bytes, str)): - return self._strings_repr(indent=indent, val=src) - return "{spc:<{indent}}{val!r}".format(spc="", indent=indent, val=src) + str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): + """Repr iterable item. + + :param nl: newline before item + :type nl: bool + :param obj_type: Object type + :type obj_type: str + :param prefix: prefix + :type prefix: str + :param indent: start indentation + :type indent: int + :param result: result of pre-formatting + :type result: str + :param suffix: suffix + :type suffix: str + :return: formatted repr of "result" with prefix and suffix to explain type. + :rtype: str + """ + return ( + "{nl}" + "{spc:<{indent}}{obj_type:}({prefix}{result}\n" + "{spc:<{indent}}{suffix})".format( + nl="\n" if nl else "", + spc="", + indent=indent, + obj_type=obj_type, + prefix=prefix, + result=result, + suffix=suffix, + ) + ) def _repr_dict_items(self, dict src, unsigned int indent=0) -> typing.Iterator[str]: """Repr dict items. @@ -360,74 +433,6 @@ cdef class PrettyRepr(PrettyFormat): val=self.process_element(val, indent=self.next_indent(indent, multiplier=2), no_indent_start=True), ) - cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): - """Repr callable object (function or method). - - :param src: Callable to process - :type src: typing.Union[types.FunctionType, types.MethodType] - :param indent: start indentation - :type indent: int - :return: Repr of function or method with signature. - :rtype: str - """ - cdef str param_str = "" - cdef str annotation - - for param in _prepare_repr(src): - param_str += "\n{spc:<{indent}}{param.name}".format(spc="", indent=self.next_indent(indent), param=param) - if param.annotation is not param.empty: - param_str += ": {param.annotation}".format(param=param) - if param.value is not param.empty: - param_str += "={val}".format( - val=self.process_element(src=param.value, indent=indent, no_indent_start=True) - ) - param_str += "," - - if param_str: - param_str += "\n" + " " * indent - - sig = inspect.signature(src) - if sig.return_annotation is inspect.Parameter.empty: - annotation = "" - else: - annotation = " -> {sig.return_annotation!r}".format(sig=sig) - - return "\n{spc:<{indent}}<{obj!r} with interface ({args}){annotation}>".format( - spc="", indent=indent, obj=src, args=param_str, annotation=annotation - ) - - cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): - """Repr iterable item. - - :param nl: newline before item - :type nl: bool - :param obj_type: Object type - :type obj_type: str - :param prefix: prefix - :type prefix: str - :param indent: start indentation - :type indent: int - :param result: result of pre-formatting - :type result: str - :param suffix: suffix - :type suffix: str - :return: formatted repr of "result" with prefix and suffix to explain type. - :rtype: str - """ - return ( - "{nl}" - "{spc:<{indent}}{obj_type:}({prefix}{result}\n" - "{spc:<{indent}}{suffix})".format( - nl="\n" if nl else "", - spc="", - indent=indent, - obj_type=obj_type, - prefix=prefix, - result=result, - suffix=suffix, - ) - ) - cdef class PrettyStr(PrettyFormat): """Pretty str. @@ -438,30 +443,94 @@ cdef class PrettyStr(PrettyFormat): def __cinit__(self, unsigned int max_indent=20, unsigned int indent_step=4): self._magic_method_name = "__pretty_str__" - cdef str _strings_str(self, unsigned int indent, val: typing.Union[bytes, str]): - """Custom repr for strings and binary strings.""" - if isinstance(val, bytes): - val = val.decode(encoding="utf-8", errors="backslashreplace") - return "{spc:<{indent}}{string}".format(spc="", indent=indent, string=val) + cdef: + str _strings_str(self, unsigned int indent, val: typing.Union[bytes, str]): + """Custom repr for strings and binary strings.""" + if isinstance(val, bytes): + val = val.decode(encoding="utf-8", errors="backslashreplace") + return "{spc:<{indent}}{string}".format(spc="", indent=indent, string=val) + + str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): + """Repr object without iteration. + + :param src: Source object + :type src: typing.Any + :param indent: start indentation + :type indent: int + :param no_indent_start: ignore indent + :type no_indent_start: bool + :return: simple repr() over object, except strings (decode) and set (uniform py2/py3) + :rtype: str + """ + indent = 0 if no_indent_start else indent + if isinstance(src, set): + return "{spc:<{indent}}{val}".format(spc="", indent=indent, val="set(" + " ,".join(map(str, src)) + ")") + if isinstance(src, (bytes, str)): + return self._strings_str(indent=indent, val=src) + return "{spc:<{indent}}{val!s}".format(spc="", indent=indent, val=src) + + str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): + """Repr callable object (function or method). + + :param src: Callable to process + :type src: typing.Union[types.FunctionType, types.MethodType] + :param indent: start indentation + :type indent: int + :return: Repr of function or method with signature. + :rtype: str + """ + cdef: + str param_str = "" + str annotation + + for param in _prepare_repr(src): + param_str += "\n{spc:<{indent}}{param.name}".format(spc="", indent=self.next_indent(indent), param=param) + if param.annotation is not param.empty: + param_str += ": {param.annotation}".format(param=param) + if param.value is not param.empty: + param_str += "={val}".format( + val=self.process_element(src=param.value, indent=indent, no_indent_start=True) + ) + param_str += "," + + if param_str: + param_str += "\n" + " " * indent + + sig = inspect.signature(src) + if sig.return_annotation is inspect.Parameter.empty: + annotation = "" + else: + annotation = " -> {sig.return_annotation!r}".format(sig=sig) - cdef str _repr_simple(self, src: typing.Any, unsigned int indent=0, bint no_indent_start=False): - """Repr object without iteration. + return "\n{spc:<{indent}}<{obj!s} with interface ({args}){annotation}>".format( + spc="", indent=indent, obj=src, args=param_str, annotation=annotation + ) - :param src: Source object - :type src: typing.Any - :param indent: start indentation - :type indent: int - :param no_indent_start: ignore indent - :type no_indent_start: bool - :return: simple repr() over object, except strings (decode) and set (uniform py2/py3) - :rtype: str - """ - indent = 0 if no_indent_start else indent - if isinstance(src, set): - return "{spc:<{indent}}{val}".format(spc="", indent=indent, val="set(" + " ,".join(map(str, src)) + ")") - if isinstance(src, (bytes, str)): - return self._strings_str(indent=indent, val=src) - return "{spc:<{indent}}{val!s}".format(spc="", indent=indent, val=src) + str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): + """Repr iterable item. + + :param nl: newline before item + :type nl: bool + :param obj_type: Object type + :type obj_type: str + :param prefix: prefix + :type prefix: str + :param indent: start indentation + :type indent: int + :param result: result of pre-formatting + :type result: str + :param suffix: suffix + :type suffix: str + :return: formatted repr of "result" with prefix and suffix to explain type. + :rtype: str + """ + return ( + "{nl}" + "{spc:<{indent}}{prefix}{result}\n" + "{spc:<{indent}}{suffix}".format( + nl="\n" if nl else "", spc="", indent=indent, prefix=prefix, result=result, suffix=suffix + ) + ) def _repr_dict_items(self, dict src, unsigned int indent=0) -> typing.Iterator[str]: """Repr dict items. @@ -483,76 +552,14 @@ cdef class PrettyStr(PrettyFormat): val=self.process_element(val, indent=self.next_indent(indent, multiplier=2), no_indent_start=True), ) - cdef str _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], unsigned int indent=0): - """Repr callable object (function or method). - - :param src: Callable to process - :type src: typing.Union[types.FunctionType, types.MethodType] - :param indent: start indentation - :type indent: int - :return: Repr of function or method with signature. - :rtype: str - """ - cdef str param_str = "" - cdef str annotation - - for param in _prepare_repr(src): - param_str += "\n{spc:<{indent}}{param.name}".format(spc="", indent=self.next_indent(indent), param=param) - if param.annotation is not param.empty: - param_str += ": {param.annotation}".format(param=param) - if param.value is not param.empty: - param_str += "={val}".format( - val=self.process_element(src=param.value, indent=indent, no_indent_start=True) - ) - param_str += "," - - if param_str: - param_str += "\n" + " " * indent - - sig = inspect.signature(src) - if sig.return_annotation is inspect.Parameter.empty: - annotation = "" - else: - annotation = " -> {sig.return_annotation!r}".format(sig=sig) - - return "\n{spc:<{indent}}<{obj!s} with interface ({args}){annotation}>".format( - spc="", indent=indent, obj=src, args=param_str, annotation=annotation - ) - - cdef str _repr_iterable_item(self, bint nl, str obj_type, str prefix, unsigned int indent, str result, str suffix): - """Repr iterable item. - - :param nl: newline before item - :type nl: bool - :param obj_type: Object type - :type obj_type: str - :param prefix: prefix - :type prefix: str - :param indent: start indentation - :type indent: int - :param result: result of pre-formatting - :type result: str - :param suffix: suffix - :type suffix: str - :return: formatted repr of "result" with prefix and suffix to explain type. - :rtype: str - """ - return ( - "{nl}" - "{spc:<{indent}}{prefix}{result}\n" - "{spc:<{indent}}{suffix}".format( - nl="\n" if nl else "", spc="", indent=indent, prefix=prefix, result=result, suffix=suffix - ) - ) - -def pretty_repr( +cpdef str pretty_repr( src: typing.Any, unsigned int indent=0, bint no_indent_start=False, unsigned int max_indent=20, unsigned int indent_step=4 -) -> str: +): """Make human readable repr of object. :param src: object to process @@ -573,13 +580,13 @@ def pretty_repr( ) -def pretty_str( +cpdef str pretty_str( src: typing.Any, unsigned int indent=0, bint no_indent_start=False, unsigned int max_indent=20, unsigned int indent_step=4 -) -> str: +): """Make human readable str of object. :param src: object to process