Source code for spectree.response

from http import HTTPStatus
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union

from ._pydantic import BaseModel
from ._types import BaseModelSubclassType, ModelType, NamingStrategy, OptionalModelType
from .utils import gen_list_model, get_model_key, parse_code

# according to https://tools.ietf.org/html/rfc2616#section-10
# https://tools.ietf.org/html/rfc7231#section-6.1
# https://developer.mozilla.org/sv-SE/docs/Web/HTTP/Status
DEFAULT_CODE_DESC: Dict[str, str] = dict(
    (f"HTTP_{status.value}", f"{status.phrase}") for status in HTTPStatus
)
# additional status codes
for code, phrase in [
    ("HTTP_418", "I'm a teapot"),
    ("HTTP_425", "Too Early"),
]:
    DEFAULT_CODE_DESC[code] = phrase


[docs] class Response: """ response object :param codes: list of HTTP status code, format('HTTP_[0-9]{3}'), 'HTTP_200' :param code_models: dict of <HTTP status code>: <`pydantic.BaseModel`> or None or a two element tuple of (<`pydantic.BaseModel`> or None) as the first item and a custom status code description string as the second item. examples: >>> from typing import List >>> from spectree.response import Response >>> from pydantic import BaseModel ... >>> class User(BaseModel): ... id: int ... >>> response = Response("HTTP_200") >>> response = Response(HTTP_200=None) >>> response = Response(HTTP_200=User) >>> response = Response(HTTP_200=(User, "status code description")) >>> response = Response(HTTP_200=List[User]) >>> response = Response(HTTP_200=(List[User], "status code description")) """
[docs] def __init__( self, *codes: str, **code_models: Union[ OptionalModelType, Tuple[OptionalModelType, str], Type[List[BaseModelSubclassType]], Tuple[Type[List[BaseModelSubclassType]], str], ], ) -> None: self.codes: List[str] = [] for code in codes: assert code in DEFAULT_CODE_DESC, "invalid HTTP status code" self.codes.append(code) self.code_models: Dict[str, ModelType] = {} self.code_descriptions: Dict[str, Optional[str]] = {} self.code_list_item_types: Dict[str, ModelType] = {} for code, model_and_description in code_models.items(): assert code in DEFAULT_CODE_DESC, "invalid HTTP status code" description: Optional[str] = None if isinstance(model_and_description, tuple): assert len(model_and_description) == 2, ( "unexpected number of arguments for a tuple of " "`pydantic.BaseModel` and HTTP status code description" ) model = model_and_description[0] description = model_and_description[1] else: model = model_and_description if model: origin_type = getattr(model, "__origin__", None) if origin_type is list or origin_type is List: # type is List[BaseModel] list_item_type = model.__args__[0] # type: ignore model = gen_list_model(list_item_type) self.code_list_item_types[code] = list_item_type assert issubclass(model, BaseModel), "invalid `pydantic.BaseModel`" assert description is None or isinstance( description, str ), "invalid HTTP status code description" self.code_models[code] = model else: self.codes.append(code) if description: self.code_descriptions[code] = description
[docs] def add_model( self, code: int, model: ModelType, replace: bool = True, description: Optional[str] = None, ) -> None: """Add data *model* for the specified status *code*. :param code: An HTTP status code. :param model: A `pydantic.BaseModel`. :param replace: If `True` and a data *model* already exists for the given status *code* it will be replaced, if `False` the existing data *model* will be retained. :param description: The description string for the code. """ if not replace and self.find_model(code): return code_name: str = f"HTTP_{code}" self.code_models[code_name] = model if description: self.code_descriptions[code_name] = description
[docs] def has_model(self) -> bool: """ :returns: boolean -- does this response has models or not """ return bool(self.code_models)
[docs] def find_model(self, code: int) -> OptionalModelType: """ :param code: ``r'\\d{3}'`` """ return self.code_models.get(f"HTTP_{code}")
[docs] def expect_list_result(self, code: int) -> bool: """Check whether a specific HTTP code expects a list result. :param code: Status code (example: 200) """ return f"HTTP_{code}" in self.code_list_item_types
[docs] def get_expected_list_item_type(self, code: int) -> ModelType: """Get the expected list result item type. :param code: Status code (example: 200) """ return self.code_list_item_types[f"HTTP_{code}"]
[docs] def get_code_description(self, code: str) -> str: """Get the description of the given status code. :param code: Status code string, format('HTTP_[0-9]_{3}'), 'HTTP_200'. :returns: The status code's description. """ return self.code_descriptions.get(code) or DEFAULT_CODE_DESC[code]
@property def models(self) -> Iterable[ModelType]: """ :returns: dict_values -- all the models in this response """ return self.code_models.values()
[docs] def generate_spec( self, naming_strategy: NamingStrategy = get_model_key ) -> Dict[str, Any]: """ generate the spec for responses :returns: JSON """ responses: Dict[str, Any] = {} for code in self.codes: responses[parse_code(code)] = { "description": self.get_code_description(code) } for code, model in self.code_models.items(): model_name = naming_strategy(model) responses[parse_code(code)] = { "description": self.get_code_description(code), "content": { "application/json": { "schema": {"$ref": f"#/components/schemas/{model_name}"} } }, } return responses