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__ = 'petar@google.com (Petar Petrov)'
40
41from google.protobuf.internal import api_implementation
42
43if api_implementation.Type() == 'cpp':
44  # pylint: disable=g-import-not-at-top
45  from google.protobuf.pyext import _message
46
47
48class GeneratedServiceType(type):
49
50  """Metaclass for service classes created at runtime from ServiceDescriptors.
51
52  Implementations for all methods described in the Service class are added here
53  by this class. We also create properties to allow getting/setting all fields
54  in the protocol message.
55
56  The protocol compiler currently uses this metaclass to create protocol service
57  classes at runtime. Clients can also manually create their own classes at
58  runtime, as in this example::
59
60    mydescriptor = ServiceDescriptor(.....)
61    class MyProtoService(service.Service):
62      __metaclass__ = GeneratedServiceType
63      DESCRIPTOR = mydescriptor
64    myservice_instance = MyProtoService()
65    # ...
66  """
67
68  _DESCRIPTOR_KEY = 'DESCRIPTOR'
69
70  def __init__(cls, name, bases, dictionary):
71    """Creates a message service class.
72
73    Args:
74      name: Name of the class (ignored, but required by the metaclass
75        protocol).
76      bases: Base classes of the class being constructed.
77      dictionary: The class dictionary of the class being constructed.
78        dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object
79        describing this protocol service type.
80    """
81    # Don't do anything if this class doesn't have a descriptor. This happens
82    # when a service class is subclassed.
83    if GeneratedServiceType._DESCRIPTOR_KEY not in dictionary:
84      return
85
86    descriptor = dictionary[GeneratedServiceType._DESCRIPTOR_KEY]
87    if isinstance(descriptor, str):
88      descriptor = _message.default_pool.FindServiceByName(descriptor)
89      dictionary[GeneratedServiceType._DESCRIPTOR_KEY] = descriptor
90
91    service_builder = _ServiceBuilder(descriptor)
92    service_builder.BuildService(cls)
93    cls.DESCRIPTOR = descriptor
94
95
96class GeneratedServiceStubType(GeneratedServiceType):
97
98  """Metaclass for service stubs created at runtime from ServiceDescriptors.
99
100  This class has similar responsibilities as GeneratedServiceType, except that
101  it creates the service stub classes.
102  """
103
104  _DESCRIPTOR_KEY = 'DESCRIPTOR'
105
106  def __init__(cls, name, bases, dictionary):
107    """Creates a message service stub class.
108
109    Args:
110      name: Name of the class (ignored, here).
111      bases: Base classes of the class being constructed.
112      dictionary: The class dictionary of the class being constructed.
113        dictionary[_DESCRIPTOR_KEY] must contain a ServiceDescriptor object
114        describing this protocol service type.
115    """
116    descriptor = dictionary.get(cls._DESCRIPTOR_KEY)
117    if isinstance(descriptor, str):
118      descriptor = _message.default_pool.FindServiceByName(descriptor)
119      dictionary[GeneratedServiceStubType._DESCRIPTOR_KEY] = descriptor
120    super(GeneratedServiceStubType, cls).__init__(name, bases, dictionary)
121    # Don't do anything if this class doesn't have a descriptor. This happens
122    # when a service stub is subclassed.
123    if GeneratedServiceStubType._DESCRIPTOR_KEY not in dictionary:
124      return
125
126    service_stub_builder = _ServiceStubBuilder(descriptor)
127    service_stub_builder.BuildServiceStub(cls)
128
129
130class _ServiceBuilder(object):
131
132  """This class constructs a protocol service class using a service descriptor.
133
134  Given a service descriptor, this class constructs a class that represents
135  the specified service descriptor. One service builder instance constructs
136  exactly one service class. That means all instances of that class share the
137  same builder.
138  """
139
140  def __init__(self, service_descriptor):
141    """Initializes an instance of the service class builder.
142
143    Args:
144      service_descriptor: ServiceDescriptor to use when constructing the
145        service class.
146    """
147    self.descriptor = service_descriptor
148
149  def BuildService(self, cls):
150    """Constructs the service class.
151
152    Args:
153      cls: The class that will be constructed.
154    """
155
156    # CallMethod needs to operate with an instance of the Service class. This
157    # internal wrapper function exists only to be able to pass the service
158    # instance to the method that does the real CallMethod work.
159    def _WrapCallMethod(srvc, method_descriptor,
160                        rpc_controller, request, callback):
161      return self._CallMethod(srvc, method_descriptor,
162                       rpc_controller, request, callback)
163    self.cls = cls
164    cls.CallMethod = _WrapCallMethod
165    cls.GetDescriptor = staticmethod(lambda: self.descriptor)
166    cls.GetDescriptor.__doc__ = "Returns the service descriptor."
167    cls.GetRequestClass = self._GetRequestClass
168    cls.GetResponseClass = self._GetResponseClass
169    for method in self.descriptor.methods:
170      setattr(cls, method.name, self._GenerateNonImplementedMethod(method))
171
172  def _CallMethod(self, srvc, method_descriptor,
173                  rpc_controller, request, callback):
174    """Calls the method described by a given method descriptor.
175
176    Args:
177      srvc: Instance of the service for which this method is called.
178      method_descriptor: Descriptor that represent the method to call.
179      rpc_controller: RPC controller to use for this method's execution.
180      request: Request protocol message.
181      callback: A callback to invoke after the method has completed.
182    """
183    if method_descriptor.containing_service != self.descriptor:
184      raise RuntimeError(
185          'CallMethod() given method descriptor for wrong service type.')
186    method = getattr(srvc, method_descriptor.name)
187    return method(rpc_controller, request, callback)
188
189  def _GetRequestClass(self, method_descriptor):
190    """Returns the class of the request protocol message.
191
192    Args:
193      method_descriptor: Descriptor of the method for which to return the
194        request protocol message class.
195
196    Returns:
197      A class that represents the input protocol message of the specified
198      method.
199    """
200    if method_descriptor.containing_service != self.descriptor:
201      raise RuntimeError(
202          'GetRequestClass() given method descriptor for wrong service type.')
203    return method_descriptor.input_type._concrete_class
204
205  def _GetResponseClass(self, method_descriptor):
206    """Returns the class of the response protocol message.
207
208    Args:
209      method_descriptor: Descriptor of the method for which to return the
210        response protocol message class.
211
212    Returns:
213      A class that represents the output protocol message of the specified
214      method.
215    """
216    if method_descriptor.containing_service != self.descriptor:
217      raise RuntimeError(
218          'GetResponseClass() given method descriptor for wrong service type.')
219    return method_descriptor.output_type._concrete_class
220
221  def _GenerateNonImplementedMethod(self, method):
222    """Generates and returns a method that can be set for a service methods.
223
224    Args:
225      method: Descriptor of the service method for which a method is to be
226        generated.
227
228    Returns:
229      A method that can be added to the service class.
230    """
231    return lambda inst, rpc_controller, request, callback: (
232        self._NonImplementedMethod(method.name, rpc_controller, callback))
233
234  def _NonImplementedMethod(self, method_name, rpc_controller, callback):
235    """The body of all methods in the generated service class.
236
237    Args:
238      method_name: Name of the method being executed.
239      rpc_controller: RPC controller used to execute this method.
240      callback: A callback which will be invoked when the method finishes.
241    """
242    rpc_controller.SetFailed('Method %s not implemented.' % method_name)
243    callback(None)
244
245
246class _ServiceStubBuilder(object):
247
248  """Constructs a protocol service stub class using a service descriptor.
249
250  Given a service descriptor, this class constructs a suitable stub class.
251  A stub is just a type-safe wrapper around an RpcChannel which emulates a
252  local implementation of the service.
253
254  One service stub builder instance constructs exactly one class. It means all
255  instances of that class share the same service stub builder.
256  """
257
258  def __init__(self, service_descriptor):
259    """Initializes an instance of the service stub class builder.
260
261    Args:
262      service_descriptor: ServiceDescriptor to use when constructing the
263        stub class.
264    """
265    self.descriptor = service_descriptor
266
267  def BuildServiceStub(self, cls):
268    """Constructs the stub class.
269
270    Args:
271      cls: The class that will be constructed.
272    """
273
274    def _ServiceStubInit(stub, rpc_channel):
275      stub.rpc_channel = rpc_channel
276    self.cls = cls
277    cls.__init__ = _ServiceStubInit
278    for method in self.descriptor.methods:
279      setattr(cls, method.name, self._GenerateStubMethod(method))
280
281  def _GenerateStubMethod(self, method):
282    return (lambda inst, rpc_controller, request, callback=None:
283        self._StubMethod(inst, method, rpc_controller, request, callback))
284
285  def _StubMethod(self, stub, method_descriptor,
286                  rpc_controller, request, callback):
287    """The body of all service methods in the generated stub class.
288
289    Args:
290      stub: Stub instance.
291      method_descriptor: Descriptor of the invoked method.
292      rpc_controller: Rpc controller to execute the method.
293      request: Request protocol message.
294      callback: A callback to execute when the method finishes.
295    Returns:
296      Response message (in case of blocking call).
297    """
298    return stub.rpc_channel.CallMethod(
299        method_descriptor, rpc_controller, request,
300        method_descriptor.output_type._concrete_class, callback)
301