From 41be4e5076ca8633ee2aa2103ab3cbd14db75fd5 Mon Sep 17 00:00:00 2001 From: Allen Lee Date: Sat, 13 Jul 2024 21:27:58 -0700 Subject: [PATCH] feat: add 422 Error and status_code attribute to DataCiteError-s - add 422 Unprocessable Entity error class, resolves #89 - minor refactoring to use a defaultdict data structure to map status codes to error classes instead of an if/elif conditional chain. - future status codes can be supported by adding a new DataCiteError subtype and an entry to the DataCiteErrorFactory.ERROR_CLASSES dictionary - includes the status code in the Error class so client code can retrieve the status code from the raised Exception, resolves #98 defaults to DataCiteServerError for any unhandled status_code >= 500 and DataCiteRequestError if there is not an exact match --- datacite/errors.py | 76 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/datacite/errors.py b/datacite/errors.py index 7d2dad5..e5ce5b2 100644 --- a/datacite/errors.py +++ b/datacite/errors.py @@ -3,11 +3,14 @@ # This file is part of DataCite. # # Copyright (C) 2015 CERN. +# Copyright (C) 2024 Arizona State University. # # DataCite is free software; you can redistribute it and/or modify it # under the terms of the Revised BSD License; see LICENSE file for # more details. +from collections import defaultdict + """Errors for the DataCite API. MDS error responses will be converted into an exception from this module. @@ -32,27 +35,21 @@ class DataCiteError(Exception): * 403 Forbidden * 404 Not Found * 410 Gone (deleted) + * 412 Precondition Failed + * 422 Unprocessable Entity """ + status_code = 400 + + def __init__(self, *args, status_code=500): + """Initialize this exception with an http status code error.""" + super().__init__(*args) + self.status_code = status_code + @staticmethod - def factory(err_code, *args): + def factory(status_code, *args): """Create exceptions through a Factory based on the HTTP error code.""" - if err_code == 204: - return DataCiteNoContentError(*args) - elif err_code == 400: - return DataCiteBadRequestError(*args) - elif err_code == 401: - return DataCiteUnauthorizedError(*args) - elif err_code == 403: - return DataCiteForbiddenError(*args) - elif err_code == 404: - return DataCiteNotFoundError(*args) - elif err_code == 410: - return DataCiteGoneError(*args) - elif err_code == 412: - return DataCitePreconditionError(*args) - else: - return DataCiteServerError(*args) + return DataCiteErrorFactory.create(status_code, *args) class DataCiteServerError(DataCiteError): @@ -104,3 +101,48 @@ class DataCiteGoneError(DataCiteRequestError): class DataCitePreconditionError(DataCiteRequestError): """Metadata must be uploaded first.""" + + +class DataCiteUnprocessableEntityError(DataCiteRequestError): + """Invalid metadata format or content.""" + + +class DataCiteErrorFactory: + """ + Factory class to create specific DataCiteError instances based on the HTTP status code + + Attributes: + ERROR_CLASSES (defaultdict): A dictionary mapping HTTP status codes to corresponding DataCiteError classes. + """ + + ERROR_CLASSES = defaultdict( + lambda status_code: ( + DataCiteServerError if status_code >= 500 else DataCiteRequestError + ), + { + 204: DataCiteNoContentError, + 400: DataCiteBadRequestError, + 401: DataCiteUnauthorizedError, + 403: DataCiteForbiddenError, + 404: DataCiteNotFoundError, + 410: DataCiteGoneError, + 412: DataCitePreconditionError, + 422: DataCiteUnprocessableEntityError, + }, + ) + + @staticmethod + def create(status_code, *args): + """ + Create a specific DataCiteError instance based on the provided error code. + + Args: + status_code (int): The HTTP status code representing the error. + args: Additional arguments to be passed to the DataCiteError constructor. + + Returns: + DataCiteError: An instance of the appropriate DataCiteError subclass. + + """ + DataCiteErrorClass = DataCiteErrorFactory.ERROR_CLASSES[status_code] + return DataCiteErrorClass(*args, status_code=status_code)