Repository: huge-success/sanic
Pull request #1481 (2019-01-29 10:56:34)

sanic#1480 Allow negative int/number in path
I would like to be able to use possibly negative int and number values in routes.

This passes tests in:

* python 3.5.6;  with two skips in test_app.py
* 3.6.8; also with two skips in test_app.py; and;
* 3.7.2 without any skips.

I've rerun ``make beautify`` on my newly added tests.

codecov[bot]: # [Codecov](https://codecov.io/gh/huge-success/sanic/pull/1481?src=pr&el=h1) Report
> Merging [#1481](https://codecov.io/gh/huge-success/sanic/pull/1481?src=pr&el=desc) into [master](https://codecov.io/gh/huge-success/sanic/commit/bc7d0f0da57e6b0d6c7d57ca3bb9d0dda66a7199?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/huge-success/sanic/pull/1481/graphs/tree.svg?width=650&token=NqLbJHUqoR&height=150&src=pr)](https://codecov.io/gh/huge-success/sanic/pull/1481?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1481   +/-   ##
=======================================
  Coverage   91.44%   91.44%           
=======================================
  Files          17       17           
  Lines        1730     1730           
  Branches      328      328           
=======================================
  Hits         1582     1582           
  Misses        123      123           
  Partials       25       25
```


| [Impacted Files](https://codecov.io/gh/huge-success/sanic/pull/1481?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sanic/router.py](https://codecov.io/gh/huge-success/sanic/pull/1481/diff?src=pr&el=tree#diff-c2FuaWMvcm91dGVyLnB5) | `95.89% <ø> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/huge-success/sanic/pull/1481?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/huge-success/sanic/pull/1481?src=pr&el=footer). Last update [bc7d0f0...b752255](https://codecov.io/gh/huge-success/sanic/pull/1481?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/sanic/streaming.md b/docs/sanic/streaming.md
index 53eebad7..769d269d 100644
--- a/docs/sanic/streaming.md
+++ b/docs/sanic/streaming.md
@@ -42,7 +42,7 @@ async def handler(request):
 
 
 @bp.put('/bp_stream', stream=True)
-async def bp_handler(request):
+async def bp_put_handler(request):
     result = ''
     while True:
         body = await request.stream.read()
@@ -52,6 +52,19 @@ async def bp_handler(request):
     return text(result)
 
 
+# You can also use `bp.add_route()` with stream argument
+async def bp_post_handler(request):
+    result = ''
+    while True:
+        body = await request.stream.read()
+        if body is None:
+            break
+        result += body.decode('utf-8').replace('1', 'A')
+    return text(result)
+
+bp.add_route(bp_post_handler, '/bp_stream', methods=['POST'], stream=True)
+
+
 async def post_handler(request):
     result = ''
     while True:
diff --git a/sanic/blueprints.py b/sanic/blueprints.py
index 5945c3ae..f2575e57 100644
--- a/sanic/blueprints.py
+++ b/sanic/blueprints.py
@@ -212,6 +212,7 @@ class Blueprint:
         strict_slashes=None,
         version=None,
         name=None,
+        stream=False,
     ):
         """Create a blueprint route from a function.
 
@@ -224,6 +225,7 @@ class Blueprint:
             training */*
         :param version: Blueprint Version
         :param name: user defined route name for url_for
+        :param stream: boolean specifying if the handler is a stream handler
         :return: function or class instance
         """
         # Handle HTTPMethodView differently
@@ -246,6 +248,7 @@ class Blueprint:
             methods=methods,
             host=host,
             strict_slashes=strict_slashes,
+            stream=stream,
             version=version,
             name=name,
         )(handler)
diff --git a/sanic/response.py b/sanic/response.py
index 7b245a82..43e24877 100644
--- a/sanic/response.py
+++ b/sanic/response.py
@@ -117,7 +117,7 @@ class StreamingHTTPResponse(BaseHTTPResponse):
 
         headers = self._parse_headers()
 
-        if self.status is 200:
+        if self.status == 200:
             status = b"OK"
         else:
             status = STATUS_CODES.get(self.status)
@@ -176,7 +176,7 @@ class HTTPResponse(BaseHTTPResponse):
 
         headers = self._parse_headers()
 
-        if self.status is 200:
+        if self.status == 200:
             status = b"OK"
         else:
             status = STATUS_CODES.get(self.status, b"UNKNOWN RESPONSE")
