Repository: sanic-org/sanic

Issue:
A signal handler cannot be a method on an object
**Describe the bug**
<!-- A clear and concise description of what the bug is, make sure to paste any exceptions and tracebacks. -->
If I try and register a method on an object as a signal handler, an exception is thrown because it is not possible to set an attribute on a bound method
```
    self.add_signal(self.after_routing, 'http.routing.after')
  File "sanic/lib/python3.7/site-packages/sanic/mixins/signals.py", line 77, in add_signal
    handler
  File "sanic/lib/python3.7/site-packages/sanic/mixins/signals.py", line 57, in decorator
    self._apply_signal(future_signal)
  File "sanic/lib/python3.7/site-packages/sanic/app.py", line 420, in _apply_signal
    return self.signal_router.add(*signal)
  File "sanic/lib/python3.7/site-packages/sanic/signals.py", line 222, in add
    handler.__requirements__ = condition  # type: ignore
AttributeError: 'method' object has no attribute '__requirements__'
```


**Code snippet**
<!-- Relevant source code, make sure to remove what is not necessary. -->
```python
import sanic

class MyApp(sanic.Sanic):
  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.add_signal(self.after_routing, 'http.routing.after')

  def after_routing(self, request, route, kwargs, handler):
    pass

app = MyApp(name='method_signal_handler')
```

**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
I would expect the bound method to be registered, and then called when the signal is dispatched.

**Environment (please complete the following information):**
<!-- Please provide the information below. Instead, you can copy and paste the message that Sanic shows on startup. If you do, please remember to format it with ``` -->
 - OS: MacOS
 - Sanic Version: 22.9


**Additional context**
<!-- Add any other context about the problem here. -->



Discussion:
Added code tags for readability.

Resolving patch:
diff --git a/sanic/signals.py b/sanic/signals.py
--- a/sanic/signals.py
+++ b/sanic/signals.py
@@ -154,9 +154,7 @@ async def _dispatch(
         try:
             for signal in signals:
                 params.pop("__trigger__", None)
-                requirements = getattr(
-                    signal.handler, "__requirements__", None
-                )
+                requirements = signal.extra.requirements
                 if (
                     (condition is None and signal.ctx.exclusive is False)
                     or (condition is None and not requirements)
@@ -219,8 +217,13 @@ def add(  # type: ignore
         if not trigger:
             event = ".".join([*parts[:2], "<__trigger__>"])
 
-        handler.__requirements__ = condition  # type: ignore
-        handler.__trigger__ = trigger  # type: ignore
+        try:
+            # Attaching __requirements__ and __trigger__ to the handler
+            # is deprecated and will be removed in v23.6.
+            handler.__requirements__ = condition  # type: ignore
+            handler.__trigger__ = trigger  # type: ignore
+        except AttributeError:
+            pass
 
         signal = super().add(
             event,
@@ -232,6 +235,7 @@ def add(  # type: ignore
         signal.ctx.exclusive = exclusive
         signal.ctx.trigger = trigger
         signal.ctx.definition = event_definition
+        signal.extra.requirements = condition
 
         return cast(Signal, signal)
 


Test patch: