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:
parent
3ceb9a6eb0
commit
5a53b57b3b
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user