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