Skip to content

Commit

Permalink
Require keyword parameters in logwrap constructor/call. Related #5 Fix
Browse files Browse the repository at this point in the history
…#10 (#12)

* Not keyword parameters set are deprecated and supported up to 3.4.0
* Temporary use `*args, **kwargs`,
  pyi files set to the final variant.
* Fix empty `*args, **kwargs` repr

* Later:
    python 2: will return to normal arguments, but order can be changed
    python 3: use pyi API
  • Loading branch information
penguinolog committed Apr 30, 2018
1 parent 05d6486 commit 4ea24b5
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 89 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Version 3.3.0
-------------
* Type hints and stubs
* PEP0518
* Deprecation of *args for logwrap
* Fix empty *args and **kwargs
Version 3.2.0
-------------
Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ logwrap
-------
The main decorator. Could be used as not argumented (`@logwrap.logwrap`) and argumented (`@logwrap.logwrap()`).
Not argumented usage simple calls with default values for all positions.

.. note:: Argumens should be set via keywords only.

Argumented usage with arguments from signature:

.. code-block:: python
Expand Down
9 changes: 7 additions & 2 deletions doc/source/logwrap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ API: Decorators: `LogWrap` class and `logwrap` function.

.. versionadded:: 2.2.0

.. py:method:: __init__(log=logging.getLogger('logwrap'), log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, spec=None, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_result_obj=True, )
.. py:method:: __init__(func=None, *, log=logging.getLogger('logwrap'), log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, spec=None, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_result_obj=True, )
:param func: function to wrap
:type func: typing.Optional[typing.Callable]
:param log: logger object for decorator, by default used 'logwrap'
:type log: typing.Union[logging.Logger, typing.Callable]
:type log: logging.Logger
:param log_level: log level for successful calls
:type log_level: int
:param exc_level: log level for exception cases
Expand Down Expand Up @@ -44,6 +46,9 @@ API: Decorators: `LogWrap` class and `logwrap` function.
: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:: 3.3.0 Deprecation of *args

.. note:: Attributes/properties names the same as argument names and changes
the same fields.

Expand Down
140 changes: 115 additions & 25 deletions logwrap/_log_wrap2.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import logging
import typing # noqa # pylint: disable=unused-import
import warnings

import six
# noinspection PyUnresolvedReferences
Expand All @@ -35,11 +36,106 @@
__all__ = ('logwrap', 'LogWrap')


def _apply_old_spec(*args, **kwargs): # type: (...) -> typing.Dict[str, typing.Any]
# pylint: disable=unused-argument
def old_spec(
log=_log_wrap_shared.logger, # type: typing.Union[logging.Logger, typing.Callable]
log_level=logging.DEBUG, # type: int
exc_level=logging.ERROR, # type: int
max_indent=20, # type: int
spec=None, # type: typing.Optional[typing.Callable]
blacklisted_names=None, # type: typing.Optional[typing.List[str]]
blacklisted_exceptions=None, # type: typing.Optional[typing.List[Exception]]
log_call_args=True, # type: bool
log_call_args_on_exc=True, # type: bool
log_result_obj=True, # type: bool
): # type: (...) -> None
"""Old spec."""
pass # pragma: no cover

# pylint: enable=unused-argument

sig = funcsigs.signature(old_spec) # type: funcsigs.Signature
parameters = tuple(sig.parameters.values()) # type: typing.Tuple[funcsigs.Parameter, ...]

real_parameters = {
parameter.name: parameter.default for parameter in parameters
} # type: typing.Dict[str, typing.Any]

bound = sig.bind(*args, **kwargs).arguments

final_kwargs = {
key: bound.get(key, real_parameters[key])
for key in real_parameters
} # type: typing.Dict[str, typing.Any]

return final_kwargs


class LogWrap(_log_wrap_shared.BaseLogWrap):
"""LogWrap."""

__slots__ = ()

def __init__( # pylint: disable=keyword-arg-before-vararg
self,
func=None, # type: typing.Optional[typing.Callable]
*args,
**kwargs
): # type: (...) -> None
"""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[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_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.
"""
if isinstance(func, logging.Logger):
args = (func,) + args
func = None

if args:
warnings.warn(
'Logwrap should use keyword-only parameters starting from version 3.4.0\n'
'After version 3.4.0 arguments list and order may be changed.',
DeprecationWarning
)

super(LogWrap, self).__init__(func=func, **_apply_old_spec(*args, **kwargs))

def _get_function_wrapper(
self,
func # type: typing.Callable
Expand Down Expand Up @@ -79,22 +175,17 @@ def wrapper(*args, **kwargs):


# pylint: disable=unexpected-keyword-arg, no-value-for-parameter
def logwrap(
log=_log_wrap_shared.logger, # type: typing.Union[logging.Logger, typing.Callable]
log_level=logging.DEBUG, # type: int
exc_level=logging.ERROR, # type: int
max_indent=20, # type: int
spec=None, # type: typing.Optional[typing.Callable]
blacklisted_names=None, # type: typing.Optional[typing.List[str]]
blacklisted_exceptions=None, # type: typing.Optional[typing.List[Exception]]
log_call_args=True, # type: bool
log_call_args_on_exc=True, # type: bool
log_result_obj=True, # type: bool
def logwrap( # pylint: disable=keyword-arg-before-vararg
func=None, # type: typing.Optional[typing.Callable]
*args,
**kwargs
): # type: (...) -> typing.Union[LogWrap, typing.Callable]
"""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: typing.Union[logging.Logger, typing.Callable]
:type log: logging.Logger
:param log_level: log level for successful calls
:type log_level: int
:param exc_level: log level for exception cases
Expand Down Expand Up @@ -123,23 +214,22 @@ def logwrap(
: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.
"""
if isinstance(log, logging.Logger):
if isinstance(func, logging.Logger):
args = (func, ) + args
func = None
else:
log, func = _log_wrap_shared.logger, log # type: logging.Logger, typing.Callable

if args:
warnings.warn(
'Logwrap should use keyword-only parameters starting from version 3.4.0\n'
'After version 3.4.0 arguments list and order may be changed.',
DeprecationWarning
)

wrapper = LogWrap(
log=log,
log_level=log_level,
exc_level=exc_level,
max_indent=max_indent,
spec=spec,
blacklisted_names=blacklisted_names,
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
**_apply_old_spec(*args, **kwargs)
)
if func is not None:
return wrapper(func)
Expand Down
18 changes: 17 additions & 1 deletion logwrap/_log_wrap2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@ import typing
from . import _log_wrap_shared

class LogWrap(_log_wrap_shared.BaseLogWrap):
def __init__(
self,
func: typing.Optional[typing.Callable]=None,
log: logging.Logger=...,
log_level: int=...,
exc_level: int=...,
max_indent: int=...,
spec: typing.Optional[typing.Callable]=...,
blacklisted_names: typing.Optional[typing.List[str]]=...,
blacklisted_exceptions: typing.Optional[typing.List[Exception]]=...,
log_call_args: bool=...,
log_call_args_on_exc: bool=...,
log_result_obj: bool=...
) -> None: ...

def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: ...

def logwrap(
log: typing.Union[logging.Logger, typing.Callable]=...,
func: typing.Optional[typing.Callable]=None,
log: logging.Logger=...,
log_level: int=...,
exc_level: int=...,
max_indent: int=...,
Expand Down
Loading

0 comments on commit 4ea24b5

Please sign in to comment.