Repository: falconry/falcon
Pull request #1487 (2019-03-28 08:24:34)

refactor(API): add a compatibility shim to support the Falcon<=1.4.1 error handler signature

codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1487?src=pr&el=h1) Report
> Merging [#1487](https://codecov.io/gh/falconry/falcon/pull/1487?src=pr&el=desc) into [master](https://codecov.io/gh/falconry/falcon/commit/54e227dd7aa884e855337ecfa05f147efaa46741?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/falconry/falcon/pull/1487/graphs/tree.svg?width=650&token=zOtkfNQ3h6&height=150&src=pr)](https://codecov.io/gh/falconry/falcon/pull/1487?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master   #1487   +/-   ##
======================================
  Coverage     100%    100%           
======================================
  Files          39      39           
  Lines        2613    2613           
  Branches      416     416           
======================================
  Hits         2613    2613
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1487?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/falconry/falcon/pull/1487?src=pr&el=footer). Last update [54e227d...c8d62c4](https://codecov.io/gh/falconry/falcon/pull/1487?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/falcon/api.py b/falcon/api.py
index 287c4b3..e74a3bf 100644
--- a/falcon/api.py
+++ b/falcon/api.py
@@ -16,6 +16,7 @@
 
 """Falcon API class."""
 
+from functools import wraps
 import re
 
 from falcon import api_helpers as helpers, DEFAULT_MEDIA_TYPE, routing
@@ -25,7 +26,7 @@ from falcon.request import Request, RequestOptions
 import falcon.responders
 from falcon.response import Response, ResponseOptions
 import falcon.status_codes as status
-from falcon.util import compat
+from falcon.util import compat, misc
 
 
 # PERF(vytas): on Python 2.7+, Python 3.5+ (including cythonized modules),
@@ -552,6 +553,11 @@ class API(object):
                 a single type, the handler must be explicitly specified.
 
         """
+        def wrap_old_handler(old_handler):
+            @wraps(old_handler)
+            def handler(req, resp, ex, params):
+                old_handler(ex, req, resp, params)
+            return handler
 
         if handler is None:
             try:
@@ -562,6 +568,12 @@ class API(object):
                                      'method named "handle" that is a '
                                      'member of the given exception class.')
 
+        # TODO(vytas): Remove this shimming in a future Falcon version.
+        arg_names = tuple(misc.get_argnames(handler))
+        if (arg_names[0:1] in (('e',), ('err',), ('error',), ('ex',), ('exception',)) or
+                arg_names[1:3] in (('req', 'resp'), ('request', 'response'))):
+            handler = wrap_old_handler(handler)
+
         try:
             exception_tuple = tuple(exception)
         except TypeError:


Test patch:
diff --git a/tests/test_error_handlers.py b/tests/test_error_handlers.py
index b21cb33..b0c37d0 100644
--- a/tests/test_error_handlers.py
+++ b/tests/test_error_handlers.py
@@ -146,3 +146,26 @@ class TestErrorHandler(object):
     def test_invalid_add_exception_handler_input(self, client, exceptions):
         with pytest.raises(TypeError):
             client.app.add_error_handler(exceptions, capture_error)
+
+    def test_handler_signature_shim(self, client):
+        def check_args(ex, req, resp):
+            assert isinstance(ex, BaseException)
+            assert isinstance(req, falcon.Request)
+            assert isinstance(resp, falcon.Response)
+
+        def legacy_handler1(ex, req, resp, params):
+            check_args(ex, req, resp)
+
+        def legacy_handler2(error_obj, request, response, params):
+            check_args(error_obj, request, response)
+
+        def legacy_handler3(err, rq, rs, prms):
+            check_args(err, rq, rs)
+
+        client.app.add_error_handler(Exception, legacy_handler1)
+        client.app.add_error_handler(CustomBaseException, legacy_handler2)
+        client.app.add_error_handler(CustomException, legacy_handler3)
+
+        client.simulate_delete()
+        client.simulate_get()
+        client.simulate_head()