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"""Command-line interface to gen_client.""" 18 19import argparse 20import contextlib 21import io 22import json 23import logging 24import os 25import pkgutil 26import sys 27 28from apitools.base.py import exceptions 29from apitools.gen import gen_client_lib 30from apitools.gen import util 31 32 33def _CopyLocalFile(filename): 34 with contextlib.closing(io.open(filename, 'w')) as out: 35 src_data = pkgutil.get_data( 36 'apitools.base.py', filename) 37 if src_data is None: 38 raise exceptions.GeneratedClientError( 39 'Could not find file %s' % filename) 40 out.write(src_data) 41 42 43def _GetDiscoveryDocFromFlags(args): 44 """Get the discovery doc from flags.""" 45 if args.discovery_url: 46 try: 47 return util.FetchDiscoveryDoc(args.discovery_url) 48 except exceptions.CommunicationError: 49 raise exceptions.GeneratedClientError( 50 'Could not fetch discovery doc') 51 52 infile = os.path.expanduser(args.infile) or '/dev/stdin' 53 with io.open(infile, encoding='utf8') as f: 54 return json.loads(util.ReplaceHomoglyphs(f.read())) 55 56 57def _GetCodegenFromFlags(args): 58 """Create a codegen object from flags.""" 59 discovery_doc = _GetDiscoveryDocFromFlags(args) 60 names = util.Names( 61 args.strip_prefix, 62 args.experimental_name_convention, 63 args.experimental_capitalize_enums) 64 65 if args.client_json: 66 try: 67 with io.open(args.client_json, encoding='utf8') as client_json: 68 f = json.loads(util.ReplaceHomoglyphs(client_json.read())) 69 web = f.get('installed', f.get('web', {})) 70 client_id = web.get('client_id') 71 client_secret = web.get('client_secret') 72 except IOError: 73 raise exceptions.NotFoundError( 74 'Failed to open client json file: %s' % args.client_json) 75 else: 76 client_id = args.client_id 77 client_secret = args.client_secret 78 79 if not client_id: 80 logging.warning('No client ID supplied') 81 client_id = '' 82 83 if not client_secret: 84 logging.warning('No client secret supplied') 85 client_secret = '' 86 87 client_info = util.ClientInfo.Create( 88 discovery_doc, args.scope, client_id, client_secret, 89 args.user_agent, names, args.api_key) 90 outdir = os.path.expanduser(args.outdir) or client_info.default_directory 91 if os.path.exists(outdir) and not args.overwrite: 92 raise exceptions.ConfigurationValueError( 93 'Output directory exists, pass --overwrite to replace ' 94 'the existing files.') 95 if not os.path.exists(outdir): 96 os.makedirs(outdir) 97 98 return gen_client_lib.DescriptorGenerator( 99 discovery_doc, client_info, names, args.root_package, outdir, 100 base_package=args.base_package, 101 protorpc_package=args.protorpc_package, 102 init_wildcards_file=(args.init_file == 'wildcards'), 103 use_proto2=args.experimental_proto2_output, 104 unelidable_request_methods=args.unelidable_request_methods, 105 apitools_version=args.apitools_version) 106 107 108# TODO(user): Delete this if we don't need this functionality. 109def _WriteBaseFiles(codegen): 110 with util.Chdir(codegen.outdir): 111 _CopyLocalFile('base_api.py') 112 _CopyLocalFile('credentials_lib.py') 113 _CopyLocalFile('exceptions.py') 114 115 116def _WriteIntermediateInit(codegen): 117 with io.open('__init__.py', 'w') as out: 118 codegen.WriteIntermediateInit(out) 119 120 121def _WriteProtoFiles(codegen): 122 with util.Chdir(codegen.outdir): 123 with io.open(codegen.client_info.messages_proto_file_name, 'w') as out: 124 codegen.WriteMessagesProtoFile(out) 125 with io.open(codegen.client_info.services_proto_file_name, 'w') as out: 126 codegen.WriteServicesProtoFile(out) 127 128 129def _WriteGeneratedFiles(args, codegen): 130 if codegen.use_proto2: 131 _WriteProtoFiles(codegen) 132 with util.Chdir(codegen.outdir): 133 with io.open(codegen.client_info.messages_file_name, 'w') as out: 134 codegen.WriteMessagesFile(out) 135 with io.open(codegen.client_info.client_file_name, 'w') as out: 136 codegen.WriteClientLibrary(out) 137 138 139def _WriteInit(codegen): 140 with util.Chdir(codegen.outdir): 141 with io.open('__init__.py', 'w') as out: 142 codegen.WriteInit(out) 143 144 145def _WriteSetupPy(codegen): 146 with io.open('setup.py', 'w') as out: 147 codegen.WriteSetupPy(out) 148 149 150def GenerateClient(args): 151 152 """Driver for client code generation.""" 153 154 codegen = _GetCodegenFromFlags(args) 155 if codegen is None: 156 logging.error('Failed to create codegen, exiting.') 157 return 128 158 _WriteGeneratedFiles(args, codegen) 159 if args.init_file != 'none': 160 _WriteInit(codegen) 161 162 163def GeneratePipPackage(args): 164 165 """Generate a client as a pip-installable tarball.""" 166 167 discovery_doc = _GetDiscoveryDocFromFlags(args) 168 package = discovery_doc['name'] 169 original_outdir = os.path.expanduser(args.outdir) 170 args.outdir = os.path.join( 171 args.outdir, 'apitools/clients/%s' % package) 172 args.root_package = 'apitools.clients.%s' % package 173 codegen = _GetCodegenFromFlags(args) 174 if codegen is None: 175 logging.error('Failed to create codegen, exiting.') 176 return 1 177 _WriteGeneratedFiles(args, codegen) 178 _WriteInit(codegen) 179 with util.Chdir(original_outdir): 180 _WriteSetupPy(codegen) 181 with util.Chdir('apitools'): 182 _WriteIntermediateInit(codegen) 183 with util.Chdir('clients'): 184 _WriteIntermediateInit(codegen) 185 186 187def GenerateProto(args): 188 """Generate just the two proto files for a given API.""" 189 190 codegen = _GetCodegenFromFlags(args) 191 _WriteProtoFiles(codegen) 192 193 194class _SplitCommaSeparatedList(argparse.Action): 195 196 def __call__(self, parser, namespace, values, option_string=None): 197 setattr(namespace, self.dest, values.split(',')) 198 199 200def main(argv=None): 201 if argv is None: 202 argv = sys.argv 203 parser = argparse.ArgumentParser( 204 description='Apitools Client Code Generator') 205 206 discovery_group = parser.add_mutually_exclusive_group() 207 discovery_group.add_argument( 208 '--infile', 209 help=('Filename for the discovery document. Mutually exclusive with ' 210 '--discovery_url')) 211 212 discovery_group.add_argument( 213 '--discovery_url', 214 help=('URL (or "name.version") of the discovery document to use. ' 215 'Mutually exclusive with --infile.')) 216 217 parser.add_argument( 218 '--base_package', 219 default='apitools.base.py', 220 help='Base package path of apitools (defaults to apitools.base.py') 221 222 parser.add_argument( 223 '--protorpc_package', 224 default='apitools.base.protorpclite', 225 help=('Base package path of protorpc ' 226 '(defaults to apitools.base.protorpclite')) 227 228 parser.add_argument( 229 '--outdir', 230 default='', 231 help='Directory name for output files. (Defaults to the API name.)') 232 233 parser.add_argument( 234 '--overwrite', 235 default=False, action='store_true', 236 help='Only overwrite the output directory if this flag is specified.') 237 238 parser.add_argument( 239 '--root_package', 240 default='', 241 help=('Python import path for where these modules ' 242 'should be imported from.')) 243 244 parser.add_argument( 245 '--strip_prefix', nargs='*', 246 default=[], 247 help=('Prefix to strip from type names in the discovery document. ' 248 '(May be specified multiple times.)')) 249 250 parser.add_argument( 251 '--api_key', 252 help=('API key to use for API access.')) 253 254 parser.add_argument( 255 '--client_json', 256 help=('Use the given file downloaded from the dev. console for ' 257 'client_id and client_secret.')) 258 259 parser.add_argument( 260 '--client_id', 261 default='1042881264118.apps.googleusercontent.com', 262 help='Client ID to use for the generated client.') 263 264 parser.add_argument( 265 '--client_secret', 266 default='x_Tw5K8nnjoRAqULM9PFAC2b', 267 help='Client secret for the generated client.') 268 269 parser.add_argument( 270 '--scope', nargs='*', 271 default=[], 272 help=('Scopes to request in the generated client. ' 273 'May be specified more than once.')) 274 275 parser.add_argument( 276 '--user_agent', 277 default='x_Tw5K8nnjoRAqULM9PFAC2b', 278 help=('User agent for the generated client. ' 279 'Defaults to <api>-generated/0.1.')) 280 281 parser.add_argument( 282 '--generate_cli', dest='generate_cli', action='store_true', 283 help='Ignored.') 284 parser.add_argument( 285 '--nogenerate_cli', dest='generate_cli', action='store_false', 286 help='Ignored.') 287 288 parser.add_argument( 289 '--init-file', 290 choices=['none', 'empty', 'wildcards'], 291 type=lambda s: s.lower(), 292 default='wildcards', 293 help='Controls whether and how to generate package __init__.py file.') 294 295 parser.add_argument( 296 '--unelidable_request_methods', 297 action=_SplitCommaSeparatedList, 298 default=[], 299 help=('Full method IDs of methods for which we should NOT try to ' 300 'elide the request type. (Should be a comma-separated list.')) 301 302 parser.add_argument( 303 '--apitools_version', 304 default='', dest='apitools_version', 305 help=('Apitools version used as a requirement in generated clients. ' 306 'Defaults to version of apitools used to generate the clients.')) 307 308 parser.add_argument( 309 '--experimental_capitalize_enums', 310 default=False, action='store_true', 311 help='Dangerous: attempt to rewrite enum values to be uppercase.') 312 313 parser.add_argument( 314 '--experimental_name_convention', 315 choices=util.Names.NAME_CONVENTIONS, 316 default=util.Names.DEFAULT_NAME_CONVENTION, 317 help='Dangerous: use a particular style for generated names.') 318 319 parser.add_argument( 320 '--experimental_proto2_output', 321 default=False, action='store_true', 322 help='Dangerous: also output a proto2 message file.') 323 324 subparsers = parser.add_subparsers(help='Type of generated code') 325 326 client_parser = subparsers.add_parser( 327 'client', help='Generate apitools client in destination folder') 328 client_parser.set_defaults(func=GenerateClient) 329 330 pip_package_parser = subparsers.add_parser( 331 'pip_package', help='Generate apitools client pip package') 332 pip_package_parser.set_defaults(func=GeneratePipPackage) 333 334 proto_parser = subparsers.add_parser( 335 'proto', help='Generate apitools client protos') 336 proto_parser.set_defaults(func=GenerateProto) 337 338 args = parser.parse_args(argv[1:]) 339 return args.func(args) or 0 340 341 342if __name__ == '__main__': 343 sys.exit(main()) 344