Repository: python-useful-helpers/logwrap

Issue:
@logwrap incorrectly log empty *args and **kwargs
```python
@logwrap.logwrap
def func(*args, **kwargs): pass
```
On call `func()` will be logged `'args'=<class 'inspect._empty'>` for `*args` and `**kwargs` instead of empty tuple and empty dict.

Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 333](https://coveralls.io/builds/16756808)

* **29** of **29**  **(100.0%)** changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/16756808/badge)](https://coveralls.io/builds/16756808) |
| :-- | --: |
| Change from base [Build 330](https://coveralls.io/builds/16691721): |  0.0% |
| Covered Lines: | 406 |
| Relevant Lines: | 406 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index eaa652d..39f201d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -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
 -------------
diff --git a/LICENSE b/LICENSE
index 8dada3e..d3591af 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright {yyyy} {name of copyright owner}
+   Copyright 2016-2018 Alexey Stepanov aka penguinolog
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.rst b/README.rst
index 4b5b277..f92271a 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/doc/source/logwrap.rst b/doc/source/logwrap.rst
index e12484f..4e7cf7e 100644
--- a/doc/source/logwrap.rst
+++ b/doc/source/logwrap.rst
@@ -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
@@ -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.
 
diff --git a/logwrap/_log_wrap2.py b/logwrap/_log_wrap2.py
index e19078e..9464dde 100644
--- a/logwrap/_log_wrap2.py
+++ b/logwrap/_log_wrap2.py
@@ -25,6 +25,7 @@ from __future__ import unicode_literals
 
 import logging
 import typing  # noqa # pylint: disable=unused-import
+import warnings
 
 import six
 # noinspection PyUnresolvedReferences
@@ -35,11 +36,106 @@ from . import _log_wrap_shared
 __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
@@ -79,22 +175,17 @@ class LogWrap(_log_wrap_shared.BaseLogWrap):
 
 
 # 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
@@ -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)
diff --git a/logwrap/_log_wrap2.pyi b/logwrap/_log_wrap2.pyi
index 0e286c0..2513215 100644
--- a/logwrap/_log_wrap2.pyi
+++ b/logwrap/_log_wrap2.pyi
@@ -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=...,
diff --git a/logwrap/_log_wrap3.py b/logwrap/_log_wrap3.py
index 8555180..c82f692 100644
--- a/logwrap/_log_wrap3.py
+++ b/logwrap/_log_wrap3.py
@@ -29,6 +29,7 @@ import functools
 import inspect
 import logging
 import typing
+import warnings
 
 
 from . import _log_wrap_shared
@@ -36,11 +37,106 @@ from . import _log_wrap_shared
 __all__ = ('logwrap', 'LogWrap')
 
 
+def _apply_old_spec(*args, **kwargs) -> typing.Dict[str, typing.Any]:
+    # pylint: disable=unused-argument
+    def old_spec(
+        log: typing.Union[logging.Logger, typing.Callable] = _log_wrap_shared.logger,
+        log_level: int = logging.DEBUG,
+        exc_level: int = logging.ERROR,
+        max_indent: int = 20,
+        spec: typing.Optional[typing.Callable] = None,
+        blacklisted_names: typing.Optional[typing.List[str]] = None,
+        blacklisted_exceptions: typing.Optional[typing.List[Exception]] = None,
+        log_call_args: bool = True,
+        log_call_args_on_exc: bool = True,
+        log_result_obj: bool = True,
+    ) -> None:
+        """Old spec."""
+        pass  # pragma: no cover
+
+    # pylint: enable=unused-argument
+
+    sig = inspect.signature(old_spec)  # type: inspect.Signature
+    parameters = tuple(sig.parameters.values())  # type: typing.Tuple[inspect.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):
     """Python 3.4+ version of LogWrap."""
 
     __slots__ = ()
 
+    def __init__(  # pylint: disable=keyword-arg-before-vararg
+        self,
+        func: typing.Optional[typing.Callable] = None,
+        *args,
+        **kwargs
+    ) -> 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.
+        .. versionchanged:: 3.3.0 Deprecation of *args
+        """
+        if isinstance(func, logging.Logger):
+            args = (func,) + args
+            func = None
+
+        if args:
+            warnings.warn(
+                'Logwrap will accept keyword-only parameters starting from version 3.4.0',
+                DeprecationWarning
+            )
+
+        super(LogWrap, self).__init__(func=func, **_apply_old_spec(*args, **kwargs))
+
     def _get_function_wrapper(
         self,
         func: typing.Callable
@@ -108,22 +204,17 @@ class LogWrap(_log_wrap_shared.BaseLogWrap):
 
 
 # pylint: disable=unexpected-keyword-arg, no-value-for-parameter
-def logwrap(
-    log: typing.Union[logging.Logger, typing.Callable] = _log_wrap_shared.logger,
-    log_level: int = logging.DEBUG,
-    exc_level: int = logging.ERROR,
-    max_indent: int = 20,
-    spec: typing.Optional[typing.Callable] = None,
-    blacklisted_names: typing.Optional[typing.List[str]] = None,
-    blacklisted_exceptions: typing.Optional[typing.List[Exception]] = None,
-    log_call_args: bool = True,
-    log_call_args_on_exc: bool = True,
-    log_result_obj: bool = True,
+def logwrap(  # pylint: disable=keyword-arg-before-vararg
+    func: typing.Optional[typing.Callable] = None,
+    *args,
+    **kwargs
 ) -> typing.Union[LogWrap, typing.Callable]:
     """Log function calls and return values. Python 3.4+ version.
 
+    :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
@@ -137,12 +228,9 @@ def logwrap(
                  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.
+    :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.
+    :param blacklisted_exceptions: list of exceptions, 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
@@ -152,23 +240,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.
+    .. versionchanged:: 3.3.0 Deprecation of *args
     """
-    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 will accept keyword-only parameters starting from version 3.4.0',
+            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)
diff --git a/logwrap/_log_wrap3.pyi b/logwrap/_log_wrap3.pyi
index 0e286c0..6d16152 100644
--- a/logwrap/_log_wrap3.pyi
+++ b/logwrap/_log_wrap3.pyi
@@ -3,10 +3,28 @@ 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=...,
diff --git a/logwrap/_log_wrap_shared.py b/logwrap/_log_wrap_shared.py
index 467a4f6..d886294 100644
--- a/logwrap/_log_wrap_shared.py
+++ b/logwrap/_log_wrap_shared.py
@@ -88,7 +88,8 @@ class BaseLogWrap(_class_decorator.BaseDecorator):
 
     def __init__(
         self,
-        log=logger,  # type: typing.Union[logging.Logger, typing.Callable]
+        func=None,  # type: typing.Optional[typing.Callable]
+        log=logger,  # type: logging.Logger
         log_level=logging.DEBUG,  # type: int
         exc_level=logging.ERROR,  # type: int
         max_indent=20,  # type: int
@@ -101,8 +102,10 @@ class BaseLogWrap(_class_decorator.BaseDecorator):
     ):  # 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: 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
@@ -134,7 +137,11 @@ class BaseLogWrap(_class_decorator.BaseDecorator):
         :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.
         """
+        super(BaseLogWrap, self).__init__(func=func)
+
         # Typing fix:
         if blacklisted_names is None:
             self.__blacklisted_names = []  # type: typing.List[str]
@@ -145,11 +152,7 @@ class BaseLogWrap(_class_decorator.BaseDecorator):
         else:
             self.__blacklisted_exceptions = list(blacklisted_exceptions)
 
-        if not isinstance(log, logging.Logger):
-            func, self.__logger = log, logger  # type: typing.Callable, logging.Logger
-        else:
-            func, self.__logger = None, log  # type: None, logging.Logger
-        super(BaseLogWrap, self).__init__(func=func)
+        self.__logger = log
 
         self.__log_level = log_level
         self.__exc_level = exc_level
@@ -345,10 +348,18 @@ class BaseLogWrap(_class_decorator.BaseDecorator):
             if last_kind != param.kind:
                 param_str += comment(kind=param.kind)
                 last_kind = param.kind
+
+            src = bound.get(param.name, param.default)
+            if param.empty == src:
+                if param.VAR_POSITIONAL == param.kind:
+                    src = ()
+                elif param.VAR_KEYWORD == param.kind:
+                    src = {}
+
             param_str += fmt(
                 key=param.name,
                 val=core.pretty_repr(
-                    src=bound.get(param.name, param.default),
+                    src=src,
                     indent=indent + 4,
                     no_indent_start=True,
                     max_indent=self.max_indent,
diff --git a/logwrap/_log_wrap_shared.pyi b/logwrap/_log_wrap_shared.pyi
index 99d709f..2231e54 100644
--- a/logwrap/_log_wrap_shared.pyi
+++ b/logwrap/_log_wrap_shared.pyi
@@ -13,7 +13,8 @@ def _check_type(expected: typing.Type) -> typing.Callable: ...
 class BaseLogWrap(_class_decorator.BaseDecorator):
     def __init__(
         self,
-        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=...,
diff --git a/requirements.txt b/requirements.txt
index 6875d5e..b273544 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-six
-typing >= 3.6
+six >=1.9
+typing >= 3.6 ; python_version < "3.7"
diff --git a/tox.ini b/tox.ini
index 86f807b..ec2fb9c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -26,7 +26,7 @@ deps =
 
 commands =
     py.test -vv --junitxml=unit_result.xml --html=report.html --cov-config .coveragerc --cov-report html --cov=logwrap {posargs:test}
-    coverage report --fail-under 89
+    coverage report --fail-under 85
 
 [testenv:py34-nocov]
 usedevelop = False
@@ -111,3 +111,11 @@ commands = python setup.py build_sphinx
 [testenv:bandit]
 deps = bandit
 commands = bandit -r logwrap
+
+[testenv:dep-graph]
+envdir = {toxworkdir}/dep-graph
+deps =
+    pipenv
+commands =
+    pipenv install -r {toxinidir}/build_requirements.txt --skip-lock
+    pipenv graph


Test patch:
diff --git a/test/test_log_wrap.py b/test/test_log_wrap.py
index 636dd7f..724c1a8 100644
--- a/test/test_log_wrap.py
+++ b/test/test_log_wrap.py
@@ -39,7 +39,7 @@ else:
 # noinspection PyUnusedLocal,PyMissingOrEmptyDocstring
 @mock.patch('logwrap._log_wrap_shared.logger', autospec=True)
 class TestLogWrap(unittest.TestCase):
-    def test_no_args(self, logger):
+    def test_001_no_args(self, logger):
         @logwrap.logwrap
         def func():
             return 'No args'
@@ -61,7 +61,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_args_simple(self, logger):
+    def test_002_args_simple(self, logger):
         arg = 'test arg'
 
         @logwrap.logwrap
@@ -97,7 +97,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_args_defaults(self, logger):
+    def test_003_args_defaults(self, logger):
         arg = 'test arg'
 
         @logwrap.logwrap
@@ -133,7 +133,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_args_complex(self, logger):
+    def test_004_args_complex(self, logger):
         string = 'string'
         dictionary = {'key': 'dictionary'}
 
@@ -173,7 +173,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_args_kwargs(self, logger):
+    def test_005_args_kwargs(self, logger):
         targs = ['string1', 'string2']
         tkwargs = {'key': 'tkwargs'}
 
@@ -213,7 +213,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_renamed_args_kwargs(self, logger):
+    def test_006_renamed_args_kwargs(self, logger):
         arg = 'arg'
         targs = ['string1', 'string2']
         tkwargs = {'key': 'tkwargs'}
@@ -259,7 +259,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_negative(self, logger):
+    def test_007_negative(self, logger):
         @logwrap.logwrap
         def func():
             raise ValueError('as expected')
@@ -282,7 +282,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_negative_substitutions(self, logger):
+    def test_008_negative_substitutions(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -314,7 +314,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_spec(self, logger):
+    def test_009_spec(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -351,7 +351,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_indent(self, logger):
+    def test_010_indent(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -382,7 +382,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_method(self, logger):
+    def test_011_method(self, logger):
         class Tst(object):
             @logwrap.logwrap
             def func(tst_self):
@@ -413,7 +413,7 @@ class TestLogWrap(unittest.TestCase):
             ]
         )
 
-    def test_class_decorator(self, logger):
+    def test_012_class_decorator(self, logger):
         @logwrap.LogWrap
         def func():
             return 'No args'
@@ -439,7 +439,7 @@ class TestLogWrap(unittest.TestCase):
         six.PY3,
         'Strict python 3 syntax'
     )
-    def test_py3_args(self, logger):
+    def test_013_py3_args(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -488,7 +488,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_wrapped(self, logger):
+    def test_014_wrapped(self, logger):
         # noinspection PyShadowingNames
         def simpledeco(func):
             @six.wraps(func)
@@ -543,7 +543,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_args_blacklist(self, logger):
+    def test_015_args_blacklist(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -584,7 +584,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_exceptions_blacklist(self, logger):
+    def test_016_exceptions_blacklist(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -607,7 +607,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_disable_args(self, logger):
+    def test_017_disable_args(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -637,7 +637,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_disable_args_exc(self, logger):
+    def test_018_disable_args_exc(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -686,7 +686,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_disable_all_args(self, logger):
+    def test_019_disable_all_args(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -722,7 +722,7 @@ def tst(arg, darg=1, *args, kwarg, dkwarg=4, **kwargs):
             ]
         )
 
-    def test_disable_result(self, logger):
+    def test_020_disable_result(self, logger):
         new_logger = mock.Mock(spec=logging.Logger, name='logger')
         log = mock.Mock(name='log')
         new_logger.attach_mock(log, 'log')
@@ -801,3 +801,81 @@ class TestObject(unittest.TestCase):
             ),
             repr(log_call),
         )
+
+
+# noinspection PyUnusedLocal,PyMissingOrEmptyDocstring
+@mock.patch('logwrap._log_wrap_shared.logger', autospec=True)
+class TestDeprecation(unittest.TestCase):
+    def test_001_args_func(self, logger):
+        new_logger = mock.Mock(spec=logging.Logger, name='logger')
+        log = mock.Mock(name='log')
+        new_logger.attach_mock(log, 'log')
+
+        arg = 'test arg'
+
+        with mock.patch('warnings.warn') as warn:
+            @logwrap.logwrap(
+                new_logger,
+            )
+            def func(*args, **kwargs):
+                return args[0] if args else kwargs.get('arg', arg)
+
+            self.assertTrue(bool(warn.mock_calls))
+
+        result = func()
+        self.assertEqual(result, arg)
+        self.assertEqual(
+            log.mock_calls,
+            [
+                mock.call(
+                    level=logging.DEBUG,
+                    msg="Calling: \n"
+                        "'func'(\n"
+                        "    # VAR_POSITIONAL:\n"
+                        "    'args'=(),\n"
+                        "    # VAR_KEYWORD:\n"
+                        "    'kwargs'={},\n"
+                        ")"),
+                mock.call(
+                    level=logging.DEBUG,
+                    msg="Done: 'func' with result:\n"
+                        "u'''test arg'''"),
+            ]
+        )
+
+    def test_002_args_cls(self, logger):
+        new_logger = mock.Mock(spec=logging.Logger, name='logger')
+        log = mock.Mock(name='log')
+        new_logger.attach_mock(log, 'log')
+
+        arg = 'test arg'
+
+        with mock.patch('warnings.warn') as warn:
+            @logwrap.LogWrap(
+                new_logger,
+            )
+            def func(*args, **kwargs):
+                return args[0] if args else kwargs.get('arg', arg)
+
+            self.assertTrue(bool(warn.mock_calls))
+
+        result = func()
+        self.assertEqual(result, arg)
+        self.assertEqual(
+            log.mock_calls,
+            [
+                mock.call(
+                    level=logging.DEBUG,
+                    msg="Calling: \n"
+                        "'func'(\n"
+                        "    # VAR_POSITIONAL:\n"
+                        "    'args'=(),\n"
+                        "    # VAR_KEYWORD:\n"
+                        "    'kwargs'={},\n"
+                        ")"),
+                mock.call(
+                    level=logging.DEBUG,
+                    msg="Done: 'func' with result:\n"
+                        "u'''test arg'''"),
+            ]
+        )