diff --git a/README.rst b/README.rst index 82affd3..b6f88f1 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,8 @@ logwrap :target: https://pypi.python.org/pypi/logwrap .. image:: https://img.shields.io/github/license/python-useful-helpers/logwrap.svg :target: https://raw.githubusercontent.com/python-useful-helpers/logwrap/master/LICENSE - +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black logwrap is a helper for logging in human-readable format function arguments and call result on function call. Why? Because logging of `*args, **kwargs` become useless with project grow and you need more details in call log. diff --git a/logwrap/__init__.py b/logwrap/__init__.py index 393875f..7f1e391 100644 --- a/logwrap/__init__.py +++ b/logwrap/__init__.py @@ -22,35 +22,29 @@ later it has been reworked and extended for support of special cases. """ -from ._repr_utils import ( - PrettyFormat, - PrettyRepr, - PrettyStr, - pretty_repr, - pretty_str -) +from ._repr_utils import PrettyFormat, PrettyRepr, PrettyStr, pretty_repr, pretty_str from ._log_wrap import logwrap, LogWrap, BoundParameter, bind_args_kwargs __all__ = ( - 'LogWrap', - 'logwrap', - 'PrettyFormat', - 'PrettyRepr', - 'PrettyStr', - 'pretty_repr', - 'pretty_str', - 'BoundParameter', - 'bind_args_kwargs' + "LogWrap", + "logwrap", + "PrettyFormat", + "PrettyRepr", + "PrettyStr", + "pretty_repr", + "pretty_str", + "BoundParameter", + "bind_args_kwargs", ) -__version__ = '4.9.0' +__version__ = "4.9.0" __author__ = "Alexey Stepanov" -__author_email__ = 'penguinolog@gmail.com' +__author_email__ = "penguinolog@gmail.com" __maintainers__ = { - 'Alexey Stepanov': 'penguinolog@gmail.com', - 'Antonio Esposito': 'esposito.cloud@gmail.com', - 'Dennis Dmitriev': 'dis-xcom@gmail.com', + "Alexey Stepanov": "penguinolog@gmail.com", + "Antonio Esposito": "esposito.cloud@gmail.com", + "Dennis Dmitriev": "dis-xcom@gmail.com", } -__url__ = 'https://github.com/python-useful-helpers/logwrap' +__url__ = "https://github.com/python-useful-helpers/logwrap" __description__ = "Decorator for logging function arguments and return value by human-readable way" __license__ = "Apache License, Version 2.0" diff --git a/logwrap/_class_decorator.py b/logwrap/_class_decorator.py index 1cd39f5..1659b24 100644 --- a/logwrap/_class_decorator.py +++ b/logwrap/_class_decorator.py @@ -62,10 +62,7 @@ class BaseDecorator(metaclass=abc.ABCMeta): False """ - def __init__( - self, - func: typing.Optional[typing.Callable] = None - ) -> None: + def __init__(self, func: typing.Optional[typing.Callable] = None) -> None: """Decorator. :param func: function to wrap @@ -80,9 +77,7 @@ def __init__( # pylint: enable=assigning-non-slot @property - def _func( - self - ) -> typing.Optional[typing.Callable]: + def _func(self) -> typing.Optional[typing.Callable]: """Get wrapped function. :rtype: typing.Optional[typing.Callable] @@ -90,10 +85,7 @@ def _func( return self.__func # pragma: no cover @abc.abstractmethod - def _get_function_wrapper( - self, - func: typing.Callable - ) -> typing.Callable: + def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: """Here should be constructed and returned real decorator. :param func: Wrapped function @@ -102,11 +94,7 @@ def _get_function_wrapper( """ raise NotImplementedError() # pragma: no cover - def __call__( - self, - *args: typing.Union[typing.Callable, typing.Any], - **kwargs: typing.Any - ) -> typing.Any: + def __call__(self, *args: typing.Union[typing.Callable, typing.Any], **kwargs: typing.Any) -> typing.Any: """Main decorator getter.""" l_args = list(args) @@ -123,14 +111,13 @@ def __call__( def __repr__(self) -> str: """For debug purposes.""" return "<{cls}({func!r}) at 0x{id:X}>".format( - cls=self.__class__.__name__, - func=self.__func, - id=id(self) + cls=self.__class__.__name__, func=self.__func, id=id(self) ) # pragma: no cover # 8<---------------------------------------------------------------------------- -if __name__ == '__main__': +if __name__ == "__main__": import doctest # pragma: no cover + doctest.testmod(verbose=True) # pragma: no cover diff --git a/logwrap/_log_wrap.py b/logwrap/_log_wrap.py index e831262..e9f6acb 100644 --- a/logwrap/_log_wrap.py +++ b/logwrap/_log_wrap.py @@ -26,19 +26,14 @@ from . import _class_decorator -__all__ = ( - 'LogWrap', - 'logwrap', - 'BoundParameter', - 'bind_args_kwargs', -) +__all__ = ("LogWrap", "logwrap", "BoundParameter", "bind_args_kwargs") -logger = logging.getLogger('logwrap') # type: logging.Logger +logger = logging.getLogger("logwrap") # type: logging.Logger indent = 4 -fmt = "\n{spc:<{indent}}{{key!r}}={{val}},{{annotation}}".format(spc='', indent=indent, ).format -comment = "\n{spc:<{indent}}# {{kind!s}}:".format(spc='', indent=indent).format +fmt = "\n{spc:<{indent}}{{key!r}}={{val}},{{annotation}}".format(spc="", indent=indent).format +comment = "\n{spc:<{indent}}# {{kind!s}}:".format(spc="", indent=indent).format class BoundParameter: @@ -47,10 +42,7 @@ class BoundParameter: .. versionadded:: 3.3.0 """ - __slots__ = ( - '_parameter', - '_value' - ) + __slots__ = ("_parameter", "_value") POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD @@ -60,11 +52,7 @@ class BoundParameter: empty = inspect.Parameter.empty - def __init__( - self, - parameter: inspect.Parameter, - value: typing.Any = inspect.Parameter.empty - ) -> None: + def __init__(self, parameter: inspect.Parameter, value: typing.Any = inspect.Parameter.empty) -> None: """Parameter-like object store BOUND with value parameter. :param parameter: parameter from signature @@ -77,7 +65,7 @@ def __init__( if value is self.empty: if parameter.default is self.empty and parameter.kind not in (self.VAR_POSITIONAL, self.VAR_KEYWORD): - raise ValueError('Value is not set and no default value') + raise ValueError("Value is not set and no default value") self._value = parameter.default else: self._value = value @@ -112,6 +100,7 @@ def value(self) -> typing.Any: """Parameter value.""" return self._value + # noinspection PyTypeChecker def __hash__(self) -> int: # pragma: no cover """Block hashing. @@ -124,13 +113,13 @@ def __str__(self) -> str: """Debug purposes.""" # POSITIONAL_ONLY is only in precompiled functions if self.kind == self.POSITIONAL_ONLY: # pragma: no cover - as_str = '' if self.name is None else '<{as_str}>'.format(as_str=self.name) + as_str = "" if self.name is None else "<{as_str}>".format(as_str=self.name) else: - as_str = self.name or '' + as_str = self.name or "" # Add annotation if applicable (python 3 only) if self.annotation is not self.empty: # pragma: no cover - as_str += ': {annotation!s}'.format(annotation=inspect.formatannotation(self.annotation)) + as_str += ": {annotation!s}".format(annotation=inspect.formatannotation(self.annotation)) value = self.value if self.empty is value: @@ -139,15 +128,15 @@ def __str__(self) -> str: elif self.VAR_KEYWORD == self.kind: value = {} - as_str += '={value!r}'.format(value=value) + as_str += "={value!r}".format(value=value) if self.default is not self.empty: - as_str += ' # {self.default!r}'.format(self=self) + as_str += " # {self.default!r}".format(self=self) if self.kind == self.VAR_POSITIONAL: - as_str = '*' + as_str + as_str = "*" + as_str elif self.kind == self.VAR_KEYWORD: - as_str = '**' + as_str + as_str = "**" + as_str return as_str @@ -157,9 +146,7 @@ def __repr__(self) -> str: def bind_args_kwargs( - sig: inspect.Signature, - *args: typing.Any, - **kwargs: typing.Any + sig: inspect.Signature, *args: typing.Any, **kwargs: typing.Any ) -> typing.Iterator[BoundParameter]: """Bind *args and **kwargs to signature and get Bound Parameters. @@ -177,10 +164,7 @@ def bind_args_kwargs( bound = sig.bind(*args, **kwargs).arguments parameters = list(sig.parameters.values()) for param in parameters: - yield BoundParameter( - parameter=param, - value=bound.get(param.name, param.default) - ) + yield BoundParameter(parameter=param, value=bound.get(param.name, param.default)) # pylint: disable=assigning-non-slot,abstract-method @@ -189,16 +173,16 @@ class LogWrap(_class_decorator.BaseDecorator): """Base class for LogWrap implementation.""" __slots__ = ( - '__blacklisted_names', - '__blacklisted_exceptions', - '__logger', - '__log_level', - '__exc_level', - '__max_indent', - '__spec', - '__log_call_args', - '__log_call_args_on_exc', - '__log_result_obj', + "__blacklisted_names", + "__blacklisted_exceptions", + "__logger", + "__log_level", + "__exc_level", + "__max_indent", + "__spec", + "__log_call_args", + "__log_call_args_on_exc", + "__log_result_obj", ) def __init__( @@ -290,12 +274,7 @@ def log_level(self, val: int) -> None: :raises TypeError: log level is not integer """ if not isinstance(val, int): - raise TypeError( - 'Unexpected type: {}. Should be {}.'.format( - val.__class__.__name__, - int.__name__, - ) - ) + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, int.__name__)) self.__log_level = val @property @@ -315,12 +294,7 @@ def exc_level(self, val: int) -> None: :raises TypeError: log level is not integer """ if not isinstance(val, int): - raise TypeError( - 'Unexpected type: {}. Should be {}.'.format( - val.__class__.__name__, - int.__name__, - ) - ) + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, int.__name__)) self.__exc_level = val @property @@ -340,12 +314,7 @@ def max_indent(self, val: int) -> None: :raises TypeError: indent is not integer """ if not isinstance(val, int): - raise TypeError( - 'Unexpected type: {}. Should be {}.'.format( - val.__class__.__name__, - int.__name__, - ) - ) + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, int.__name__)) self.__max_indent = val @property @@ -381,12 +350,7 @@ def log_call_args(self, val: bool) -> None: :raises TypeError: Value is not bool """ if not isinstance(val, bool): - raise TypeError( - 'Unexpected type: {}. Should be {}.'.format( - val.__class__.__name__, - bool.__name__, - ) - ) + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, bool.__name__)) self.__log_call_args = val @property @@ -406,12 +370,7 @@ def log_call_args_on_exc(self, val: bool) -> None: :raises TypeError: Value is not bool """ if not isinstance(val, bool): - raise TypeError( - 'Unexpected type: {}. Should be {}.'.format( - val.__class__.__name__, - bool.__name__, - ) - ) + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, bool.__name__)) self.__log_call_args_on_exc = val @property @@ -431,12 +390,7 @@ def log_result_obj(self, val: bool) -> None: :raises TypeError: Value is not bool """ if not isinstance(val, bool): - raise TypeError( - 'Unexpected type: {}. Should be {}.'.format( - val.__class__.__name__, - bool.__name__, - ) - ) + raise TypeError("Unexpected type: {}. Should be {}.".format(val.__class__.__name__, bool.__name__)) self.__log_result_obj = val @property @@ -468,17 +422,12 @@ def __repr__(self) -> str: "blacklisted_exceptions={self.blacklisted_exceptions}, " "log_call_args={self.log_call_args}, " "log_call_args_on_exc={self.log_call_args_on_exc}, " - "log_result_obj={self.log_result_obj}, )".format( - cls=self.__class__.__name__, - self=self, - spec=self._spec - ) + "log_result_obj={self.log_result_obj}, )".format(cls=self.__class__.__name__, self=self, spec=self._spec) ) # noinspection PyMethodMayBeStatic def pre_process_param( # pylint: disable=no-self-use - self, - arg: BoundParameter + self, arg: BoundParameter ) -> typing.Union[BoundParameter, typing.Tuple[BoundParameter, typing.Any], None]: """Process parameter for the future logging. @@ -495,9 +444,7 @@ def pre_process_param( # pylint: disable=no-self-use # noinspection PyMethodMayBeStatic,PyUnusedLocal def post_process_param( # pylint: disable=unused-argument, no-self-use - self, - arg: BoundParameter, - arg_repr: str + self, arg: BoundParameter, arg_repr: str ) -> str: """Process parameter for the future logging. @@ -515,10 +462,7 @@ def post_process_param( # pylint: disable=unused-argument, no-self-use return arg_repr def _get_func_args_repr( - self, - sig: inspect.Signature, - args: typing.Tuple, - kwargs: typing.Dict[str, typing.Any] + self, sig: inspect.Signature, args: typing.Tuple, kwargs: typing.Dict[str, typing.Any] ) -> str: """Internal helper for reducing complexity of decorator code. @@ -534,7 +478,7 @@ def _get_func_args_repr( .. 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 '' + return "" param_str = "" @@ -558,12 +502,7 @@ def _get_func_args_repr( elif param.VAR_KEYWORD == param.kind: value = {} - val = core.pretty_repr( - src=value, - indent=indent + 4, - no_indent_start=True, - max_indent=self.max_indent, - ) + val = core.pretty_repr(src=value, indent=indent + 4, no_indent_start=True, max_indent=self.max_indent) val = self.post_process_param(param, val) @@ -576,20 +515,12 @@ def _get_func_args_repr( else: annotation = " # type: {param.annotation!s}".format(param=param) - param_str += fmt( - key=param.name, - annotation=annotation, - val=val, - ) + param_str += fmt(key=param.name, annotation=annotation, val=val) if param_str: param_str += "\n" return param_str - def _make_done_record( - self, - func_name: str, - result: typing.Any - ) -> None: + def _make_done_record(self, func_name: str, result: typing.Any) -> None: """Construct success record. :type func_name: str @@ -598,20 +529,10 @@ def _make_done_record( msg = "Done: {name!r}".format(name=func_name) if self.log_result_obj: - msg += " with result:\n{result}".format( - result=core.pretty_repr( - result, - max_indent=self.max_indent, - ) - ) + msg += " with result:\n{result}".format(result=core.pretty_repr(result, max_indent=self.max_indent)) self._logger.log(level=self.log_level, msg=msg) # type: ignore - def _make_calling_record( - self, - name: str, - arguments: str, - method: str = "Calling" - ) -> None: + def _make_calling_record(self, name: str, arguments: str, method: str = "Calling") -> None: """Make log record before execution. :type name: str @@ -621,17 +542,11 @@ def _make_calling_record( 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 '' - ) + method=method, name=name, arguments=arguments if self.log_call_args else "" + ), ) - def _make_exc_record( - self, - name: str, - arguments: str - ) -> None: + def _make_exc_record(self, name: str, arguments: str) -> None: """Make log record if exception raised. :type name: str @@ -640,16 +555,12 @@ def _make_exc_record( self._logger.log( # type: ignore level=self.exc_level, msg="Failed: \n{name!r}({arguments})".format( - name=name, - arguments=arguments if self.log_call_args_on_exc else '', + name=name, arguments=arguments if self.log_call_args_on_exc else "" ), - exc_info=True + exc_info=True, ) - def _get_function_wrapper( - self, - func: typing.Callable - ) -> typing.Callable: + def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: """Here should be constructed and returned real decorator. :param func: Wrapped function @@ -663,22 +574,11 @@ def _get_function_wrapper( # noinspection PyCompatibility,PyMissingOrEmptyDocstring @functools.wraps(func) @asyncio.coroutine - def async_wrapper( - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> typing.Any: - args_repr = self._get_func_args_repr( - sig=sig, - args=args, - kwargs=kwargs, - ) + def async_wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> typing.Any + args_repr = self._get_func_args_repr(sig=sig, args=args, kwargs=kwargs) try: - self._make_calling_record( - name=func.__name__, - arguments=args_repr, - method='Awaiting' - ) + self._make_calling_record(name=func.__name__, arguments=args_repr, method="Awaiting") result = yield from func(*args, **kwargs) self._make_done_record(func.__name__, result) except BaseException as e: @@ -690,21 +590,11 @@ def async_wrapper( # noinspection PyCompatibility,PyMissingOrEmptyDocstring @functools.wraps(func) - def wrapper( - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> typing.Any: - args_repr = self._get_func_args_repr( - sig=sig, - args=args, - kwargs=kwargs, - ) + def wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> typing.Any + args_repr = self._get_func_args_repr(sig=sig, args=args, kwargs=kwargs) try: - self._make_calling_record( - name=func.__name__, - arguments=args_repr - ) + self._make_calling_record(name=func.__name__, arguments=args_repr) result = func(*args, **kwargs) self._make_done_record(func.__name__, result) except BaseException as e: @@ -718,9 +608,7 @@ def wrapper( return async_wrapper if asyncio.iscoroutinefunction(func) else wrapper def __call__( # pylint: disable=useless-super-delegation - self, - *args: typing.Union[typing.Callable, typing.Any], - **kwargs: typing.Any + self, *args: typing.Union[typing.Callable, typing.Any], **kwargs: typing.Any ) -> typing.Union[typing.Callable[..., typing.Any], typing.Any]: """Callable instance.""" return super(LogWrap, self).__call__(*args, **kwargs) @@ -829,9 +717,11 @@ def logwrap( # noqa: F811 # pylint: disable=unexpected-keyword-arg, no-value-f blacklisted_exceptions=blacklisted_exceptions, log_call_args=log_call_args, log_call_args_on_exc=log_call_args_on_exc, - log_result_obj=log_result_obj + log_result_obj=log_result_obj, ) if func is not None: return wrapper(func) return wrapper + + # pylint: enable=function-redefined diff --git a/logwrap/_repr_utils.py b/logwrap/_repr_utils.py index 828f6cf..47ceebf 100644 --- a/logwrap/_repr_utils.py +++ b/logwrap/_repr_utils.py @@ -39,10 +39,7 @@ def _simple(item: typing.Any) -> bool: class ReprParameter: """Parameter wrapper wor repr and str operations over signature.""" - __slots__ = ( - '_value', - '_parameter' - ) + __slots__ = ("_value", "_parameter") POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD @@ -52,11 +49,7 @@ class ReprParameter: empty = inspect.Parameter.empty - def __init__( - self, - parameter: inspect.Parameter, - value: typing.Optional[typing.Any] = None - ) -> None: + def __init__(self, parameter: inspect.Parameter, value: typing.Optional[typing.Any] = None) -> None: """Parameter-like object store for repr and str tasks. :param parameter: parameter from signature @@ -79,9 +72,9 @@ def name(self) -> typing.Union[None, str]: For `*args` and `**kwargs` add prefixes """ if self.kind == inspect.Parameter.VAR_POSITIONAL: - return '*' + self.parameter.name + return "*" + self.parameter.name if self.kind == inspect.Parameter.VAR_KEYWORD: - return '**' + self.parameter.name + return "**" + self.parameter.name return self.parameter.name @property @@ -102,6 +95,7 @@ def kind(self) -> int: """Parameter kind.""" return self.parameter.kind # type: ignore + # noinspection PyTypeChecker def __hash__(self) -> int: # pragma: no cover """Block hashing. @@ -116,9 +110,7 @@ def __repr__(self) -> str: # pylint: disable=no-member -def _prepare_repr( - func: typing.Union[types.FunctionType, types.MethodType] -) -> typing.Iterator[ReprParameter]: +def _prepare_repr(func: typing.Union[types.FunctionType, types.MethodType]) -> typing.Iterator[ReprParameter]: """Get arguments lists with defaults. :param func: Callable object to process @@ -142,6 +134,8 @@ def _prepare_repr( return for arg in params: yield ReprParameter(arg) + + # pylint: enable=no-member @@ -151,16 +145,9 @@ class PrettyFormat: Designed for usage as __repr__ and __str__ replacement on complex objects """ - __slots__ = ( - '__max_indent', - '__indent_step', - ) + __slots__ = ("__max_indent", "__indent_step") - def __init__( - self, - max_indent: int = 20, - indent_step: int = 4, - ) -> None: + def __init__(self, max_indent: int = 20, indent_step: int = 4) -> None: """Pretty Formatter. :param max_indent: maximal indent before classic repr() call @@ -200,11 +187,7 @@ def next_indent(self, indent: int, multiplier: int = 1) -> int: return indent + multiplier * self.indent_step @abc.abstractmethod - def _repr_callable( - self, - src: typing.Union[types.FunctionType, types.MethodType], - indent: int = 0 - ) -> str: + def _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], indent: int = 0) -> str: """Repr callable object (function or method). :param src: Callable to process @@ -217,12 +200,7 @@ def _repr_callable( raise NotImplementedError() # pragma: no cover @abc.abstractmethod - def _repr_simple( - self, - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False - ) -> str: + def _repr_simple(self, src: typing.Any, indent: int = 0, no_indent_start: bool = False) -> str: """Repr object without iteration. :param src: Source object @@ -237,11 +215,7 @@ def _repr_simple( raise NotImplementedError() # pragma: no cover @abc.abstractmethod - def _repr_dict_items( - self, - src: typing.Dict, - indent: int = 0 # type - ) -> typing.Iterator[str]: + def _repr_dict_items(self, src: typing.Dict, indent: int = 0) -> typing.Iterator[str]: # type """Repr dict items. :param src: object to process @@ -253,14 +227,7 @@ def _repr_dict_items( raise NotImplementedError() # pragma: no cover @staticmethod - def _repr_iterable_item( - nl: bool, - obj_type: str, - prefix: str, - indent: int, - result: str, - suffix: str - ) -> str: + def _repr_iterable_item(nl: bool, obj_type: str, prefix: str, indent: int, result: str, suffix: str) -> str: """Repr iterable item. :param nl: newline before item @@ -279,11 +246,7 @@ def _repr_iterable_item( """ raise NotImplementedError() # pragma: no cover - def _repr_iterable_items( - self, - src: typing.Iterable, - indent: int = 0 - ) -> typing.Iterator[str]: + def _repr_iterable_items(self, src: typing.Iterable, indent: int = 0) -> typing.Iterator[str]: """Repr iterable items (not designed for dicts). :param src: object to process @@ -294,10 +257,7 @@ def _repr_iterable_items( :rtype: typing.Iterator[str] """ for elem in src: - yield '\n' + self.process_element( - src=elem, - indent=self.next_indent(indent), - ) + ',' + yield "\n" + self.process_element(src=elem, indent=self.next_indent(indent)) + "," @property @abc.abstractmethod @@ -308,12 +268,7 @@ def _magic_method_name(self) -> str: """ raise NotImplementedError() # pragma: no cover - def process_element( - self, - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False - ) -> str: + def process_element(self, src: typing.Any, indent: int = 0, no_indent_start: bool = False) -> str: """Make human readable representation of object. :param src: object to process @@ -326,57 +281,36 @@ def process_element( :rtype: str """ if hasattr(src, self._magic_method_name): - result = getattr( - src, - self._magic_method_name - )( - self, - indent=indent, - no_indent_start=no_indent_start - ) + result = getattr(src, self._magic_method_name)(self, indent=indent, no_indent_start=no_indent_start) return result # type: ignore if _known_callable(src): - return self._repr_callable( - src=src, - indent=indent, - ) + return self._repr_callable(src=src, indent=indent) if _simple(src) or indent >= self.max_indent or not src: - return self._repr_simple( - src=src, - indent=indent, - no_indent_start=no_indent_start, - ) + return self._repr_simple(src=src, indent=indent, no_indent_start=no_indent_start) if isinstance(src, dict): - prefix, suffix = '{', '}' - result = ''.join(self._repr_dict_items(src=src, indent=indent)) + prefix, suffix = "{", "}" + result = "".join(self._repr_dict_items(src=src, indent=indent)) else: if isinstance(src, list): - prefix, suffix = '[', ']' + prefix, suffix = "[", "]" elif isinstance(src, tuple): - prefix, suffix = '(', ')' + prefix, suffix = "(", ")" else: - prefix, suffix = '{', '}' - result = ''.join(self._repr_iterable_items(src=src, indent=indent)) - return ( - self._repr_iterable_item( - nl=no_indent_start, - obj_type=src.__class__.__name__, - prefix=prefix, - indent=indent, - result=result, - suffix=suffix, - ) + prefix, suffix = "{", "}" + result = "".join(self._repr_iterable_items(src=src, indent=indent)) + return self._repr_iterable_item( + nl=no_indent_start, + obj_type=src.__class__.__name__, + prefix=prefix, + indent=indent, + result=result, + suffix=suffix, ) - def __call__( - self, - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False - ) -> str: + def __call__(self, src: typing.Any, indent: int = 0, no_indent_start: bool = False) -> str: """Make human readable representation of object. The main entry point. :param src: object to process @@ -388,11 +322,7 @@ def __call__( :return: formatted string :rtype: str """ - result = self.process_element( - src, - indent=indent, - no_indent_start=no_indent_start - ) + result = self.process_element(src, indent=indent, no_indent_start=no_indent_start) return result @@ -410,32 +340,19 @@ def _magic_method_name(self) -> str: :rtype: str """ - return '__pretty_repr__' + return "__pretty_repr__" @staticmethod - def _strings_repr( - indent: int, - val: typing.Union[bytes, str] - ) -> str: + def _strings_repr(indent: int, val: typing.Union[bytes, str]) -> str: """Custom repr for strings and binary strings.""" if isinstance(val, bytes): - val = val.decode(encoding='utf-8', errors='backslashreplace') - prefix = 'b' + 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 - ) + prefix = "u" + return "{spc:<{indent}}{prefix}'''{string}'''".format(spc="", indent=indent, prefix=prefix, string=val) - def _repr_simple( - self, - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False - ) -> str: + def _repr_simple(self, src: typing.Any, indent: int = 0, no_indent_start: bool = False) -> str: """Repr object without iteration. :param src: Source object @@ -449,24 +366,12 @@ def _repr_simple( """ 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)) + ")" - ) + 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, - ) + return "{spc:<{indent}}{val!r}".format(spc="", indent=indent, val=src) - def _repr_dict_items( - self, - src: typing.Dict, - indent: int = 0 - ) -> typing.Iterator[str]: + def _repr_dict_items(self, src: typing.Dict, indent: int = 0) -> typing.Iterator[str]: """Repr dict items. :param src: object to process @@ -479,22 +384,14 @@ def _repr_dict_items( max_len = max((len(repr(key)) for key in src)) if src else 0 for key, val in src.items(): yield "\n{spc:<{indent}}{key!r:{size}}: {val},".format( - spc='', + spc="", indent=self.next_indent(indent), size=max_len, key=key, - val=self.process_element( - val, - indent=self.next_indent(indent, multiplier=2), - no_indent_start=True, - ) + val=self.process_element(val, indent=self.next_indent(indent, multiplier=2), no_indent_start=True), ) - def _repr_callable( - self, - src: typing.Union[types.FunctionType, types.MethodType], - indent: int = 0 - ) -> str: + def _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], indent: int = 0) -> str: """Repr callable object (function or method). :param src: Callable to process @@ -507,49 +404,30 @@ def _repr_callable( param_str = "" for param in _prepare_repr(src): - param_str += "\n{spc:<{indent}}{param.name}".format( - spc='', - indent=self.next_indent(indent), - param=param - ) + 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) + 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 += "={val}".format( + val=self.process_element(src=param.value, indent=indent, no_indent_start=True) ) - param_str += ',' + param_str += "," if param_str: param_str += "\n" + " " * indent sig = inspect.signature(src) if sig.return_annotation is inspect.Parameter.empty: - annotation = '' + annotation = "" else: - annotation = ' -> {sig.return_annotation!r}'.format(sig=sig) + 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 + spc="", indent=indent, obj=src, args=param_str, annotation=annotation ) @staticmethod - def _repr_iterable_item( - nl: bool, - obj_type: str, - prefix: str, - indent: int, - result: str, - suffix: str, - ) -> str: + def _repr_iterable_item(nl: bool, obj_type: str, prefix: str, indent: int, result: str, suffix: str) -> str: """Repr iterable item. :param nl: newline before item @@ -571,13 +449,13 @@ def _repr_iterable_item( "{nl}" "{spc:<{indent}}{obj_type:}({prefix}{result}\n" "{spc:<{indent}}{suffix})".format( - nl='\n' if nl else '', - spc='', + nl="\n" if nl else "", + spc="", indent=indent, obj_type=obj_type, prefix=prefix, result=result, - suffix=suffix + suffix=suffix, ) ) @@ -596,28 +474,16 @@ def _magic_method_name(self) -> str: :rtype: str """ - return '__pretty_str__' + return "__pretty_str__" @staticmethod - def _strings_str( - indent: int, - val: typing.Union[bytes, str] - ) -> str: + def _strings_str(indent: int, val: typing.Union[bytes, str]) -> 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 - ) + val = val.decode(encoding="utf-8", errors="backslashreplace") + return "{spc:<{indent}}{string}".format(spc="", indent=indent, string=val) - def _repr_simple( - self, - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False - ) -> str: + def _repr_simple(self, src: typing.Any, indent: int = 0, no_indent_start: bool = False) -> str: """Repr object without iteration. :param src: Source object @@ -631,24 +497,12 @@ def _repr_simple( """ 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)) + ")" - ) + 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, - ) + return "{spc:<{indent}}{val!s}".format(spc="", indent=indent, val=src) - def _repr_dict_items( - self, - src: typing.Dict, - indent: int = 0 - ) -> typing.Iterator[str]: + def _repr_dict_items(self, src: typing.Dict, indent: int = 0) -> typing.Iterator[str]: """Repr dict items. :param src: object to process @@ -661,22 +515,14 @@ def _repr_dict_items( 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( - spc='', + spc="", indent=self.next_indent(indent), size=max_len, key=key, - val=self.process_element( - val, - indent=self.next_indent(indent, multiplier=2), - no_indent_start=True, - ) + val=self.process_element(val, indent=self.next_indent(indent, multiplier=2), no_indent_start=True), ) - def _repr_callable( - self, - src: typing.Union[types.FunctionType, types.MethodType], - indent: int = 0 - ) -> str: + def _repr_callable(self, src: typing.Union[types.FunctionType, types.MethodType], indent: int = 0) -> str: """Repr callable object (function or method). :param src: Callable to process @@ -689,49 +535,30 @@ def _repr_callable( param_str = "" for param in _prepare_repr(src): - param_str += "\n{spc:<{indent}}{param.name}".format( - spc='', - indent=self.next_indent(indent), - param=param - ) + 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) + 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 += "={val}".format( + val=self.process_element(src=param.value, indent=indent, no_indent_start=True) ) - param_str += ',' + param_str += "," if param_str: param_str += "\n" + " " * indent sig = inspect.signature(src) if sig.return_annotation is inspect.Parameter.empty: - annotation = '' + annotation = "" else: - annotation = ' -> {sig.return_annotation!r}'.format(sig=sig) + 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 + spc="", indent=indent, obj=src, args=param_str, annotation=annotation ) @staticmethod - def _repr_iterable_item( - nl: bool, - obj_type: str, - prefix: str, - indent: int, - result: str, - suffix: str - ) -> str: + def _repr_iterable_item(nl: bool, obj_type: str, prefix: str, indent: int, result: str, suffix: str) -> str: """Repr iterable item. :param nl: newline before item @@ -753,22 +580,13 @@ def _repr_iterable_item( "{nl}" "{spc:<{indent}}{prefix}{result}\n" "{spc:<{indent}}{suffix}".format( - nl='\n' if nl else '', - spc='', - indent=indent, - prefix=prefix, - result=result, - suffix=suffix + nl="\n" if nl else "", spc="", indent=indent, prefix=prefix, result=result, suffix=suffix ) ) def pretty_repr( - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False, - max_indent: int = 20, - indent_step: int = 4, + src: typing.Any, indent: int = 0, no_indent_start: bool = False, max_indent: int = 20, indent_step: int = 4 ) -> str: """Make human readable repr of object. @@ -785,22 +603,13 @@ def pretty_repr( :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, + return PrettyRepr(max_indent=max_indent, indent_step=indent_step)( + src=src, indent=indent, no_indent_start=no_indent_start ) def pretty_str( - src: typing.Any, - indent: int = 0, - no_indent_start: bool = False, - max_indent: int = 20, - indent_step: int = 4, + src: typing.Any, indent: int = 0, no_indent_start: bool = False, max_indent: int = 20, indent_step: int = 4 ) -> str: """Make human readable str of object. @@ -816,20 +625,9 @@ def pretty_str( :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, + 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', -) +__all__ = ("PrettyFormat", "PrettyRepr", "PrettyStr", "pretty_repr", "pretty_str") diff --git a/pyproject.toml b/pyproject.toml index be03eaa..bc53596 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,7 @@ requires = [ "setuptools > 20.2 ", "wheel", ] + +[tool.black] +line-length = 120 +safe = true diff --git a/setup.cfg b/setup.cfg index fb16d2d..ec934c5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,13 +40,23 @@ exclude = __init__.py, docs ignore = + E203 +# whitespace before ':' show-pep8 = True show-source = True count = True max-line-length = 120 [pydocstyle] -ignore = D401, D203, D213 +ignore = + D401, + D202, + D203, + D213 +# First line should be in imperative mood; try rephrasing +# No blank lines allowed after function docstring +# 1 blank line required before class docstring +# Multi-line docstring summary should start at the second line [aliases] test=pytest diff --git a/setup.py b/setup.py index b18138b..3088001 100644 --- a/setup.py +++ b/setup.py @@ -27,57 +27,53 @@ import sys try: + # noinspection PyPackageRequirements from Cython.Build import cythonize except ImportError: cythonize = None import setuptools -with open( - os.path.join( - os.path.dirname(__file__), - 'logwrap', '__init__.py' - ) -) as f: +with open(os.path.join(os.path.dirname(__file__), "logwrap", "__init__.py")) as f: source = f.read() -with open('requirements.txt') as f: +with open("requirements.txt") as f: required = f.read().splitlines() -with open('README.rst',) as f: +with open("README.rst") as f: long_description = f.read() def _extension(modpath): """Make setuptools.Extension.""" - return setuptools.Extension(modpath, [modpath.replace('.', '/') + '.py']) + return setuptools.Extension(modpath, [modpath.replace(".", "/") + ".py"]) requires_optimization = [ - _extension('logwrap._class_decorator'), - _extension('logwrap._log_wrap'), - _extension('logwrap._repr_utils'), + _extension("logwrap._class_decorator"), + _extension("logwrap._log_wrap"), + _extension("logwrap._repr_utils"), ] -if 'win32' != sys.platform: - requires_optimization.append( - _extension('logwrap.__init__') - ) +if "win32" != sys.platform: + requires_optimization.append(_extension("logwrap.__init__")) -ext_modules = cythonize( - requires_optimization, - compiler_directives=dict( - always_allow_keywords=True, - binding=True, - embedsignature=True, - overflowcheck=True, - language_level=3, +# noinspection PyCallingNonCallable +ext_modules = ( + cythonize( + requires_optimization, + compiler_directives=dict( + always_allow_keywords=True, binding=True, embedsignature=True, overflowcheck=True, language_level=3 + ), ) -) if cythonize is not None else [] + if cythonize is not None + else [] +) class BuildFailed(Exception): """For install clear scripts.""" + pass @@ -91,14 +87,14 @@ def run(self): # Copy __init__.py back to repair package. build_dir = os.path.abspath(self.build_lib) - root_dir = os.path.abspath(os.path.join(__file__, '..')) + root_dir = os.path.abspath(os.path.join(__file__, "..")) target_dir = build_dir if not self.inplace else root_dir src_files = ( - os.path.join('logwrap', '__init__.py'), + os.path.join("logwrap", "__init__.py"), # _log_wrap3 should not be compiled due to specific bug: # Exception inside `async def` crashes python. - os.path.join('logwrap', '_log_wrap.py'), + os.path.join("logwrap", "_log_wrap.py"), ) for src_file in src_files: @@ -109,7 +105,7 @@ def run(self): shutil.copyfile(src, dst) except ( distutils.errors.DistutilsPlatformError, - getattr(globals()['__builtins__'], 'FileNotFoundError', OSError) + getattr(globals()["__builtins__"], "FileNotFoundError", OSError), ): raise BuildFailed() @@ -121,7 +117,7 @@ def build_extension(self, ext): distutils.errors.CCompilerError, distutils.errors.DistutilsExecError, distutils.errors.DistutilsPlatformError, - ValueError + ValueError, ): raise BuildFailed() @@ -173,11 +169,7 @@ def get_simple_vars_from_src(src): >>> get_simple_vars_from_src(multiple_assign) OrderedDict([('e', 1), ('f', 1), ('g', 1)]) """ - ast_data = ( - ast.Str, ast.Num, - ast.List, ast.Set, ast.Dict, ast.Tuple, - ast.Bytes, ast.NameConstant, - ) + ast_data = (ast.Str, ast.Num, ast.List, ast.Set, ast.Dict, ast.Tuple, ast.Bytes, ast.NameConstant) tree = ast.parse(src) @@ -189,9 +181,7 @@ def get_simple_vars_from_src(src): try: if isinstance(node.value, ast_data): value = ast.literal_eval(node.value) - elif isinstance( # NameConstant in python < 3.4 - node.value, ast.Name - ) and isinstance( + elif isinstance(node.value, ast.Name) and isinstance( # NameConstant in python < 3.4 node.value.ctx, ast.Load # Read constant ): value = ast.literal_eval(node.value) @@ -200,11 +190,7 @@ def get_simple_vars_from_src(src): except ValueError: continue for tgt in node.targets: - if isinstance( - tgt, ast.Name - ) and isinstance( - tgt.ctx, ast.Store - ): + if isinstance(tgt, ast.Name) and isinstance(tgt.ctx, ast.Store): result[tgt.id] = value return result @@ -212,41 +198,32 @@ def get_simple_vars_from_src(src): variables = get_simple_vars_from_src(source) classifiers = [ - 'Development Status :: 5 - Production/Stable', - - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules', - - 'License :: OSI Approved :: Apache Software License', - - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - - 'Programming Language :: Python :: Implementation :: CPython', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: Implementation :: CPython", ] -keywords = [ - 'logging', - 'debugging', - 'development', -] +keywords = ["logging", "debugging", "development"] setup_args = dict( - name='logwrap', - author=variables['__author__'], - author_email=variables['__author_email__'], - maintainer=', '.join( - '{name} <{email}>'.format(name=name, email=email) - for name, email in variables['__maintainers__'].items() + name="logwrap", + author=variables["__author__"], + author_email=variables["__author_email__"], + maintainer=", ".join( + "{name} <{email}>".format(name=name, email=email) for name, email in variables["__maintainers__"].items() ), - url=variables['__url__'], - version=variables['__version__'], - license=variables['__license__'], - description=variables['__description__'], + url=variables["__url__"], + version=variables["__version__"], + license=variables["__license__"], + description=variables["__description__"], long_description=long_description, classifiers=classifiers, keywords=keywords, - python_requires='>=3.4.0,<3.5', + python_requires=">=3.4.0,<3.5", # While setuptools cannot deal with pre-installed incompatible versions, # setting a lower bound is not harmful - it makes error messages cleaner. DO # NOT set an upper bound on setuptools, as that will lead to uninstallable @@ -254,26 +231,19 @@ def get_simple_vars_from_src(src): # Blacklist setuptools 34.0.0-34.3.2 due to https://github.com/pypa/setuptools/issues/951 # Blacklist setuptools 36.2.0 due to https://github.com/pypa/setuptools/issues/1086 setup_requires="setuptools >= 21.0.0,!=24.0.0," - "!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2," - "!=36.2.0", + "!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2," + "!=36.2.0", install_requires=required, - package_data={ - 'logwrap': ['py.typed'], - }, + package_data={"logwrap": ["py.typed"]}, ) if cythonize is not None: - setup_args['ext_modules'] = ext_modules - setup_args['cmdclass'] = dict(build_ext=AllowFailRepair) + setup_args["ext_modules"] = ext_modules + setup_args["cmdclass"] = dict(build_ext=AllowFailRepair) try: setuptools.setup(**setup_args) except BuildFailed: - print( - '*' * 80 + '\n' - '* Build Failed!\n' - '* Use clear scripts version.\n' - '*' * 80 + '\n' - ) - del setup_args['ext_modules'] - del setup_args['cmdclass'] + print("*" * 80 + "\n" "* Build Failed!\n" "* Use clear scripts version.\n" "*" * 80 + "\n") + del setup_args["ext_modules"] + del setup_args["cmdclass"] setuptools.setup(**setup_args) diff --git a/tox.ini b/tox.ini index cb3919b..9d45c56 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] minversion = 2.0 -envlist = pep8, pylint, mypy, bandit, pep257, py34, docs, py34-nocov +envlist = black, pep8, pylint, mypy, bandit, pep257, py34, docs, py34-nocov skipsdist = True skip_missing_interpreters = True @@ -60,7 +60,7 @@ commands = pip install ./ -vvv -U [testenv:pylint] usedevelop = False deps = - pylint >= 2.0 + pylint >= 2.1 -r{toxinidir}/CI_REQUIREMENTS.txt commands = pylint logwrap @@ -78,13 +78,23 @@ exclude = __init__.py, docs ignore = + E203 +# whitespace before ':' show-pep8 = True show-source = True count = True max-line-length = 120 [pydocstyle] -ignore = D401, D203, D213 +ignore = + D401, + D202, + D203, + D213 +# First line should be in imperative mood; try rephrasing +# No blank lines allowed after function docstring +# 1 blank line required before class docstring +# Multi-line docstring summary should start at the second line [testenv:docs] usedevelop = False @@ -105,9 +115,16 @@ commands = pipenv install -r {toxinidir}/build_requirements.txt --skip-lock pipenv graph +[testenv:black] +deps = + black +usedevelop = False +commands = + black logwrap + [testenv:mypy] usedevelop = False deps = - mypy>=0.620 + mypy>=0.630 -r{toxinidir}/CI_REQUIREMENTS.txt commands = mypy --strict logwrap