Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/third_party/protorpc/LICENSE b/third_party/protorpc/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/third_party/protorpc/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/protorpc/README.monorail b/third_party/protorpc/README.monorail
new file mode 100644
index 0000000..5f8eaf1
--- /dev/null
+++ b/third_party/protorpc/README.monorail
@@ -0,0 +1,14 @@
+Name: ProtoRPC
+Short Name: protorpc
+URL: https://github.com/google/protorpc
+Version: 0.12.0
+License: Apache 2.0
+License File: LICENSE
+Security Critical: no
+Description:
+Local Modifications:
+1. Retain only the protorpc/remote.py file.
+2. Rename my_service.async to my_service.async_
+   We don't use the async feature, and it's a reserved keyword in Python 3.
+3. array.array.tostring() and array.array.fromstring() are renamed in Python 3.
+   Use array.array.tobytes() and array.array.frombytes(), respectively.
diff --git a/third_party/protorpc/protobuf.py b/third_party/protorpc/protobuf.py
new file mode 100644
index 0000000..6de3bce
--- /dev/null
+++ b/third_party/protorpc/protobuf.py
@@ -0,0 +1,360 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Protocol buffer support for message types.
+
+For more details about protocol buffer encoding and decoding please see:
+
+  http://code.google.com/apis/protocolbuffers/docs/encoding.html
+
+Public Exceptions:
+  DecodeError: Raised when a decode error occurs from incorrect protobuf format.
+
+Public Functions:
+  encode_message: Encodes a message in to a protocol buffer string.
+  decode_message: Decode from a protocol buffer string to a message.
+"""
+import six
+
+__author__ = 'rafek@google.com (Rafe Kaplan)'
+
+
+import array
+
+from . import message_types
+from . import messages
+from . import util
+from .google_imports import ProtocolBuffer
+
+
+__all__ = ['ALTERNATIVE_CONTENT_TYPES',
+           'CONTENT_TYPE',
+           'encode_message',
+           'decode_message',
+          ]
+
+CONTENT_TYPE = 'application/octet-stream'
+
+ALTERNATIVE_CONTENT_TYPES = ['application/x-google-protobuf']
+
+
+class _Encoder(ProtocolBuffer.Encoder):
+  """Extension of protocol buffer encoder.
+
+  Original protocol buffer encoder does not have complete set of methods
+  for handling required encoding.  This class adds them.
+  """
+
+  # TODO(rafek): Implement the missing encoding types.
+  def no_encoding(self, value):
+    """No encoding available for type.
+
+    Args:
+      value: Value to encode.
+
+    Raises:
+      NotImplementedError at all times.
+    """
+    raise NotImplementedError()
+
+  def encode_enum(self, value):
+    """Encode an enum value.
+
+    Args:
+      value: Enum to encode.
+    """
+    self.putVarInt32(value.number)
+
+  def encode_message(self, value):
+    """Encode a Message in to an embedded message.
+
+    Args:
+      value: Message instance to encode.
+    """
+    self.putPrefixedString(encode_message(value))
+
+
+  def encode_unicode_string(self, value):
+    """Helper to properly pb encode unicode strings to UTF-8.
+
+    Args:
+      value: String value to encode.
+    """
+    if isinstance(value, six.text_type):
+      value = value.encode('utf-8')
+    self.putPrefixedString(value)
+
+
+class _Decoder(ProtocolBuffer.Decoder):
+  """Extension of protocol buffer decoder.
+
+  Original protocol buffer decoder does not have complete set of methods
+  for handling required decoding.  This class adds them.
+  """
+
+  # TODO(rafek): Implement the missing encoding types.
+  def no_decoding(self):
+    """No decoding available for type.
+
+    Raises:
+      NotImplementedError at all times.
+    """
+    raise NotImplementedError()
+
+  def decode_string(self):
+    """Decode a unicode string.
+
+    Returns:
+      Next value in stream as a unicode string.
+    """
+    return self.getPrefixedString().decode('UTF-8')
+
+  def decode_boolean(self):
+    """Decode a boolean value.
+
+    Returns:
+      Next value in stream as a boolean.
+    """
+    return bool(self.getBoolean())
+
+
+# Number of bits used to describe a protocol buffer bits used for the variant.
+_WIRE_TYPE_BITS = 3
+_WIRE_TYPE_MASK = 7
+
+
+# Maps variant to underlying wire type.  Many variants map to same type.
+_VARIANT_TO_WIRE_TYPE = {
+    messages.Variant.DOUBLE: _Encoder.DOUBLE,
+    messages.Variant.FLOAT: _Encoder.FLOAT,
+    messages.Variant.INT64: _Encoder.NUMERIC,
+    messages.Variant.UINT64: _Encoder.NUMERIC,
+    messages.Variant.INT32:  _Encoder.NUMERIC,
+    messages.Variant.BOOL: _Encoder.NUMERIC,
+    messages.Variant.STRING: _Encoder.STRING,
+    messages.Variant.MESSAGE: _Encoder.STRING,
+    messages.Variant.BYTES: _Encoder.STRING,
+    messages.Variant.UINT32: _Encoder.NUMERIC,
+    messages.Variant.ENUM:  _Encoder.NUMERIC,
+    messages.Variant.SINT32: _Encoder.NUMERIC,
+    messages.Variant.SINT64: _Encoder.NUMERIC,
+}
+
+
+# Maps variant to encoder method.
+_VARIANT_TO_ENCODER_MAP = {
+    messages.Variant.DOUBLE: _Encoder.putDouble,
+    messages.Variant.FLOAT: _Encoder.putFloat,
+    messages.Variant.INT64: _Encoder.putVarInt64,
+    messages.Variant.UINT64: _Encoder.putVarUint64,
+    messages.Variant.INT32: _Encoder.putVarInt32,
+    messages.Variant.BOOL: _Encoder.putBoolean,
+    messages.Variant.STRING: _Encoder.encode_unicode_string,
+    messages.Variant.MESSAGE: _Encoder.encode_message,
+    messages.Variant.BYTES: _Encoder.encode_unicode_string,
+    messages.Variant.UINT32: _Encoder.no_encoding,
+    messages.Variant.ENUM: _Encoder.encode_enum,
+    messages.Variant.SINT32: _Encoder.no_encoding,
+    messages.Variant.SINT64: _Encoder.no_encoding,
+}
+
+
+# Basic wire format decoders.  Used for reading unknown values.
+_WIRE_TYPE_TO_DECODER_MAP = {
+  _Encoder.NUMERIC: _Decoder.getVarInt64,
+  _Encoder.DOUBLE: _Decoder.getDouble,
+  _Encoder.STRING: _Decoder.getPrefixedString,
+  _Encoder.FLOAT: _Decoder.getFloat,
+}
+
+
+# Map wire type to variant.  Used to find a variant for unknown values.
+_WIRE_TYPE_TO_VARIANT_MAP = {
+  _Encoder.NUMERIC: messages.Variant.INT64,
+  _Encoder.DOUBLE: messages.Variant.DOUBLE,
+  _Encoder.STRING: messages.Variant.STRING,
+  _Encoder.FLOAT: messages.Variant.FLOAT,
+}
+
+
+# Wire type to name mapping for error messages.
+_WIRE_TYPE_NAME = {
+  _Encoder.NUMERIC: 'NUMERIC',
+  _Encoder.DOUBLE: 'DOUBLE',
+  _Encoder.STRING: 'STRING',
+  _Encoder.FLOAT: 'FLOAT',
+}
+
+
+# Maps variant to decoder method.
+_VARIANT_TO_DECODER_MAP = {
+    messages.Variant.DOUBLE: _Decoder.getDouble,
+    messages.Variant.FLOAT: _Decoder.getFloat,
+    messages.Variant.INT64: _Decoder.getVarInt64,
+    messages.Variant.UINT64: _Decoder.getVarUint64,
+    messages.Variant.INT32:  _Decoder.getVarInt32,
+    messages.Variant.BOOL: _Decoder.decode_boolean,
+    messages.Variant.STRING: _Decoder.decode_string,
+    messages.Variant.MESSAGE: _Decoder.getPrefixedString,
+    messages.Variant.BYTES: _Decoder.getPrefixedString,
+    messages.Variant.UINT32: _Decoder.no_decoding,
+    messages.Variant.ENUM:  _Decoder.getVarInt32,
+    messages.Variant.SINT32: _Decoder.no_decoding,
+    messages.Variant.SINT64: _Decoder.no_decoding,
+}
+
+
+def encode_message(message):
+  """Encode Message instance to protocol buffer.
+
+  Args:
+    Message instance to encode in to protocol buffer.
+
+  Returns:
+    String encoding of Message instance in protocol buffer format.
+
+  Raises:
+    messages.ValidationError if message is not initialized.
+  """
+  message.check_initialized()
+  encoder = _Encoder()
+
+  # Get all fields, from the known fields we parsed and the unknown fields
+  # we saved.  Note which ones were known, so we can process them differently.
+  all_fields = [(field.number, field) for field in message.all_fields()]
+  all_fields.extend((key, None)
+                    for key in message.all_unrecognized_fields()
+                    if isinstance(key, six.integer_types))
+  all_fields.sort()
+  for field_num, field in all_fields:
+    if field:
+      # Known field.
+      value = message.get_assigned_value(field.name)
+      if value is None:
+        continue
+      variant = field.variant
+      repeated = field.repeated
+    else:
+      # Unrecognized field.
+      value, variant = message.get_unrecognized_field_info(field_num)
+      if not isinstance(variant, messages.Variant):
+        continue
+      repeated = isinstance(value, (list, tuple))
+
+    tag = ((field_num << _WIRE_TYPE_BITS) | _VARIANT_TO_WIRE_TYPE[variant])
+
+    # Write value to wire.
+    if repeated:
+      values = value
+    else:
+      values = [value]
+    for next in values:
+      encoder.putVarInt32(tag)
+      if isinstance(field, messages.MessageField):
+        next = field.value_to_message(next)
+      field_encoder = _VARIANT_TO_ENCODER_MAP[variant]
+      field_encoder(encoder, next)
+
+  buffer = encoder.buffer()
+  return buffer.tobytes()
+
+
+def decode_message(message_type, encoded_message):
+  """Decode protocol buffer to Message instance.
+
+  Args:
+    message_type: Message type to decode data to.
+    encoded_message: Encoded version of message as string.
+
+  Returns:
+    Decoded instance of message_type.
+
+  Raises:
+    DecodeError if an error occurs during decoding, such as incompatible
+      wire format for a field.
+    messages.ValidationError if merged message is not initialized.
+  """
+  message = message_type()
+  message_array = array.array('B')
+  message_array.frombytes(encoded_message)
+  try:
+    decoder = _Decoder(message_array, 0, len(message_array))
+
+    while decoder.avail() > 0:
+      # Decode tag and variant information.
+      encoded_tag = decoder.getVarInt32()
+      tag = encoded_tag >> _WIRE_TYPE_BITS
+      wire_type = encoded_tag & _WIRE_TYPE_MASK
+      try:
+        found_wire_type_decoder = _WIRE_TYPE_TO_DECODER_MAP[wire_type]
+      except:
+        raise messages.DecodeError('No such wire type %d' % wire_type)
+
+      if tag < 1:
+        raise messages.DecodeError('Invalid tag value %d' % tag)
+
+      try:
+        field = message.field_by_number(tag)
+      except KeyError:
+        # Unexpected tags are ok.
+        field = None
+        wire_type_decoder = found_wire_type_decoder
+      else:
+        expected_wire_type = _VARIANT_TO_WIRE_TYPE[field.variant]
+        if expected_wire_type != wire_type:
+          raise messages.DecodeError('Expected wire type %s but found %s' % (
+              _WIRE_TYPE_NAME[expected_wire_type],
+              _WIRE_TYPE_NAME[wire_type]))
+
+        wire_type_decoder = _VARIANT_TO_DECODER_MAP[field.variant]
+
+      value = wire_type_decoder(decoder)
+
+      # Save unknown fields and skip additional processing.
+      if not field:
+        # When saving this, save it under the tag number (which should
+        # be unique), and set the variant and value so we know how to
+        # interpret the value later.
+        variant = _WIRE_TYPE_TO_VARIANT_MAP.get(wire_type)
+        if variant:
+          message.set_unrecognized_field(tag, value, variant)
+        continue
+
+      # Special case Enum and Message types.
+      if isinstance(field, messages.EnumField):
+        try:
+          value = field.type(value)
+        except TypeError:
+          raise messages.DecodeError('Invalid enum value %s' % value)
+      elif isinstance(field, messages.MessageField):
+        value = decode_message(field.message_type, value)
+        value = field.value_from_message(value)
+
+      # Merge value in to message.
+      if field.repeated:
+        values = getattr(message, field.name)
+        if values is None:
+          setattr(message, field.name, [value])
+        else:
+          values.append(value)
+      else:
+        setattr(message, field.name, value)
+  except ProtocolBuffer.ProtocolBufferDecodeError as err:
+    raise messages.DecodeError('Decoding error: %s' % str(err))
+
+  message.check_initialized()
+  return message
diff --git a/third_party/protorpc/remote.py b/third_party/protorpc/remote.py
new file mode 100644
index 0000000..7983573
--- /dev/null
+++ b/third_party/protorpc/remote.py
@@ -0,0 +1,1248 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Remote service library.
+
+This module contains classes that are useful for building remote services that
+conform to a standard request and response model.  To conform to this model
+a service must be like the following class:
+
+  # Each service instance only handles a single request and is then discarded.
+  # Make these objects light weight.
+  class Service(object):
+
+    # It must be possible to construct service objects without any parameters.
+    # If your constructor needs extra information you should provide a
+    # no-argument factory function to create service instances.
+    def __init__(self):
+      ...
+
+    # Each remote method must use the 'method' decorator, passing the request
+    # and response message types.  The remote method itself must take a single
+    # parameter which is an instance of RequestMessage and return an instance
+    # of ResponseMessage.
+    @method(RequestMessage, ResponseMessage)
+    def remote_method(self, request):
+      # Return an instance of ResponseMessage.
+
+    # A service object may optionally implement an 'initialize_request_state'
+    # method that takes as a parameter a single instance of a RequestState.  If
+    # a service does not implement this method it will not receive the request
+    # state.
+    def initialize_request_state(self, state):
+      ...
+
+The 'Service' class is provided as a convenient base class that provides the
+above functionality.  It implements all required and optional methods for a
+service.  It also has convenience methods for creating factory functions that
+can pass persistent global state to a new service instance.
+
+The 'method' decorator is used to declare which methods of a class are
+meant to service RPCs.  While this decorator is not responsible for handling
+actual remote method invocations, such as handling sockets, handling various
+RPC protocols and checking messages for correctness, it does attach information
+to methods that responsible classes can examine and ensure the correctness
+of the RPC.
+
+When the method decorator is used on a method, the wrapper method will have a
+'remote' property associated with it.  The 'remote' property contains the
+request_type and response_type expected by the methods implementation.
+
+On its own, the method decorator does not provide any support for subclassing
+remote methods.  In order to extend a service, one would need to redecorate
+the sub-classes methods.  For example:
+
+  class MyService(Service):
+
+    @method(DoSomethingRequest, DoSomethingResponse)
+    def do_stuff(self, request):
+      ... implement do_stuff ...
+
+  class MyBetterService(MyService):
+
+    @method(DoSomethingRequest, DoSomethingResponse)
+    def do_stuff(self, request):
+      response = super(MyBetterService, self).do_stuff.remote.method(request)
+      ... do stuff with response ...
+      return response
+
+A Service subclass also has a Stub class that can be used with a transport for
+making RPCs.  When a stub is created, it is capable of doing both synchronous
+and asynchronous RPCs if the underlying transport supports it.  To make a stub
+using an HTTP transport do:
+
+  my_service = MyService.Stub(HttpTransport('<my service URL>'))
+
+For synchronous calls, just call the expected methods on the service stub:
+
+  request = DoSomethingRequest()
+  ...
+  response = my_service.do_something(request)
+
+Each stub instance has an async object that can be used for initiating
+asynchronous RPCs if the underlying protocol transport supports it.  To
+make an asynchronous call, do:
+
+  rpc = my_service.async_.do_something(request)
+  response = rpc.get_response()
+"""
+
+from __future__ import with_statement
+import six
+
+__author__ = 'rafek@google.com (Rafe Kaplan)'
+
+import functools
+import logging
+import sys
+import threading
+from wsgiref import headers as wsgi_headers
+
+from . import message_types
+from . import messages
+from . import protobuf
+from . import protojson
+from . import util
+
+
+__all__ = [
+    'ApplicationError',
+    'MethodNotFoundError',
+    'NetworkError',
+    'RequestError',
+    'RpcError',
+    'ServerError',
+    'ServiceConfigurationError',
+    'ServiceDefinitionError',
+
+    'HttpRequestState',
+    'ProtocolConfig',
+    'Protocols',
+    'RequestState',
+    'RpcState',
+    'RpcStatus',
+    'Service',
+    'StubBase',
+    'check_rpc_status',
+    'get_remote_method_info',
+    'is_error_status',
+    'method',
+    'remote',
+]
+
+
+class ServiceDefinitionError(messages.Error):
+  """Raised when a service is improperly defined."""
+
+
+class ServiceConfigurationError(messages.Error):
+  """Raised when a service is incorrectly configured."""
+
+
+# TODO: Use error_name to map to specific exception message types.
+class RpcStatus(messages.Message):
+  """Status of on-going or complete RPC.
+
+  Fields:
+    state: State of RPC.
+    error_name: Error name set by application.  Only set when
+      status is APPLICATION_ERROR.  For use by application to transmit
+      specific reason for error.
+    error_message: Error message associated with status.
+  """
+
+  class State(messages.Enum):
+    """Enumeration of possible RPC states.
+
+    Values:
+      OK: Completed successfully.
+      RUNNING: Still running, not complete.
+      REQUEST_ERROR: Request was malformed or incomplete.
+      SERVER_ERROR: Server experienced an unexpected error.
+      NETWORK_ERROR: An error occured on the network.
+      APPLICATION_ERROR: The application is indicating an error.
+        When in this state, RPC should also set application_error.
+    """
+    OK = 0
+    RUNNING = 1
+
+    REQUEST_ERROR = 2
+    SERVER_ERROR = 3
+    NETWORK_ERROR = 4
+    APPLICATION_ERROR = 5
+    METHOD_NOT_FOUND_ERROR = 6
+
+  state = messages.EnumField(State, 1, required=True)
+  error_message = messages.StringField(2)
+  error_name = messages.StringField(3)
+
+
+RpcState = RpcStatus.State
+
+
+class RpcError(messages.Error):
+  """Base class for RPC errors.
+
+  Each sub-class of RpcError is associated with an error value from RpcState
+  and has an attribute STATE that refers to that value.
+  """
+
+  def __init__(self, message, cause=None):
+    super(RpcError, self).__init__(message)
+    self.cause = cause
+
+  @classmethod
+  def from_state(cls, state):
+    """Get error class from RpcState.
+
+    Args:
+      state: RpcState value.  Can be enum value itself, string or int.
+
+    Returns:
+      Exception class mapped to value if state is an error.  Returns None
+      if state is OK or RUNNING.
+    """
+    return _RPC_STATE_TO_ERROR.get(RpcState(state))
+
+
+class RequestError(RpcError):
+  """Raised when wrong request objects received during method invocation."""
+
+  STATE = RpcState.REQUEST_ERROR
+
+
+class MethodNotFoundError(RequestError):
+  """Raised when unknown method requested by RPC."""
+
+  STATE = RpcState.METHOD_NOT_FOUND_ERROR
+
+
+class NetworkError(RpcError):
+  """Raised when network error occurs during RPC."""
+
+  STATE = RpcState.NETWORK_ERROR
+
+
+class ServerError(RpcError):
+  """Unexpected error occured on server."""
+
+  STATE = RpcState.SERVER_ERROR
+
+
+class ApplicationError(RpcError):
+  """Raised for application specific errors.
+
+  Attributes:
+    error_name: Application specific error name for exception.
+  """
+
+  STATE = RpcState.APPLICATION_ERROR
+
+  def __init__(self, message, error_name=None):
+    """Constructor.
+
+    Args:
+      message: Application specific error message.
+      error_name: Application specific error name.  Must be None, string
+      or unicode string.
+    """
+    super(ApplicationError, self).__init__(message)
+    self.error_name = error_name
+
+  def __str__(self):
+    return self.args[0] or ''
+
+  def __repr__(self):
+    if self.error_name is None:
+      error_format = ''
+    else:
+      error_format = ', %r' % self.error_name
+    return '%s(%r%s)' % (type(self).__name__, self.args[0], error_format)
+
+
+_RPC_STATE_TO_ERROR = {
+  RpcState.REQUEST_ERROR: RequestError,
+  RpcState.NETWORK_ERROR: NetworkError,
+  RpcState.SERVER_ERROR: ServerError,
+  RpcState.APPLICATION_ERROR: ApplicationError,
+  RpcState.METHOD_NOT_FOUND_ERROR: MethodNotFoundError,
+}
+
+class _RemoteMethodInfo(object):
+  """Object for encapsulating remote method information.
+
+  An instance of this method is associated with the 'remote' attribute
+  of the methods 'invoke_remote_method' instance.
+
+  Instances of this class are created by the remote decorator and should not
+  be created directly.
+  """
+
+  def __init__(self,
+               method,
+               request_type,
+               response_type):
+    """Constructor.
+
+    Args:
+      method: The method which implements the remote method.  This is a
+        function that will act as an instance method of a class definition
+        that is decorated by '@method'.  It must always take 'self' as its
+        first parameter.
+      request_type: Expected request type for the remote method.
+      response_type: Expected response type for the remote method.
+    """
+    self.__method = method
+    self.__request_type = request_type
+    self.__response_type = response_type
+
+  @property
+  def method(self):
+    """Original undecorated method."""
+    return self.__method
+
+  @property
+  def request_type(self):
+    """Expected request type for remote method."""
+    if isinstance(self.__request_type, six.string_types):
+      self.__request_type = messages.find_definition(
+        self.__request_type,
+        relative_to=sys.modules[self.__method.__module__])
+    return self.__request_type
+
+  @property
+  def response_type(self):
+    """Expected response type for remote method."""
+    if isinstance(self.__response_type, six.string_types):
+      self.__response_type = messages.find_definition(
+        self.__response_type,
+        relative_to=sys.modules[self.__method.__module__])
+    return self.__response_type
+
+
+def method(request_type=message_types.VoidMessage,
+           response_type=message_types.VoidMessage):
+  """Method decorator for creating remote methods.
+
+  Args:
+    request_type: Message type of expected request.
+    response_type: Message type of expected response.
+
+  Returns:
+    'remote_method_wrapper' function.
+
+  Raises:
+    TypeError: if the request_type or response_type parameters are not
+      proper subclasses of messages.Message.
+  """
+  if (not isinstance(request_type, six.string_types) and
+      (not isinstance(request_type, type) or
+       not issubclass(request_type, messages.Message) or
+       request_type is messages.Message)):
+    raise TypeError(
+        'Must provide message class for request-type.  Found %s',
+        request_type)
+
+  if (not isinstance(response_type, six.string_types) and
+      (not isinstance(response_type, type) or
+       not issubclass(response_type, messages.Message) or
+       response_type is messages.Message)):
+    raise TypeError(
+        'Must provide message class for response-type.  Found %s',
+        response_type)
+
+  def remote_method_wrapper(method):
+    """Decorator used to wrap method.
+
+    Args:
+      method: Original method being wrapped.
+
+    Returns:
+      'invoke_remote_method' function responsible for actual invocation.
+      This invocation function instance is assigned an attribute 'remote'
+      which contains information about the remote method:
+        request_type: Expected request type for remote method.
+        response_type: Response type returned from remote method.
+
+    Raises:
+      TypeError: If request_type or response_type is not a subclass of Message
+        or is the Message class itself.
+    """
+
+    @functools.wraps(method)
+    def invoke_remote_method(service_instance, request):
+      """Function used to replace original method.
+
+      Invoke wrapped remote method.  Checks to ensure that request and
+      response objects are the correct types.
+
+      Does not check whether messages are initialized.
+
+      Args:
+        service_instance: The service object whose method is being invoked.
+          This is passed to 'self' during the invocation of the original
+          method.
+        request: Request message.
+
+      Returns:
+        Results of calling wrapped remote method.
+
+      Raises:
+        RequestError: Request object is not of the correct type.
+        ServerError: Response object is not of the correct type.
+      """
+      if not isinstance(request, remote_method_info.request_type):
+        raise RequestError('Method %s.%s expected request type %s, '
+                           'received %s' %
+                           (type(service_instance).__name__,
+                            method.__name__,
+                            remote_method_info.request_type,
+                            type(request)))
+      response = method(service_instance, request)
+      if not isinstance(response, remote_method_info.response_type):
+        raise ServerError('Method %s.%s expected response type %s, '
+                          'sent %s' %
+                          (type(service_instance).__name__,
+                           method.__name__,
+                           remote_method_info.response_type,
+                           type(response)))
+      return response
+
+    remote_method_info = _RemoteMethodInfo(method,
+                                           request_type,
+                                           response_type)
+
+    invoke_remote_method.remote = remote_method_info
+    return invoke_remote_method
+
+  return remote_method_wrapper
+
+
+def remote(request_type, response_type):
+  """Temporary backward compatibility alias for method."""
+  logging.warning('The remote decorator has been renamed method.  It will be '
+                  'removed in very soon from future versions of ProtoRPC.')
+  return method(request_type, response_type)
+
+
+def get_remote_method_info(method):
+  """Get remote method info object from remote method.
+
+  Returns:
+    Remote method info object if method is a remote method, else None.
+  """
+  if not callable(method):
+    return None
+
+  try:
+    method_info = method.remote
+  except AttributeError:
+    return None
+
+  if not isinstance(method_info, _RemoteMethodInfo):
+    return None
+
+  return method_info
+
+
+class StubBase(object):
+  """Base class for client side service stubs.
+
+  The remote method stubs are created by the _ServiceClass meta-class
+  when a Service class is first created.  The resulting stub will
+  extend both this class and the service class it handles communications for.
+
+  Assume that there is a service:
+
+    class NewContactRequest(messages.Message):
+
+      name = messages.StringField(1, required=True)
+      phone = messages.StringField(2)
+      email = messages.StringField(3)
+
+    class NewContactResponse(message.Message):
+
+      contact_id = messages.StringField(1)
+
+    class AccountService(remote.Service):
+
+      @remote.method(NewContactRequest, NewContactResponse):
+      def new_contact(self, request):
+        ... implementation ...
+
+  A stub of this service can be called in two ways.  The first is to pass in a
+  correctly initialized NewContactRequest message:
+
+    request = NewContactRequest()
+    request.name = 'Bob Somebody'
+    request.phone = '+1 415 555 1234'
+
+    response = account_service_stub.new_contact(request)
+
+  The second way is to pass in keyword parameters that correspond with the root
+  request message type:
+
+      account_service_stub.new_contact(name='Bob Somebody',
+                                       phone='+1 415 555 1234')
+
+  The second form will create a request message of the appropriate type.
+  """
+
+  def __init__(self, transport):
+    """Constructor.
+
+    Args:
+      transport: Underlying transport to communicate with remote service.
+    """
+    self.__transport = transport
+
+  @property
+  def transport(self):
+    """Transport used to communicate with remote service."""
+    return self.__transport
+
+
+class _ServiceClass(type):
+  """Meta-class for service class."""
+
+  def __new_async_method(cls, remote):
+    """Create asynchronous method for Async handler.
+
+    Args:
+      remote: RemoteInfo to create method for.
+    """
+    def async_method(self, *args, **kwargs):
+      """Asynchronous remote method.
+
+      Args:
+        self: Instance of StubBase.Async subclass.
+
+        Stub methods either take a single positional argument when a full
+        request message is passed in, or keyword arguments, but not both.
+
+        See docstring for StubBase for more information on how to use remote
+        stub methods.
+
+      Returns:
+        Rpc instance used to represent asynchronous RPC.
+      """
+      if args and kwargs:
+        raise TypeError('May not provide both args and kwargs')
+
+      if not args:
+        # Construct request object from arguments.
+        request = remote.request_type()
+        for name, value in six.iteritems(kwargs):
+          setattr(request, name, value)
+      else:
+        # First argument is request object.
+        request = args[0]
+
+      return self.transport.send_rpc(remote, request)
+
+    async_method.__name__ = remote.method.__name__
+    async_method = util.positional(2)(async_method)
+    async_method.remote = remote
+    return async_method
+
+  def __new_sync_method(cls, async_method):
+    """Create synchronous method for stub.
+
+    Args:
+      async_method: asynchronous method to delegate calls to.
+    """
+    def sync_method(self, *args, **kwargs):
+      """Synchronous remote method.
+
+      Args:
+        self: Instance of StubBase.Async subclass.
+        args: Tuple (request,):
+          request: Request object.
+        kwargs: Field values for request.  Must be empty if request object
+          is provided.
+
+      Returns:
+        Response message from synchronized RPC.
+      """
+      return async_method(self.async_, *args, **kwargs).response
+    sync_method.__name__ = async_method.__name__
+    sync_method.remote = async_method.remote
+    return sync_method
+
+  def __create_async_methods(cls, remote_methods):
+    """Construct a dictionary of asynchronous methods based on remote methods.
+
+    Args:
+      remote_methods: Dictionary of methods with associated RemoteInfo objects.
+
+    Returns:
+      Dictionary of asynchronous methods with assocaited RemoteInfo objects.
+      Results added to AsyncStub subclass.
+    """
+    async_methods = {}
+    for method_name, method in remote_methods.items():
+      async_methods[method_name] = cls.__new_async_method(method.remote)
+    return async_methods
+
+  def __create_sync_methods(cls, async_methods):
+    """Construct a dictionary of synchronous methods based on remote methods.
+
+    Args:
+      async_methods: Dictionary of async methods to delegate calls to.
+
+    Returns:
+      Dictionary of synchronous methods with assocaited RemoteInfo objects.
+      Results added to Stub subclass.
+    """
+    sync_methods = {}
+    for method_name, async_method in async_methods.items():
+      sync_methods[method_name] = cls.__new_sync_method(async_method)
+    return sync_methods
+
+  def __new__(cls, name, bases, dct):
+    """Instantiate new service class instance."""
+    if StubBase not in bases:
+      # Collect existing remote methods.
+      base_methods = {}
+      for base in bases:
+        try:
+          remote_methods = base.__remote_methods
+        except AttributeError:
+          pass
+        else:
+          base_methods.update(remote_methods)
+
+      # Set this class private attribute so that base_methods do not have
+      # to be recacluated in __init__.
+      dct['_ServiceClass__base_methods'] = base_methods
+
+      for attribute, value in dct.items():
+        base_method = base_methods.get(attribute, None)
+        if base_method:
+          if not callable(value):
+            raise ServiceDefinitionError(
+              'Must override %s in %s with a method.' % (
+                attribute, name))
+
+          if get_remote_method_info(value):
+            raise ServiceDefinitionError(
+              'Do not use method decorator when overloading remote method %s '
+              'on service %s.' %
+              (attribute, name))
+
+          base_remote_method_info = get_remote_method_info(base_method)
+          remote_decorator = method(
+            base_remote_method_info.request_type,
+            base_remote_method_info.response_type)
+          new_remote_method = remote_decorator(value)
+          dct[attribute] = new_remote_method
+
+    return type.__new__(cls, name, bases, dct)
+
+  def __init__(cls, name, bases, dct):
+    """Create uninitialized state on new class."""
+    type.__init__(cls, name, bases, dct)
+
+    # Only service implementation classes should have remote methods and stub
+    # sub classes created.  Stub implementations have their own methods passed
+    # in to the type constructor.
+    if StubBase not in bases:
+      # Create list of remote methods.
+      cls.__remote_methods = dict(cls.__base_methods)
+
+      for attribute, value in dct.items():
+        value = getattr(cls, attribute)
+        remote_method_info = get_remote_method_info(value)
+        if remote_method_info:
+          cls.__remote_methods[attribute] = value
+
+      # Build asynchronous stub class.
+      stub_attributes = {'Service': cls}
+      async_methods = cls.__create_async_methods(cls.__remote_methods)
+      stub_attributes.update(async_methods)
+      async_class = type('AsyncStub', (StubBase, cls), stub_attributes)
+      cls.AsyncStub = async_class
+
+      # Constructor for synchronous stub class.
+      def __init__(self, transport):
+        """Constructor.
+
+        Args:
+          transport: Underlying transport to communicate with remote service.
+        """
+        super(cls.Stub, self).__init__(transport)
+        self.async_ = cls.AsyncStub(transport)
+
+      # Build synchronous stub class.
+      stub_attributes = {'Service': cls,
+                         '__init__': __init__}
+      stub_attributes.update(cls.__create_sync_methods(async_methods))
+
+      cls.Stub = type('Stub', (StubBase, cls), stub_attributes)
+
+  @staticmethod
+  def all_remote_methods(cls):
+    """Get all remote methods of service.
+
+    Returns:
+      Dict from method name to unbound method.
+    """
+    return dict(cls.__remote_methods)
+
+
+class RequestState(object):
+  """Request state information.
+
+  Properties:
+    remote_host: Remote host name where request originated.
+    remote_address: IP address where request originated.
+    server_host: Host of server within which service resides.
+    server_port: Post which service has recevied request from.
+  """
+
+  @util.positional(1)
+  def __init__(self,
+               remote_host=None,
+               remote_address=None,
+               server_host=None,
+               server_port=None):
+    """Constructor.
+
+    Args:
+      remote_host: Assigned to property.
+      remote_address: Assigned to property.
+      server_host: Assigned to property.
+      server_port: Assigned to property.
+    """
+    self.__remote_host = remote_host
+    self.__remote_address = remote_address
+    self.__server_host = server_host
+    self.__server_port = server_port
+
+  @property
+  def remote_host(self):
+    return self.__remote_host
+
+  @property
+  def remote_address(self):
+    return self.__remote_address
+
+  @property
+  def server_host(self):
+    return self.__server_host
+
+  @property
+  def server_port(self):
+    return self.__server_port
+
+  def _repr_items(self):
+    for name in ['remote_host',
+                 'remote_address',
+                 'server_host',
+                 'server_port']:
+      yield name, getattr(self, name)
+
+  def __repr__(self):
+    """String representation of state."""
+    state = [self.__class__.__name__]
+    for name, value in self._repr_items():
+      if value:
+        state.append('%s=%r' % (name, value))
+
+    return '<%s>' % (' '.join(state),)
+
+
+class HttpRequestState(RequestState):
+  """HTTP request state information.
+
+  NOTE: Does not attempt to represent certain types of information from the
+  request such as the query string as query strings are not permitted in
+  ProtoRPC URLs unless required by the underlying message format.
+
+  Properties:
+    headers: wsgiref.headers.Headers instance of HTTP request headers.
+    http_method: HTTP method as a string.
+    service_path: Path on HTTP service where service is mounted.  This path
+      will not include the remote method name.
+  """
+
+  @util.positional(1)
+  def __init__(self,
+               http_method=None,
+               service_path=None,
+               headers=None,
+               **kwargs):
+    """Constructor.
+
+    Args:
+      Same as RequestState, including:
+        http_method: Assigned to property.
+        service_path: Assigned to property.
+        headers: HTTP request headers.  If instance of Headers, assigned to
+          property without copying.  If dict, will convert to name value pairs
+          for use with Headers constructor.  Otherwise, passed as parameters to
+          Headers constructor.
+    """
+    super(HttpRequestState, self).__init__(**kwargs)
+
+    self.__http_method = http_method
+    self.__service_path = service_path
+
+    # Initialize headers.
+    if isinstance(headers, dict):
+      header_list = []
+      for key, value in sorted(headers.items()):
+        if not isinstance(value, list):
+          value = [value]
+        for item in value:
+          header_list.append((key, item))
+        headers = header_list
+    self.__headers = wsgi_headers.Headers(headers or [])
+
+  @property
+  def http_method(self):
+    return self.__http_method
+
+  @property
+  def service_path(self):
+    return self.__service_path
+
+  @property
+  def headers(self):
+    return self.__headers
+
+  def _repr_items(self):
+    for item in super(HttpRequestState, self)._repr_items():
+      yield item
+
+    for name in ['http_method', 'service_path']:
+      yield name, getattr(self, name)
+
+    yield 'headers', list(self.headers.items())
+
+
+class Service(six.with_metaclass(_ServiceClass, object)):
+  """Service base class.
+
+  Base class used for defining remote services.  Contains reflection functions,
+  useful helpers and built-in remote methods.
+
+  Services are expected to be constructed via either a constructor or factory
+  which takes no parameters.  However, it might be required that some state or
+  configuration is passed in to a service across multiple requests.
+
+  To do this, define parameters to the constructor of the service and use
+  the 'new_factory' class method to build a constructor that will transmit
+  parameters to the constructor.  For example:
+
+    class MyService(Service):
+
+      def __init__(self, configuration, state):
+        self.configuration = configuration
+        self.state = state
+
+    configuration = MyServiceConfiguration()
+    global_state = MyServiceState()
+
+    my_service_factory = MyService.new_factory(configuration,
+                                               state=global_state)
+
+  The contract with any service handler is that a new service object is created
+  to handle each user request, and that the construction does not take any
+  parameters.  The factory satisfies this condition:
+
+    new_instance = my_service_factory()
+    assert new_instance.state is global_state
+
+  Attributes:
+    request_state: RequestState set via initialize_request_state.
+  """
+
+  __request_state = None
+
+  @classmethod
+  def all_remote_methods(cls):
+    """Get all remote methods for service class.
+
+    Built-in methods do not appear in the dictionary of remote methods.
+
+    Returns:
+      Dictionary mapping method name to remote method.
+    """
+    return _ServiceClass.all_remote_methods(cls)
+
+  @classmethod
+  def new_factory(cls, *args, **kwargs):
+    """Create factory for service.
+
+    Useful for passing configuration or state objects to the service.  Accepts
+    arbitrary parameters and keywords, however, underlying service must accept
+    also accept not other parameters in its constructor.
+
+    Args:
+      args: Args to pass to service constructor.
+      kwargs: Keyword arguments to pass to service constructor.
+
+    Returns:
+      Factory function that will create a new instance and forward args and
+      keywords to the constructor.
+    """
+
+    def service_factory():
+      return cls(*args, **kwargs)
+
+    # Update docstring so that it is easier to debug.
+    full_class_name = '%s.%s' % (cls.__module__, cls.__name__)
+    service_factory.__doc__ = (
+        'Creates new instances of service %s.\n\n'
+        'Returns:\n'
+        '  New instance of %s.'
+        % (cls.__name__, full_class_name))
+
+    # Update name so that it is easier to debug the factory function.
+    service_factory.__name__ = '%s_service_factory' % cls.__name__
+
+    service_factory.service_class = cls
+
+    return service_factory
+
+  def initialize_request_state(self, request_state):
+    """Save request state for use in remote method.
+
+    Args:
+      request_state: RequestState instance.
+    """
+    self.__request_state = request_state
+
+  @classmethod
+  def definition_name(cls):
+    """Get definition name for Service class.
+
+    Package name is determined by the global 'package' attribute in the
+    module that contains the Service definition.  If no 'package' attribute
+    is available, uses module name.  If no module is found, just uses class
+    name as name.
+
+    Returns:
+      Fully qualified service name.
+    """
+    try:
+      return cls.__definition_name
+    except AttributeError:
+      outer_definition_name = cls.outer_definition_name()
+      if outer_definition_name is None:
+        cls.__definition_name = cls.__name__
+      else:
+        cls.__definition_name = '%s.%s' % (outer_definition_name, cls.__name__)
+
+      return cls.__definition_name
+
+  @classmethod
+  def outer_definition_name(cls):
+    """Get outer definition name.
+
+    Returns:
+      Package for service.  Services are never nested inside other definitions.
+    """
+    return cls.definition_package()
+
+  @classmethod
+  def definition_package(cls):
+    """Get package for service.
+
+    Returns:
+      Package name for service.
+    """
+    try:
+      return cls.__definition_package
+    except AttributeError:
+      cls.__definition_package = util.get_package_for_module(cls.__module__)
+
+    return cls.__definition_package
+
+  @property
+  def request_state(self):
+    """Request state associated with this Service instance."""
+    return self.__request_state
+
+
+def is_error_status(status):
+  """Function that determines whether the RPC status is an error.
+
+  Args:
+    status: Initialized RpcStatus message to check for errors.
+  """
+  status.check_initialized()
+  return RpcError.from_state(status.state) is not None
+
+
+def check_rpc_status(status):
+  """Function converts an error status to a raised exception.
+
+  Args:
+    status: Initialized RpcStatus message to check for errors.
+
+  Raises:
+    RpcError according to state set on status, if it is an error state.
+  """
+  status.check_initialized()
+  error_class = RpcError.from_state(status.state)
+  if error_class is not None:
+    if error_class is ApplicationError:
+      raise error_class(status.error_message, status.error_name)
+    else:
+      raise error_class(status.error_message)
+
+
+class ProtocolConfig(object):
+  """Configuration for single protocol mapping.
+
+  A read-only protocol configuration provides a given protocol implementation
+  with a name and a set of content-types that it recognizes.
+
+  Properties:
+    protocol: The protocol implementation for configuration (usually a module,
+      for example, protojson, protobuf, etc.).  This is an object that has the
+      following attributes:
+        CONTENT_TYPE: Used as the default content-type if default_content_type
+          is not set.
+        ALTERNATIVE_CONTENT_TYPES (optional): A list of alternative
+          content-types to the default that indicate the same protocol.
+        encode_message: Function that matches the signature of
+          ProtocolConfig.encode_message.  Used for encoding a ProtoRPC message.
+        decode_message: Function that matches the signature of
+          ProtocolConfig.decode_message.  Used for decoding a ProtoRPC message.
+    name: Name of protocol configuration.
+    default_content_type: The default content type for the protocol.  Overrides
+      CONTENT_TYPE defined on protocol.
+    alternative_content_types: A list of alternative content-types supported
+      by the protocol.  Must not contain the default content-type, nor
+      duplicates.  Overrides ALTERNATIVE_CONTENT_TYPE defined on protocol.
+    content_types: A list of all content-types supported by configuration.
+      Combination of default content-type and alternatives.
+  """
+
+  def __init__(self,
+               protocol,
+               name,
+               default_content_type=None,
+               alternative_content_types=None):
+    """Constructor.
+
+    Args:
+      protocol: The protocol implementation for configuration.
+      name: The name of the protocol configuration.
+      default_content_type: The default content-type for protocol.  If none
+        provided it will check protocol.CONTENT_TYPE.
+      alternative_content_types:  A list of content-types.  If none provided,
+        it will check protocol.ALTERNATIVE_CONTENT_TYPES.  If that attribute
+        does not exist, will be an empty tuple.
+
+    Raises:
+      ServiceConfigurationError if there are any duplicate content-types.
+    """
+    self.__protocol = protocol
+    self.__name = name
+    self.__default_content_type = (default_content_type or
+                                   protocol.CONTENT_TYPE).lower()
+    if alternative_content_types is None:
+      alternative_content_types = getattr(protocol,
+                                          'ALTERNATIVE_CONTENT_TYPES',
+                                          ())
+    self.__alternative_content_types = tuple(
+      content_type.lower() for content_type in alternative_content_types)
+    self.__content_types = (
+      (self.__default_content_type,) + self.__alternative_content_types)
+
+    # Detect duplicate content types in definition.
+    previous_type = None
+    for content_type in sorted(self.content_types):
+      if content_type == previous_type:
+        raise ServiceConfigurationError(
+          'Duplicate content-type %s' % content_type)
+      previous_type = content_type
+
+  @property
+  def protocol(self):
+    return self.__protocol
+
+  @property
+  def name(self):
+    return self.__name
+
+  @property
+  def default_content_type(self):
+    return self.__default_content_type
+
+  @property
+  def alternate_content_types(self):
+    return self.__alternative_content_types
+
+  @property
+  def content_types(self):
+    return self.__content_types
+
+  def encode_message(self, message):
+    """Encode message.
+
+    Args:
+      message: Message instance to encode.
+
+    Returns:
+      String encoding of Message instance encoded in protocol's format.
+    """
+    return self.__protocol.encode_message(message)
+
+  def decode_message(self, message_type, encoded_message):
+    """Decode buffer to Message instance.
+
+    Args:
+      message_type: Message type to decode data to.
+      encoded_message: Encoded version of message as string.
+
+    Returns:
+      Decoded instance of message_type.
+    """
+    return self.__protocol.decode_message(message_type, encoded_message)
+
+
+class Protocols(object):
+  """Collection of protocol configurations.
+
+  Used to describe a complete set of content-type mappings for multiple
+  protocol configurations.
+
+  Properties:
+    names: Sorted list of the names of registered protocols.
+    content_types: Sorted list of supported content-types.
+  """
+
+  __default_protocols = None
+  __lock = threading.Lock()
+
+  def __init__(self):
+    """Constructor."""
+    self.__by_name = {}
+    self.__by_content_type = {}
+
+  def add_protocol_config(self, config):
+    """Add a protocol configuration to protocol mapping.
+
+    Args:
+      config: A ProtocolConfig.
+
+    Raises:
+      ServiceConfigurationError if protocol.name is already registered
+        or any of it's content-types are already registered.
+    """
+    if config.name in self.__by_name:
+      raise ServiceConfigurationError(
+        'Protocol name %r is already in use' % config.name)
+    for content_type in config.content_types:
+      if content_type in self.__by_content_type:
+        raise ServiceConfigurationError(
+          'Content type %r is already in use' % content_type)
+
+    self.__by_name[config.name] = config
+    self.__by_content_type.update((t, config) for t in config.content_types)
+
+  def add_protocol(self, *args, **kwargs):
+    """Add a protocol configuration from basic parameters.
+
+    Simple helper method that creates and registeres a ProtocolConfig instance.
+    """
+    self.add_protocol_config(ProtocolConfig(*args, **kwargs))
+
+  @property
+  def names(self):
+    return tuple(sorted(self.__by_name))
+
+  @property
+  def content_types(self):
+    return tuple(sorted(self.__by_content_type))
+
+  def lookup_by_name(self, name):
+    """Look up a ProtocolConfig by name.
+
+    Args:
+      name: Name of protocol to look for.
+
+    Returns:
+      ProtocolConfig associated with name.
+
+    Raises:
+      KeyError if there is no protocol for name.
+    """
+    return self.__by_name[name.lower()]
+
+  def lookup_by_content_type(self, content_type):
+    """Look up a ProtocolConfig by content-type.
+
+    Args:
+      content_type: Content-type to find protocol configuration for.
+
+    Returns:
+      ProtocolConfig associated with content-type.
+
+    Raises:
+      KeyError if there is no protocol for content-type.
+    """
+    return self.__by_content_type[content_type.lower()]
+
+  @classmethod
+  def new_default(cls):
+    """Create default protocols configuration.
+
+    Returns:
+      New Protocols instance configured for protobuf and protorpc.
+    """
+    protocols = cls()
+    protocols.add_protocol(protobuf, 'protobuf')
+    protocols.add_protocol(protojson.ProtoJson.get_default(), 'protojson')
+    return protocols
+
+  @classmethod
+  def get_default(cls):
+    """Get the global default Protocols instance.
+
+    Returns:
+      Current global default Protocols instance.
+    """
+    default_protocols = cls.__default_protocols
+    if default_protocols is None:
+      with cls.__lock:
+        default_protocols = cls.__default_protocols
+        if default_protocols is None:
+          default_protocols = cls.new_default()
+          cls.__default_protocols = default_protocols
+    return default_protocols
+
+  @classmethod
+  def set_default(cls, protocols):
+    """Set the global default Protocols instance.
+
+    Args:
+      protocols: A Protocols instance.
+
+    Raises:
+      TypeError: If protocols is not an instance of Protocols.
+    """
+    if not isinstance(protocols, Protocols):
+      raise TypeError(
+        'Expected value of type "Protocols", found %r' % protocols)
+    with cls.__lock:
+      cls.__default_protocols = protocols