Skip to content

Add message when ASGI and WSGI is enabled #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 42 additions & 22 deletions azure/functions/_http_asgi.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import asyncio
from typing import Callable, Dict, List, Tuple, Optional, Any, Union
from typing import Dict, List, Tuple, Optional, Any, Union
import logging
import asyncio
from wsgiref.headers import Headers

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


class AsgiMiddleware:
"""This middleware is to adapt an ASGI supported Python server
framework into Azure Functions. It can be used by either calling the
.handle() function or exposing the .main property in a HttpTrigger.
"""
_logger = logging.getLogger('azure.functions.AsgiMiddleware')
_usage_reported = False

def __init__(self, app):
logging.debug("Instantiating ASGI middleware.")
"""Instantiate an ASGI middleware to convert Azure Functions HTTP
request into ASGI Python object. Example on handling ASGI app in a HTTP
trigger by overwriting the .main() method:

import azure.functions as func

from FastapiApp import app

main = func.AsgiMiddleware(app).main
"""
if not self._usage_reported:
self._logger.info("Instantiating Azure Functions ASGI middleware.")
self._usage_reported = True

self._app = app
self.loop = asyncio.new_event_loop()
logging.debug("asyncio event loop initialized.")

# Usage
# main = func.AsgiMiddleware(app).main
@property
def main(self) -> Callable[[HttpRequest, Context], HttpResponse]:
return self._handle

# Usage
# return func.AsgiMiddleware(app).handle(req, context)
def handle(
self, req: HttpRequest, context: Optional[Context] = None
) -> HttpResponse:
logging.info(f"Handling {req.url} as ASGI request.")
self._loop = asyncio.new_event_loop()
self.main = self._handle

def handle(self, req: HttpRequest, context: Optional[Context] = None):
"""Method to convert an Azure Functions HTTP request into a ASGI
Python object. Example on handling ASGI app in a HTTP trigger by
calling .handle() in .main() method:

import azure.functions as func

from FastapiApp import app

def main(req, context):
return func.AsgiMiddleware(app).handle(req, context)
"""
self._logger.debug(f"Handling {req.url} as an ASGI request.")
return self._handle(req, context)

def _handle(self, req: HttpRequest,
context: Optional[Context]) -> HttpResponse:
def _handle(self, req, context):
asgi_request = AsgiRequest(req, context)
asyncio.set_event_loop(self.loop)
asyncio.set_event_loop(self._loop)
scope = asgi_request.to_asgi_http_scope()
asgi_response = self.loop.run_until_complete(
asgi_response = self._loop.run_until_complete(
AsgiResponse.from_app(self._app, scope, req.get_body())
)

Expand Down
52 changes: 37 additions & 15 deletions azure/functions/_http_wsgi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

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


class WsgiMiddleware:
"""This middleware is to adapt a WSGI supported Python server
framework into Azure Functions. It can be used by either calling the
.handle() function or exposing the .main property in a HttpTrigger.
"""
_logger = logging.getLogger('azure.functions.WsgiMiddleware')
_usage_reported = False

def __init__(self, app):
"""Instantiate a WSGI middleware to convert Azure Functions HTTP
request into WSGI Python object. Example on handling WSGI app in a HTTP
trigger by overwriting the .main() method:

import azure.functions as func

from FlaskApp import app

main = func.WsgiMiddleware(app.wsgi_app).main
"""
if not self._usage_reported:
self._logger.info("Instantiating Azure Functions WSGI middleware.")
self._usage_reported = True

self._app = app
self._wsgi_error_buffer = StringIO()
self.main = self._handle

def handle(self, req: HttpRequest, context: Optional[Context] = None):
"""Method to convert an Azure Functions HTTP request into a WSGI
Python object. Example on handling WSGI app in a HTTP trigger by
calling .handle() in .main() method:

import azure.functions as func

from FlaskApp import app

# Usage
# main = func.WsgiMiddleware(app).main
@property
def main(self) -> Callable[[HttpRequest, Context], HttpResponse]:
return self._handle

# Usage
# return func.WsgiMiddleware(app).handle(req, context)
def handle(self,
req: HttpRequest,
context: Optional[Context] = None) -> HttpResponse:
def main(req, context):
return func.WsgiMiddleware(app.wsgi_app).handle(req, context)
"""
return self._handle(req, context)

def _handle(self,
req: HttpRequest,
context: Optional[Context]) -> HttpResponse:
def _handle(self, req, context):
wsgi_request = WsgiRequest(req, context)
environ = wsgi_request.to_environ(self._wsgi_error_buffer)
wsgi_response = WsgiResponse.from_app(self._app, environ)
Expand Down
24 changes: 24 additions & 0 deletions tests/test_http_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ def test_middleware_calls_app(self):
self.assertEqual(response.get_body(), test_body)

def test_middleware_calls_app_with_context(self):
"""Test if the middleware can be used by exposing the .handle method,
specifically when the middleware is used as
def main(req, context):
return AsgiMiddleware(app).handle(req, context)
"""
app = MockAsgiApplication()
test_body = b'Hello world!'
app.response_body = test_body
Expand All @@ -153,3 +158,22 @@ def test_middleware_calls_app_with_context(self):
# Verify asserted
self.assertEqual(response.status_code, 200)
self.assertEqual(response.get_body(), test_body)

def test_middleware_wrapper(self):
"""Test if the middleware can be used by exposing the .main property,
specifically when the middleware is used as
main = AsgiMiddleware(app).main
"""
app = MockAsgiApplication()
test_body = b'Hello world!'
app.response_body = test_body
app.response_code = 200
req = self._generate_func_request()
ctx = self._generate_func_context()

main = AsgiMiddleware(app).main
response = main(req, ctx)

# Verify asserted
self.assertEqual(response.status_code, 200)
self.assertEqual(response.get_body(), test_body)
19 changes: 19 additions & 0 deletions tests/test_http_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,29 @@ def test_response_with_exception(self):
self.assertEqual(e.exception.message, 'wsgi excpt')

def test_middleware_handle(self):
"""Test if the middleware can be used by exposing the .handle method,
specifically when the middleware is used as
def main(req, context):
return WsgiMiddleware(app).handle(req, context)
"""
app = self._generate_wsgi_app()
func_request = self._generate_func_request()
func_response = WsgiMiddleware(app).handle(func_request)
self.assertEqual(func_response.status_code, 200)
self.assertEqual(func_response.get_body(), b'sample string')

def test_middleware_wrapper(self):
"""Test if the middleware can be used by exposing the .main property,
specifically when the middleware is used as
main = WsgiMiddleware(app).main
"""
app = self._generate_wsgi_app()
main = WsgiMiddleware(app).main
func_request = self._generate_func_request()
func_context = self._generate_func_context()
func_response = main(func_request, func_context)
self.assertEqual(func_response.status_code, 200)
self.assertEqual(func_response.get_body(), b'sample string')

def _generate_func_request(
self,
Expand Down