From abd284a06e567b3b1724ee7b4d1ee17175bfd80c Mon Sep 17 00:00:00 2001 From: kyokukou Date: Wed, 6 Nov 2024 10:09:08 -0800 Subject: [PATCH] added OAI errors, handler --- oaipmh/data/oai_errors.py | 46 ++++++++++++++++++++++++++++++++++ oaipmh/factory.py | 19 +++++++++++++- oaipmh/requests/routes.py | 4 +-- oaipmh/requests/verb_sorter.py | 4 +-- oaipmh/templates/base.xml | 7 ++++-- oaipmh/templates/error.html | 0 oaipmh/templates/errors.xml | 9 +++++++ tests/test_basic.py | 6 ++++- tests/test_verb_sorting.py | 22 +++++++++++++++- 9 files changed, 108 insertions(+), 9 deletions(-) delete mode 100644 oaipmh/templates/error.html create mode 100644 oaipmh/templates/errors.xml diff --git a/oaipmh/data/oai_errors.py b/oaipmh/data/oai_errors.py index e69de29..291e957 100644 --- a/oaipmh/data/oai_errors.py +++ b/oaipmh/data/oai_errors.py @@ -0,0 +1,46 @@ + +class OAIException(Exception): + """General class for all OAI defined errors""" + code: str + description: str + pass + +class OAIBadArgument(OAIException): + #dont include attributes + code="badArgument" + description="The request includes illegal arguments, is missing required arguments, includes a repeated argument, or values for arguments have an illegal syntax." + +class OAIBadResumptionToken(OAIException): + #TODO consider including params + code="badResumptionToken" + description="The value of the resumptionToken argument is invalid or expired." + +class OAIBadVerb(OAIException): + #dont include attributes + code="badVerb" + description="Value of the verb argument is not a legal OAI-PMH verb, the verb argument is missing, or the verb argument is repeated." + +class OAIBadFormat(OAIException): + #TODO consider including params + code="cannotDisseminateFormat" + description="The metadata format identified by the value given for the metadataPrefix argument is not supported by the item or by the repository." + +class OAINonexistentID(OAIException): + #TODO consider including params + code="idDoesNotExist" + description="The value of the identifier argument is unknown or illegal in this repository." + +class OAINoRecordsMatch(OAIException): + #TODO params + code="noRecordsMatch" + description="The combination of the values of the from, until, set and metadataPrefix arguments results in an empty list." + +class OAINoMetadataFormats(OAIException): + #TODO consider including params + code="noMetadataFormats" + description="There are no metadata formats available for the specified item." + +class OAINoSetHierarchy(OAIException): + #should not be triggered for arXiv implementation + code="noSetHierarchy" + description="The repository does not support sets. This exception should not be true for the arXiv implementation." \ No newline at end of file diff --git a/oaipmh/factory.py b/oaipmh/factory.py index 5c27bf1..e3f1400 100644 --- a/oaipmh/factory.py +++ b/oaipmh/factory.py @@ -1,12 +1,15 @@ import logging +from datetime import datetime, timezone -from flask import Flask +from flask import Flask, render_template from flask_s3 import FlaskS3 from flask.logging import default_handler +from werkzeug.exceptions import HTTPException from arxiv.base import Base from arxiv.db import config_query_timing, configure_db +from oaipmh.data.oai_errors import OAIException from oaipmh.config import Settings from oaipmh.requests import routes @@ -33,6 +36,20 @@ def create_web_app(**kwargs) -> Flask: # type: ignore app.register_blueprint(routes.blueprint) s3.init_app(app) + @app.errorhandler(OAIException) + def handle_oai_error(e): + response=render_template("errors.xml", + response_date=datetime.now(timezone.utc), + error=e) + headers={"Content-Type":"application/xml"} + return response, 200, headers + + #TODO make this actually trigger + @app.errorhandler(HTTPException) + def handle_http_error(e): + print("main error handler ran!") + return e.description, e.code, {} + app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True if not app.jinja_env.globals: diff --git a/oaipmh/requests/routes.py b/oaipmh/requests/routes.py index 115b913..ac75de7 100644 --- a/oaipmh/requests/routes.py +++ b/oaipmh/requests/routes.py @@ -10,10 +10,10 @@ @blueprint.route("/oai", methods=['GET', 'POST']) def oai() -> Response: + #TODO what happens if duplicate params params: Dict[str, str] = request.args.to_dict() if request.method == 'GET' else request.form.to_dict() result=verb_sorter(params) - response_xml=render_template("base.xml", response_date=datetime.now(timezone.utc), request_info="request info", #TODO @@ -26,4 +26,4 @@ def oai() -> Response: @blueprint.route('/favicon.ico') def favicon(): #TODO - return '', 204 \ No newline at end of file + return '', 204 diff --git a/oaipmh/requests/verb_sorter.py b/oaipmh/requests/verb_sorter.py index 60f9e09..d49e7cb 100644 --- a/oaipmh/requests/verb_sorter.py +++ b/oaipmh/requests/verb_sorter.py @@ -3,6 +3,7 @@ from oaipmh.requests.info_queries import identify, list_metadata_formats, list_sets from oaipmh.requests.data_queries import get_record, list_identifiers, list_records from oaipmh.serializers.output_formats import InteriorData +from oaipmh.data.oai_errors import OAIBadVerb def verb_sorter(params: Dict[str, str]) -> InteriorData: """ @@ -26,5 +27,4 @@ def verb_sorter(params: Dict[str, str]) -> InteriorData: case "ListSets": return list_sets(params) case _: - #TODO bad/no verb case error - return InteriorData() + raise OAIBadVerb #dont keep invalid verb diff --git a/oaipmh/templates/base.xml b/oaipmh/templates/base.xml index 546ed87..cf966db 100644 --- a/oaipmh/templates/base.xml +++ b/oaipmh/templates/base.xml @@ -4,6 +4,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd"> {{ response_date.strftime("%Y-%m-%dT%H:%M:%SZ") }} - {{macros.request_element()}} - {{interior_xml}} + {% block request_element %} + {% endblock %} + + {% block interior_xml %} + {% endblock %} \ No newline at end of file diff --git a/oaipmh/templates/error.html b/oaipmh/templates/error.html deleted file mode 100644 index e69de29..0000000 diff --git a/oaipmh/templates/errors.xml b/oaipmh/templates/errors.xml new file mode 100644 index 0000000..2822712 --- /dev/null +++ b/oaipmh/templates/errors.xml @@ -0,0 +1,9 @@ +{% extends "base.xml" %} + +{% block request_element %} + {{ macros.request_element() }} +{% endblock %} + +{% block interior_xml %} + {{error.description}} +{% endblock %} \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index 939ef34..f74f6e8 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -2,4 +2,8 @@ def test_basic(test_client): response = test_client.get("/oai") - assert response.status_code == 200 \ No newline at end of file + text=response.text + assert response.status_code == 200 + assert 'xmlns="http://www.openarchives.org/OAI/2.0/"' in text + assert '' in text + assert '/oai' in text \ No newline at end of file diff --git a/tests/test_verb_sorting.py b/tests/test_verb_sorting.py index e2eed25..ca713b4 100644 --- a/tests/test_verb_sorting.py +++ b/tests/test_verb_sorting.py @@ -78,4 +78,24 @@ def test_list_sets(test_client): assert response.status_code == 200 mock_list_sets.assert_called_once_with(params) -#TODO test no/bad verb \ No newline at end of file +def test_no_verb(test_client): + params = {"not_verb": "ListSets"} + + response = test_client.get("/oai", query_string=params) + assert response.status_code == 200 + assert "" in response.text + + response = test_client.post("/oai", data=params) + assert response.status_code == 200 + assert "" in response.text + +def test_bad_verb(test_client): + params = {"verb": "chaos!"} + + response = test_client.get("/oai", query_string=params) + assert response.status_code == 200 + assert "" in response.text + + response = test_client.post("/oai", data=params) + assert response.status_code == 200 + assert "" in response.text \ No newline at end of file