azurefunctions-extensions-base/azurefunctions/extensions/base/web.py (131 lines of code) (raw):
import abc
import inspect
from abc import abstractmethod
from enum import Enum
from typing import Callable
base_extension_module = __name__
# Base extension pkg
class ModuleTrackerMeta(type):
_module = None
def __new__(cls, name, bases, dct, **kwargs):
new_class = super().__new__(cls, name, bases, dct)
new_module = dct.get("__module__")
if new_module != base_extension_module:
if cls._module is None:
cls._module = new_module
elif cls._module != new_module:
raise Exception(
f"Only one web extension package shall be imported, "
f"{cls._module} and {new_module} are imported"
)
return new_class
@classmethod
def get_module(cls):
return cls._module
@classmethod
def module_imported(cls):
return cls._module is not None
class RequestTrackerMeta(type):
_request_type = None
_synchronizer: None
def __new__(cls, name, bases, dct, **kwargs):
new_class = super().__new__(cls, name, bases, dct)
request_type = dct.get("request_type")
if request_type is None:
raise TypeError(f"Request type not provided for class {name}")
if cls._request_type is not None and cls._request_type != request_type:
raise TypeError(
f"Only one request type shall be recorded for class {name} "
f"but found {cls._request_type} and {request_type}"
)
cls._request_type = request_type
cls._synchronizer = dct.get("synchronizer")
if cls._synchronizer is None:
raise TypeError(f"Request synchronizer not provided for class {name}")
return new_class
@classmethod
def get_request_type(cls):
return cls._request_type
@classmethod
def get_synchronizer(cls):
return cls._synchronizer
@classmethod
def check_type(cls, pytype: type) -> bool:
if pytype is not None and inspect.isclass(pytype):
return cls._request_type is not None and issubclass(
pytype, cls._request_type
)
return False
class RequestSynchronizer(abc.ABC):
@abstractmethod
def sync_route_params(self, request, path_params):
raise NotImplementedError()
class ResponseTrackerMeta(type):
_response_types = {}
def __new__(cls, name, bases, dct, **kwargs):
new_class = super().__new__(cls, name, bases, dct)
label = dct.get("label")
response_type = dct.get("response_type")
if label is None:
raise TypeError(f"Response label not provided for class {name}")
if response_type is None:
raise TypeError(f"Response type not provided for class {name}")
if (
cls._response_types.get(label) is not None
and cls._response_types.get(label) != response_type
):
raise TypeError(
f"Only one response type shall be recorded for class {name} "
f"but found {cls._response_types.get(label)} and {response_type}"
)
cls._response_types[label] = response_type
return new_class
@classmethod
def get_standard_response_type(cls):
return cls.get_response_type(ResponseLabels.STANDARD)
@classmethod
def get_response_type(cls, label):
return cls._response_types.get(label)
@classmethod
def check_type(cls, pytype: type) -> bool:
if pytype is not None and inspect.isclass(pytype):
return cls._response_types is not None and any(
issubclass(pytype, response_type)
for response_type in cls._response_types.values()
)
return False
class WebApp(metaclass=ModuleTrackerMeta):
@abstractmethod
def route(self, func: Callable):
raise NotImplementedError()
@abstractmethod
def get_app(self):
raise NotImplementedError() # pragma: no cover
class WebServer(metaclass=ModuleTrackerMeta):
def __init__(self, hostname, port, web_app: WebApp):
self.hostname = hostname
self.port = port
self.web_app = web_app.get_app()
@abstractmethod
async def serve(self):
raise NotImplementedError() # pragma: no cover
class HttpV2FeatureChecker:
@staticmethod
def http_v2_enabled():
return ModuleTrackerMeta.module_imported()
class ResponseLabels(Enum):
STANDARD = "standard"
STREAMING = "streaming"
FILE = "file"
HTML = "html"
JSON = "json"
ORJSON = "orjson"
PLAIN_TEXT = "plain_text"
REDIRECT = "redirect"
UJSON = "ujson"
INT = "int"
FLOAT = "float"
STR = "str"
LIST = "list"
DICT = "dict"
BOOL = "bool"
PYDANTIC = "pydantic"