def _generate_api_config_with_root(self, request): """Generate an API config with a specific root hostname. This uses the backend object and the ApiConfigGenerator to create an API config specific to the hostname of the incoming request. This allows for flexible API configs for non-standard environments, such as localhost. Args: request: An ApiRequest, the transformed request sent to the Discovery API. Returns: A string representation of the generated API config. """ actual_root = self._get_actual_root(request) generator = api_config.ApiConfigGenerator() api = request.body_json['api'] version = request.body_json['version'] lookup_key = (api, version) service_factories = self._backend.api_name_version_map.get(lookup_key) if not service_factories: return None service_classes = [service_factory.service_class for service_factory in service_factories] config_dict = generator.get_config_dict( service_classes, hostname=actual_root) # Save to cache for config in config_dict.get('items', []): lookup_key_with_root = ( config.get('name', ''), config.get('version', ''), actual_root) self._config_manager.save_config(lookup_key_with_root, config) return config_dict
def GenApiConfig(service_class_names, config_string_generator=None, hostname=None, application_path=None): """Write an API configuration for endpoints annotated ProtoRPC services. Args: service_class_names: A list of fully qualified ProtoRPC service classes. config_string_generator: A generator object that produces API config strings using its pretty_print_config_to_json method. hostname: A string hostname which will be used as the default version hostname. If no hostname is specificied in the @endpoints.api decorator, this value is the fallback. application_path: A string with the path to the AppEngine application. Raises: TypeError: If any service classes don't inherit from remote.Service. messages.DefinitionNotFoundError: If a service can't be found. Returns: A map from service names to a string containing the API configuration of the service in JSON format. """ # First, gather together all the different APIs implemented by these # classes. There may be fewer APIs than service classes. Each API is # uniquely identified by (name, version). Order needs to be preserved here, # so APIs that were listed first are returned first. api_service_map = collections.OrderedDict() for service_class_name in service_class_names: module_name, base_service_class_name = service_class_name.rsplit('.', 1) module = __import__(module_name, fromlist=base_service_class_name) service = getattr(module, base_service_class_name) if not isinstance(service, type) or not issubclass(service, remote.Service): raise TypeError('%s is not a ProtoRPC service' % service_class_name) services = api_service_map.setdefault( (service.api_info.name, service.api_info.version), []) services.append(service) # If hostname isn't specified in the API or on the command line, we'll # try to build it from information in app.yaml. app_yaml_hostname = _GetAppYamlHostname(application_path) service_map = collections.OrderedDict() config_string_generator = ( config_string_generator or api_config.ApiConfigGenerator()) for api_info, services in api_service_map.iteritems(): assert services, 'An API must have at least one ProtoRPC service' # Only override hostname if None. Hostname will be the same for all # services within an API, since it's stored in common info. hostname = services[0].api_info.hostname or hostname or app_yaml_hostname # Map each API by name-version. service_map['%s-%s' % api_info] = ( config_string_generator.pretty_print_config_to_json( services, hostname=hostname)) return service_map
def __register_services(api_name_version_map, api_config_registry): """Register & return a list of each URL and class that handles that URL. This finds every service class in api_name_version_map, registers it with the given ApiConfigRegistry, builds the URL for that class, and adds the URL and its factory to a list that's returned. Args: api_name_version_map: A mapping from (api name, api version) to a list of service factories, as returned by __create_name_version_map. api_config_registry: The ApiConfigRegistry where service classes will be registered. Returns: A list of (URL, service_factory) for each service class in api_name_version_map. Raises: ApiConfigurationError: If a Service class appears more than once in api_name_version_map. This could happen if one class is used to implement multiple APIs. """ generator = api_config.ApiConfigGenerator() protorpc_services = [] for service_factories in api_name_version_map.itervalues(): service_classes = [ service_factory.service_class for service_factory in service_factories ] config_file = generator.pretty_print_config_to_json( service_classes) api_config_registry.register_backend(config_file) for service_factory in service_factories: protorpc_class_name = service_factory.service_class.__name__ root = '%s%s' % (service_factory.service_class.api_info. base_path, protorpc_class_name) if any(service_map[0] == root or service_map[1] == service_factory for service_map in protorpc_services): raise api_config.ApiConfigurationError( 'Can\'t reuse the same class in multiple APIs: %s' % protorpc_class_name) protorpc_services.append((root, service_factory)) return protorpc_services