Repository: mplanchard/pydecor

Issue:
Support for decorating methods
Hello,

I just came across this package and contains several facilities that I find very useful to create expressive code, so I wanted to give it a try. However, when trying to use the "intercept" decorator on an instance method, it seems that the 'self' parameter gets swallowed, raising an error:

```
{TypeError}myfunction() missing 1 required positional argument: 'x'
```

Code example:
```
def _handle_exception(exception):
    exception_name = type(exception).__name__
    if exception_name == 'NoSuchKey':
        raise EntityNotFound('The specified key does not exist')
    else:
        raise exception

... skipping class definition and details...

@intercept(handler=_handle_exception)
def get_node_information(self, key):
     return self._key_to_meta(key)
```

Any workarounds for this case? Thanks!
 

Discussion:

Patch:
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 0b7b8da..5c67b4e 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -10,7 +10,9 @@ pylint;implementation_name=="cpython"
 
 coverage
 mock;python_version<"3.0"
-pytest
-pytest-cov
+pytest>=4.6
+pytest-cov;implementation_name=="cpython"
+pytest-cov==2.8.1;implementation_name=="pypy"
+pytest-cov==2.8.1;python_version<="3.7"
 tox
 wheel
diff --git a/src/pydecor/decorators/_utility.py b/src/pydecor/decorators/_utility.py
index cd04ffb..300d28b 100644
--- a/src/pydecor/decorators/_utility.py
+++ b/src/pydecor/decorators/_utility.py
@@ -59,7 +59,7 @@ def get_fn_args(decorated, args):
             # types)
             if decor_name in cls_dict and isfunction(cls_dict[decor_name]):
                 # The first argument is probably a "self" variable
-                fn_args = args[1:]
+                fn_args = args
 
     return fn_args
 


Test patch:
diff --git a/tests/decorators/test_generics.py b/tests/decorators/test_generics.py
index 6340048..af0e412 100644
--- a/tests/decorators/test_generics.py
+++ b/tests/decorators/test_generics.py
@@ -317,12 +317,13 @@ class TestBefore:
             def to_call_static(*args):
                 tracker.append({4: args})
 
-        _ToDecorate().to_call(1, 2)
+        instance = _ToDecorate()
+        instance.to_call(1, 2)
         _ToDecorate().to_call_cls(3, 4)
         _ToDecorate().to_call_static(5, 6)
 
         assert len(tracker) == 6
-        assert tracker[0] == {1: (1, 2)}
+        assert tracker[0] == {1: (instance, 1, 2)}
         assert tracker[1] == {2: (1, 2)}
         assert tracker[2] == {1: (3, 4)}
         assert tracker[3] == {3: (3, 4)}
@@ -606,13 +607,14 @@ class TestAfter:
             def to_call_static(*args):
                 tracker.append({4: args})
 
-        _ToDecorate().to_call(1, 2)
+        instance = _ToDecorate()
+        instance.to_call(1, 2)
         _ToDecorate().to_call_cls(3, 4)
         _ToDecorate().to_call_static(5, 6)
 
         assert len(tracker) == 6
         assert tracker[0] == {2: (1, 2)}
-        assert tracker[1] == {1: (1, 2)}
+        assert tracker[1] == {1: (instance, 1, 2)}
         assert tracker[2] == {3: (3, 4)}
         assert tracker[3] == {1: (3, 4)}
         assert tracker[4] == {4: (5, 6)}
@@ -890,12 +892,13 @@ class TestInstead:
             def to_call_static(*args):
                 tracker.append({4: args})
 
-        _ToDecorate().to_call(1, 2)
+        instance = _ToDecorate()
+        instance.to_call(1, 2)
         _ToDecorate().to_call_cls(3, 4)
         _ToDecorate().to_call_static(5, 6)
 
         assert len(tracker) == 3
-        assert tracker[0] == {1: (1, 2)}
+        assert tracker[0] == {1: (instance, 1, 2)}
         assert tracker[1] == {1: (3, 4)}
         assert tracker[2] == {1: (5, 6)}
 
diff --git a/tests/decorators/test_ready_to_wear.py b/tests/decorators/test_ready_to_wear.py
index b5cd57d..876b4a8 100644
--- a/tests/decorators/test_ready_to_wear.py
+++ b/tests/decorators/test_ready_to_wear.py
@@ -68,7 +68,25 @@ def test_intercept(raises, catch, reraise, include_handler):
     if handler is not None and not will_catch:
         handler.assert_not_called()
 
-    wrapped.assert_called_once_with(*(), **{})
+    wrapped.assert_called_once_with(*(), **{})  # type: ignore
+
+
+def test_intercept_method():
+    """Test decorating an instance method with intercept."""
+
+    calls = []
+
+    def _handler(exc):
+        calls.append(exc)
+
+    class SomeClass:
+        @intercept(handler=_handler)
+        def it_raises(self, val):
+            raise ValueError(val)
+
+    SomeClass().it_raises("a")
+    assert len(calls) == 1
+    assert isinstance(calls[0], ValueError)
 
 
 def test_log_call():
@@ -89,7 +107,7 @@ def test_log_call():
         name="func", args=call_args, kwargs=call_kwargs, result=call_res
     )
 
-    exp_logger.debug.assert_called_once_with(exp_msg)  # type: ignore
+    exp_logger.debug.assert_called_once_with(exp_msg)
 
 
 class TestMemoization:
diff --git a/tests/test_functions.py b/tests/test_functions.py
index 87015ec..ede8133 100644
--- a/tests/test_functions.py
+++ b/tests/test_functions.py
@@ -66,7 +66,7 @@ def test_interceptor(raises, catch, reraise, include_handler):
     if handler is not None and not will_catch:
         handler.assert_not_called()
 
-    wrapped.assert_called_once_with(*(), **{})
+    wrapped.assert_called_once_with(*(), **{})  # type: ignore
 
 
 def test_log_call():
@@ -88,4 +88,4 @@ def test_log_call():
         name="func", args=call_args, kwargs=call_kwargs, result=call_res
     )
 
-    exp_logger.debug.assert_called_once_with(exp_msg)  # type: ignore
+    exp_logger.debug.assert_called_once_with(exp_msg)