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.
smime.p7s
Description: S/MIME Cryptographic Signature
