1#!/usr/bin/env python 2# 3# Copyright 2015 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"""Simple tool for generating a client library. 18 19Relevant links: 20 https://developers.google.com/discovery/v1/reference/apis#resource 21""" 22 23import datetime 24 25from apitools.gen import message_registry 26from apitools.gen import service_registry 27from apitools.gen import util 28 29 30def _ApitoolsVersion(): 31 """Returns version of the currently installed google-apitools package.""" 32 try: 33 import pkg_resources 34 except ImportError: 35 return 'X.X.X' 36 try: 37 return pkg_resources.get_distribution('google-apitools').version 38 except pkg_resources.DistributionNotFound: 39 return 'X.X.X' 40 41 42def _StandardQueryParametersSchema(discovery_doc): 43 """Sets up dict of standard query parameters.""" 44 standard_query_schema = { 45 'id': 'StandardQueryParameters', 46 'type': 'object', 47 'description': 'Query parameters accepted by all methods.', 48 'properties': discovery_doc.get('parameters', {}), 49 } 50 # We add an entry for the trace, since Discovery doesn't. 51 standard_query_schema['properties']['trace'] = { 52 'type': 'string', 53 'description': ('A tracing token of the form "token:<tokenid>" ' 54 'to include in api requests.'), 55 'location': 'query', 56 } 57 return standard_query_schema 58 59 60class DescriptorGenerator(object): 61 62 """Code generator for a given discovery document.""" 63 64 def __init__(self, discovery_doc, client_info, names, root_package, outdir, 65 base_package, protorpc_package, init_wildcards_file=True, 66 use_proto2=False, unelidable_request_methods=None, 67 apitools_version=''): 68 self.__discovery_doc = discovery_doc 69 self.__client_info = client_info 70 self.__outdir = outdir 71 self.__use_proto2 = use_proto2 72 self.__description = util.CleanDescription( 73 self.__discovery_doc.get('description', '')) 74 self.__package = self.__client_info.package 75 self.__version = self.__client_info.version 76 self.__revision = discovery_doc.get('revision', '1') 77 self.__init_wildcards_file = init_wildcards_file 78 self.__root_package = root_package 79 self.__base_files_package = base_package 80 self.__protorpc_package = protorpc_package 81 self.__names = names 82 83 # Order is important here: we need the schemas before we can 84 # define the services. 85 self.__message_registry = message_registry.MessageRegistry( 86 self.__client_info, self.__names, self.__description, 87 self.__root_package, self.__base_files_package, 88 self.__protorpc_package) 89 schemas = self.__discovery_doc.get('schemas', {}) 90 for schema_name, schema in sorted(schemas.items()): 91 self.__message_registry.AddDescriptorFromSchema( 92 schema_name, schema) 93 94 # We need to add one more message type for the global parameters. 95 standard_query_schema = _StandardQueryParametersSchema( 96 self.__discovery_doc) 97 self.__message_registry.AddDescriptorFromSchema( 98 standard_query_schema['id'], standard_query_schema) 99 100 # Now that we know all the messages, we need to correct some 101 # fields from MessageFields to EnumFields. 102 self.__message_registry.FixupMessageFields() 103 104 self.__services_registry = service_registry.ServiceRegistry( 105 self.__client_info, 106 self.__message_registry, 107 self.__names, 108 self.__root_package, 109 self.__base_files_package, 110 unelidable_request_methods or []) 111 services = self.__discovery_doc.get('resources', {}) 112 for service_name, methods in sorted(services.items()): 113 self.__services_registry.AddServiceFromResource( 114 service_name, methods) 115 # We might also have top-level methods. 116 api_methods = self.__discovery_doc.get('methods', []) 117 if api_methods: 118 self.__services_registry.AddServiceFromResource( 119 'api', {'methods': api_methods}) 120 # pylint: disable=protected-access 121 self.__client_info = self.__client_info._replace( 122 scopes=self.__services_registry.scopes) 123 124 # The apitools version that will be used in prerequisites for the 125 # generated packages. 126 self.__apitools_version = ( 127 apitools_version if apitools_version else _ApitoolsVersion()) 128 129 @property 130 def client_info(self): 131 return self.__client_info 132 133 @property 134 def discovery_doc(self): 135 return self.__discovery_doc 136 137 @property 138 def names(self): 139 return self.__names 140 141 @property 142 def outdir(self): 143 return self.__outdir 144 145 @property 146 def package(self): 147 return self.__package 148 149 @property 150 def use_proto2(self): 151 return self.__use_proto2 152 153 @property 154 def apitools_version(self): 155 return self.__apitools_version 156 157 def _GetPrinter(self, out): 158 printer = util.SimplePrettyPrinter(out) 159 return printer 160 161 def WriteInit(self, out): 162 """Write a simple __init__.py for the generated client.""" 163 printer = self._GetPrinter(out) 164 if self.__init_wildcards_file: 165 printer('"""Common imports for generated %s client library."""', 166 self.__client_info.package) 167 printer('# pylint:disable=wildcard-import') 168 else: 169 printer('"""Package marker file."""') 170 printer() 171 printer('from __future__ import absolute_import') 172 printer() 173 printer('import pkgutil') 174 printer() 175 if self.__init_wildcards_file: 176 printer('from %s import *', self.__base_files_package) 177 if self.__root_package == '.': 178 import_prefix = '.' 179 else: 180 import_prefix = '%s.' % self.__root_package 181 printer('from %s%s import *', 182 import_prefix, self.__client_info.client_rule_name) 183 printer('from %s%s import *', 184 import_prefix, self.__client_info.messages_rule_name) 185 printer() 186 printer('__path__ = pkgutil.extend_path(__path__, __name__)') 187 188 def WriteIntermediateInit(self, out): 189 """Write a simple __init__.py for an intermediate directory.""" 190 printer = self._GetPrinter(out) 191 printer('#!/usr/bin/env python') 192 printer('"""Shared __init__.py for apitools."""') 193 printer() 194 printer('from pkgutil import extend_path') 195 printer('__path__ = extend_path(__path__, __name__)') 196 197 def WriteSetupPy(self, out): 198 """Write a setup.py for upload to PyPI.""" 199 printer = self._GetPrinter(out) 200 year = datetime.datetime.now().year 201 printer('# Copyright %s Google Inc. All Rights Reserved.' % year) 202 printer('#') 203 printer('# Licensed under the Apache License, Version 2.0 (the' 204 '"License");') 205 printer('# you may not use this file except in compliance with ' 206 'the License.') 207 printer('# You may obtain a copy of the License at') 208 printer('#') 209 printer('# http://www.apache.org/licenses/LICENSE-2.0') 210 printer('#') 211 printer('# Unless required by applicable law or agreed to in writing, ' 212 'software') 213 printer('# distributed under the License is distributed on an "AS IS" ' 214 'BASIS,') 215 printer('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ' 216 'express or implied.') 217 printer('# See the License for the specific language governing ' 218 'permissions and') 219 printer('# limitations under the License.') 220 printer() 221 printer('import setuptools') 222 printer('REQUIREMENTS = [') 223 with printer.Indent(indent=' '): 224 parts = self.apitools_version.split('.') 225 major = parts.pop(0) 226 minor = parts.pop(0) 227 printer('"google-apitools>=%s,~=%s.%s",', 228 self.apitools_version, major, minor) 229 printer('"httplib2>=0.9",') 230 printer('"oauth2client>=1.4.12",') 231 printer(']') 232 printer('_PACKAGE = "apitools.clients.%s"' % self.__package) 233 printer() 234 printer('setuptools.setup(') 235 # TODO(craigcitro): Allow customization of these options. 236 with printer.Indent(indent=' '): 237 printer('name="google-apitools-%s-%s",', 238 self.__package, self.__version) 239 printer('version="%s.%s",', 240 self.apitools_version, self.__revision) 241 printer('description="Autogenerated apitools library for %s",' % ( 242 self.__package,)) 243 printer('url="https://github.com/google/apitools",') 244 printer('author="Craig Citro",') 245 printer('author_email="craigcitro@google.com",') 246 printer('packages=setuptools.find_packages(),') 247 printer('install_requires=REQUIREMENTS,') 248 printer('classifiers=[') 249 with printer.Indent(indent=' '): 250 printer('"Programming Language :: Python :: 2.7",') 251 printer('"License :: OSI Approved :: Apache Software ' 252 'License",') 253 printer('],') 254 printer('license="Apache 2.0",') 255 printer('keywords="apitools apitools-%s %s",' % ( 256 self.__package, self.__package)) 257 printer(')') 258 259 def WriteMessagesFile(self, out): 260 self.__message_registry.WriteFile(self._GetPrinter(out)) 261 262 def WriteMessagesProtoFile(self, out): 263 self.__message_registry.WriteProtoFile(self._GetPrinter(out)) 264 265 def WriteServicesProtoFile(self, out): 266 self.__services_registry.WriteProtoFile(self._GetPrinter(out)) 267 268 def WriteClientLibrary(self, out): 269 self.__services_registry.WriteFile(self._GetPrinter(out)) 270