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('import pkgutil')
172        printer()
173        if self.__init_wildcards_file:
174            printer('from %s import *', self.__base_files_package)
175            if self.__root_package == '.':
176                import_prefix = ''
177            else:
178                import_prefix = '%s.' % self.__root_package
179            printer('from %s%s import *',
180                    import_prefix, self.__client_info.client_rule_name)
181            printer('from %s%s import *',
182                    import_prefix, self.__client_info.messages_rule_name)
183            printer()
184        printer('__path__ = pkgutil.extend_path(__path__, __name__)')
185
186    def WriteIntermediateInit(self, out):
187        """Write a simple __init__.py for an intermediate directory."""
188        printer = self._GetPrinter(out)
189        printer('#!/usr/bin/env python')
190        printer('"""Shared __init__.py for apitools."""')
191        printer()
192        printer('from pkgutil import extend_path')
193        printer('__path__ = extend_path(__path__, __name__)')
194
195    def WriteSetupPy(self, out):
196        """Write a setup.py for upload to PyPI."""
197        printer = self._GetPrinter(out)
198        year = datetime.datetime.now().year
199        printer('# Copyright %s Google Inc. All Rights Reserved.' % year)
200        printer('#')
201        printer('# Licensed under the Apache License, Version 2.0 (the'
202                '"License");')
203        printer('# you may not use this file except in compliance with '
204                'the License.')
205        printer('# You may obtain a copy of the License at')
206        printer('#')
207        printer('#   http://www.apache.org/licenses/LICENSE-2.0')
208        printer('#')
209        printer('# Unless required by applicable law or agreed to in writing, '
210                'software')
211        printer('# distributed under the License is distributed on an "AS IS" '
212                'BASIS,')
213        printer('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either '
214                'express or implied.')
215        printer('# See the License for the specific language governing '
216                'permissions and')
217        printer('# limitations under the License.')
218        printer()
219        printer('import setuptools')
220        printer('REQUIREMENTS = [')
221        with printer.Indent(indent='    '):
222            parts = self.apitools_version.split('.')
223            major = parts.pop(0)
224            minor = parts.pop(0)
225            printer('"google-apitools>=%s,~=%s.%s",',
226                    self.apitools_version, major, minor)
227            printer('"httplib2>=0.9",')
228            printer('"oauth2client>=1.4.12",')
229        printer(']')
230        printer('_PACKAGE = "apitools.clients.%s"' % self.__package)
231        printer()
232        printer('setuptools.setup(')
233        # TODO(craigcitro): Allow customization of these options.
234        with printer.Indent(indent='    '):
235            printer('name="google-apitools-%s-%s",',
236                    self.__package, self.__version)
237            printer('version="%s.%s",',
238                    self.apitools_version, self.__revision)
239            printer('description="Autogenerated apitools library for %s",' % (
240                self.__package,))
241            printer('url="https://github.com/google/apitools",')
242            printer('author="Craig Citro",')
243            printer('author_email="[email protected]",')
244            printer('packages=setuptools.find_packages(),')
245            printer('install_requires=REQUIREMENTS,')
246            printer('classifiers=[')
247            with printer.Indent(indent='    '):
248                printer('"Programming Language :: Python :: 2.7",')
249                printer('"License :: OSI Approved :: Apache Software '
250                        'License",')
251            printer('],')
252            printer('license="Apache 2.0",')
253            printer('keywords="apitools apitools-%s %s",' % (
254                self.__package, self.__package))
255        printer(')')
256
257    def WriteMessagesFile(self, out):
258        self.__message_registry.WriteFile(self._GetPrinter(out))
259
260    def WriteMessagesProtoFile(self, out):
261        self.__message_registry.WriteProtoFile(self._GetPrinter(out))
262
263    def WriteServicesProtoFile(self, out):
264        self.__services_registry.WriteProtoFile(self._GetPrinter(out))
265
266    def WriteClientLibrary(self, out):
267        self.__services_registry.WriteFile(self._GetPrinter(out))
268