Skip to content

Commit 800a75b

Browse files
authored
Add message when ASGI and WSGI is enabled (#88)
* Fix annotation too strict issue in wrapper * Add logging in WSGI and ASGI middleware * Add docstring * Add docstrings in test cases
1 parent 4eb1e45 commit 800a75b

File tree

4 files changed

+122
-37
lines changed

4 files changed

+122
-37
lines changed

azure/functions/_http_asgi.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
import asyncio
5-
from typing import Callable, Dict, List, Tuple, Optional, Any, Union
4+
from typing import Dict, List, Tuple, Optional, Any, Union
65
import logging
6+
import asyncio
77
from wsgiref.headers import Headers
88

99
from ._abc import Context
@@ -104,32 +104,52 @@ async def _send(self, message):
104104

105105

106106
class AsgiMiddleware:
107+
"""This middleware is to adapt an ASGI supported Python server
108+
framework into Azure Functions. It can be used by either calling the
109+
.handle() function or exposing the .main property in a HttpTrigger.
110+
"""
111+
_logger = logging.getLogger('azure.functions.AsgiMiddleware')
112+
_usage_reported = False
113+
107114
def __init__(self, app):
108-
logging.debug("Instantiating ASGI middleware.")
115+
"""Instantiate an ASGI middleware to convert Azure Functions HTTP
116+
request into ASGI Python object. Example on handling ASGI app in a HTTP
117+
trigger by overwriting the .main() method:
118+
119+
import azure.functions as func
120+
121+
from FastapiApp import app
122+
123+
main = func.AsgiMiddleware(app).main
124+
"""
125+
if not self._usage_reported:
126+
self._logger.info("Instantiating Azure Functions ASGI middleware.")
127+
self._usage_reported = True
128+
109129
self._app = app
110-
self.loop = asyncio.new_event_loop()
111-
logging.debug("asyncio event loop initialized.")
112-
113-
# Usage
114-
# main = func.AsgiMiddleware(app).main
115-
@property
116-
def main(self) -> Callable[[HttpRequest, Context], HttpResponse]:
117-
return self._handle
118-
119-
# Usage
120-
# return func.AsgiMiddleware(app).handle(req, context)
121-
def handle(
122-
self, req: HttpRequest, context: Optional[Context] = None
123-
) -> HttpResponse:
124-
logging.info(f"Handling {req.url} as ASGI request.")
130+
self._loop = asyncio.new_event_loop()
131+
self.main = self._handle
132+
133+
def handle(self, req: HttpRequest, context: Optional[Context] = None):
134+
"""Method to convert an Azure Functions HTTP request into a ASGI
135+
Python object. Example on handling ASGI app in a HTTP trigger by
136+
calling .handle() in .main() method:
137+
138+
import azure.functions as func
139+
140+
from FastapiApp import app
141+
142+
def main(req, context):
143+
return func.AsgiMiddleware(app).handle(req, context)
144+
"""
145+
self._logger.debug(f"Handling {req.url} as an ASGI request.")
125146
return self._handle(req, context)
126147

127-
def _handle(self, req: HttpRequest,
128-
context: Optional[Context]) -> HttpResponse:
148+
def _handle(self, req, context):
129149
asgi_request = AsgiRequest(req, context)
130-
asyncio.set_event_loop(self.loop)
150+
asyncio.set_event_loop(self._loop)
131151
scope = asgi_request.to_asgi_http_scope()
132-
asgi_response = self.loop.run_until_complete(
152+
asgi_response = self._loop.run_until_complete(
133153
AsgiResponse.from_app(self._app, scope, req.get_body())
134154
)
135155

azure/functions/_http_wsgi.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
from typing import Callable, Dict, List, Optional, Any
4+
from typing import Dict, List, Optional, Any
5+
import logging
56
from io import BytesIO, StringIO
67
from os import linesep
78
from urllib.parse import urlparse
@@ -142,26 +143,47 @@ def _start_response(self, status: str, response_headers: List[Any]):
142143

143144

144145
class WsgiMiddleware:
146+
"""This middleware is to adapt a WSGI supported Python server
147+
framework into Azure Functions. It can be used by either calling the
148+
.handle() function or exposing the .main property in a HttpTrigger.
149+
"""
150+
_logger = logging.getLogger('azure.functions.WsgiMiddleware')
151+
_usage_reported = False
152+
145153
def __init__(self, app):
154+
"""Instantiate a WSGI middleware to convert Azure Functions HTTP
155+
request into WSGI Python object. Example on handling WSGI app in a HTTP
156+
trigger by overwriting the .main() method:
157+
158+
import azure.functions as func
159+
160+
from FlaskApp import app
161+
162+
main = func.WsgiMiddleware(app.wsgi_app).main
163+
"""
164+
if not self._usage_reported:
165+
self._logger.info("Instantiating Azure Functions WSGI middleware.")
166+
self._usage_reported = True
167+
146168
self._app = app
147169
self._wsgi_error_buffer = StringIO()
170+
self.main = self._handle
171+
172+
def handle(self, req: HttpRequest, context: Optional[Context] = None):
173+
"""Method to convert an Azure Functions HTTP request into a WSGI
174+
Python object. Example on handling WSGI app in a HTTP trigger by
175+
calling .handle() in .main() method:
176+
177+
import azure.functions as func
178+
179+
from FlaskApp import app
148180
149-
# Usage
150-
# main = func.WsgiMiddleware(app).main
151-
@property
152-
def main(self) -> Callable[[HttpRequest, Context], HttpResponse]:
153-
return self._handle
154-
155-
# Usage
156-
# return func.WsgiMiddleware(app).handle(req, context)
157-
def handle(self,
158-
req: HttpRequest,
159-
context: Optional[Context] = None) -> HttpResponse:
181+
def main(req, context):
182+
return func.WsgiMiddleware(app.wsgi_app).handle(req, context)
183+
"""
160184
return self._handle(req, context)
161185

162-
def _handle(self,
163-
req: HttpRequest,
164-
context: Optional[Context]) -> HttpResponse:
186+
def _handle(self, req, context):
165187
wsgi_request = WsgiRequest(req, context)
166188
environ = wsgi_request.to_environ(self._wsgi_error_buffer)
167189
wsgi_response = WsgiResponse.from_app(self._app, environ)

tests/test_http_asgi.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ def test_middleware_calls_app(self):
142142
self.assertEqual(response.get_body(), test_body)
143143

144144
def test_middleware_calls_app_with_context(self):
145+
"""Test if the middleware can be used by exposing the .handle method,
146+
specifically when the middleware is used as
147+
def main(req, context):
148+
return AsgiMiddleware(app).handle(req, context)
149+
"""
145150
app = MockAsgiApplication()
146151
test_body = b'Hello world!'
147152
app.response_body = test_body
@@ -153,3 +158,22 @@ def test_middleware_calls_app_with_context(self):
153158
# Verify asserted
154159
self.assertEqual(response.status_code, 200)
155160
self.assertEqual(response.get_body(), test_body)
161+
162+
def test_middleware_wrapper(self):
163+
"""Test if the middleware can be used by exposing the .main property,
164+
specifically when the middleware is used as
165+
main = AsgiMiddleware(app).main
166+
"""
167+
app = MockAsgiApplication()
168+
test_body = b'Hello world!'
169+
app.response_body = test_body
170+
app.response_code = 200
171+
req = self._generate_func_request()
172+
ctx = self._generate_func_context()
173+
174+
main = AsgiMiddleware(app).main
175+
response = main(req, ctx)
176+
177+
# Verify asserted
178+
self.assertEqual(response.status_code, 200)
179+
self.assertEqual(response.get_body(), test_body)

tests/test_http_wsgi.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,29 @@ def test_response_with_exception(self):
164164
self.assertEqual(e.exception.message, 'wsgi excpt')
165165

166166
def test_middleware_handle(self):
167+
"""Test if the middleware can be used by exposing the .handle method,
168+
specifically when the middleware is used as
169+
def main(req, context):
170+
return WsgiMiddleware(app).handle(req, context)
171+
"""
167172
app = self._generate_wsgi_app()
168173
func_request = self._generate_func_request()
169174
func_response = WsgiMiddleware(app).handle(func_request)
170175
self.assertEqual(func_response.status_code, 200)
176+
self.assertEqual(func_response.get_body(), b'sample string')
177+
178+
def test_middleware_wrapper(self):
179+
"""Test if the middleware can be used by exposing the .main property,
180+
specifically when the middleware is used as
181+
main = WsgiMiddleware(app).main
182+
"""
183+
app = self._generate_wsgi_app()
184+
main = WsgiMiddleware(app).main
185+
func_request = self._generate_func_request()
186+
func_context = self._generate_func_context()
187+
func_response = main(func_request, func_context)
188+
self.assertEqual(func_response.status_code, 200)
189+
self.assertEqual(func_response.get_body(), b'sample string')
171190

172191
def _generate_func_request(
173192
self,

0 commit comments

Comments
 (0)