Skip to content

Commit

Permalink
feat: add 422 Error and status_code attribute to DataCiteError-s
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
alee authored and tmorrell committed Oct 17, 2024
1 parent eb31d24 commit 41be4e5
Showing 1 changed file with 59 additions and 17 deletions.
76 changes: 59 additions & 17 deletions datacite/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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):
Expand Down Expand Up @@ -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)

0 comments on commit 41be4e5

Please sign in to comment.