Repository: huge-success/sanic
Pull request #1483 (2019-01-31 14:24:31)

sanic-zipkin
add zipkin plugin.

Features:
- adding "Request span" by default
- if Request is from another micro-service endpoint, span will be attached (Inject/Extract) to that endpoint
- use "logger" decorator to create span for "methods" calls
- use "sz_rpc" method to create sub-span for RPC calls, attaching to parent span


Patch:
diff --git a/docs/sanic/extensions.md b/docs/sanic/extensions.md
index d3653069..d27735cb 100644
--- a/docs/sanic/extensions.md
+++ b/docs/sanic/extensions.md
@@ -67,6 +67,7 @@ A list of Sanic extensions created by the community.
 ## Monitoring and Reporting
 
 - [sanic-prometheus](https://github.com/dkruchinin/sanic-prometheus): Prometheus metrics for Sanic
+- [sanic-zipkin](https://github.com/kevinqqnj/sanic-zipkin): Easily report request/function/RPC traces to zipkin/jaeger, through aiozipkin.
 
 
 ## Sample Applications
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):