Package: yagrc
Yet Another gRPC Reflection ClientA minimalistgRPCreflection client for Python. Reflected grpc type classes can be used either by getting them by the names defined in their proto files, or by importing the same modules you would when using protoc togenerate them.Because sometimes, having to run protoc can be inconvenient.PrerequisitesTo make use of the modules in this project, you will need to be using gRPC services on a server that hasserver reflectionenabled.InstallationpipinstallyagrcUsageGiven the following non-reflection-based client request:importgrpcfromarithmeticimportsubtract_pb2fromarithmeticimportsubtract_pb2_grpc...withgrpc.insecure_channel(target)aschannel:stub=subtract_pb2_grpc.SubtractionStub(channel)response=stub.SubtractOne(subtract_pb2.Minuend(number=5))the equivalent usingyagrc.reflectorwould be:importgrpcfromyagrcimportreflectorasyagrc_reflector...reflector=yagrc_reflector.GrpcReflectionClient()withgrpc.insecure_channel(target)aschannel:reflector.load_protocols(channel,symbols=["Arithmetic.Subtraction"])stub_class=reflector.service_stub_class("Arithmetic.Subtraction")request_class=reflector.message_class("Arithmetic.Minuend")stub=stub_class(channel)response=stub.SubtractOne(request_class(number=5))and the equivalent usingyagrc.importerwould be:importgrpcfromyagrcimportimporterasyagrc_importer...importer=yagrc_importer.GrpcImporter()withgrpc.insecure_channel(target)aschannel:importer.configure(channel,filenames=["arithmetic/subtract.proto"])fromarithmeticimportsubtract_pb2fromarithmeticimportsubtract_pb2_grpcstub=subtract_pb2_grpc.SubtractionStub(channel)response=stub.SubtractOne(subtract_pb2.Minuend(number=5))In both cases, the relevant protocol files must first be loaded, usingGrpcReflectionClient.load_protocolsorGrpcImporter.configure. The requested files will be loaded in addition to all dependencies of those files. In general, when using the reflector module, requesting to load the file that defines the service you're using will also load any message types used in that service. When using the importer module, it's best to request the proto files corresponding with the_pb2modules you import. Or you can just callGrpcReflectionClient.load_protocolsorGrpcImporter.configurewithout specifying filenames or symbols and files defining all services advertised via reflection will be loaded. See the module documentation for specific detail.There is also a lazy importer inyagrc.importer, which is simpler to use but less flexible:importgrpcfromyagrcimportimporterasyagrc_importeryagrc_importer.add_lazy_packages(["arithmetic"])fromarithmeticimportsubtract_pb2fromarithmeticimportsubtract_pb2_grpc...withgrpc.insecure_channel(target)aschannel:yagrc_importer.resolve_lazy_imports(channel)stub=subtract_pb2_grpc.SubtractionStub(channel)response=stub.SubtractOne(subtract_pb2.Minuend(number=5))Practical use of the importer moduleThe primary motivation foryagrc.importeris to provide a drop-in replacement of the protoc-generated modules without having to rewrite the client code to access the classes differently. However, a grpcchannelis needed to in order to load the protocol files, and that's usually not something you'll want to be opening at the top level of a module, so unless you use the lazy variant, the imports will probably need to be deferred to a function call. You can do this just prior to using the grpc calls, as in the example above, but doing so would result in a lot of unnecessary work every time each function that uses grpc is called.An alternative is to load them in one place along with the deferred imports and keep track of whether or not it needs to be run:importgrpctry:fromarithmeticimportsubtract_pb2fromarithmeticimportsubtract_pb2_grpcimport_ok=TrueexceptImportError:fromyagrcimportimporterasyagrc_importerimport_ok=Falsedefimport_protocols(channel):importer=yagrc_importer.GrpcImporter()importer.configure(channel,filenames=["arithmetic/subtract.proto"])globalsubtract_pb2globalsubtract_pb2_grpcfromarithmeticimportsubtract_pb2fromarithmeticimportsubtract_pb2_grpcglobalimport_okimport_ok=True...defsome_function_that_uses_grpc():withgrpc.insecure_channel(target)aschannel:ifnotimport_ok:import_protocols(channel)stub=subtract_pb2_grpc.SubtractionStub(channel)response=stub.SubtractOne(subtract_pb2.Minuend(number=5))With this pattern, if the protoc-generated files are available in the module import path, they will be used. If not, they will be loaded when needed via reflection.Note thatGrpcImporter.configureis not especially tread safe, so calling it in multiple threads simultaneously should be avoided. If there is a possibility that multiple threads may run grpc calls simultaneously, it would probably be better to just ensureGrpcImporter.configureis called in a main thread prior to starting the other threads.This is somewhat messy. The lazy import mechanism attempts to cut back on the mess by allowing the imports to happen in advance, outside of function context. The lazy importer example in the prior section will still prefer the protoc-generated files if they are available in the module import path.importer.resolve_lazy_importsis safe to call multiple times even if no more imports need to be resolved, it just won't do anything in that case.LimitationsFor the_pb2_grpcmodules, theimportermodule only provides the client stub class. It does not provide the client class that uses thegrpc.experimentalAPI, nor does it provide either of the two server classes. It would be possible to provide these, but the use case for the server classes would be limited to a service implemented on a server that wants to get the protocol modules via reflection from another server that implements the same service, which seems a bit of a stretch. And the experimental API is... well... experimental, which implies its API is subject to change.Security considerationsAll the functionality that communicates with the grpc reflection service uses a grpcChannelpassed in by the caller. The security of that communication is only going to be as secure as the channel passed in. However, even if the channel is secure, using classes that are dynamically created based on reflection data is always going to be less secure than using classes that were generated in advance using protoc. Thus, use of the modules in this project is not advised for security sensitive applications.Similar projectsgrpc requestsis a Python grpc client that supports reflection, but it exposes a different type class interface than the protoc-generated ones (as far as I can tell...).Eagrincludes a Python grpc reflection client interface, but it's a small part of a much larger project, most of which is unrelated to reflection.There's lots of reflection clients for other languages, too, and probably others for Python.gRPCurl, in particular, is useful if you want to examine reflected service protocols interactively.