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