| Index: tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/remote.py
|
| diff --git a/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/remote.py b/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/remote.py
|
| deleted file mode 100755
|
| index b6c97687196bb91cc7940eda42fab93769b75c10..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/remote.py
|
| +++ /dev/null
|
| @@ -1,1247 +0,0 @@
|
| -#!/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 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]
|
| -
|
| - 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.
|
| - """
|
| -
|
| - 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
|
| - invoke_remote_method.__name__ = method.__name__
|
| - 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
|
|
|