Deserialize custom exceptions to instance

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

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

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