Skip to content

Commit 529c3af

Browse files
Convert pystein asgi to use handle_async (#145)
* convert asgi to use async handle * refactor * remove externalhttpapp class * fix mypy * fix mypy --------- Co-authored-by: gavin-aguiar <[email protected]>
1 parent 093825e commit 529c3af

File tree

4 files changed

+84
-74
lines changed

4 files changed

+84
-74
lines changed

azure/functions/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
Cardinality, AccessRights, HttpMethod,
1212
AsgiFunctionApp, WsgiFunctionApp)
1313
from ._durable_functions import OrchestrationContext, EntityContext
14-
from .decorators.function_app import (FunctionRegister, TriggerApi, BindingApi,
15-
ExternalHttpFunctionApp)
14+
from .decorators.function_app import (FunctionRegister, TriggerApi,
15+
BindingApi)
1616
from .extension import (ExtensionMeta, FunctionExtensionException,
1717
FuncExtensionBase, AppExtensionBase)
1818
from ._http_wsgi import WsgiMiddleware
@@ -85,7 +85,6 @@
8585
'TriggerApi',
8686
'BindingApi',
8787
'Blueprint',
88-
'ExternalHttpFunctionApp',
8988
'AsgiFunctionApp',
9089
'WsgiFunctionApp',
9190
'DataType',

azure/functions/decorators/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .core import Cardinality, AccessRights
44
from .function_app import FunctionApp, Function, DecoratorApi, DataType, \
55
AuthLevel, Blueprint, AsgiFunctionApp, WsgiFunctionApp, \
6-
ExternalHttpFunctionApp, FunctionRegister, TriggerApi, BindingApi
6+
FunctionRegister, TriggerApi, BindingApi
77
from .http import HttpMethod
88

