1
0
mirror of https://github.com/ilri/dspace-statistics-api.git synced 2024-11-22 14:25:01 +01:00

Refactor /items POST handler to use a before hook

This allows us to do the dirty work of parsing, validating, and
setting local variables from the POST parameters outside of the
on_post function. We then share the parameters via the req.context
object. Functionally it is the same, but readability is better
and it's a neat trick that I could use elsewhere.

See: https://falcon.readthedocs.io/en/stable/user/faq.html#how-can-i-pass-data-from-a-hook-to-a-responder-and-between-hooks
This commit is contained in:
Alan Orth 2020-09-26 18:37:14 +03:00
parent 3ceb9a6eb0
commit 5a53b57b3b
Signed by: alanorth
GPG Key ID: 0FB860CC9C45B1B9
2 changed files with 87 additions and 64 deletions

View File

@ -1,6 +1,7 @@
import falcon import falcon
from .database import DatabaseManager from .database import DatabaseManager
from .util import validate_items_post_parameters
class RootResource: class RootResource:
@ -55,74 +56,29 @@ class AllItemsResource:
resp.media = message resp.media = message
@falcon.before(validate_items_post_parameters)
def on_post(self, req, resp): def on_post(self, req, resp):
"""Handles POST requests""" """Handles POST requests"""
import json
from .items import get_views from .items import get_views
from .items import get_downloads from .items import get_downloads
from .util import is_valid_date
# Only attempt to read the POSTed request if its length is not 0 (or
# rather, in the Python sense, if length is not a False-y value).
if req.content_length:
doc = json.load(req.bounded_stream)
else:
raise falcon.HTTPBadRequest(
title="Invalid request", description=f"Request body is empty."
)
# Parse date parameters from request body (will raise an HTTPBadRequest
# from is_valid_date() if any parameters are invalid)
req_dateFrom = (
doc["dateFrom"]
if "dateFrom" in doc and is_valid_date(doc["dateFrom"])
else None
)
req_dateTo = (
doc["dateTo"] if "dateTo" in doc and is_valid_date(doc["dateTo"]) else None
)
# Build the Solr date string, ie: [* TO *] # Build the Solr date string, ie: [* TO *]
if req_dateFrom and req_dateTo: if req.context.dateFrom and req.context.dateTo:
solr_date_string = f"[{req_dateFrom} TO {req_dateTo}]" solr_date_string = f"[{req.context.dateFrom} TO {req.context.dateTo}]"
elif not req_dateFrom and req_dateTo: elif not req.context.dateFrom and req.context.dateTo:
solr_date_string = f"[* TO {req_dateTo}]" solr_date_string = f"[* TO {req.context.dateTo}]"
elif req_dateFrom and not req_dateTo: elif req.context.dateFrom and not req.context.dateTo:
solr_date_string = f"[{req_dateFrom} TO *]" solr_date_string = f"[{req.context.dateFrom} TO *]"
else: else:
solr_date_string = "[* TO *]" solr_date_string = "[* TO *]"
# Parse the limit parameter from the POST request body
req_limit = doc["limit"] if "limit" in doc else 100
if not isinstance(req_limit, int) or req_limit < 0 or req_limit > 100:
raise falcon.HTTPBadRequest(
title="Invalid parameter",
description=f'The "limit" parameter is invalid. The value must be an integer between 0 and 100.',
)
# Parse the page parameter from the POST request body
req_page = doc["page"] if "page" in doc else 0
if not isinstance(req_page, int) or req_page < 0:
raise falcon.HTTPBadRequest(
title="Invalid parameter",
description=f'The "page" parameter is invalid. The value must be at least 0.',
)
# Parse the list of items from the POST request body
req_items = doc["items"] if "items" in doc else list()
if not isinstance(req_items, list) or len(req_items) == 0:
raise falcon.HTTPBadRequest(
title="Invalid parameter",
description=f'The "items" parameter is invalid. The value must be a comma-separated list of item UUIDs.',
)
# Helper variables to make working with pages/items/results easier and # Helper variables to make working with pages/items/results easier and
# to make the code easier to understand # to make the code easier to understand
number_of_items: int = len(req_items) number_of_items: int = len(req.context.items)
pages: int = int(number_of_items / req_limit) pages: int = int(number_of_items / req.context.limit)
first_item: int = req_page * req_limit first_item: int = req.context.page * req.context.limit
last_item: int = first_item + req_limit last_item: int = first_item + req.context.limit
# Get a subset of the POSTed items based on our limit. Note that Python # Get a subset of the POSTed items based on our limit. Note that Python
# list slicing and indexing are both zero based, but the first and last # list slicing and indexing are both zero based, but the first and last
# items in a slice can be confusing. See this ASCII diagram: # items in a slice can be confusing. See this ASCII diagram:
@ -133,12 +89,12 @@ class AllItemsResource:
# Slice position: 0 1 2 3 4 5 6 # Slice position: 0 1 2 3 4 5 6
# Index position: 0 1 2 3 4 5 # Index position: 0 1 2 3 4 5
# #
# So if we have a list req_items with 240 items: # So if we have a list items with 240 items:
# #
# 1st set: req_items[0:100] would give items at indexes 0 to 99 # 1st set: items[0:100] would give items at indexes 0 to 99
# 2nd set: req_items[100:200] would give items at indexes 100 to 199 # 2nd set: items[100:200] would give items at indexes 100 to 199
# 3rd set: req_items[200:300] would give items at indexes 200 to 239 # 3rd set: items[200:300] would give items at indexes 200 to 239
items_subset: list = req_items[first_item:last_item] items_subset: list = req.context.items[first_item:last_item]
views: dict = get_views(solr_date_string, items_subset) views: dict = get_views(solr_date_string, items_subset)
downloads: dict = get_downloads(solr_date_string, items_subset) downloads: dict = get_downloads(solr_date_string, items_subset)
@ -152,9 +108,9 @@ class AllItemsResource:
statistics.append({"id": k, "views": v, "downloads": downloads[k]}) statistics.append({"id": k, "views": v, "downloads": downloads[k]})
message = { message = {
"currentPage": req_page, "currentPage": req.context.page,
"totalPages": pages, "totalPages": pages,
"limit": req_limit, "limit": req.context.limit,
"statistics": statistics, "statistics": statistics,
} }

