halfapi/halfapi/lib/responses.py
Maxime Alves LIRMM 4f4dac0ff2 [deps] Migration from starlette v0.18 to v0.23
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:

commit 0417f27b3f
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 11:08:44 2023 +0100

    [deps] starlette 0.23

commit 552f00a65b
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 10:59:42 2023 +0100

    [deps] starlette 0.22

commit aefe448717
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

commit 01333a200c
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)

commit f3784fab7f
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 10:54:10 2023 +0100

    [deps][breaking] starlette 0.21

commit 717d3f8bd6
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)

commit d0876e45da
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 10:25:21 2023 +0100

    [deps][breaking] starlette 0.20

commit 6504191c53
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 10:12:51 2023 +0100

    [deps] starlette 0.19

commit 7b639a8dc2
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 10:11:14 2023 +0100

    [deps] starlette 0.18

commit 20bd9077a4
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Sat Jan 14 10:07:48 2023 +0100

    pipenv update
2023-01-14 11:10:13 +01:00

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