diff --git a/sanic/router.py b/sanic/router.py
index 6bb3360c..dcf3aeb5 100644
--- a/sanic/router.py
+++ b/sanic/router.py
@@ -17,8 +17,8 @@ Parameter = namedtuple("Parameter", ["name", "cast"])
 
 REGEX_TYPES = {
     "string": (str, r"[^/]+"),
-    "int": (int, r"\d+"),
-    "number": (float, r"[0-9\\.]+"),
+    "int": (int, r"-?\d+"),
+    "number": (float, r"-?[0-9\\.]+"),
     "alpha": (str, r"[A-Za-z]+"),
     "path": (str, r"[^/].*?"),
     "uuid": (


Test patch:
diff --git a/tests/test_request_stream.py b/tests/test_request_stream.py
index bf8bf7d4..42f2852c 100644
--- a/tests/test_request_stream.py
+++ b/tests/test_request_stream.py
@@ -270,6 +270,18 @@ def test_request_stream_blueprint(app):
 
         return stream(streaming)
 
+    async def post_add_route(request):
+        assert isinstance(request.stream, StreamBuffer)
+
+        async def streaming(response):
+            while True:
+                body = await request.stream.read()
+                if body is None:
+                    break
+                await response.write(body.decode("utf-8"))
+        return stream(streaming)
+
+    bp.add_route(post_add_route, '/post/add_route', methods=['POST'], stream=True)
     app.blueprint(bp)
 
     assert app.is_request_stream is True
@@ -314,6 +326,10 @@ def test_request_stream_blueprint(app):
     assert response.status == 200
     assert response.text == data
 
+    request, response = app.test_client.post("/post/add_route", data=data)
+    assert response.status == 200
+    assert response.text == data
+
 
 def test_request_stream_composition_view(app):
     """for self.is_request_stream = True"""
diff --git a/tests/test_url_building.py b/tests/test_url_building.py
index 6145b064..d752c8a3 100644
--- a/tests/test_url_building.py
+++ b/tests/test_url_building.py
@@ -170,11 +170,27 @@ def test_fails_with_int_message(app):
 
     expected_error = (
         'Value "not_int" for parameter `foo` '
-        "does not match pattern for type `int`: \d+"
+        "does not match pattern for type `int`: -?\d+"
     )
     assert str(e.value) == expected_error
 
 
+def test_passes_with_negative_int_message(app):
+    @app.route("path/<possibly_neg:int>/another-word")
+    def good(request, possibly_neg):
+        assert isinstance(possibly_neg, int)
+        return text("this should pass with `{}`".format(possibly_neg))
+
+    u_plus_3 = app.url_for("good", possibly_neg=3)
+    assert u_plus_3 == "/path/3/another-word", u_plus_3
+    request, response = app.test_client.get(u_plus_3)
+    assert response.text == "this should pass with `3`"
+    u_neg_3 = app.url_for("good", possibly_neg=-3)
+    assert u_neg_3 == "/path/-3/another-word", u_neg_3
+    request, response = app.test_client.get(u_neg_3)
+    assert response.text == "this should pass with `-3`"
+
+
 def test_fails_with_two_letter_string_message(app):
     @app.route(COMPLEX_PARAM_URL)
     def fail(request):
@@ -207,12 +223,26 @@ def test_fails_with_number_message(app):
 
     expected_error = (
         'Value "foo" for parameter `some_number` '
-        "does not match pattern for type `float`: [0-9\\\\.]+"
+        "does not match pattern for type `float`: -?[0-9\\\\.]+"
     )
 
     assert str(e.value) == expected_error
 
 
+@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")
+    def good(request, possibly_neg):
+        assert isinstance(possibly_neg, (int, float))
+        return text("this should pass with `{}`".format(possibly_neg))
+
+    u = app.url_for("good", possibly_neg=number)
+    assert u == "/path/{}/another-word".format(number), u
+    request, response = app.test_client.get(u)
+    # For ``number``, it has been cast to a float - so a ``3`` becomes a ``3.0``
+    assert response.text == "this should pass with `{}`".format(float(number))
+
+
 def test_adds_other_supplied_values_as_query_string(app):
     @app.route(COMPLEX_PARAM_URL)
     def passes(request):