1#!/usr/bin/env python
2#
3# Copyright 2010 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""Remote service library.
19
20This module contains classes that are useful for building remote services that
21conform to a standard request and response model.  To conform to this model
22a service must be like the following class:
23
24  # Each service instance only handles a single request and is then discarded.
25  # Make these objects light weight.
26  class Service(object):
27
28    # It must be possible to construct service objects without any parameters.
29    # If your constructor needs extra information you should provide a
30    # no-argument factory function to create service instances.
31    def __init__(self):
32      ...
33
34    # Each remote method must use the 'method' decorator, passing the request
35    # and response message types.  The remote method itself must take a single
36    # parameter which is an instance of RequestMessage and return an instance
37    # of ResponseMessage.
38    @method(RequestMessage, ResponseMessage)
39    def remote_method(self, request):
40      # Return an instance of ResponseMessage.
41
42    # A service object may optionally implement an 'initialize_request_state'
43    # method that takes as a parameter a single instance of a RequestState.  If
44    # a service does not implement this method it will not receive the request
45    # state.
46    def initialize_request_state(self, state):
47      ...
48
49The 'Service' class is provided as a convenient base class that provides the
50above functionality.  It implements all required and optional methods for a
51service.  It also has convenience methods for creating factory functions that
52can pass persistent global state to a new service instance.
53
54The 'method' decorator is used to declare which methods of a class are
55meant to service RPCs.  While this decorator is not responsible for handling
56actual remote method invocations, such as handling sockets, handling various
57RPC protocols and checking messages for correctness, it does attach information
58to methods that responsible classes can examine and ensure the correctness
59of the RPC.
60
61When the method decorator is used on a method, the wrapper method will have a
62'remote' property associated with it.  The 'remote' property contains the
63request_type and response_type expected by the methods implementation.
64
65On its own, the method decorator does not provide any support for subclassing
66remote methods.  In order to extend a service, one would need to redecorate
67the sub-classes methods.  For example:
68
69  class MyService(Service):
70
71    @method(DoSomethingRequest, DoSomethingResponse)
72    def do_stuff(self, request):
73      ... implement do_stuff ...
74
75  class MyBetterService(MyService):
76
77    @method(DoSomethingRequest, DoSomethingResponse)
78    def do_stuff(self, request):
79      response = super(MyBetterService, self).do_stuff.remote.method(request)
80      ... do stuff with response ...
81      return response
82
83A Service subclass also has a Stub class that can be used with a transport for
84making RPCs.  When a stub is created, it is capable of doing both synchronous
85and asynchronous RPCs if the underlying transport supports it.  To make a stub
86using an HTTP transport do:
87
88  my_service = MyService.Stub(HttpTransport('<my service URL>'))
89
90For synchronous calls, just call the expected methods on the service stub:
91
92  request = DoSomethingRequest()
93  ...
94  response = my_service.do_something(request)
95
96Each stub instance has an async object that can be used for initiating
97asynchronous RPCs if the underlying protocol transport supports it.  To
98make an asynchronous call, do:
99
100  rpc = my_service.async.do_something(request)
101  response = rpc.get_response()
102"""
103
104from __future__ import with_statement
105import six
106
107__author__ = 'rafek@google.com (Rafe Kaplan)'
108
109import logging
110import sys
111import threading
112from wsgiref import headers as wsgi_headers
113
114from . import message_types
115from . import messages
116from . import protobuf
117from . import protojson
118from . import util
119
120
121__all__ = [
122    'ApplicationError',
123    'MethodNotFoundError',
124    'NetworkError',
125    'RequestError',
126    'RpcError',
127    'ServerError',
128    'ServiceConfigurationError',
129    'ServiceDefinitionError',
130
131    'HttpRequestState',
132    'ProtocolConfig',
133    'Protocols',
134    'RequestState',
135    'RpcState',
136    'RpcStatus',
137    'Service',
138    'StubBase',
139    'check_rpc_status',
140    'get_remote_method_info',
141    'is_error_status',
142    'method',
143    'remote',
144]
145
146
147class ServiceDefinitionError(messages.Error):
148  """Raised when a service is improperly defined."""
149
150
151class ServiceConfigurationError(messages.Error):
152  """Raised when a service is incorrectly configured."""
153
154
155# TODO: Use error_name to map to specific exception message types.
156class RpcStatus(messages.Message):
157  """Status of on-going or complete RPC.
158
159  Fields:
160    state: State of RPC.
161    error_name: Error name set by application.  Only set when
162      status is APPLICATION_ERROR.  For use by application to transmit
163      specific reason for error.
164    error_message: Error message associated with status.
165  """
166
167  class State(messages.Enum):
168    """Enumeration of possible RPC states.
169
170    Values:
171      OK: Completed successfully.
172      RUNNING: Still running, not complete.
173      REQUEST_ERROR: Request was malformed or incomplete.
174      SERVER_ERROR: Server experienced an unexpected error.
175      NETWORK_ERROR: An error occured on the network.
176      APPLICATION_ERROR: The application is indicating an error.
177        When in this state, RPC should also set application_error.
178    """
179    OK = 0
180    RUNNING = 1
181
182    REQUEST_ERROR = 2
183    SERVER_ERROR = 3
184    NETWORK_ERROR = 4
185    APPLICATION_ERROR = 5
186    METHOD_NOT_FOUND_ERROR = 6
187
188  state = messages.EnumField(State, 1, required=True)
189  error_message = messages.StringField(2)
190  error_name = messages.StringField(3)
191
192
193RpcState = RpcStatus.State
194
195
196class RpcError(messages.Error):
197  """Base class for RPC errors.
198
199  Each sub-class of RpcError is associated with an error value from RpcState
200  and has an attribute STATE that refers to that value.
201  """
202
203  def __init__(self, message, cause=None):
204    super(RpcError, self).__init__(message)
205    self.cause = cause
206
207  @classmethod
208  def from_state(cls, state):
209    """Get error class from RpcState.
210
211    Args:
212      state: RpcState value.  Can be enum value itself, string or int.
213
214    Returns:
215      Exception class mapped to value if state is an error.  Returns None
216      if state is OK or RUNNING.
217    """
218    return _RPC_STATE_TO_ERROR.get(RpcState(state))
219
220
221class RequestError(RpcError):
222  """Raised when wrong request objects received during method invocation."""
223
224  STATE = RpcState.REQUEST_ERROR
225
226
227class MethodNotFoundError(RequestError):
228  """Raised when unknown method requested by RPC."""
229
230  STATE = RpcState.METHOD_NOT_FOUND_ERROR
231
232
233class NetworkError(RpcError):
234  """Raised when network error occurs during RPC."""
235
236  STATE = RpcState.NETWORK_ERROR
237
238
239class ServerError(RpcError):
240  """Unexpected error occured on server."""
241
242  STATE = RpcState.SERVER_ERROR
243
244
245class ApplicationError(RpcError):
246  """Raised for application specific errors.
247
248  Attributes:
249    error_name: Application specific error name for exception.
250  """
251
252  STATE = RpcState.APPLICATION_ERROR
253
254  def __init__(self, message, error_name=None):
255    """Constructor.
256
257    Args:
258      message: Application specific error message.
259      error_name: Application specific error name.  Must be None, string
260      or unicode string.
261    """
262    super(ApplicationError, self).__init__(message)
263    self.error_name = error_name
264
265  def __str__(self):
266    return self.args[0]
267
268  def __repr__(self):
269    if self.error_name is None:
270      error_format = ''
271    else:
272      error_format = ', %r' % self.error_name
273    return '%s(%r%s)' % (type(self).__name__, self.args[0], error_format)
274
275
276_RPC_STATE_TO_ERROR = {
277  RpcState.REQUEST_ERROR: RequestError,
278  RpcState.NETWORK_ERROR: NetworkError,
279  RpcState.SERVER_ERROR: ServerError,
280  RpcState.APPLICATION_ERROR: ApplicationError,
281  RpcState.METHOD_NOT_FOUND_ERROR: MethodNotFoundError,
282}
283
284class _RemoteMethodInfo(object):
285  """Object for encapsulating remote method information.
286
287  An instance of this method is associated with the 'remote' attribute
288  of the methods 'invoke_remote_method' instance.
289
290  Instances of this class are created by the remote decorator and should not
291  be created directly.
292  """
293
294  def __init__(self,
295               method,
296               request_type,
297               response_type):
298    """Constructor.
299
300    Args:
301      method: The method which implements the remote method.  This is a
302        function that will act as an instance method of a class definition
303        that is decorated by '@method'.  It must always take 'self' as its
304        first parameter.
305      request_type: Expected request type for the remote method.
306      response_type: Expected response type for the remote method.
307    """
308    self.__method = method
309    self.__request_type = request_type
310    self.__response_type = response_type
311
312  @property
313  def method(self):
314    """Original undecorated method."""
315    return self.__method
316
317  @property
318  def request_type(self):
319    """Expected request type for remote method."""
320    if isinstance(self.__request_type, six.string_types):
321      self.__request_type = messages.find_definition(
322        self.__request_type,
323        relative_to=sys.modules[self.__method.__module__])
324    return self.__request_type
325
326  @property
327  def response_type(self):
328    """Expected response type for remote method."""
329    if isinstance(self.__response_type, six.string_types):
330      self.__response_type = messages.find_definition(
331        self.__response_type,
332        relative_to=sys.modules[self.__method.__module__])
333    return self.__response_type
334
335
336def method(request_type=message_types.VoidMessage,
337           response_type=message_types.VoidMessage):
338  """Method decorator for creating remote methods.
339
340  Args:
341    request_type: Message type of expected request.
342    response_type: Message type of expected response.
343
344  Returns:
345    'remote_method_wrapper' function.
346
347  Raises:
348    TypeError: if the request_type or response_type parameters are not
349      proper subclasses of messages.Message.
350  """
351  if (not isinstance(request_type, six.string_types) and
352      (not isinstance(request_type, type) or
353       not issubclass(request_type, messages.Message) or
354       request_type is messages.Message)):
355    raise TypeError(
356        'Must provide message class for request-type.  Found %s',
357        request_type)
358
359  if (not isinstance(response_type, six.string_types) and
360      (not isinstance(response_type, type) or
361       not issubclass(response_type, messages.Message) or
362       response_type is messages.Message)):
363    raise TypeError(
364        'Must provide message class for response-type.  Found %s',
365        response_type)
366
367  def remote_method_wrapper(method):
368    """Decorator used to wrap method.
369
370    Args:
371      method: Original method being wrapped.
372
373    Returns:
374      'invoke_remote_method' function responsible for actual invocation.
375      This invocation function instance is assigned an attribute 'remote'
376      which contains information about the remote method:
377        request_type: Expected request type for remote method.
378        response_type: Response type returned from remote method.
379
380    Raises:
381      TypeError: If request_type or response_type is not a subclass of Message
382        or is the Message class itself.
383    """
384
385    def invoke_remote_method(service_instance, request):
386      """Function used to replace original method.
387
388      Invoke wrapped remote method.  Checks to ensure that request and
389      response objects are the correct types.
390
391      Does not check whether messages are initialized.
392
393      Args:
394        service_instance: The service object whose method is being invoked.
395          This is passed to 'self' during the invocation of the original
396          method.
397        request: Request message.
398
399      Returns:
400        Results of calling wrapped remote method.
401
402      Raises:
403        RequestError: Request object is not of the correct type.
404        ServerError: Response object is not of the correct type.
405      """
406      if not isinstance(request, remote_method_info.request_type):
407        raise RequestError('Method %s.%s expected request type %s, '
408                           'received %s' %
409                           (type(service_instance).__name__,
410                            method.__name__,
411                            remote_method_info.request_type,
412                            type(request)))
413      response = method(service_instance, request)
414      if not isinstance(response, remote_method_info.response_type):
415        raise ServerError('Method %s.%s expected response type %s, '
416                          'sent %s' %
417                          (type(service_instance).__name__,
418                           method.__name__,
419                           remote_method_info.response_type,
420                           type(response)))
421      return response
422
423    remote_method_info = _RemoteMethodInfo(method,
424                                           request_type,
425                                           response_type)
426
427    invoke_remote_method.remote = remote_method_info
428    invoke_remote_method.__name__ = method.__name__
429    return invoke_remote_method
430
431  return remote_method_wrapper
432
433
434def remote(request_type, response_type):
435  """Temporary backward compatibility alias for method."""
436  logging.warning('The remote decorator has been renamed method.  It will be '
437                  'removed in very soon from future versions of ProtoRPC.')
438  return method(request_type, response_type)
439
440
441def get_remote_method_info(method):
442  """Get remote method info object from remote method.
443
444  Returns:
445    Remote method info object if method is a remote method, else None.
446  """
447  if not callable(method):
448    return None
449
450  try:
451    method_info = method.remote
452  except AttributeError:
453    return None
454
455  if not isinstance(method_info, _RemoteMethodInfo):
456    return None
457
458  return method_info
459
460
461class StubBase(object):
462  """Base class for client side service stubs.
463
464  The remote method stubs are created by the _ServiceClass meta-class
465  when a Service class is first created.  The resulting stub will
466  extend both this class and the service class it handles communications for.
467
468  Assume that there is a service:
469
470    class NewContactRequest(messages.Message):
471
472      name = messages.StringField(1, required=True)
473      phone = messages.StringField(2)
474      email = messages.StringField(3)
475
476    class NewContactResponse(message.Message):
477
478      contact_id = messages.StringField(1)
479
480    class AccountService(remote.Service):
481
482      @remote.method(NewContactRequest, NewContactResponse):
483      def new_contact(self, request):
484        ... implementation ...
485
486  A stub of this service can be called in two ways.  The first is to pass in a
487  correctly initialized NewContactRequest message:
488
489    request = NewContactRequest()
490    request.name = 'Bob Somebody'
491    request.phone = '+1 415 555 1234'
492
493    response = account_service_stub.new_contact(request)
494
495  The second way is to pass in keyword parameters that correspond with the root
496  request message type:
497
498      account_service_stub.new_contact(name='Bob Somebody',
499                                       phone='+1 415 555 1234')
500
501  The second form will create a request message of the appropriate type.
502  """
503
504  def __init__(self, transport):
505    """Constructor.
506
507    Args:
508      transport: Underlying transport to communicate with remote service.
509    """
510    self.__transport = transport
511
512  @property
513  def transport(self):
514    """Transport used to communicate with remote service."""
515    return self.__transport
516
517
518class _ServiceClass(type):
519  """Meta-class for service class."""
520
521  def __new_async_method(cls, remote):
522    """Create asynchronous method for Async handler.
523
524    Args:
525      remote: RemoteInfo to create method for.
526    """
527    def async_method(self, *args, **kwargs):
528      """Asynchronous remote method.
529
530      Args:
531        self: Instance of StubBase.Async subclass.
532
533        Stub methods either take a single positional argument when a full
534        request message is passed in, or keyword arguments, but not both.
535
536        See docstring for StubBase for more information on how to use remote
537        stub methods.
538
539      Returns:
540        Rpc instance used to represent asynchronous RPC.
541      """
542      if args and kwargs:
543        raise TypeError('May not provide both args and kwargs')
544
545      if not args:
546        # Construct request object from arguments.
547        request = remote.request_type()
548        for name, value in six.iteritems(kwargs):
549          setattr(request, name, value)
550      else:
551        # First argument is request object.
552        request = args[0]
553
554      return self.transport.send_rpc(remote, request)
555
556    async_method.__name__ = remote.method.__name__
557    async_method = util.positional(2)(async_method)
558    async_method.remote = remote
559    return async_method
560
561  def __new_sync_method(cls, async_method):
562    """Create synchronous method for stub.
563
564    Args:
565      async_method: asynchronous method to delegate calls to.
566    """
567    def sync_method(self, *args, **kwargs):
568      """Synchronous remote method.
569
570      Args:
571        self: Instance of StubBase.Async subclass.
572        args: Tuple (request,):
573          request: Request object.
574        kwargs: Field values for request.  Must be empty if request object
575          is provided.
576
577      Returns:
578        Response message from synchronized RPC.
579      """
580      return async_method(self.async, *args, **kwargs).response
581    sync_method.__name__ = async_method.__name__
582    sync_method.remote = async_method.remote
583    return sync_method
584
585  def __create_async_methods(cls, remote_methods):
586    """Construct a dictionary of asynchronous methods based on remote methods.
587
588    Args:
589      remote_methods: Dictionary of methods with associated RemoteInfo objects.
590
591    Returns:
592      Dictionary of asynchronous methods with assocaited RemoteInfo objects.
593      Results added to AsyncStub subclass.
594    """
595    async_methods = {}
596    for method_name, method in remote_methods.items():
597      async_methods[method_name] = cls.__new_async_method(method.remote)
598    return async_methods
599
600  def __create_sync_methods(cls, async_methods):
601    """Construct a dictionary of synchronous methods based on remote methods.
602
603    Args:
604      async_methods: Dictionary of async methods to delegate calls to.
605
606    Returns:
607      Dictionary of synchronous methods with assocaited RemoteInfo objects.
608      Results added to Stub subclass.
609    """
610    sync_methods = {}
611    for method_name, async_method in async_methods.items():
612      sync_methods[method_name] = cls.__new_sync_method(async_method)
613    return sync_methods
614
615  def __new__(cls, name, bases, dct):
616    """Instantiate new service class instance."""
617    if StubBase not in bases:
618      # Collect existing remote methods.
619      base_methods = {}
620      for base in bases:
621        try:
622          remote_methods = base.__remote_methods
623        except AttributeError:
624          pass
625        else:
626          base_methods.update(remote_methods)
627
628      # Set this class private attribute so that base_methods do not have
629      # to be recacluated in __init__.
630      dct['_ServiceClass__base_methods'] = base_methods
631
632      for attribute, value in dct.items():
633        base_method = base_methods.get(attribute, None)
634        if base_method:
635          if not callable(value):
636            raise ServiceDefinitionError(
637              'Must override %s in %s with a method.' % (
638                attribute, name))
639
640          if get_remote_method_info(value):
641            raise ServiceDefinitionError(
642              'Do not use method decorator when overloading remote method %s '
643              'on service %s.' %
644              (attribute, name))
645
646          base_remote_method_info = get_remote_method_info(base_method)
647          remote_decorator = method(
648            base_remote_method_info.request_type,
649            base_remote_method_info.response_type)
650          new_remote_method = remote_decorator(value)
651          dct[attribute] = new_remote_method
652
653    return type.__new__(cls, name, bases, dct)
654
655  def __init__(cls, name, bases, dct):
656    """Create uninitialized state on new class."""
657    type.__init__(cls, name, bases, dct)
658
659    # Only service implementation classes should have remote methods and stub
660    # sub classes created.  Stub implementations have their own methods passed
661    # in to the type constructor.
662    if StubBase not in bases:
663      # Create list of remote methods.
664      cls.__remote_methods = dict(cls.__base_methods)
665
666      for attribute, value in dct.items():
667        value = getattr(cls, attribute)
668        remote_method_info = get_remote_method_info(value)
669        if remote_method_info:
670          cls.__remote_methods[attribute] = value
671
672      # Build asynchronous stub class.
673      stub_attributes = {'Service': cls}
674      async_methods = cls.__create_async_methods(cls.__remote_methods)
675      stub_attributes.update(async_methods)
676      async_class = type('AsyncStub', (StubBase, cls), stub_attributes)
677      cls.AsyncStub = async_class
678
679      # Constructor for synchronous stub class.
680      def __init__(self, transport):
681        """Constructor.
682
683        Args:
684          transport: Underlying transport to communicate with remote service.
685        """
686        super(cls.Stub, self).__init__(transport)
687        self.async = cls.AsyncStub(transport)
688
689      # Build synchronous stub class.
690      stub_attributes = {'Service': cls,
691                         '__init__': __init__}
692      stub_attributes.update(cls.__create_sync_methods(async_methods))
693
694      cls.Stub = type('Stub', (StubBase, cls), stub_attributes)
695
696  @staticmethod
697  def all_remote_methods(cls):
698    """Get all remote methods of service.
699
700    Returns:
701      Dict from method name to unbound method.
702    """
703    return dict(cls.__remote_methods)
704
705
706class RequestState(object):
707  """Request state information.
708
709  Properties:
710    remote_host: Remote host name where request originated.
711    remote_address: IP address where request originated.
712    server_host: Host of server within which service resides.
713    server_port: Post which service has recevied request from.
714  """
715
716  @util.positional(1)
717  def __init__(self,
718               remote_host=None,
719               remote_address=None,
720               server_host=None,
721               server_port=None):
722    """Constructor.
723
724    Args:
725      remote_host: Assigned to property.
726      remote_address: Assigned to property.
727      server_host: Assigned to property.
728      server_port: Assigned to property.
729    """
730    self.__remote_host = remote_host
731    self.__remote_address = remote_address
732    self.__server_host = server_host
733    self.__server_port = server_port
734
735  @property
736  def remote_host(self):
737    return self.__remote_host
738
739  @property
740  def remote_address(self):
741    return self.__remote_address
742
743  @property
744  def server_host(self):
745    return self.__server_host
746
747  @property
748  def server_port(self):
749    return self.__server_port
750
751  def _repr_items(self):
752    for name in ['remote_host',
753                 'remote_address',
754                 'server_host',
755                 'server_port']:
756      yield name, getattr(self, name)
757
758  def __repr__(self):
759    """String representation of state."""
760    state = [self.__class__.__name__]
761    for name, value in self._repr_items():
762      if value:
763        state.append('%s=%r' % (name, value))
764
765    return '<%s>' % (' '.join(state),)
766
767
768class HttpRequestState(RequestState):
769  """HTTP request state information.
770
771  NOTE: Does not attempt to represent certain types of information from the
772  request such as the query string as query strings are not permitted in
773  ProtoRPC URLs unless required by the underlying message format.
774
775  Properties:
776    headers: wsgiref.headers.Headers instance of HTTP request headers.
777    http_method: HTTP method as a string.
778    service_path: Path on HTTP service where service is mounted.  This path
779      will not include the remote method name.
780  """
781
782  @util.positional(1)
783  def __init__(self,
784               http_method=None,
785               service_path=None,
786               headers=None,
787               **kwargs):
788    """Constructor.
789
790    Args:
791      Same as RequestState, including:
792        http_method: Assigned to property.
793        service_path: Assigned to property.
794        headers: HTTP request headers.  If instance of Headers, assigned to
795          property without copying.  If dict, will convert to name value pairs
796          for use with Headers constructor.  Otherwise, passed as parameters to
797          Headers constructor.
798    """
799    super(HttpRequestState, self).__init__(**kwargs)
800
801    self.__http_method = http_method
802    self.__service_path = service_path
803
804    # Initialize headers.
805    if isinstance(headers, dict):
806      header_list = []
807      for key, value in sorted(headers.items()):
808        if not isinstance(value, list):
809          value = [value]
810        for item in value:
811          header_list.append((key, item))
812        headers = header_list
813    self.__headers = wsgi_headers.Headers(headers or [])
814
815  @property
816  def http_method(self):
817    return self.__http_method
818
819  @property
820  def service_path(self):
821    return self.__service_path
822
823  @property
824  def headers(self):
825    return self.__headers
826
827  def _repr_items(self):
828    for item in super(HttpRequestState, self)._repr_items():
829      yield item
830
831    for name in ['http_method', 'service_path']:
832      yield name, getattr(self, name)
833
834    yield 'headers', list(self.headers.items())
835
836
837class Service(six.with_metaclass(_ServiceClass, object)):
838  """Service base class.
839
840  Base class used for defining remote services.  Contains reflection functions,
841  useful helpers and built-in remote methods.
842
843  Services are expected to be constructed via either a constructor or factory
844  which takes no parameters.  However, it might be required that some state or
845  configuration is passed in to a service across multiple requests.
846
847  To do this, define parameters to the constructor of the service and use
848  the 'new_factory' class method to build a constructor that will transmit
849  parameters to the constructor.  For example:
850
851    class MyService(Service):
852
853      def __init__(self, configuration, state):
854        self.configuration = configuration
855        self.state = state
856
857    configuration = MyServiceConfiguration()
858    global_state = MyServiceState()
859
860    my_service_factory = MyService.new_factory(configuration,
861                                               state=global_state)
862
863  The contract with any service handler is that a new service object is created
864  to handle each user request, and that the construction does not take any
865  parameters.  The factory satisfies this condition:
866
867    new_instance = my_service_factory()
868    assert new_instance.state is global_state
869
870  Attributes:
871    request_state: RequestState set via initialize_request_state.
872  """
873
874  __request_state = None
875
876  @classmethod
877  def all_remote_methods(cls):
878    """Get all remote methods for service class.
879
880    Built-in methods do not appear in the dictionary of remote methods.
881
882    Returns:
883      Dictionary mapping method name to remote method.
884    """
885    return _ServiceClass.all_remote_methods(cls)
886
887  @classmethod
888  def new_factory(cls, *args, **kwargs):
889    """Create factory for service.
890
891    Useful for passing configuration or state objects to the service.  Accepts
892    arbitrary parameters and keywords, however, underlying service must accept
893    also accept not other parameters in its constructor.
894
895    Args:
896      args: Args to pass to service constructor.
897      kwargs: Keyword arguments to pass to service constructor.
898
899    Returns:
900      Factory function that will create a new instance and forward args and
901      keywords to the constructor.
902    """
903
904    def service_factory():
905      return cls(*args, **kwargs)
906
907    # Update docstring so that it is easier to debug.
908    full_class_name = '%s.%s' % (cls.__module__, cls.__name__)
909    service_factory.__doc__ = (
910        'Creates new instances of service %s.\n\n'
911        'Returns:\n'
912        '  New instance of %s.'
913        % (cls.__name__, full_class_name))
914
915    # Update name so that it is easier to debug the factory function.
916    service_factory.__name__ = '%s_service_factory' % cls.__name__
917
918    service_factory.service_class = cls
919
920    return service_factory
921
922  def initialize_request_state(self, request_state):
923    """Save request state for use in remote method.
924
925    Args:
926      request_state: RequestState instance.
927    """
928    self.__request_state = request_state
929
930  @classmethod
931  def definition_name(cls):
932    """Get definition name for Service class.
933
934    Package name is determined by the global 'package' attribute in the
935    module that contains the Service definition.  If no 'package' attribute
936    is available, uses module name.  If no module is found, just uses class
937    name as name.
938
939    Returns:
940      Fully qualified service name.
941    """
942    try:
943      return cls.__definition_name
944    except AttributeError:
945      outer_definition_name = cls.outer_definition_name()
946      if outer_definition_name is None:
947        cls.__definition_name = cls.__name__
948      else:
949        cls.__definition_name = '%s.%s' % (outer_definition_name, cls.__name__)
950
951      return cls.__definition_name
952
953  @classmethod
954  def outer_definition_name(cls):
955    """Get outer definition name.
956
957    Returns:
958      Package for service.  Services are never nested inside other definitions.
959    """
960    return cls.definition_package()
961
962  @classmethod
963  def definition_package(cls):
964    """Get package for service.
965
966    Returns:
967      Package name for service.
968    """
969    try:
970      return cls.__definition_package
971    except AttributeError:
972      cls.__definition_package = util.get_package_for_module(cls.__module__)
973
974    return cls.__definition_package
975
976  @property
977  def request_state(self):
978    """Request state associated with this Service instance."""
979    return self.__request_state
980
981
982def is_error_status(status):
983  """Function that determines whether the RPC status is an error.
984
985  Args:
986    status: Initialized RpcStatus message to check for errors.
987  """
988  status.check_initialized()
989  return RpcError.from_state(status.state) is not None
990
991
992def check_rpc_status(status):
993  """Function converts an error status to a raised exception.
994
995  Args:
996    status: Initialized RpcStatus message to check for errors.
997
998  Raises:
999    RpcError according to state set on status, if it is an error state.
1000  """
1001  status.check_initialized()
1002  error_class = RpcError.from_state(status.state)
1003  if error_class is not None:
1004    if error_class is ApplicationError:
1005      raise error_class(status.error_message, status.error_name)
1006    else:
1007      raise error_class(status.error_message)
1008
1009
1010class ProtocolConfig(object):
1011  """Configuration for single protocol mapping.
1012
1013  A read-only protocol configuration provides a given protocol implementation
1014  with a name and a set of content-types that it recognizes.
1015
1016  Properties:
1017    protocol: The protocol implementation for configuration (usually a module,
1018      for example, protojson, protobuf, etc.).  This is an object that has the
1019      following attributes:
1020        CONTENT_TYPE: Used as the default content-type if default_content_type
1021          is not set.
1022        ALTERNATIVE_CONTENT_TYPES (optional): A list of alternative
1023          content-types to the default that indicate the same protocol.
1024        encode_message: Function that matches the signature of
1025          ProtocolConfig.encode_message.  Used for encoding a ProtoRPC message.
1026        decode_message: Function that matches the signature of
1027          ProtocolConfig.decode_message.  Used for decoding a ProtoRPC message.
1028    name: Name of protocol configuration.
1029    default_content_type: The default content type for the protocol.  Overrides
1030      CONTENT_TYPE defined on protocol.
1031    alternative_content_types: A list of alternative content-types supported
1032      by the protocol.  Must not contain the default content-type, nor
1033      duplicates.  Overrides ALTERNATIVE_CONTENT_TYPE defined on protocol.
1034    content_types: A list of all content-types supported by configuration.
1035      Combination of default content-type and alternatives.
1036  """
1037
1038  def __init__(self,
1039               protocol,
1040               name,
1041               default_content_type=None,
1042               alternative_content_types=None):
1043    """Constructor.
1044
1045    Args:
1046      protocol: The protocol implementation for configuration.
1047      name: The name of the protocol configuration.
1048      default_content_type: The default content-type for protocol.  If none
1049        provided it will check protocol.CONTENT_TYPE.
1050      alternative_content_types:  A list of content-types.  If none provided,
1051        it will check protocol.ALTERNATIVE_CONTENT_TYPES.  If that attribute
1052        does not exist, will be an empty tuple.
1053
1054    Raises:
1055      ServiceConfigurationError if there are any duplicate content-types.
1056    """
1057    self.__protocol = protocol
1058    self.__name = name
1059    self.__default_content_type = (default_content_type or
1060                                   protocol.CONTENT_TYPE).lower()
1061    if alternative_content_types is None:
1062      alternative_content_types = getattr(protocol,
1063                                          'ALTERNATIVE_CONTENT_TYPES',
1064                                          ())
1065    self.__alternative_content_types = tuple(
1066      content_type.lower() for content_type in alternative_content_types)
1067    self.__content_types = (
1068      (self.__default_content_type,) + self.__alternative_content_types)
1069
1070    # Detect duplicate content types in definition.
1071    previous_type = None
1072    for content_type in sorted(self.content_types):
1073      if content_type == previous_type:
1074        raise ServiceConfigurationError(
1075          'Duplicate content-type %s' % content_type)
1076      previous_type = content_type
1077
1078  @property
1079  def protocol(self):
1080    return self.__protocol
1081
1082  @property
1083  def name(self):
1084    return self.__name
1085
1086  @property
1087  def default_content_type(self):
1088    return self.__default_content_type
1089
1090  @property
1091  def alternate_content_types(self):
1092    return self.__alternative_content_types
1093
1094  @property
1095  def content_types(self):
1096    return self.__content_types
1097
1098  def encode_message(self, message):
1099    """Encode message.
1100
1101    Args:
1102      message: Message instance to encode.
1103
1104    Returns:
1105      String encoding of Message instance encoded in protocol's format.
1106    """
1107    return self.__protocol.encode_message(message)
1108
1109  def decode_message(self, message_type, encoded_message):
1110    """Decode buffer to Message instance.
1111
1112    Args:
1113      message_type: Message type to decode data to.
1114      encoded_message: Encoded version of message as string.
1115
1116    Returns:
1117      Decoded instance of message_type.
1118    """
1119    return self.__protocol.decode_message(message_type, encoded_message)
1120
1121
1122class Protocols(object):
1123  """Collection of protocol configurations.
1124
1125  Used to describe a complete set of content-type mappings for multiple
1126  protocol configurations.
1127
1128  Properties:
1129    names: Sorted list of the names of registered protocols.
1130    content_types: Sorted list of supported content-types.
1131  """
1132
1133  __default_protocols = None
1134  __lock = threading.Lock()
1135
1136  def __init__(self):
1137    """Constructor."""
1138    self.__by_name = {}
1139    self.__by_content_type = {}
1140
1141  def add_protocol_config(self, config):
1142    """Add a protocol configuration to protocol mapping.
1143
1144    Args:
1145      config: A ProtocolConfig.
1146
1147    Raises:
1148      ServiceConfigurationError if protocol.name is already registered
1149        or any of it's content-types are already registered.
1150    """
1151    if config.name in self.__by_name:
1152      raise ServiceConfigurationError(
1153        'Protocol name %r is already in use' % config.name)
1154    for content_type in config.content_types:
1155      if content_type in self.__by_content_type:
1156        raise ServiceConfigurationError(
1157          'Content type %r is already in use' % content_type)
1158
1159    self.__by_name[config.name] = config
1160    self.__by_content_type.update((t, config) for t in config.content_types)
1161
1162  def add_protocol(self, *args, **kwargs):
1163    """Add a protocol configuration from basic parameters.
1164
1165    Simple helper method that creates and registeres a ProtocolConfig instance.
1166    """
1167    self.add_protocol_config(ProtocolConfig(*args, **kwargs))
1168
1169  @property
1170  def names(self):
1171    return tuple(sorted(self.__by_name))
1172
1173  @property
1174  def content_types(self):
1175    return tuple(sorted(self.__by_content_type))
1176
1177  def lookup_by_name(self, name):
1178    """Look up a ProtocolConfig by name.
1179
1180    Args:
1181      name: Name of protocol to look for.
1182
1183    Returns:
1184      ProtocolConfig associated with name.
1185
1186    Raises:
1187      KeyError if there is no protocol for name.
1188    """
1189    return self.__by_name[name.lower()]
1190
1191  def lookup_by_content_type(self, content_type):
1192    """Look up a ProtocolConfig by content-type.
1193
1194    Args:
1195      content_type: Content-type to find protocol configuration for.
1196
1197    Returns:
1198      ProtocolConfig associated with content-type.
1199
1200    Raises:
1201      KeyError if there is no protocol for content-type.
1202    """
1203    return self.__by_content_type[content_type.lower()]
1204
1205  @classmethod
1206  def new_default(cls):
1207    """Create default protocols configuration.
1208
1209    Returns:
1210      New Protocols instance configured for protobuf and protorpc.
1211    """
1212    protocols = cls()
1213    protocols.add_protocol(protobuf, 'protobuf')
1214    protocols.add_protocol(protojson.ProtoJson.get_default(), 'protojson')
1215    return protocols
1216
1217  @classmethod
1218  def get_default(cls):
1219    """Get the global default Protocols instance.
1220
1221    Returns:
1222      Current global default Protocols instance.
1223    """
1224    default_protocols = cls.__default_protocols
1225    if default_protocols is None:
1226      with cls.__lock:
1227        default_protocols = cls.__default_protocols
1228        if default_protocols is None:
1229          default_protocols = cls.new_default()
1230          cls.__default_protocols = default_protocols
1231    return default_protocols
1232
1233  @classmethod
1234  def set_default(cls, protocols):
1235    """Set the global default Protocols instance.
1236
1237    Args:
1238      protocols: A Protocols instance.
1239
1240    Raises:
1241      TypeError: If protocols is not an instance of Protocols.
1242    """
1243    if not isinstance(protocols, Protocols):
1244      raise TypeError(
1245        'Expected value of type "Protocols", found %r' % protocols)
1246    with cls.__lock:
1247      cls.__default_protocols = protocols
1248