You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
4.4 KiB

from fastapi import HTTPException, status
from fastapi.encoders import jsonable_encoder
from starlette.responses import JSONResponse
from starlette.requests import Request
from pydantic.main import ModelMetaclass
from pydantic import parse_obj_as
from typing import Dict, Union, _GenericAlias, Optional
class ResponseStructure:
'''Класс структуры данных, для получения HATEOAS ответа на запросы
клиента.'''
def __init__(self, base_url: str):
self._base_url: str = base_url.strip("/")
self._data: dict = dict()
self._links: Dict[str, Dict[str, str]] = dict()
self._embedded: Dict[str, dict] = dict()
def get_dict(self) -> dict:
dictionary = dict()
if self._data:
dictionary["data"] = self._data
if self._links:
dictionary["_links"] = self._links
if self._embedded:
dictionary["_embedded"] = self._embedded
return dictionary
def add_data(self, *args, **kwargs) -> "ResponseStructure":
if args:
if isinstance(self._data, dict):
if len(args) > 1:
self._data = list(args)
else:
self._data = args[0]
elif isinstance(self._data, list):
if len(args) > 1:
self._data.extend(args)
else:
self._data.append(args[0])
else:
self._data = [self._data, *args]
elif kwargs:
if not isinstance(self._data, dict):
self._data = {}
self._data.update(kwargs)
return self
def embed(self, resource: str,
content: Union[dict, list]) -> "ResponseStructure":
self._embedded[resource] = content
return self
def add_link(self, action: str, uri: str,
templated: bool = False) -> "ResponseStructure":
uri = self._get_uri(uri)
if action in self._links:
link_section = self._links[action]
if link_section["href"] != uri:
link_section["href"] = uri
else:
link_section = {"href": uri}
self._links[action] = link_section
if templated:
print("TEMPLATED:", action)
link_section["templated"] = templated
return self
def _get_uri(self, uri: str) -> str:
if uri.startswith(self._base_url):
return uri
return f"{self._base_url}/{uri.strip('/')}"
def get_base_url(request: Request) -> str:
base_url = f"{request.base_url.scheme}://{request.base_url.netloc}"
return base_url
def get_command_not_found(command_id: str) -> HTTPException:
return HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=(f'Command with id "{command_id}"'
' is not found.'))
def get_cl_command_not_found(console_command: str) -> HTTPException:
return HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=(f'Console command "{console_command}" is not'
' found.'))
def get_configuration_not_found(wid: int) -> HTTPException:
return HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f"Configuration with id={wid} is not found.")
def validate_response(data: Union[dict, list],
schema: Union[ModelMetaclass, _GenericAlias, type],
media_type: Optional[str] = None,
status_code: int = 200
) -> JSONResponse:
"""Функция для валидации данных ответа сервера по указанной схеме с учетом
наличия псевдонимов полей и возможности того, что схема задана обычным
типом, типом из typing или BaseModel с __root__. Возвращает объект ответа.
"""
if isinstance(schema, ModelMetaclass):
if "__root__" in schema.__annotations__:
validated = schema(__root__=data)
else:
validated = schema(**data)
elif isinstance(schema, (_GenericAlias, type)):
validated = parse_obj_as(schema, data)
return JSONResponse(content=jsonable_encoder(validated, by_alias=True),
media_type=media_type, status_code=status_code)