Hi, decorating with `deserialize_to_instance` will do the job if the
exception is handled by the same service where it is raised (or if one
service imports exceptions from the remote service package).
We hit a similar need with communication between different services where
the exceptions raised on service A should be handled on service B.
We use the following solution::
from nameko.exceptions import registry
def remote_error(exc_path):
"""
Decorator that registers remote exception with matching ``exc_path``
to be deserialized to decorated exception instance, rather than
wrapped in ``RemoteError``.
"""
def wrapper(exc_type):
registry[exc_path] = exc_type
return exc_type
return wrapper
@remote_error('users.exceptions.NotFound')
class NotFound(Exception):
pass
class Service:
users = RpcProxy('users')
@http('GET', '/users/<int:id>')
def get_user(self, id_):
try:
self.users.get(id_)
except NotFound:
return 404, "User ID {} does not exist".format(id_)
This way one can also decorate same error with multiple "sources" -
handling the same problem (NotFound) when it comes from multiple places::
@remote_error('users.exceptions.NotFound')
@remote_error('organisations.exceptions.NotFound')
class NotFound(Exception):
pass
or, to separate the exceptions if that better suits the caller needs::
@remote_error('users.exceptions.NotFound')
class UserNotFound(Exception):
pass
@remote_error('organisations.exceptions.NotFound')
class OrganisationNotFound(Exception):
pass
The link between the two exceptions (local and remote) is declared
explicitly in the decorator, which is quite straight forward. The advantage
is that there is no change or registration needed on the called side which
raises the exception, but the drawback is that the caller - the side which
handles the remote exception - needs to know the exact path of the remote
exception in order to register it for the error deserializer. Now the
Nameko's exception module does the mapping based on the exc_path. If the
deserialization registry were also to accept just the exception name and
the name of the service the remote exception originates in, then it can be
simplified to just::
@remote_error('users', 'NotFoud')
class NotFound(Exception):
pass
Regards,
Ondrej
···
On Saturday, 30 July 2016 10:23:03 UTC+1, David Szotten wrote:
Hi Grace,
The apis for this aren't documented and so might change in the future, but
it is possible:
Provided the exception is defined in a shared location (importable by the
caller as well), you should be able to just decorate the exception class
with `nameko.exceptions.deserialize_to_instance`. (Note that the caller
need to make sure the exception is imported, so that it gets registered)
from nameko.exceptions import deserialize_to_instance
@deserialize_to_instance
class MyError(Exception):
pass
Best,
David
On Friday, 29 July 2016 16:49:09 UTC+1, Grace Wong wrote:
Hi,
Can anyone suggest a good way to handle deserialization of custom
exceptions?
I have a custom exception class that I raise in my service for expected
errors. Since this exception type isn't in the
`nameko.exceptions.registry`, when I make a RPC call to my service, this
gets deserialized to a `RemoteError`.
I'd like instead for this to be deserialized back to instance of the
exception type, just like how nameko's `MalformedRequest` behaves. Is there
an easy way to control this (other than overriding `deserialize`) that I'm
missing?
Thanks!
Grace