From 0b2a9ca09fed1ca62bb58524edc0e668af33eda5 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 30 Jan 2025 13:33:37 +0100 Subject: [PATCH] First run at 500 error handling in fetch_resource --- dspace_rest_client/client.py | 15 ++++++++- dspace_rest_client/models.py | 21 +++++++++++- example_exceptions.py | 64 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 example_exceptions.py diff --git a/dspace_rest_client/client.py b/dspace_rest_client/client.py index 44db8c3..51e965f 100644 --- a/dspace_rest_client/client.py +++ b/dspace_rest_client/client.py @@ -36,6 +36,7 @@ User, Group, DSpaceObject, + DSpaceServerError ) from . import __version__ @@ -594,7 +595,19 @@ def fetch_resource(self, url, params=None): r = self.api_get(url, params, None) if r.status_code != 200: logging.error("Error encountered fetching resource: %s", r.text) - return None + if r.status_code == 500 or r.status_code == 405: + error_json = r.json() + raise DSpaceServerError( + message=error_json.get('message'), + status=error_json.get('status'), + error=error_json.get('error'), + timestamp=error_json.get('timestamp'), + path=error_json.get('path') + ) + else: + # TODO: This is a hack to handle the case where the server returns a 404 or some + # other error code that isn't as 'unexpected', so we don't break older scripts for now + return None # ValueError / JSON handling moved to static method return parse_json(r) diff --git a/dspace_rest_client/models.py b/dspace_rest_client/models.py index 52e356d..ce6f1a4 100644 --- a/dspace_rest_client/models.py +++ b/dspace_rest_client/models.py @@ -639,4 +639,23 @@ def as_dict(self): 'sections': self.sections, 'type': self.type } - return {**parent_dict, **dict} \ No newline at end of file + return {**parent_dict, **dict} + +class DSpaceServerError(Exception): + """Exception raised when DSpace returns a 500 Internal Server Error response""" + def __init__(self, message, status=500, error=None, timestamp=None, path=None): + self.status = status + self.timestamp = timestamp + self.path = path + self.message = message + self.error = error + super().__init__(self.format_message()) + + def format_message(self): + parts = [f"DSpace Server Error ({self.status} {self.error})"] + if self.timestamp: + parts.append(f"Time: {self.timestamp}") + if self.path: + parts.append(f"Path: {self.path}") + parts.append(f"Message: {self.message}") + return " | ".join(parts) diff --git a/example_exceptions.py b/example_exceptions.py new file mode 100644 index 0000000..9f16d45 --- /dev/null +++ b/example_exceptions.py @@ -0,0 +1,64 @@ +# This software is licenced under the BSD 3-Clause licence +# available at https://opensource.org/licenses/BSD-3-Clause +# and described in the LICENCE file in the root of this project + +""" +Example Python 3 application using the dspace.py API client library to retrieve basic DSOs in a +DSpace repository +""" + +import sys +import os +from pprint import pprint + +from dspace_rest_client.client import DSpaceClient + +# Import models as below if needed +from dspace_rest_client.models import Community, Collection, Item, Bundle, Bitstream, DSpaceServerError + +# Example variables needed for authentication and basic API requests +# SET THESE TO MATCH YOUR TEST SYSTEM BEFORE RUNNING THE EXAMPLE SCRIPT +# You can also leave them out of the constructor and set environment variables instead: +# DSPACE_API_ENDPOINT= +# DSPACE_API_USERNAME= +# DSPACE_API_PASSWORD= +# USER_AGENT= + +DEFAULT_URL = 'http://localhost:8080/server/api' +DEFAULT_USERNAME = 'username@test.system.edu' +DEFAULT_PASSWORD = 'password' + +# Configuration from environment variables +URL = os.environ.get('DSPACE_API_ENDPOINT', DEFAULT_URL) +USERNAME = os.environ.get('DSPACE_API_USERNAME', DEFAULT_USERNAME) +PASSWORD = os.environ.get('DSPACE_API_PASSWORD', DEFAULT_PASSWORD) +# Instantiate DSpace client +# Note the 'fake_user_agent' setting here -- this will set a string like the following, +# to get by Cloudfront: +# Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \ +# Chrome/39.0.2171.95 Safari/537.36 +# The default is to *not* fake the user agent, and instead use the default of +# DSpace-Python-REST-Client/x.y.z. +# To specify a custom user agent, set the USER_AGENT env variable and leave/set +# fake_user_agent as False +d = DSpaceClient( + api_endpoint=URL, username=USERNAME, password=PASSWORD, fake_user_agent=True +) + +# Authenticate against the DSpace client +authenticated = d.authenticate() +if not authenticated: + print("Error logging in! Giving up.") + sys.exit(1) + +# Forcing a 405 error to test (500 errors are handled too but are a bit harder to 'force' in a working system!) +try: + r = d.fetch_resource(f"{d.API_ENDPOINT}/config/properties") + print(r.status_code) +except DSpaceServerError as e: + # Here you can see the formatted error message + print("Here is a nice formatted error message:") + print(e.format_message()) + # Here you can see a pretty print of the exception as dict, with the properties you can read + print("\nHere is the exception as a dict:") + pprint(e.__dict__) \ No newline at end of file