On Mon, Feb 26, 2018 at 11:16 AM, aamit via grpc.io <
[email protected]> wrote:

> I had some questions about the api design of RpcError.
>

You're not alone; it's a part of the gRPC Python API that's somewhat
different than how APIs are often shaped in Python (we knew this all along)
and with which users have had some confusion (more than we expected!).

>From what I can tell in docs I should catch RpcError and switch on code
> (since it's also implicitly a `Call`).
>

Not all grpc.RpcError instances are also grpc.Call instances, but many are.
Speaking just for those that are: sure, switch on the value of the code if
that's what you want to do, but you don't have to do so.

A few things frustrate me about this.
>
>    1. There are certain codes I'd like to treat one way and certain codes
>    I'd like to treat another way. It seems slightly more pythonic to have one
>    exception class per code,
>
>
It seems code-bloaty <https://en.wikipedia.org/wiki/Code_bloat> to have one
exception class per code! We believe that classes that have no *behavioral*
differences (for example: ever see a subclass that contains no method
definitions but rather only a constructor that calls its superclass'
constructor?) are a code smell. Exception classes are a bit of a special
case because they're mostly behaviorless anyway, but they're not so special
that different data *values* deserve different class definitions.


>    1. especially given the number of codes is fixed
>
>
Fun fact: the set of status codes is (at least in theory) not fixed.
Additional codes are *supposed* to be able to be added one day without
breaking working code. This may or may not ever happen, but it was a
concern at the time we were designing the gRPC Python API.


>    1. and the python client will treat any code that is not one of the
>    known status codes as "unknown". This would let me catch the specific codes
>    I care about by `excepting` only the classes that represent those codes,
>    rather than catching all RpcError, switching, and reraising otherwise.
>
>
One of the reasons this wasn't a priority for us in the API design was that
we figured that if there were some common behavior that you wanted to be
done for seven or eight different status codes, you would write a helper
function and call it from inside your except:. If there were different
Exception classes for each status code... would you be writing

except (<first chosen Exception class>, <five or six others>, <last chosen
Exception class>,) as my_exception:
  <common behavior using <my_exception>

? Or are you saying something else, that you'd want seven or eight
different except: syntax elements?


>    1. The api of `_Rendezvous` is pretty large,
>
>
I'm glad you wrote this as you did, because the response is the exact
opposite: the API of _Rendezvous is pretty small, because it is zero,
because _Rendezvous is a private code element not even visible to
applications. (Yes, it shows its name in stack traces when something goes
wrong, but that's to aid debugging only.)


>    1. and we're told to count on behavior that is implicitly there as a
>    result of it being a `Call`,
>
>
It's not implicit; it's explicitly right there in the API specification
<https://grpc.io/grpc/python/grpc.html#grpc.UnaryUnaryMultiCallable.__call__>.
Count on it. It is guaranteed as much as any other part of the API.

Let's step away from gRPC Python for a moment: consider in otherwise
complete isolation a function that takes no arguments and returns an object
that is a sequence of length-one strings. You're perfectly well able to
write code that uses this function, right? It doesn't matter that sequence
is an abstract type
<https://docs.python.org/3.7/library/collections.abc.html#collections.abc.Sequence>,
right? You don't need to know the *class* of the returned value, right? The
usability of the function comes from the way that it guarantees that the
returned value is usable *according to a specified type*. The class of the
returned value could be tuple, or list, or some private sequence
implementation ("_RendezSequenceOfLengthOneStrings" :-P). It could even be
string (since for better or worse and for all the trouble it's caused over
the years in Python strings happen to be sequences of length-one strings).
This isn't controversial or challenging; this is foundational abstraction
that's pretty universal throughout all software.

Now back to gRPC Python: we did two things that in combination have thrown
for a loop folks that hadn't seen them before:

1) Rather than an abstract *return value*, implemented by some private
internal class, we have methods with an abstract *raises value*,
implemented by some private internal class. This may be rare, but it
shouldn't be strange. Imagine that the Python interpreter were changed one
day so that when you made an out-of-bounds access on a Tuple, what was
raised was not an IndexError but rather a private subclass of IndexError.
This might be curious, but it shouldn't break anything; all your code that
says "except IndexError:" should still work just as it worked before. What
drives this is the Liskov Substitution Principle
<https://en.wikipedia.org/wiki/Liskov_substitution_principle>: your code
should not depend in any way upon superclasses being implementing classes
and break when instances of subclasses are provided in places where you
thought you were getting instances of superclasses.

2) Rather than providing *one* type according to which an object passed
from gRPC Python to applications may be used, we (at some points in the
API) provide *two types according to which an object passed from gRPC
Python to applications may be used*. I didn't think at the time that I was
starting a revolution but... that's kind of how it's played out among some
users. But there's also nothing conceptually difficult or challenging about
this either. The fact that the object is a grpc.RpcError is what allows
applications to catch it. The fact that the object is a grpc.Call is what
allows applications to do interesting things with it after having caught
it. The sum of those two offers all the functionality that is needed,
right? (When you talk about a hierarchy of Exception classes, this is about
making something more *convenient* than it is today, right? Not making
*possible* something that today is impossible?)


>    1. rather that an api where we can catch an `GrpcException` that has a
>    base api of methods that are available and make sense to call on the
>    exception. I'm not sure which methods out of Call/Future make sense to call
>    when I catch the RpcError (that is actually a _Rendezvous). Right now I use
>    `code` and `details` and the `metadata`. I think changing this would also
>    help toward the goal of "Support static type-checking of both gRPC Python
>    itself and of code that uses gRPC Python" mentioned in the gsoc list.
>    <https://github.com/grpc/grpc/blob/master/summerofcode/ideas.md>
>
> In my opinion, it makes more sense that instead of the opaque RpcError,
>

grpc.RpcError isn't opaque; it's one of the few concrete classes exposed in
the gRPC Python API. It defines no *behavior*, but it's there and it's an
Exception subclass.

grpc python exposed a exception class per code that derived from a base
> class that actually had a meaningful set of methods to call (rather than
> inheriting most of the future methods that don't make sense for me to call
> on the error). I was wondering if I'm missing something about idiomatically
> handling errors on the client, and what went into the current design of the
> exception api.
>

If you haven't yet watched it I'd suggest looking at this talk
<https://www.youtube.com/watch?v=3MNVP9-hglc>; a lot of the ideas developed
there motivated the gRPC Python API. Committing to maintaining a stable API
over the course of several years to come was also at the forefront of our
minds and manifested as design pressures against concrete classes in the
API (though there are a few) and against introducing code elements that
were merely the combination of other code elements (we needed
grpc.RpcError, and we needed grpc.Call, but we decided that we did not need
a third thing that was nothing but both a grpc.RpcError and a grpc.Call).
Both of those push toward saying "no" to a subclass-per-status-code
Exception hierarchy: we already have the set of status codes expressed in
the API and we already have Exceptions expressed in the API; we're
reluctant to add additional expressions of the same concepts. Yes, there
might be some convenience to be gained (and there are lots of places in the
API where we have done things for application convenience!) but not so much
in this case that we think it's worth it.
-Nathaniel

-- 
You received this message because you are subscribed to the Google Groups 
"grpc.io" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/grpc-io.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/grpc-io/CAEOYnASN5xuAERBL0sc-5vhaVjfUiZp4kZtFd0r%3Deo4N4jqCZQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to