Repository: sanic-org/sanic

Issue:
Handler would be modify after wrapped by `app.route`
**Is your feature request related to a problem? Please describe.**

Handler would be modify after wrapped by `app.route`

```python
from sanic import Sanic
from sanic.response import text

app = Sanic("test")


@app.get("/")
def get(request):
    """
    here is my doc
    """
    return text("123")

print(get.__doc__)
print(get.__name__)
```
Original function has been changed into tuple.

```sh
Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple.
If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.
Traceback (most recent call last):
  File "/Users/zinklu/code/WebProject/SanicDebug/app.py", line 22, in <module>
    print(get.__name__)
AttributeError: 'tuple' object has no attribute '__name__'
```


**Describe the solution you'd like**

`app.route` won't change original handler.

```python
from sanic import Sanic
from sanic.response import text

app = Sanic("test")


@app.get("/")
def get(request):
    """
    here is my doc
    """
    return text("123")

print(get.__doc__)
print(get.__name__)
```

```sh

    here is my doc
    
get
```

**Additional context**

Handler would be modify after wrapped by `app.route`
**Is your feature request related to a problem? Please describe.**

Handler would be modify after wrapped by `app.route`

```python
from sanic import Sanic
from sanic.response import text

app = Sanic("test")


@app.get("/")
def get(request):
    """
    here is my doc
    """
    return text("123")

print(get.__doc__)
print(get.__name__)
```
Original function has been changed into tuple.

```sh
Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple.
If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.
Traceback (most recent call last):
  File "/Users/zinklu/code/WebProject/SanicDebug/app.py", line 22, in <module>
    print(get.__name__)
AttributeError: 'tuple' object has no attribute '__name__'
```


**Describe the solution you'd like**

`app.route` won't change original handler.

```python
from sanic import Sanic
from sanic.response import text

app = Sanic("test")


@app.get("/")
def get(request):
    """
    here is my doc
    """
    return text("123")

print(get.__doc__)
print(get.__name__)
```

```sh

    here is my doc
    
get
```

**Additional context**



Discussion:

Resolving patch:
diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py
--- a/sanic/mixins/routes.py
+++ b/sanic/mixins/routes.py
@@ -160,7 +160,9 @@ def decorator(handler):
             if apply:
                 self._apply_route(route)
 
-            return route, handler
+            if static:
+                return route, handler
+            return handler
 
         return decorator
 
diff --git a/sanic/signals.py b/sanic/signals.py
--- a/sanic/signals.py
+++ b/sanic/signals.py
@@ -48,7 +48,7 @@ def get(  # type: ignore
                 f".{event}",
                 self.DEFAULT_METHOD,
                 self,
-                {"__params__": {}},
+                {"__params__": {}, "__matches__": {}},
                 extra=extra,
             )
         except NotFound:
