Skip to content

Commit 0d4ad6e

Browse files
Add ExternalHttpFunctionApp (#176)
* Add ExternalHttpFunctionApp * fix err * fix type hint * fix linting * fix linting * fix linting * add test * declare as abstract * make externalhttpapp methods raise unimplementederror * fix err --------- Co-authored-by: Bill Wang <[email protected]>
1 parent 2b4cbd5 commit 0d4ad6e

File tree

3 files changed

+72
-12
lines changed

3 files changed

+72
-12
lines changed

azure/functions/decorators/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# Licensed under the MIT License.
33
from .core import Cardinality, AccessRights
44
from .function_app import FunctionApp, Function, DecoratorApi, DataType, \
5-
AuthLevel, Blueprint, AsgiFunctionApp, WsgiFunctionApp, \
6-
FunctionRegister, TriggerApi, BindingApi
5+
AuthLevel, Blueprint, ExternalHttpFunctionApp, AsgiFunctionApp, \
6+
WsgiFunctionApp, FunctionRegister, TriggerApi, BindingApi
77
from .http import HttpMethod
88

99
__all__ = [
@@ -14,6 +14,7 @@
1414
'TriggerApi',
1515
'BindingApi',
1616
'Blueprint',
17+
'ExternalHttpFunctionApp',
1718
'AsgiFunctionApp',
1819
'WsgiFunctionApp',
1920
'DataType',

azure/functions/decorators/function_app.py

Lines changed: 41 additions & 9 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 abc
34
import json
45
import logging
56
from abc import ABC
@@ -254,8 +255,7 @@ def app_script_file(self) -> str:
254255
return self._app_script_file
255256

256257
def _validate_type(self,
257-
func: Union[Callable[..., Any],
258-
FunctionBuilder]) \
258+
func: Union[Callable[..., Any], FunctionBuilder]) \
259259
-> FunctionBuilder:
260260
"""Validate the type of the function object and return the created
261261
:class:`FunctionBuilder` object.
@@ -817,7 +817,7 @@ def cosmos_db_trigger_v3(self,
817817
lease_collection_name=lease_collection_name,
818818
lease_connection_string_setting=lease_connection_string_setting,
819819
lease_database_name=lease_database_name,
820-
create_lease_collection_if_not_exists=create_lease_collection_if_not_exists, # NoQA
820+
create_lease_collection_if_not_exists=create_lease_collection_if_not_exists, # NoQA
821821
leases_collection_throughput=leases_collection_throughput,
822822
lease_collection_prefix=lease_collection_prefix,
823823
checkpoint_interval=checkpoint_interval,
@@ -2014,7 +2014,24 @@ class Blueprint(TriggerApi, BindingApi):
20142014
pass
20152015

20162016

2017-
class AsgiFunctionApp(FunctionRegister, TriggerApi):
2017+
class ExternalHttpFunctionApp(FunctionRegister, TriggerApi, ABC):
2018+
"""Interface to extend for building third party http function apps."""
2019+
2020+
@abc.abstractmethod
2021+
def _add_http_app(self,
2022+
http_middleware: Union[
2023+
AsgiMiddleware, WsgiMiddleware]) -> None:
2024+
"""Add a Wsgi or Asgi app integrated http function.
2025+
2026+
:param http_middleware: :class:`WsgiMiddleware`
2027+
or class:`AsgiMiddleware` instance.
2028+
2029+
:return: None
2030+
"""
2031+
raise NotImplementedError()
2032+
2033+
2034+
class AsgiFunctionApp(ExternalHttpFunctionApp):
20182035
def __init__(self, app,
20192036
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
20202037
"""Constructor of :class:`AsgiFunctionApp` object.
@@ -2027,13 +2044,21 @@ def __init__(self, app,
20272044
super().__init__(auth_level=http_auth_level)
20282045
self._add_http_app(AsgiMiddleware(app))
20292046

2030-
def _add_http_app(self, asgi_middleware: AsgiMiddleware) -> None:
2047+
def _add_http_app(self,
2048+
http_middleware: Union[
2049+
AsgiMiddleware, WsgiMiddleware]) -> None:
20312050
"""Add an Asgi app integrated http function.
20322051
2033-
:param asgi_middleware: :class:`AsgiMiddleware` instance.
2052+
:param http_middleware: :class:`WsgiMiddleware`
2053+
or class:`AsgiMiddleware` instance.
20342054
20352055
:return: None
20362056
"""
2057+
if not isinstance(http_middleware, AsgiMiddleware):
2058+
raise TypeError("Please pass AsgiMiddleware instance"
2059+
" as parameter.")
2060+
2061+
asgi_middleware: AsgiMiddleware = http_middleware
20372062

20382063
@self.http_type(http_type='asgi')
20392064
@self.route(methods=(method for method in HttpMethod),
@@ -2043,7 +2068,7 @@ async def http_app_func(req: HttpRequest, context: Context):
20432068
return await asgi_middleware.handle_async(req, context)
20442069

20452070

2046-
class WsgiFunctionApp(FunctionRegister, TriggerApi):
2071+
class WsgiFunctionApp(ExternalHttpFunctionApp):
20472072
def __init__(self, app,
20482073
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION):
20492074
"""Constructor of :class:`WsgiFunctionApp` object.
@@ -2054,13 +2079,20 @@ def __init__(self, app,
20542079
self._add_http_app(WsgiMiddleware(app))
20552080

20562081
def _add_http_app(self,
2057-
wsgi_middleware: WsgiMiddleware) -> None:
2082+
http_middleware: Union[
2083+
AsgiMiddleware, WsgiMiddleware]) -> None:
20582084
"""Add a Wsgi app integrated http function.
20592085
2060-
:param wsgi_middleware: :class:`WsgiMiddleware` instance.
2086+
:param http_middleware: :class:`WsgiMiddleware`
2087+
or class:`AsgiMiddleware` instance.
20612088
20622089
:return: None
20632090
"""
2091+
if not isinstance(http_middleware, WsgiMiddleware):
2092+
raise TypeError("Please pass WsgiMiddleware instance"
2093+
" as parameter.")
2094+
2095+
wsgi_middleware: WsgiMiddleware = http_middleware
20642096

20652097
@self.http_type(http_type='wsgi')
20662098
@self.route(methods=(method for method in HttpMethod),

tests/decorators/test_function_app.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import unittest
66
from unittest import mock
7+
from unittest.mock import patch
78

89
from azure.functions import WsgiMiddleware, AsgiMiddleware
910
from azure.functions.decorators.constants import HTTP_OUTPUT, HTTP_TRIGGER, \
@@ -12,7 +13,8 @@
1213
BindingDirection, SCRIPT_FILE_NAME
1314
from azure.functions.decorators.function_app import FunctionBuilder, \
1415
FunctionApp, Function, Blueprint, DecoratorApi, AsgiFunctionApp, \
15-
WsgiFunctionApp, HttpFunctionsAuthLevelMixin, FunctionRegister, TriggerApi
16+
WsgiFunctionApp, HttpFunctionsAuthLevelMixin, FunctionRegister, \
17+
TriggerApi, ExternalHttpFunctionApp
1618
from azure.functions.decorators.http import HttpTrigger, HttpOutput, \
1719
HttpMethod
1820
from tests.decorators.test_core import DummyTrigger
@@ -527,6 +529,31 @@ def test_wsgi_function_app_is_http_function(self):
527529
self.assertEqual(len(funcs), 1)
528530
self.assertTrue(funcs[0].is_http_function())
529531

532+
def test_asgi_function_app_add_wsgi_app(self):
533+
with self.assertRaises(TypeError) as err:
534+
app = AsgiFunctionApp(app=object(),
535+
http_auth_level=AuthLevel.ANONYMOUS)
536+
app._add_http_app(WsgiMiddleware(object()))
537+
538+
self.assertEqual(err.exception.args[0],
539+
"Please pass AsgiMiddleware instance as parameter.")
540+
541+
def test_wsgi_function_app_add_asgi_app(self):
542+
with self.assertRaises(TypeError) as err:
543+
app = WsgiFunctionApp(app=object(),
544+
http_auth_level=AuthLevel.ANONYMOUS)
545+
app._add_http_app(AsgiMiddleware(object()))
546+
547+
self.assertEqual(err.exception.args[0],
548+
"Please pass WsgiMiddleware instance as parameter.")
549+
550+
@patch("azure.functions.decorators.function_app.ExternalHttpFunctionApp"
551+
".__abstractmethods__", set())
552+
def test_external_http_function_app(self):
553+
with self.assertRaises(NotImplementedError):
554+
app = ExternalHttpFunctionApp(auth_level=AuthLevel.ANONYMOUS)
555+
app._add_http_app(AsgiMiddleware(object()))
556+
530557
def _test_http_external_app(self, app, is_async):
531558
funcs = app.get_functions()
532559
self.assertEqual(len(funcs), 1)

0 commit comments

Comments
 (0)