1# Protocol Buffers - Google's data interchange format 2# Copyright 2008 Google Inc. All rights reserved. 3# https://developers.google.com/protocol-buffers/ 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions are 7# met: 8# 9# * Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# * Redistributions in binary form must reproduce the above 12# copyright notice, this list of conditions and the following disclaimer 13# in the documentation and/or other materials provided with the 14# distribution. 15# * Neither the name of Google Inc. nor the names of its 16# contributors may be used to endorse or promote products derived from 17# this software without specific prior written permission. 18# 19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31"""Contains metaclasses used to create protocol service and service stub 32classes from ServiceDescriptor objects at runtime. 33 34The GeneratedServiceType and GeneratedServiceStubType metaclasses are used to 35inject all useful functionality into the classes output by the protocol 36compiler at compile-time. 37""" 38 39__author__ = '[email protected] (Petar Petrov)' 40 41 42class GeneratedServiceType(type): 43 44 """Metaclass for service classes created at runtime from ServiceDescriptors. 45 46 Implementations for all methods described in the Service class are added here 47 by this class. We also create properties to allow getting/setting all fields 48 in the protocol message. 49 50 The protocol compiler currently uses this metaclass to create protocol service 51 classes at runtime. Clients can also manually create their own classes at 52 runtime, as in this example:: 53 54 mydescriptor = ServiceDescriptor(.....) 55 class MyProtoService(service.Service): 56 __metaclass__ = GeneratedServiceType 57 DESCRIPTOR = mydescriptor 58 myservice_instance = MyProtoService() 59 # ... 60 """ 61 62 _DESCRIPTOR_KEY = 'DESCRIPTOR' 63 64 def __init__(cls, name, bases, dictionary): 65 """Creates a message service class. 66 67 Args: 68 name: Name of the class (ignored, but required by the metaclass 69 protocol). 70 bases: Base classes of the class being constructed. 71 dictionary: The class dictionary of the class being constructed. 72 dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object 73 describing this protocol service type. 74 """ 75 # Don't do anything if this class doesn't have a descriptor. This happens 76 # when a service class is subclassed. 77 if GeneratedServiceType._DESCRIPTOR_KEY not in dictionary: 78 return 79 80 descriptor = dictionary[GeneratedServiceType._DESCRIPTOR_KEY] 81 service_builder = _ServiceBuilder(descriptor) 82 service_builder.BuildService(cls) 83 cls.DESCRIPTOR = descriptor 84 85 86class GeneratedServiceStubType(GeneratedServiceType): 87 88 """Metaclass for service stubs created at runtime from ServiceDescriptors. 89 90 This class has similar responsibilities as GeneratedServiceType, except that 91 it creates the service stub classes. 92 """ 93 94 _DESCRIPTOR_KEY = 'DESCRIPTOR' 95 96 def __init__(cls, name, bases, dictionary): 97 """Creates a message service stub class. 98 99 Args: 100 name: Name of the class (ignored, here). 101 bases: Base classes of the class being constructed. 102 dictionary: The class dictionary of the class being constructed. 103 dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object 104 describing this protocol service type. 105 """ 106 super(GeneratedServiceStubType, cls).__init__(name, bases, dictionary) 107 # Don't do anything if this class doesn't have a descriptor. This happens 108 # when a service stub is subclassed. 109 if GeneratedServiceStubType._DESCRIPTOR_KEY not in dictionary: 110 return 111 112 descriptor = dictionary[GeneratedServiceStubType._DESCRIPTOR_KEY] 113 service_stub_builder = _ServiceStubBuilder(descriptor) 114 service_stub_builder.BuildServiceStub(cls) 115 116 117class _ServiceBuilder(object): 118 119 """This class constructs a protocol service class using a service descriptor. 120 121 Given a service descriptor, this class constructs a class that represents 122 the specified service descriptor. One service builder instance constructs 123 exactly one service class. That means all instances of that class share the 124 same builder. 125 """ 126 127 def __init__(self, service_descriptor): 128 """Initializes an instance of the service class builder. 129 130 Args: 131 service_descriptor: ServiceDescriptor to use when constructing the 132 service class. 133 """ 134 self.descriptor = service_descriptor 135 136 def BuildService(builder, cls): 137 """Constructs the service class. 138 139 Args: 140 cls: The class that will be constructed. 141 """ 142 143 # CallMethod needs to operate with an instance of the Service class. This 144 # internal wrapper function exists only to be able to pass the service 145 # instance to the method that does the real CallMethod work. 146 # Making sure to use exact argument names from the abstract interface in 147 # service.py to match the type signature 148 def _WrapCallMethod(self, method_descriptor, rpc_controller, request, done): 149 return builder._CallMethod(self, method_descriptor, rpc_controller, 150 request, done) 151 152 def _WrapGetRequestClass(self, method_descriptor): 153 return builder._GetRequestClass(method_descriptor) 154 155 def _WrapGetResponseClass(self, method_descriptor): 156 return builder._GetResponseClass(method_descriptor) 157 158 builder.cls = cls 159 cls.CallMethod = _WrapCallMethod 160 cls.GetDescriptor = staticmethod(lambda: builder.descriptor) 161 cls.GetDescriptor.__doc__ = 'Returns the service descriptor.' 162 cls.GetRequestClass = _WrapGetRequestClass 163 cls.GetResponseClass = _WrapGetResponseClass 164 for method in builder.descriptor.methods: 165 setattr(cls, method.name, builder._GenerateNonImplementedMethod(method)) 166 167 def _CallMethod(self, srvc, method_descriptor, 168 rpc_controller, request, callback): 169 """Calls the method described by a given method descriptor. 170 171 Args: 172 srvc: Instance of the service for which this method is called. 173 method_descriptor: Descriptor that represent the method to call. 174 rpc_controller: RPC controller to use for this method's execution. 175 request: Request protocol message. 176 callback: A callback to invoke after the method has completed. 177 """ 178 if method_descriptor.containing_service != self.descriptor: 179 raise RuntimeError( 180 'CallMethod() given method descriptor for wrong service type.') 181 method = getattr(srvc, method_descriptor.name) 182 return method(rpc_controller, request, callback) 183 184 def _GetRequestClass(self, method_descriptor): 185 """Returns the class of the request protocol message. 186 187 Args: 188 method_descriptor: Descriptor of the method for which to return the 189 request protocol message class. 190 191 Returns: 192 A class that represents the input protocol message of the specified 193 method. 194 """ 195 if method_descriptor.containing_service != self.descriptor: 196 raise RuntimeError( 197 'GetRequestClass() given method descriptor for wrong service type.') 198 return method_descriptor.input_type._concrete_class 199 200 def _GetResponseClass(self, method_descriptor): 201 """Returns the class of the response protocol message. 202 203 Args: 204 method_descriptor: Descriptor of the method for which to return the 205 response protocol message class. 206 207 Returns: 208 A class that represents the output protocol message of the specified 209 method. 210 """ 211 if method_descriptor.containing_service != self.descriptor: 212 raise RuntimeError( 213 'GetResponseClass() given method descriptor for wrong service type.') 214 return method_descriptor.output_type._concrete_class 215 216 def _GenerateNonImplementedMethod(self, method): 217 """Generates and returns a method that can be set for a service methods. 218 219 Args: 220 method: Descriptor of the service method for which a method is to be 221 generated. 222 223 Returns: 224 A method that can be added to the service class. 225 """ 226 return lambda inst, rpc_controller, request, callback: ( 227 self._NonImplementedMethod(method.name, rpc_controller, callback)) 228 229 def _NonImplementedMethod(self, method_name, rpc_controller, callback): 230 """The body of all methods in the generated service class. 231 232 Args: 233 method_name: Name of the method being executed. 234 rpc_controller: RPC controller used to execute this method. 235 callback: A callback which will be invoked when the method finishes. 236 """ 237 rpc_controller.SetFailed('Method %s not implemented.' % method_name) 238 callback(None) 239 240 241class _ServiceStubBuilder(object): 242 243 """Constructs a protocol service stub class using a service descriptor. 244 245 Given a service descriptor, this class constructs a suitable stub class. 246 A stub is just a type-safe wrapper around an RpcChannel which emulates a 247 local implementation of the service. 248 249 One service stub builder instance constructs exactly one class. It means all 250 instances of that class share the same service stub builder. 251 """ 252 253 def __init__(self, service_descriptor): 254 """Initializes an instance of the service stub class builder. 255 256 Args: 257 service_descriptor: ServiceDescriptor to use when constructing the 258 stub class. 259 """ 260 self.descriptor = service_descriptor 261 262 def BuildServiceStub(self, cls): 263 """Constructs the stub class. 264 265 Args: 266 cls: The class that will be constructed. 267 """ 268 269 def _ServiceStubInit(stub, rpc_channel): 270 stub.rpc_channel = rpc_channel 271 self.cls = cls 272 cls.__init__ = _ServiceStubInit 273 for method in self.descriptor.methods: 274 setattr(cls, method.name, self._GenerateStubMethod(method)) 275 276 def _GenerateStubMethod(self, method): 277 return (lambda inst, rpc_controller, request, callback=None: 278 self._StubMethod(inst, method, rpc_controller, request, callback)) 279 280 def _StubMethod(self, stub, method_descriptor, 281 rpc_controller, request, callback): 282 """The body of all service methods in the generated stub class. 283 284 Args: 285 stub: Stub instance. 286 method_descriptor: Descriptor of the invoked method. 287 rpc_controller: Rpc controller to execute the method. 288 request: Request protocol message. 289 callback: A callback to execute when the method finishes. 290 Returns: 291 Response message (in case of blocking call). 292 """ 293 return stub.rpc_channel.CallMethod( 294 method_descriptor, rpc_controller, request, 295 method_descriptor.output_type._concrete_class, callback) 296