from __future__ import annotations
import re
from copy import deepcopy
from typing import TYPE_CHECKING, Any, Mapping
from apispec.ext.marshmallow import MarshmallowPlugin
from marshmallow import Schema
if TYPE_CHECKING:
from ..route import Route
__all__ = ["LambdaSpecPlugin"]
class LambdaSpecPlugin(MarshmallowPlugin):
@staticmethod
def _process_path(route: Route) -> str:
return re.sub(r"\(\?P<(?P.+?)>.+?\)", r"{\g}", route.pattern).strip("^$")
def _process_parameters(self, parameters_def: Mapping[str, Schema], parameters: list):
if self.resolver is None:
return
for location, schema in parameters_def.items():
parameters.extend(self.resolver.resolve_parameters([{"in": location, "schema": schema}]))
@staticmethod
def _process_extra(spec: Mapping[str, Any], operation: dict):
operation.update(spec)
@staticmethod
def _process_body(body_schema: Schema | list[Schema] | None, operation: dict):
if body_schema is None:
return
schema: Schema | dict[str, list[Schema]] | None = None
if isinstance(body_schema, list):
if len(body_schema) == 1:
schema = body_schema[0]
elif len(body_schema) > 1:
schema = {"oneOf": body_schema}
else:
schema = body_schema
operation["requestBody"] = {"content": {"application/json": {"schema": schema}}}
@staticmethod
def _process_responses(responses_spec: Mapping[str, Any], operation: dict):
_responses = {}
for code, response in responses_spec.items():
_responses[code] = {"description": response["description"]}
if response["schema"] is not None:
_responses[code]["content"] = {"application/json": {"schema": response["schema"]}}
operation["responses"] = _responses
def path_helper(
self,
path: str | None = None,
operations: dict | None = None,
parameters: list[dict] | None = None,
route: Route | None = None,
**kwargs: Any,
) -> str | None:
if route is None or operations is None or parameters is None:
return None
spec = deepcopy(route.handler.__apispec__)
if not spec["responses"]:
return None
operation: dict[str, Any] = {}
path = self._process_path(route)
request_spec = spec.pop("request", {})
self._process_body(request_spec.pop("body", None), operation)
self._process_parameters(request_spec, parameters)
self._process_responses(spec.pop("responses", {}), operation)
self._process_extra(spec, operation)
method = route.method.lower()
if method not in operations:
operations[method] = operation
return path