PageRenderTime 200ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/protorpc-1.0/protorpc/registry.py

https://github.com/theosp/google_appengine
Python | 240 lines | 89 code | 24 blank | 127 comment | 3 complexity | a19c72ec9cb8c3a5ccac9449c0525f1d MD5 | raw file
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2010 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. """Service regsitry for service discovery.
  18. The registry service can be deployed on a server in order to provide a
  19. central place where remote clients can discover available.
  20. On the server side, each service is registered by their name which is unique
  21. to the registry. Typically this name provides enough information to identify
  22. the service and locate it within a server. For example, for an HTTP based
  23. registry the name is the URL path on the host where the service is invocable.
  24. The registry is also able to resolve the full descriptor.FileSet necessary to
  25. describe the service and all required data-types (messages and enums).
  26. A configured registry is itself a remote service and should reference itself.
  27. """
  28. import sys
  29. from . import descriptor
  30. from . import messages
  31. from . import remote
  32. from . import util
  33. __all__ = [
  34. 'ServiceMapping',
  35. 'ServicesResponse',
  36. 'GetFileSetRequest',
  37. 'GetFileSetResponse',
  38. 'RegistryService',
  39. ]
  40. class ServiceMapping(messages.Message):
  41. """Description of registered service.
  42. Fields:
  43. name: Name of service. On HTTP based services this will be the
  44. URL path used for invocation.
  45. definition: Fully qualified name of the service definition. Useful
  46. for clients that can look up service definitions based on an existing
  47. repository of definitions.
  48. """
  49. name = messages.StringField(1, required=True)
  50. definition = messages.StringField(2, required=True)
  51. class ServicesResponse(messages.Message):
  52. """Response containing all registered services.
  53. May also contain complete descriptor file-set for all services known by the
  54. registry.
  55. Fields:
  56. services: Service mappings for all registered services in registry.
  57. file_set: Descriptor file-set describing all services, messages and enum
  58. types needed for use with all requested services if asked for in the
  59. request.
  60. """
  61. services = messages.MessageField(ServiceMapping, 1, repeated=True)
  62. class GetFileSetRequest(messages.Message):
  63. """Request for service descriptor file-set.
  64. Request to retrieve file sets for specific services.
  65. Fields:
  66. names: Names of services to retrieve file-set for.
  67. """
  68. names = messages.StringField(1, repeated=True)
  69. class GetFileSetResponse(messages.Message):
  70. """Descriptor file-set for all names in GetFileSetRequest.
  71. Fields:
  72. file_set: Descriptor file-set containing all descriptors for services,
  73. messages and enum types needed for listed names in request.
  74. """
  75. file_set = messages.MessageField(descriptor.FileSet, 1, required=True)
  76. class RegistryService(remote.Service):
  77. """Registry service.
  78. Maps names to services and is able to describe all descriptor file-sets
  79. necessary to use contined services.
  80. On an HTTP based server, the name is the URL path to the service.
  81. """
  82. @util.positional(2)
  83. def __init__(self, registry, modules=None):
  84. """Constructor.
  85. Args:
  86. registry: Map of name to service class. This map is not copied and may
  87. be modified after the reigstry service has been configured.
  88. modules: Module dict to draw descriptors from. Defaults to sys.modules.
  89. """
  90. # Private Attributes:
  91. # __registry: Map of name to service class. Refers to same instance as
  92. # registry parameter.
  93. # __modules: Mapping of module name to module.
  94. # __definition_to_modules: Mapping of definition types to set of modules
  95. # that they refer to. This cache is used to make repeated look-ups
  96. # faster and to prevent circular references from causing endless loops.
  97. self.__registry = registry
  98. if modules is None:
  99. modules = sys.modules
  100. self.__modules = modules
  101. # This cache will only last for a single request.
  102. self.__definition_to_modules = {}
  103. def __find_modules_for_message(self, message_type):
  104. """Find modules referred to by a message type.
  105. Determines the entire list of modules ultimately referred to by message_type
  106. by iterating over all of its message and enum fields. Includes modules
  107. referred to fields within its referred messages.
  108. Args:
  109. message_type: Message type to find all referring modules for.
  110. Returns:
  111. Set of modules referred to by message_type by traversing all its
  112. message and enum fields.
  113. """
  114. # TODO(rafek): Maybe this should be a method on Message and Service?
  115. def get_dependencies(message_type, seen=None):
  116. """Get all dependency definitions of a message type.
  117. This function works by collecting the types of all enumeration and message
  118. fields defined within the message type. When encountering a message
  119. field, it will recursivly find all of the associated message's
  120. dependencies. It will terminate on circular dependencies by keeping track
  121. of what definitions it already via the seen set.
  122. Args:
  123. message_type: Message type to get dependencies for.
  124. seen: Set of definitions that have already been visited.
  125. Returns:
  126. All dependency message and enumerated types associated with this message
  127. including the message itself.
  128. """
  129. if seen is None:
  130. seen = set()
  131. seen.add(message_type)
  132. for field in message_type.all_fields():
  133. if isinstance(field, messages.MessageField):
  134. if field.message_type not in seen:
  135. get_dependencies(field.message_type, seen)
  136. elif isinstance(field, messages.EnumField):
  137. seen.add(field.type)
  138. return seen
  139. found_modules = self.__definition_to_modules.setdefault(message_type, set())
  140. if not found_modules:
  141. dependencies = get_dependencies(message_type)
  142. found_modules.update(self.__modules[definition.__module__]
  143. for definition in dependencies)
  144. return found_modules
  145. def __describe_file_set(self, names):
  146. """Get file-set for named services.
  147. Args:
  148. names: List of names to get file-set for.
  149. Returns:
  150. descriptor.FileSet containing all the descriptors for all modules
  151. ultimately referred to by all service types request by names parameter.
  152. """
  153. service_modules = set()
  154. if names:
  155. for service in (self.__registry[name] for name in names):
  156. found_modules = self.__definition_to_modules.setdefault(service, set())
  157. if not found_modules:
  158. found_modules.add(self.__modules[service.__module__])
  159. for method_name in service.all_remote_methods():
  160. method = getattr(service, method_name)
  161. for message_type in (method.remote.request_type,
  162. method.remote.response_type):
  163. found_modules.update(
  164. self.__find_modules_for_message(message_type))
  165. service_modules.update(found_modules)
  166. return descriptor.describe_file_set(service_modules)
  167. @property
  168. def registry(self):
  169. """Get service registry associated with this service instance."""
  170. return self.__registry
  171. @remote.method(response_type=ServicesResponse)
  172. def services(self, request):
  173. """Get all registered services."""
  174. response = ServicesResponse()
  175. response.services = []
  176. for name, service_class in self.__registry.iteritems():
  177. mapping = ServiceMapping()
  178. mapping.name = name.decode('utf-8')
  179. mapping.definition = service_class.definition_name().decode('utf-8')
  180. response.services.append(mapping)
  181. return response
  182. @remote.method(GetFileSetRequest, GetFileSetResponse)
  183. def get_file_set(self, request):
  184. """Get file-set for registered servies."""
  185. response = GetFileSetResponse()
  186. response.file_set = self.__describe_file_set(request.names)
  187. return response