Repository: sanic-org/sanic-ext
Pull request #121 (2022-08-11 06:54:22)

fix: injecting async dependencies
Replaces #115 


Patch:
diff --git a/sanic_ext/extensions/injection/constructor.py b/sanic_ext/extensions/injection/constructor.py
index 43c7570..8669bf0 100644
--- a/sanic_ext/extensions/injection/constructor.py
+++ b/sanic_ext/extensions/injection/constructor.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from inspect import isawaitable
+from inspect import iscoroutine
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -44,7 +44,7 @@ class Constructor:
             if self.pass_kwargs:
                 args.update(kwargs)
             retval = self.func(request, **args)
-            if isawaitable(retval):
+            if iscoroutine(retval):
                 retval = await retval
             return retval
         except TypeError as e:
@@ -136,6 +136,6 @@ async def do_cast(_type, constructor, request, **kwargs):
     args = [request] if constructor else []
 
     retval = cast(*args, **kwargs)
-    if isawaitable(retval):
+    if iscoroutine(retval):
         retval = await retval
     return retval


Test patch:
diff --git a/tests/extensions/injection/test_add_dependency.py b/tests/extensions/injection/test_add_dependency.py
index 5237ecf..8885cda 100644
--- a/tests/extensions/injection/test_add_dependency.py
+++ b/tests/extensions/injection/test_add_dependency.py
@@ -38,6 +38,14 @@ class Person:
         return cls(person_id=PersonID(person_id), name="noname", age=111)
 
 
+@dataclass
+class AsyncName:
+    name: str
+
+    def __await__(self):
+        return self
+
+
 counter = count()
 
 
@@ -235,3 +243,40 @@ def test_injection_on_websocket(app):
 
     request, response = app.test_client.websocket("/foo")
     assert ev.is_set()
+
+
+def test_injection_of_awaitable_variable_in_do_cast(app):
+    """Test for do_cast() iscoroutine() check"""
+
+    @app.get("/person/<name:str>")
+    def handler(request, name: AsyncName):
+        request.ctx.name = name
+        return text(name.name)
+
+    app.ext.add_dependency(AsyncName)
+
+    request, response = app.test_client.get("/person/george")
+
+    assert response.body == b"george"
+    assert isinstance(request.ctx.name, AsyncName)
+    assert request.ctx.name.name == "george"
+
+
+def test_injection_of_awaitable_variable_in_call(app):
+    """Test for __call__() iscoroutine() check"""
+
+    @app.get("/person/<name:str>")
+    def handler(request, name: AsyncName):
+        request.ctx.name = name
+        return text(name.name)
+
+    def test():
+        return AsyncName("george")
+
+    app.ext.dependency(test())
+
+    request, response = app.test_client.get("/person/george")
+
+    assert response.body == b"george"
+    assert isinstance(request.ctx.name, AsyncName)
+    assert request.ctx.name.name == "george"
diff --git a/tests/extensions/openapi/test_parameter.py b/tests/extensions/openapi/test_parameter.py
index 68c61a9..43e5c10 100644
--- a/tests/extensions/openapi/test_parameter.py
+++ b/tests/extensions/openapi/test_parameter.py
@@ -79,6 +79,20 @@ def test_parameter(app: Sanic):
     async def handler5(request: Request, val1: int):
         return text("ok")
 
+    @app.route("/test6/<val1:strorempty>")
+    async def handler6(request: Request, val1: str):
+        """
+        openapi:
+        ---
+        operationId: get.test1
+        parameters:
+          - name: val1
+            in: path
+            description: val1 path param
+            required: false
+        """
+        return text("ok")
+
     spec = get_spec(app)
     for i in range(1, 6):
         assert f"/test{i}/{{val1}}" in spec["paths"]
@@ -88,3 +102,10 @@ def test_parameter(app: Sanic):
         assert parameter["required"] is True
         assert parameter["schema"]["type"] == TYPE
         assert parameter["description"] == DESCRIPTION
+
+    assert "/test6/{val1}" in spec["paths"]
+    parameter = spec["paths"]["/test6/{val1}"]["get"]["parameters"][0]
+    assert parameter["name"] == NAME
+    assert parameter["in"] == LOCATION
+    assert parameter["required"] is False
+    assert parameter["description"] == DESCRIPTION