Skip to content

Commit

Permalink
api.views.MimeType as normal class (mime types need to be str, not su…
Browse files Browse the repository at this point in the history
…bclasses)
  • Loading branch information
rizac committed Jan 9, 2024
1 parent 51075fd commit 65a5ec5
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 46 deletions.
58 changes: 28 additions & 30 deletions egsim/api/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Module with the views for the web API (no GUI)"""
from __future__ import annotations
from collections.abc import Callable, Iterable
from enum import StrEnum
from http.client import responses
from datetime import date, datetime
import re
Expand All @@ -26,16 +25,18 @@
from .forms.flatfile.residuals import ResidualsForm


class MIMETYPE(StrEnum): # noqa
"""An enum of supported mime types (content_type in Django Response) loosely
class MimeType: # noqa
"""A collection of supported mime types (content_type in Django Response), loosely
copied from mimetypes.types_map (https://docs.python.org/3.8/library/mimetypes.html)
"""
CSV = "text/csv"
JSON = "application/json"
HDF = "application/x-hdf"
PNG = "image/png"
PDF = "application/pdf"
SVG = "image/svg+xml"
# NOTE: avoid Enums or alike, attributes below will be passed as `content_type` arg
# to Responses and must be pure str (subclasses NOT allowed!)
csv = "text/csv"
json = "application/json"
hdf = "application/x-hdf"
png = "image/png"
pdf = "application/pdf"
svg = "image/svg+xml"
# GZIP = "application/gzip"


Expand Down Expand Up @@ -129,10 +130,9 @@ def response(self, **form_kwargs):
:param form_kwargs: keyword arguments to be passed to this class Form
"""
try:
rformat = form_kwargs['data'].pop('format', MIMETYPE.JSON.name.lower())
rformat = form_kwargs['data'].pop('format', 'json')
try:
mimetype = MIMETYPE[rformat.upper()]
response_function = self.supported_formats()[mimetype]
response_function = self.supported_formats()[rformat]
except KeyError:
raise ValidationError(f'Invalid format {rformat}')
form = self.formclass(**form_kwargs)
Expand All @@ -147,21 +147,19 @@ def response(self, **form_kwargs):

@classmethod
def supported_formats(cls) -> \
dict[MIMETYPE, Callable[[RESTAPIView, APIForm], HttpResponse]]:
dict[str, Callable[[RESTAPIView, APIForm], HttpResponse]]:
"""Return a list of supported formats (content_types) by inspecting
this class implemented methods. Each dict key is a MIMETYPE enum,
mapped to this class method used to obtain the response data in that
this class implemented methods. Each dict key is a MimeType attr name,
mapped to a class method used to obtain the response data in that
mime type"""
formats = {}
for a in dir(cls):
if a.startswith('response_'):
meth = getattr(cls, a)
if callable(meth):
frmt = a.split('_', 1)[1]
try:
formats[MIMETYPE[frmt.upper()]] = meth
except KeyError:
pass
if hasattr(MimeType, frmt):
formats[frmt] = meth
return formats

def response_json(self, form:APIForm) -> JsonResponse:
Expand All @@ -181,10 +179,10 @@ class TrellisView(RESTAPIView):
urls = (f'{API_PATH}/trellis', f'{API_PATH}/model-model-comparison')

def response_csv(self, form:APIForm):
return pandas_response(form.response_data(), MIMETYPE.CSV)
return pandas_response(form.response_data(), MimeType.csv)

def response_hdf(self, form:APIForm):
return pandas_response(form.response_data(), MIMETYPE.HDF)
return pandas_response(form.response_data(), MimeType.hdf)

def response_json(self, form:APIForm) -> JsonResponse:
json_data = dataframe2dict(form.response_data(),
Expand All @@ -199,10 +197,10 @@ class ResidualsView(RESTAPIView):
urls = (f'{API_PATH}/residuals', f'{API_PATH}/model-data-comparison')

def response_csv(self, form:APIForm):
return pandas_response(form.response_data(), MIMETYPE.CSV)
return pandas_response(form.response_data(), MimeType.csv)

def response_hdf(self, form:APIForm):
return pandas_response(form.response_data(), MIMETYPE.HDF)
return pandas_response(form.response_data(), MimeType.hdf)

def response_json(self, form:APIForm) -> JsonResponse:
json_data = dataframe2dict(form.response_data(),
Expand Down Expand Up @@ -236,25 +234,25 @@ def error_response(error: Union[str, Exception, dict],
else:
message = str(error)
content.setdefault('message', message)
kwargs.setdefault('content_type', MIMETYPE.JSON)
kwargs.setdefault('content_type', MimeType.json)
return JsonResponse(content, status=status, **kwargs)


def pandas_response(data:pd.DataFrame, content_type:Optional[MIMETYPE]=None,
def pandas_response(data:pd.DataFrame, content_type:Optional[str]=None,
status=200, reason=None, headers=None, charset=None,
as_stream=False) -> HttpResponseBase: # usually FileResponse
"""Return a `HTTPResponse` for serving pandas dataframes as either HDF or CSV
:param content_type: optional content type. Either MIMETYPE.CSV or MIMETYPE.HDF
If None, it defaults to the former.
:param content_type: optional content type. See MimeType attr values. Defaults to
`MimeType.csv`
:param as_stream: if False (the default) return a `FileResponse`, otherwise
a `StreamingHttpResponse`
"""
if content_type is None: # the default is CSV:
content_type = MIMETYPE.CSV
if content_type == MIMETYPE.CSV:
content_type = MimeType.csv
if content_type == MimeType.csv:
content = write_csv_to_buffer(data)
elif content_type == MIMETYPE.HDF:
elif content_type == MimeType.hdf:
content = write_hdf_to_buffer({'egsim': data})
else:
return error_response(f'cannot serve {data.__class__.__name__} '
Expand Down
22 changes: 6 additions & 16 deletions egsim/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
FlatfileInspectionForm, FlatfilePlotForm)
from ..api.forms import GsimFromRegionForm
from ..api.views import (error_response, RESTAPIView, TrellisView, ResidualsView,
MIMETYPE)
MimeType)


def main(request, selected_menu=None):
Expand Down Expand Up @@ -107,7 +107,7 @@ def download_request(request, key: TAB, filename: str):
:param key: a :class:`TAB` name associated to a REST API TAB (i.e.,
with an associated Form class)
"""
form_class = TAB[key].formclass
form_class = TAB[key].formclass # FIXME remove pycharm lint warning

