Breaking : migrate your tests that use the TestDomain.client method following the instructions here https://github.com/Kludex/bump-testclient Squashed commit of the following: commit0417f27b3f
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 11:08:44 2023 +0100 [deps] starlette 0.23 commit552f00a65b
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:59:42 2023 +0100 [deps] starlette 0.22 commitaefe448717
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:55:45 2023 +0100 [tests][fix] compares the json interpreted value instead of the string commit01333a200c
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:55:20 2023 +0100 [testing] changes from requests to httpx for Starlette TestClient (breaks) commitf3784fab7f
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:54:10 2023 +0100 [deps][breaking] starlette 0.21 commit717d3f8bd6
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:26:31 2023 +0100 [responses] use a wrapper function for exception handling (fix starlette 0.20) commitd0876e45da
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:25:21 2023 +0100 [deps][breaking] starlette 0.20 commit6504191c53
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:12:51 2023 +0100 [deps] starlette 0.19 commit7b639a8dc2
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:11:14 2023 +0100 [deps] starlette 0.18 commit20bd9077a4
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr> Date: Sat Jan 14 10:07:48 2023 +0100 pipenv update
168 lines
4.4 KiB
Python
168 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
# builtins
|
|
""" Response module
|
|
|
|
Contains some base response classes
|
|
|
|
Classes :
|
|
- HJSONResponse
|
|
- InternalServerErrorResponse
|
|
- NotFoundResponse
|
|
- NotImplementedResponse
|
|
- ORJSONResponse
|
|
- PlainTextResponse
|
|
- ServiceUnavailableResponse
|
|
- UnauthorizedResponse
|
|
- ODSResponse
|
|
|
|
"""
|
|
from datetime import date
|
|
import decimal
|
|
import typing
|
|
from io import BytesIO
|
|
import orjson
|
|
|
|
# asgi framework
|
|
from starlette.responses import PlainTextResponse, Response, JSONResponse, HTMLResponse
|
|
from starlette.requests import Request
|
|
from starlette.exceptions import HTTPException
|
|
|
|
from .user import JWTUser, Nobody
|
|
from ..logging import logger
|
|
|
|
|
|
__all__ = [
|
|
'HJSONResponse',
|
|
'InternalServerErrorResponse',
|
|
'NotFoundResponse',
|
|
'NotImplementedResponse',
|
|
'ORJSONResponse',
|
|
'PlainTextResponse',
|
|
'ServiceUnavailableResponse',
|
|
'UnauthorizedResponse']
|
|
|
|
|
|
class InternalServerErrorResponse(Response):
|
|
""" The 500 Internal Server Error default Response
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(status_code=500)
|
|
|
|
|
|
class NotFoundResponse(Response):
|
|
""" The 404 Not Found default Response
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(status_code=404)
|
|
|
|
|
|
class NotImplementedResponse(Response):
|
|
""" The 501 Not Implemented default Response
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(status_code=501)
|
|
|
|
class ServiceUnavailableResponse(Response):
|
|
""" The 503 Service Unavailable default Response
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(status_code=503)
|
|
|
|
class UnauthorizedResponse(Response):
|
|
""" The 401 Not Found default Response
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(status_code = 401)
|
|
|
|
|
|
class ORJSONResponse(JSONResponse):
|
|
""" The response that encodes data into JSON
|
|
"""
|
|
def __init__(self, content, default=None, **kwargs):
|
|
self.default = default if default is not None else ORJSONResponse.default_cast
|
|
super().__init__(content, **kwargs)
|
|
|
|
def render(self, content: typing.Any) -> bytes:
|
|
return orjson.dumps(content,
|
|
option=orjson.OPT_NON_STR_KEYS,
|
|
default=self.default)
|
|
|
|
@staticmethod
|
|
def default_cast(typ):
|
|
""" Cast the data in JSON-serializable type
|
|
"""
|
|
str_types = {
|
|
decimal.Decimal
|
|
}
|
|
list_types = {
|
|
set
|
|
}
|
|
jsonable_types = {
|
|
JWTUser, Nobody
|
|
}
|
|
|
|
if callable(typ):
|
|
return typ.__name__
|
|
if type(typ) in str_types:
|
|
return str(typ)
|
|
if type(typ) in list_types:
|
|
return list(typ)
|
|
if type(typ) in jsonable_types:
|
|
return typ.json
|
|
|
|
raise TypeError(f'Type {type(typ)} is not handled by ORJSONResponse')
|
|
|
|
|
|
class HJSONResponse(ORJSONResponse):
|
|
""" The response that encodes generator data into JSON
|
|
"""
|
|
def render(self, content: typing.Generator):
|
|
return super().render(list(content))
|
|
|
|
class ODSResponse(Response):
|
|
file_type = 'ods'
|
|
|
|
def __init__(self, d_rows: typing.List[typing.Dict]):
|
|
try:
|
|
import pyexcel as pe
|
|
except ImportError:
|
|
""" ODSResponse is not handled
|
|
"""
|
|
super().__init__(content=
|
|
'pyexcel is not installed, ods format not available'
|
|
)
|
|
return
|
|
|
|
with BytesIO() as ods_file:
|
|
rows = []
|
|
if len(d_rows):
|
|
rows_names = list(d_rows[0].keys())
|
|
for elt in d_rows:
|
|
rows.append(list(elt.values()))
|
|
|
|
rows.insert(0, rows_names)
|
|
|
|
self.sheet = pe.Sheet(rows)
|
|
self.sheet.save_to_memory(
|
|
file_type=self.file_type,
|
|
stream=ods_file)
|
|
|
|
filename = f'{date.today()}.{self.file_type}'
|
|
|
|
super().__init__(
|
|
content=ods_file.getvalue(),
|
|
headers={
|
|
'Content-Type': 'application/vnd.oasis.opendocument.spreadsheet; charset=UTF-8',
|
|
'Content-Disposition': f'attachment; filename="{filename}"'},
|
|
status_code = 200)
|
|
|
|
|
|
class XLSXResponse(ODSResponse):
|
|
file_type = 'xlsx'
|
|
|
|
def gen_exception_route(response_cls):
|
|
async def exception_route(req: Request, exc: HTTPException):
|
|
return response_cls()
|
|
|
|
return exception_route
|