diff --git a/maloja/apis/_base.py b/maloja/apis/_base.py index d81232e..d230cad 100644 --- a/maloja/apis/_base.py +++ b/maloja/apis/_base.py @@ -25,6 +25,13 @@ __logmodulename__ = "apis" cla = CleanerAgent() + + +# wrapper method: calls handle. final net to catch exceptions and map them to the handlers proper json / xml response +# handle method: finds the method for this path / query. can only raise InvalidMethodException +# scrobble: NOT the exposed scrobble method - helper for all APIs to scrobble their results with self-identification + + class APIHandler: # make these classes singletons _instance = None @@ -64,35 +71,32 @@ class APIHandler: response.status,result = self.handle(path,keys) except Exception: exceptiontype = sys.exc_info()[0] - if exceptiontype in self.errors: - response.status,result = self.errors[exceptiontype] - log(f"Error with {self.__apiname__} API: {exceptiontype} (Request: {path})") + for exc_type, exc_response in self.errors.items(): + if isinstance(exceptiontype, exc_type): + response.status, result = exc_response + log(f"Error with {self.__apiname__} API: {exceptiontype} (Request: {path})") + break else: - response.status,result = 500,{"status":"Unknown error","code":500} + # THIS SHOULD NOT HAPPEN + response.status, result = 500, {"status": "Unknown error", "code": 500} log(f"Unhandled Exception with {self.__apiname__} API: {exceptiontype} (Request: {path})") return result - #else: - # result = {"error":"Invalid scrobble protocol"} - # response.status = 500 def handle(self,path,keys): try: - methodname = self.get_method(path,keys) + methodname = self.get_method(path, keys) method = self.methods[methodname] - except Exception: - log("Could not find a handler for method " + str(methodname) + " in API " + self.__apiname__,module="debug") - log("Keys: " + str(keys),module="debug") + except KeyError: + log(f"Could not find a handler for method {methodname} in API {self.__apiname__}", module="debug") + log(f"Keys: {keys}", module="debug") raise InvalidMethodException() - return method(path,keys) + return method(path, keys) def scrobble(self,rawscrobble,client=None): # fixing etc is handled by the main scrobble function - try: - return database.incoming_scrobble(rawscrobble,api=self.__apiname__,client=client) - except Exception: - raise ScrobblingException() + return database.incoming_scrobble(rawscrobble,api=self.__apiname__,client=client) diff --git a/maloja/apis/_exceptions.py b/maloja/apis/_exceptions.py index 62139c9..0a9956f 100644 --- a/maloja/apis/_exceptions.py +++ b/maloja/apis/_exceptions.py @@ -3,4 +3,4 @@ class InvalidAuthException(Exception): pass class InvalidMethodException(Exception): pass class InvalidSessionKey(Exception): pass class MalformedJSONException(Exception): pass -class ScrobblingException(Exception): pass + diff --git a/maloja/apis/audioscrobbler.py b/maloja/apis/audioscrobbler.py index 6699618..d38dda6 100644 --- a/maloja/apis/audioscrobbler.py +++ b/maloja/apis/audioscrobbler.py @@ -21,11 +21,11 @@ class Audioscrobbler(APIHandler): "track.scrobble":self.submit_scrobble } self.errors = { - BadAuthException:(400,{"error":6,"message":"Requires authentication"}), - InvalidAuthException:(401,{"error":4,"message":"Invalid credentials"}), - InvalidMethodException:(200,{"error":3,"message":"Invalid method"}), - InvalidSessionKey:(403,{"error":9,"message":"Invalid session key"}), - ScrobblingException:(500,{"error":8,"message":"Operation failed"}) + BadAuthException: (400, {"error": 6, "message": "Requires authentication"}), + InvalidAuthException: (401, {"error": 4, "message": "Invalid credentials"}), + InvalidMethodException: (200, {"error": 3, "message": "Invalid method"}), + InvalidSessionKey: (403, {"error": 9, "message": "Invalid session key"}), + Exception: (500, {"error": 8, "message": "Operation failed"}) } def get_method(self,pathnodes,keys): diff --git a/maloja/apis/audioscrobbler_legacy.py b/maloja/apis/audioscrobbler_legacy.py index 675ef08..7f03123 100644 --- a/maloja/apis/audioscrobbler_legacy.py +++ b/maloja/apis/audioscrobbler_legacy.py @@ -23,11 +23,11 @@ class AudioscrobblerLegacy(APIHandler): "scrobble":self.submit_scrobble } self.errors = { - BadAuthException:(403,"BADAUTH\n"), - InvalidAuthException:(403,"BADAUTH\n"), - InvalidMethodException:(400,"FAILED\n"), - InvalidSessionKey:(403,"BADSESSION\n"), - ScrobblingException:(500,"FAILED\n") + BadAuthException: (403, "BADAUTH\n"), + InvalidAuthException: (403, "BADAUTH\n"), + InvalidMethodException: (400, "FAILED\n"), + InvalidSessionKey: (403, "BADSESSION\n"), + Exception: (500, "FAILED\n") } def get_method(self,pathnodes,keys): diff --git a/maloja/apis/listenbrainz.py b/maloja/apis/listenbrainz.py index cb15b89..6cc77b6 100644 --- a/maloja/apis/listenbrainz.py +++ b/maloja/apis/listenbrainz.py @@ -21,11 +21,11 @@ class Listenbrainz(APIHandler): "validate-token":self.validate_token } self.errors = { - BadAuthException:(401,{"code":401,"error":"You need to provide an Authorization header."}), - InvalidAuthException:(401,{"code":401,"error":"Incorrect Authorization"}), - InvalidMethodException:(200,{"code":200,"error":"Invalid Method"}), - MalformedJSONException:(400,{"code":400,"error":"Invalid JSON document submitted."}), - ScrobblingException:(500,{"code":500,"error":"Unspecified server error."}) + BadAuthException: (401, {"code": 401, "error": "You need to provide an Authorization header."}), + InvalidAuthException: (401, {"code": 401, "error": "Incorrect Authorization"}), + InvalidMethodException: (200, {"code": 200, "error": "Invalid Method"}), + MalformedJSONException: (400, {"code": 400, "error": "Invalid JSON document submitted."}), + Exception: (500, {"code": 500, "error": "Unspecified server error."}) } def get_method(self,pathnodes,keys): diff --git a/maloja/database/exceptions.py b/maloja/database/exceptions.py index 47abbd4..f1a6891 100644 --- a/maloja/database/exceptions.py +++ b/maloja/database/exceptions.py @@ -1,44 +1,57 @@ from bottle import HTTPError + class EntityExists(Exception): - def __init__(self,entitydict): + def __init__(self, entitydict): self.entitydict = entitydict class TrackExists(EntityExists): pass + class ArtistExists(EntityExists): pass + class AlbumExists(EntityExists): pass +# if the scrobbles dont match class DuplicateTimestamp(Exception): - def __init__(self,existing_scrobble,rejected_scrobble): + def __init__(self, existing_scrobble, rejected_scrobble): self.existing_scrobble = existing_scrobble self.rejected_scrobble = rejected_scrobble +# if it's the same scrobble +class DuplicateScrobble(EntityExists): + def __init__(self, scrobble): + self.scrobble = scrobble + + class DatabaseNotBuilt(HTTPError): def __init__(self): super().__init__( status=503, body="The Maloja Database is being upgraded to support new Maloja features. This could take a while.", - headers={"Retry-After":120} + headers={"Retry-After": 120} ) class MissingScrobbleParameters(Exception): - def __init__(self,params=[]): + def __init__(self, params=[]): self.params = params + class MissingEntityParameter(Exception): pass + class EntityDoesNotExist(HTTPError): entitytype = 'Entity' + def __init__(self,entitydict): self.entitydict = entitydict super().__init__( @@ -46,9 +59,14 @@ class EntityDoesNotExist(HTTPError): body=f"The {self.entitytype} '{self.entitydict}' does not exist in the database." ) + class ArtistDoesNotExist(EntityDoesNotExist): entitytype = 'Artist' + + class AlbumDoesNotExist(EntityDoesNotExist): entitytype = 'Album' + + class TrackDoesNotExist(EntityDoesNotExist): entitytype = 'Track'