def input_dict() -> dict:
"""return the input dict. This function allows to work each time
Expand Down Expand Up @@ -144,9 +144,8 @@ def download_response(request, key: TAB, filename: str):
elif ext == '.csv_eu':
return download_ascsv(request, key, basename + '.csv', ';', ',')
try:
content_type = MIMETYPE[ext[1:].upper()]
return download_asimage(request, filename, content_type)
except KeyError:
return download_asimage(request, filename, ext[1:])
except AttributeError:
pass # filename extension not recognized as image
return error_response(f'Unsupported format "{ext[1:]}"',
RESTAPIView.CLIENT_ERR_CODE)
Expand All @@ -171,20 +170,11 @@ def download_ascsv(request, key: TAB, filename: str, sep=',', dec='.'):
return response


# FIXME: remove
# _IMG_FORMATS = {
# # 'eps': 'application/postscript', # NA (note: requires poppler library in case)
# 'pdf': 'application/pdf',
# 'svg': 'image/svg+xml',
# 'png': 'image/png'
# }


def download_asimage(request, filename: str, content_type: MIMETYPE) -> FileResponse:
def download_asimage(request, filename: str, img_format: str) -> FileResponse:
"""Return the image from the given request built in the frontend GUI
according to the chosen plots
"""
img_format = content_type.name.lower()
content_type = getattr(MimeType, img_format)
if not filename.lower().endswith(f".{img_format}"):
filename += f".{img_format}"
jsondata = json.loads(request.body.decode('utf-8'))
Expand Down

0 comments on commit 65a5ec5

Please sign in to comment.