@@ -59,7 +59,13 @@ def get(  # type: ignore
                 terms.append(extra)
             raise NotFound(message % tuple(terms))
 
-        params = param_basket.pop("__params__")
+        params = param_basket["__params__"]
+        if not params:
+            params = {
+                param.name: param_basket["__matches__"][idx]
+                for idx, param in group.params.items()
+            }
+
         return group, [route.handler for route in group], params
 
     async def _dispatch(
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -83,7 +83,7 @@ def open_local(paths, mode="r", encoding="utf8"):
 uvloop = "uvloop>=0.5.3" + env_dependency
 
 requirements = [
-    "sanic-routing>=0.6.0",
+    "sanic-routing==0.7.0rc1",
     "httptools>=0.0.10",
     uvloop,
     ujson,


Test patch:
diff --git a/tests/test_named_routes.py b/tests/test_named_routes.py
--- a/tests/test_named_routes.py
+++ b/tests/test_named_routes.py
@@ -234,7 +234,7 @@ async def handler(request, name):
         app.router.routes_all[
             (
                 "folder",
-                "<name>",
+                "<name:str>",
             )
         ].name
         == "app.route_dynamic"
@@ -369,7 +369,8 @@ async def handler(request, name):
 
     app.add_route(handler, "/folder/<name>", name="route_dynamic")
     assert (
-        app.router.routes_all[("folder", "<name>")].name == "app.route_dynamic"
+        app.router.routes_all[("folder", "<name:str>")].name
+        == "app.route_dynamic"
     )
     assert app.url_for("route_dynamic", name="test") == "/folder/test"
     with pytest.raises(URLBuildError):
diff --git a/tests/test_requests.py b/tests/test_requests.py
--- a/tests/test_requests.py
+++ b/tests/test_requests.py
@@ -2246,9 +2246,7 @@ async def delete(request, foo):
 
 
 def test_handler_overload(app):
-    @app.get(
-        "/long/sub/route/param_a/<param_a:string>/param_b/<param_b:string>"
-    )
+    @app.get("/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>")
     @app.post("/long/sub/route/")
     def handler(request, **kwargs):
         return json(kwargs)
diff --git a/tests/test_routes.py b/tests/test_routes.py
--- a/tests/test_routes.py
+++ b/tests/test_routes.py
@@ -258,7 +258,7 @@ def handler2(request):
 def test_route_invalid_parameter_syntax(app):
     with pytest.raises(ValueError):
 
-        @app.get("/get/<:string>", strict_slashes=True)
+        @app.get("/get/<:str>", strict_slashes=True)
         def handler(request):
             return text("OK")
 
@@ -478,7 +478,7 @@ async def handler(request, name):
 def test_dynamic_route_string(app):
     results = []
 
-    @app.route("/folder/<name:string>")
+    @app.route("/folder/<name:str>")
     async def handler(request, name):
         results.append(name)
         return text("OK")
@@ -513,7 +513,7 @@ async def handler(request, folder_id):
 def test_dynamic_route_number(app):
     results = []
 
-    @app.route("/weight/<weight:number>")
+    @app.route("/weight/<weight:float>")
     async def handler(request, weight):
         results.append(weight)
         return text("OK")
@@ -585,7 +585,6 @@ async def handler(request, path):
         return text("OK")
 
     app.router.finalize()
-    print(app.router.find_route_src)
 
     request, response = app.test_client.get("/path/1/info")
     assert response.status == 200
@@ -824,7 +823,7 @@ async def handler(request, name):
         results.append(name)
         return text("OK")
 
-    app.add_route(handler, "/folder/<name:string>")
+    app.add_route(handler, "/folder/<name:str>")
     request, response = app.test_client.get("/folder/test123")
 
     assert response.text == "OK"
@@ -860,7 +859,7 @@ async def handler(request, weight):
         results.append(weight)
         return text("OK")
 
-    app.add_route(handler, "/weight/<weight:number>")
+    app.add_route(handler, "/weight/<weight:float>")
 
     request, response = app.test_client.get("/weight/12345")
     assert response.text == "OK"
@@ -1067,7 +1066,8 @@ async def ad_post(request, action):
         return json({"action": action})
 
     request, response = app.test_client.get("/ads/1234")
-    assert response.status == 405
+    assert response.status == 200
+    assert response.json == {"ad_id": "1234"}
 
     request, response = app.test_client.post("/ads/post")
     assert response.status == 200
diff --git a/tests/test_url_building.py b/tests/test_url_building.py
--- a/tests/test_url_building.py
+++ b/tests/test_url_building.py
@@ -143,7 +143,7 @@ def fail(request):
 
 COMPLEX_PARAM_URL = (
     "/<foo:int>/<four_letter_string:[A-z]{4}>/"
-    "<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>"
+    "<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:float>"
 )
 PASSING_KWARGS = {
     "foo": 4,
@@ -168,7 +168,7 @@ def fail(request):
 
     expected_error = (
         r'Value "not_int" for parameter `foo` '
-        r"does not match pattern for type `int`: ^-?\d+"
+        r"does not match pattern for type `int`: ^-?\d+$"
     )
     assert str(e.value) == expected_error
 
@@ -223,7 +223,7 @@ def fail(request):
 
 @pytest.mark.parametrize("number", [3, -3, 13.123, -13.123])
 def test_passes_with_negative_number_message(app, number):
-    @app.route("path/<possibly_neg:number>/another-word")
+    @app.route("path/<possibly_neg:float>/another-word")
     def good(request, possibly_neg):
         assert isinstance(possibly_neg, (int, float))
         return text(f"this should pass with `{possibly_neg}`")