View File

@ -1,3 +1,6 @@
import falcon
def get_statistics_shards(): def get_statistics_shards():
"""Enumerate the cores in Solr to determine if statistics have been sharded into """Enumerate the cores in Solr to determine if statistics have been sharded into
yearly shards by DSpace's stats-util or not (for example: statistics-2018). yearly shards by DSpace's stats-util or not (for example: statistics-2018).
@ -55,7 +58,6 @@ def get_statistics_shards():
def is_valid_date(date): def is_valid_date(date):
import datetime import datetime
import falcon
try: try:
# Solr date format is: 2020-01-01T00:00:00Z # Solr date format is: 2020-01-01T00:00:00Z
@ -68,3 +70,68 @@ def is_valid_date(date):
title="Invalid parameter", title="Invalid parameter",
description=f"Invalid date format: {date}. The value must be in format: 2020-01-01T00:00:00Z.", description=f"Invalid date format: {date}. The value must be in format: 2020-01-01T00:00:00Z.",
) )
def validate_items_post_parameters(req, resp, resource, params):
"""Check the POSTed request parameters for the `/items` endpoint.
Meant to be used as a `before` hook.
"""
import json
# Only attempt to read the POSTed request if its length is not 0 (or
# rather, in the Python sense, if length is not a False-y value).
if req.content_length:
doc = json.load(req.bounded_stream)
else:
raise falcon.HTTPBadRequest(
title="Invalid request", description=f"Request body is empty."
)
# Parse date parameters from request body (will raise an HTTPBadRequest
# from is_valid_date() if any parameters are invalid)
if "dateFrom" in doc and is_valid_date(doc["dateFrom"]):
req.context.dateFrom = doc["dateFrom"]
else:
req.context.dateFrom = None
if "dateTo" in doc and is_valid_date(doc["dateTo"]):
req.context.dateTo = doc["dateTo"]
else:
req.context.dateTo = None
# Parse the limit parameter from the POST request body
if "limit" in doc:
if isinstance(doc["limit"], int) and 0 < doc["limit"] < 100:
req.context.limit = doc["limit"]
else:
raise falcon.HTTPBadRequest(
title="Invalid parameter",
description=f'The "limit" parameter is invalid. The value must be an integer between 0 and 100.',
)
else:
req.context.limit = 100
# Parse the page parameter from the POST request body
if "page" in doc:
if isinstance(doc["page"], int) and doc["page"] >= 0:
req.context.page = doc["page"]
else:
raise falcon.HTTPBadRequest(
title="Invalid parameter",
description=f'The "page" parameter is invalid. The value must be at least 0.',
)
else:
req.context.page = 0
# Parse the list of items from the POST request body
if "items" in doc:
if isinstance(doc["items"], list) and len(doc["items"]) > 0:
req.context.items = doc["items"]
else:
raise falcon.HTTPBadRequest(
title="Invalid parameter",
description=f'The "items" parameter is invalid. The value must be a comma-separated list of item UUIDs.',
)
else:
req.context.items = list()