99
__all__ = [
@@ -14,7 +14,6 @@
1414
'TriggerApi',
1515
'BindingApi',
1616
'Blueprint',
17-
'ExternalHttpFunctionApp',
1817
'AsgiFunctionApp',
1918
'WsgiFunctionApp',
2019
'DataType',

azure/functions/decorators/function_app.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,46 +1726,57 @@ class Blueprint(TriggerApi, BindingApi):
17261726
pass
17271727

17281728

1729-
class ExternalHttpFunctionApp(FunctionRegister, TriggerApi, ABC):
1730-
"""Interface to extend for building third party http function apps."""
1729+
class AsgiFunctionApp(FunctionRegister, TriggerApi):
1730+
def __init__(self, app,
1731+
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
1732+
"""Constructor of :class:`AsgiFunctionApp` object.
17311733
1732-
def _add_http_app(self,
1733-
http_middleware: Union[
1734-
AsgiMiddleware, WsgiMiddleware],
1735-
http_type: str) -> None:
1736-
"""Add a Wsgi or Asgi app integrated http function.
1734+
:param app: asgi app object.
1735+
:param http_auth_level: Determines what keys, if any, need to be
1736+
present
1737+
on the request in order to invoke the function.
1738+
"""
1739+
super().__init__(auth_level=http_auth_level)
1740+
self._add_http_app(AsgiMiddleware(app))
1741+
1742+
def _add_http_app(self, asgi_middleware: AsgiMiddleware) -> None:
1743+
"""Add an Asgi app integrated http function.
17371744
1738-
:param http_middleware: :class:`AsgiMiddleware` or
1739-
:class:`WsgiMiddleware` instance.
1745+
:param asgi_middleware: :class:`AsgiMiddleware` instance.
17401746
17411747
:return: None
17421748
"""
17431749

1744-
@self.http_type(http_type=http_type)
1750+
@self.http_type(http_type='asgi')
17451751
@self.route(methods=(method for method in HttpMethod),
17461752
auth_level=self.auth_level,
17471753
route="/{*route}")
1748-
def http_app_func(req: HttpRequest, context: Context):
1749-
return http_middleware.handle(req, context)
1754+
async def http_app_func(req: HttpRequest, context: Context):
1755+
return await asgi_middleware.handle_async(req, context)
17501756

17511757

1752-
class AsgiFunctionApp(ExternalHttpFunctionApp):
1758+
class WsgiFunctionApp(FunctionRegister, TriggerApi):
17531759
def __init__(self, app,
17541760
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
1755-
"""Constructor of :class:`AsgiFunctionApp` object.
1761+
"""Constructor of :class:`WsgiFunctionApp` object.
17561762
1757-
:param app: asgi app object.
1763+
:param app: wsgi app object.
17581764
"""
17591765
super().__init__(auth_level=http_auth_level)
1760-
self._add_http_app(AsgiMiddleware(app), 'asgi')
1766+
self._add_http_app(WsgiMiddleware(app))
17611767

1768+
def _add_http_app(self,
1769+
wsgi_middleware: WsgiMiddleware) -> None:
1770+
"""Add a Wsgi app integrated http function.
17621771
1763-
class WsgiFunctionApp(ExternalHttpFunctionApp):
1764-
def __init__(self, app,
1765-
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
1766-
"""Constructor of :class:`WsgiFunctionApp` object.
1772+
:param wsgi_middleware: :class:`WsgiMiddleware` instance.
17671773
1768-
:param app: wsgi app object.
1774+
:return: None
17691775
"""
1770-
super().__init__(auth_level=http_auth_level)
1771-
self._add_http_app(WsgiMiddleware(app), 'wsgi')
1776+
1777+
@self.http_type(http_type='wsgi')
1778+
@self.route(methods=(method for method in HttpMethod),
1779+
auth_level=self.auth_level,
1780+
route="/{*route}")
1781+
def http_app_func(req: HttpRequest, context: Context):
1782+
return wsgi_middleware.handle(req, context)

tests/decorators/test_function_app.py

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3+
import inspect
34
import json
45
import unittest
56
from unittest import mock
@@ -302,8 +303,6 @@ def test_add_asgi(self, add_http_app_mock):
302303
self.assertIsInstance(add_http_app_mock.call_args[0][0],
303304
AsgiMiddleware)
304305

305-
self.assertEqual(add_http_app_mock.call_args[0][1], 'asgi')
306-
307306
@mock.patch('azure.functions.decorators.function_app.WsgiFunctionApp'
308307
'._add_http_app')
309308
def test_add_wsgi(self, add_http_app_mock):
@@ -313,52 +312,12 @@ def test_add_wsgi(self, add_http_app_mock):
313312
add_http_app_mock.assert_called_once()
314313
self.assertIsInstance(add_http_app_mock.call_args[0][0],
315314
WsgiMiddleware)
316-
self.assertEqual(add_http_app_mock.call_args[0][1], 'wsgi')
317-
318-
def test_add_http_app(self):
319-
app = AsgiFunctionApp(app=object())
320-
funcs = app.get_functions()
321-
self.assertEqual(len(funcs), 1)
322-
func = funcs[0]
323315

324-
self.assertEqual(func.get_function_name(), "http_app_func")
325-
326-
raw_bindings = func.get_raw_bindings()
327-
raw_trigger = raw_bindings[0]
328-
raw_output_binding = raw_bindings[0]
316+
def test_add_asgi_app(self):
317+
self._test_http_external_app(AsgiFunctionApp(app=object()), True)
329318

330-
self.assertEqual(json.loads(raw_trigger),
331-
json.loads(
332-
'{"direction": "IN", "type": "httpTrigger", '
333-
'"authLevel": "FUNCTION", "route": "/{*route}", '
334-
'"methods": ["GET", "POST", "DELETE", "HEAD", '
335-
'"PATCH", "PUT", "OPTIONS"], "name": "req"}'))
336-
self.assertEqual(json.loads(raw_output_binding), json.loads(
337-
'{"direction": "IN", "type": "httpTrigger", "authLevel": '
338-
'"FUNCTION", "methods": ["GET", "POST", "DELETE", "HEAD", '
339-
'"PATCH", "PUT", "OPTIONS"], "name": "req", "route": "/{'
340-
'*route}"}'))
341-
342-
self.assertEqual(func.get_bindings_dict(), {
343-
"bindings": [
344-
{
345-
"authLevel": AuthLevel.FUNCTION,
346-
"direction": BindingDirection.IN,
347-
"methods": [HttpMethod.GET, HttpMethod.POST,
348-
HttpMethod.DELETE,
349-
HttpMethod.HEAD,
350-
HttpMethod.PATCH,
351-
HttpMethod.PUT, HttpMethod.OPTIONS],
352-
"name": "req",
353-
"route": "/{*route}",
354-
"type": HTTP_TRIGGER
355-
},
356-
{
357-
"direction": BindingDirection.OUT,
358-
"name": "$return",
359-
"type": HTTP_OUTPUT
360-
}
361-
]})
319+
def test_add_wsgi_app(self):
320+
self._test_http_external_app(WsgiFunctionApp(app=object()), False)
362321

363322
def test_register_function_app_error(self):
364323
with self.assertRaises(TypeError) as err:
@@ -567,3 +526,45 @@ def test_wsgi_function_app_is_http_function(self):
567526

568527
self.assertEqual(len(funcs), 1)
569528
self.assertTrue(funcs[0].is_http_function())
529+
530+
def _test_http_external_app(self, app, is_async):
531+
funcs = app.get_functions()
532+
self.assertEqual(len(funcs), 1)
533+
func = funcs[0]
534+
self.assertEqual(func.get_function_name(), "http_app_func")
535+
raw_bindings = func.get_raw_bindings()
536+
raw_trigger = raw_bindings[0]
537+
raw_output_binding = raw_bindings[0]
538+
self.assertEqual(inspect.iscoroutinefunction(func.get_user_function()),
539+
is_async)
540+
self.assertEqual(json.loads(raw_trigger),
541+
json.loads(
542+
'{"direction": "IN", "type": "httpTrigger", '
543+
'"authLevel": "FUNCTION", "route": "/{*route}", '
544+
'"methods": ["GET", "POST", "DELETE", "HEAD", '
545+
'"PATCH", "PUT", "OPTIONS"], "name": "req"}'))
546+
self.assertEqual(json.loads(raw_output_binding), json.loads(
547+
'{"direction": "IN", "type": "httpTrigger", "authLevel": '
548+
'"FUNCTION", "methods": ["GET", "POST", "DELETE", "HEAD", '
549+
'"PATCH", "PUT", "OPTIONS"], "name": "req", "route": "/{'
550+
'*route}"}'))
551+
self.assertEqual(func.get_bindings_dict(), {
552+
"bindings": [
553+
{
554+
"authLevel": AuthLevel.FUNCTION,
555+
"direction": BindingDirection.IN,
556+
"methods": [HttpMethod.GET, HttpMethod.POST,
557+
HttpMethod.DELETE,
558+
HttpMethod.HEAD,
559+
HttpMethod.PATCH,
560+
HttpMethod.PUT, HttpMethod.OPTIONS],
561+
"name": "req",
562+
"route": "/{*route}",
563+
"type": HTTP_TRIGGER
564+
},
565+
{
566+
"direction": BindingDirection.OUT,
567+
"name": "$return",
568+
"type": HTTP_OUTPUT
569+
}
570+
]})

0 commit comments

Comments
 (0)