gcloud 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data.tar.gz.sig +2 -3
  2. data/CHANGELOG +4 -0
  3. data/LICENSE +674 -0
  4. data/Manifest +111 -0
  5. data/README.md +4 -3
  6. data/bin/gcutil +53 -0
  7. data/gcloud.gemspec +4 -3
  8. data/packages/gcutil-1.7.1/CHANGELOG +197 -0
  9. data/packages/gcutil-1.7.1/LICENSE +202 -0
  10. data/packages/gcutil-1.7.1/VERSION +1 -0
  11. data/packages/gcutil-1.7.1/gcutil +53 -0
  12. data/packages/gcutil-1.7.1/lib/google_api_python_client/LICENSE +23 -0
  13. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/__init__.py +1 -0
  14. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/discovery.py +743 -0
  15. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/errors.py +123 -0
  16. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/ext/__init__.py +0 -0
  17. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/http.py +1443 -0
  18. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/mimeparse.py +172 -0
  19. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/model.py +385 -0
  20. data/packages/gcutil-1.7.1/lib/google_api_python_client/apiclient/schema.py +303 -0
  21. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/__init__.py +1 -0
  22. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/anyjson.py +32 -0
  23. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/appengine.py +528 -0
  24. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/client.py +1139 -0
  25. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/clientsecrets.py +105 -0
  26. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/crypt.py +244 -0
  27. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/django_orm.py +124 -0
  28. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/file.py +107 -0
  29. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/locked_file.py +343 -0
  30. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/multistore_file.py +379 -0
  31. data/packages/gcutil-1.7.1/lib/google_api_python_client/oauth2client/tools.py +174 -0
  32. data/packages/gcutil-1.7.1/lib/google_api_python_client/uritemplate/__init__.py +147 -0
  33. data/packages/gcutil-1.7.1/lib/google_apputils/LICENSE +202 -0
  34. data/packages/gcutil-1.7.1/lib/google_apputils/google/__init__.py +3 -0
  35. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/__init__.py +3 -0
  36. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/app.py +356 -0
  37. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/appcommands.py +783 -0
  38. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/basetest.py +1260 -0
  39. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/datelib.py +421 -0
  40. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/debug.py +60 -0
  41. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/file_util.py +181 -0
  42. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/resources.py +67 -0
  43. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/run_script_module.py +217 -0
  44. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/setup_command.py +159 -0
  45. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/shellutil.py +49 -0
  46. data/packages/gcutil-1.7.1/lib/google_apputils/google/apputils/stopwatch.py +204 -0
  47. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/__init__.py +0 -0
  48. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper.py +140 -0
  49. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auth_helper_test.py +149 -0
  50. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth.py +130 -0
  51. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/auto_auth_test.py +75 -0
  52. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds.py +128 -0
  53. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/basic_cmds_test.py +111 -0
  54. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base.py +1808 -0
  55. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/command_base_test.py +1651 -0
  56. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta13.json +2851 -0
  57. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/compute/v1beta14.json +3361 -0
  58. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds.py +342 -0
  59. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/disk_cmds_test.py +474 -0
  60. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds.py +344 -0
  61. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/firewall_cmds_test.py +231 -0
  62. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/flags_cache.py +274 -0
  63. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil +89 -0
  64. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/gcutil_logging.py +69 -0
  65. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds.py +262 -0
  66. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/image_cmds_test.py +172 -0
  67. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds.py +1506 -0
  68. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/instance_cmds_test.py +1904 -0
  69. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds.py +91 -0
  70. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/kernel_cmds_test.py +56 -0
  71. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds.py +106 -0
  72. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/machine_type_cmds_test.py +59 -0
  73. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata.py +96 -0
  74. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_lib.py +357 -0
  75. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/metadata_test.py +84 -0
  76. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_api.py +420 -0
  77. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/mock_metadata.py +58 -0
  78. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds.py +824 -0
  79. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/move_cmds_test.py +307 -0
  80. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds.py +178 -0
  81. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/network_cmds_test.py +133 -0
  82. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds.py +181 -0
  83. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/operation_cmds_test.py +196 -0
  84. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/path_initializer.py +38 -0
  85. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds.py +173 -0
  86. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/project_cmds_test.py +111 -0
  87. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes.py +61 -0
  88. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/scopes_test.py +50 -0
  89. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds.py +276 -0
  90. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/snapshot_cmds_test.py +260 -0
  91. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys.py +266 -0
  92. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/ssh_keys_test.py +128 -0
  93. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/table_formatter.py +563 -0
  94. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool.py +188 -0
  95. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/thread_pool_test.py +88 -0
  96. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils.py +208 -0
  97. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/utils_test.py +193 -0
  98. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version.py +17 -0
  99. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker.py +246 -0
  100. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/version_checker_test.py +271 -0
  101. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds.py +151 -0
  102. data/packages/gcutil-1.7.1/lib/google_compute_engine/gcutil/zone_cmds_test.py +60 -0
  103. data/packages/gcutil-1.7.1/lib/httplib2/LICENSE +21 -0
  104. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/__init__.py +1630 -0
  105. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/cacerts.txt +714 -0
  106. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/iri2uri.py +110 -0
  107. data/packages/gcutil-1.7.1/lib/httplib2/httplib2/socks.py +438 -0
  108. data/packages/gcutil-1.7.1/lib/iso8601/LICENSE +20 -0
  109. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/__init__.py +1 -0
  110. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/iso8601.py +102 -0
  111. data/packages/gcutil-1.7.1/lib/iso8601/iso8601/test_iso8601.py +111 -0
  112. data/packages/gcutil-1.7.1/lib/python_gflags/AUTHORS +2 -0
  113. data/packages/gcutil-1.7.1/lib/python_gflags/LICENSE +28 -0
  114. data/packages/gcutil-1.7.1/lib/python_gflags/gflags.py +2862 -0
  115. data/packages/gcutil-1.7.1/lib/python_gflags/gflags2man.py +544 -0
  116. data/packages/gcutil-1.7.1/lib/python_gflags/gflags_validators.py +187 -0
  117. metadata +118 -5
  118. metadata.gz.sig +0 -0
@@ -0,0 +1 @@
1
+ 1.7.1
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python
2
+ #
3
+ # Copyright 2012 Google Inc. All Rights Reserved.
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
+
18
+ """A convenience wrapper for starting gcutil."""
19
+
20
+
21
+
22
+ import os
23
+ import sys
24
+
25
+ # Checks the Python version.
26
+ if not hasattr(sys, 'version_info'):
27
+ sys.stderr.write('Very old versions of Python are not supported. Please '
28
+ 'use version 2.6 or greater.\n')
29
+ sys.exit(1)
30
+ if tuple(sys.version_info[:2]) < (2, 6):
31
+ sys.stderr.write('Python %d.%d is not supported. Please use version 2.6 '
32
+ 'or greater.\n' % tuple(sys.version_info[:2]))
33
+ sys.exit(1)
34
+
35
+
36
+ def main():
37
+ """Launches ./bin/gcutil."""
38
+ python_bin = sys.executable
39
+ if not python_bin:
40
+ sys.stderr.write('Could not find Python executable.')
41
+ sys.exit(1)
42
+
43
+ gcutil_main = os.path.join(
44
+ os.path.dirname(os.path.realpath(__file__)),
45
+ 'lib',
46
+ 'google_compute_engine',
47
+ 'gcutil',
48
+ 'gcutil')
49
+ os.execv(python_bin, [python_bin, '-S', gcutil_main] + sys.argv[1:])
50
+
51
+
52
+ if __name__ == '__main__':
53
+ main()
@@ -0,0 +1,23 @@
1
+ Copyright (C) 2010-2012 Google Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+ Dependent Modules
16
+ =================
17
+
18
+ This code has the following dependencies
19
+ above and beyond the Python standard library:
20
+
21
+ uritemplates - Apache License 2.0
22
+ httplib2 - MIT License
23
+ python-gflags - New BSD License
@@ -0,0 +1,743 @@
1
+ # Copyright (C) 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Client for discovery based APIs
16
+
17
+ A client library for Google's discovery based APIs.
18
+ """
19
+
20
+
21
+ __all__ = [
22
+ 'build',
23
+ 'build_from_document'
24
+ 'fix_method_name',
25
+ 'key2param'
26
+ ]
27
+
28
+ import copy
29
+ import httplib2
30
+ import logging
31
+ import os
32
+ import random
33
+ import re
34
+ import uritemplate
35
+ import urllib
36
+ import urlparse
37
+ import mimeparse
38
+ import mimetypes
39
+
40
+ try:
41
+ from urlparse import parse_qsl
42
+ except ImportError:
43
+ from cgi import parse_qsl
44
+
45
+ from apiclient.errors import HttpError
46
+ from apiclient.errors import InvalidJsonError
47
+ from apiclient.errors import MediaUploadSizeError
48
+ from apiclient.errors import UnacceptableMimeTypeError
49
+ from apiclient.errors import UnknownApiNameOrVersion
50
+ from apiclient.errors import UnknownLinkType
51
+ from apiclient.http import HttpRequest
52
+ from apiclient.http import MediaFileUpload
53
+ from apiclient.http import MediaUpload
54
+ from apiclient.model import JsonModel
55
+ from apiclient.model import MediaModel
56
+ from apiclient.model import RawModel
57
+ from apiclient.schema import Schemas
58
+ from email.mime.multipart import MIMEMultipart
59
+ from email.mime.nonmultipart import MIMENonMultipart
60
+ from oauth2client.anyjson import simplejson
61
+
62
+ logger = logging.getLogger(__name__)
63
+
64
+ URITEMPLATE = re.compile('{[^}]*}')
65
+ VARNAME = re.compile('[a-zA-Z0-9_-]+')
66
+ DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/'
67
+ '{api}/{apiVersion}/rest')
68
+ DEFAULT_METHOD_DOC = 'A description of how to use this function'
69
+
70
+ # Parameters accepted by the stack, but not visible via discovery.
71
+ STACK_QUERY_PARAMETERS = ['trace', 'pp', 'userip', 'strict']
72
+
73
+ # Python reserved words.
74
+ RESERVED_WORDS = ['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
75
+ 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
76
+ 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or',
77
+ 'pass', 'print', 'raise', 'return', 'try', 'while' ]
78
+
79
+
80
+ def fix_method_name(name):
81
+ """Fix method names to avoid reserved word conflicts.
82
+
83
+ Args:
84
+ name: string, method name.
85
+
86
+ Returns:
87
+ The name with a '_' prefixed if the name is a reserved word.
88
+ """
89
+ if name in RESERVED_WORDS:
90
+ return name + '_'
91
+ else:
92
+ return name
93
+
94
+
95
+ def _add_query_parameter(url, name, value):
96
+ """Adds a query parameter to a url.
97
+
98
+ Replaces the current value if it already exists in the URL.
99
+
100
+ Args:
101
+ url: string, url to add the query parameter to.
102
+ name: string, query parameter name.
103
+ value: string, query parameter value.
104
+
105
+ Returns:
106
+ Updated query parameter. Does not update the url if value is None.
107
+ """
108
+ if value is None:
109
+ return url
110
+ else:
111
+ parsed = list(urlparse.urlparse(url))
112
+ q = dict(parse_qsl(parsed[4]))
113
+ q[name] = value
114
+ parsed[4] = urllib.urlencode(q)
115
+ return urlparse.urlunparse(parsed)
116
+
117
+
118
+ def key2param(key):
119
+ """Converts key names into parameter names.
120
+
121
+ For example, converting "max-results" -> "max_results"
122
+
123
+ Args:
124
+ key: string, the method key name.
125
+
126
+ Returns:
127
+ A safe method name based on the key name.
128
+ """
129
+ result = []
130
+ key = list(key)
131
+ if not key[0].isalpha():
132
+ result.append('x')
133
+ for c in key:
134
+ if c.isalnum():
135
+ result.append(c)
136
+ else:
137
+ result.append('_')
138
+
139
+ return ''.join(result)
140
+
141
+
142
+ def build(serviceName,
143
+ version,
144
+ http=None,
145
+ discoveryServiceUrl=DISCOVERY_URI,
146
+ developerKey=None,
147
+ model=None,
148
+ requestBuilder=HttpRequest):
149
+ """Construct a Resource for interacting with an API.
150
+
151
+ Construct a Resource object for interacting with an API. The serviceName and
152
+ version are the names from the Discovery service.
153
+
154
+ Args:
155
+ serviceName: string, name of the service.
156
+ version: string, the version of the service.
157
+ http: httplib2.Http, An instance of httplib2.Http or something that acts
158
+ like it that HTTP requests will be made through.
159
+ discoveryServiceUrl: string, a URI Template that points to the location of
160
+ the discovery service. It should have two parameters {api} and
161
+ {apiVersion} that when filled in produce an absolute URI to the discovery
162
+ document for that service.
163
+ developerKey: string, key obtained from
164
+ https://code.google.com/apis/console.
165
+ model: apiclient.Model, converts to and from the wire format.
166
+ requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP
167
+ request.
168
+
169
+ Returns:
170
+ A Resource object with methods for interacting with the service.
171
+ """
172
+ params = {
173
+ 'api': serviceName,
174
+ 'apiVersion': version
175
+ }
176
+
177
+ if http is None:
178
+ http = httplib2.Http()
179
+
180
+ requested_url = uritemplate.expand(discoveryServiceUrl, params)
181
+
182
+ # REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment
183
+ # variable that contains the network address of the client sending the
184
+ # request. If it exists then add that to the request for the discovery
185
+ # document to avoid exceeding the quota on discovery requests.
186
+ if 'REMOTE_ADDR' in os.environ:
187
+ requested_url = _add_query_parameter(requested_url, 'userIp',
188
+ os.environ['REMOTE_ADDR'])
189
+ logger.info('URL being requested: %s' % requested_url)
190
+
191
+ resp, content = http.request(requested_url)
192
+
193
+ if resp.status == 404:
194
+ raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName,
195
+ version))
196
+ if resp.status >= 400:
197
+ raise HttpError(resp, content, requested_url)
198
+
199
+ try:
200
+ service = simplejson.loads(content)
201
+ except ValueError, e:
202
+ logger.error('Failed to parse as JSON: ' + content)
203
+ raise InvalidJsonError()
204
+
205
+ return build_from_document(content, discoveryServiceUrl, http=http,
206
+ developerKey=developerKey, model=model, requestBuilder=requestBuilder)
207
+
208
+
209
+ def build_from_document(
210
+ service,
211
+ base,
212
+ future=None,
213
+ http=None,
214
+ developerKey=None,
215
+ model=None,
216
+ requestBuilder=HttpRequest):
217
+ """Create a Resource for interacting with an API.
218
+
219
+ Same as `build()`, but constructs the Resource object from a discovery
220
+ document that is it given, as opposed to retrieving one over HTTP.
221
+
222
+ Args:
223
+ service: string, discovery document.
224
+ base: string, base URI for all HTTP requests, usually the discovery URI.
225
+ future: string, discovery document with future capabilities (deprecated).
226
+ http: httplib2.Http, An instance of httplib2.Http or something that acts
227
+ like it that HTTP requests will be made through.
228
+ developerKey: string, Key for controlling API usage, generated
229
+ from the API Console.
230
+ model: Model class instance that serializes and de-serializes requests and
231
+ responses.
232
+ requestBuilder: Takes an http request and packages it up to be executed.
233
+
234
+ Returns:
235
+ A Resource object with methods for interacting with the service.
236
+ """
237
+
238
+ # future is no longer used.
239
+ future = {}
240
+
241
+ service = simplejson.loads(service)
242
+ base = urlparse.urljoin(base, service['basePath'])
243
+ schema = Schemas(service)
244
+
245
+ if model is None:
246
+ features = service.get('features', [])
247
+ model = JsonModel('dataWrapper' in features)
248
+ resource = _createResource(http, base, model, requestBuilder, developerKey,
249
+ service, service, schema)
250
+
251
+ return resource
252
+
253
+
254
+ def _cast(value, schema_type):
255
+ """Convert value to a string based on JSON Schema type.
256
+
257
+ See http://tools.ietf.org/html/draft-zyp-json-schema-03 for more details on
258
+ JSON Schema.
259
+
260
+ Args:
261
+ value: any, the value to convert
262
+ schema_type: string, the type that value should be interpreted as
263
+
264
+ Returns:
265
+ A string representation of 'value' based on the schema_type.
266
+ """
267
+ if schema_type == 'string':
268
+ if type(value) == type('') or type(value) == type(u''):
269
+ return value
270
+ else:
271
+ return str(value)
272
+ elif schema_type == 'integer':
273
+ return str(int(value))
274
+ elif schema_type == 'number':
275
+ return str(float(value))
276
+ elif schema_type == 'boolean':
277
+ return str(bool(value)).lower()
278
+ else:
279
+ if type(value) == type('') or type(value) == type(u''):
280
+ return value
281
+ else:
282
+ return str(value)
283
+
284
+
285
+ MULTIPLIERS = {
286
+ "KB": 2 ** 10,
287
+ "MB": 2 ** 20,
288
+ "GB": 2 ** 30,
289
+ "TB": 2 ** 40,
290
+ }
291
+
292
+
293
+ def _media_size_to_long(maxSize):
294
+ """Convert a string media size, such as 10GB or 3TB into an integer.
295
+
296
+ Args:
297
+ maxSize: string, size as a string, such as 2MB or 7GB.
298
+
299
+ Returns:
300
+ The size as an integer value.
301
+ """
302
+ if len(maxSize) < 2:
303
+ return 0
304
+ units = maxSize[-2:].upper()
305
+ multiplier = MULTIPLIERS.get(units, 0)
306
+ if multiplier:
307
+ return int(maxSize[:-2]) * multiplier
308
+ else:
309
+ return int(maxSize)
310
+
311
+
312
+ def _createResource(http, baseUrl, model, requestBuilder,
313
+ developerKey, resourceDesc, rootDesc, schema):
314
+ """Build a Resource from the API description.
315
+
316
+ Args:
317
+ http: httplib2.Http, Object to make http requests with.
318
+ baseUrl: string, base URL for the API. All requests are relative to this
319
+ URI.
320
+ model: apiclient.Model, converts to and from the wire format.
321
+ requestBuilder: class or callable that instantiates an
322
+ apiclient.HttpRequest object.
323
+ developerKey: string, key obtained from
324
+ https://code.google.com/apis/console
325
+ resourceDesc: object, section of deserialized discovery document that
326
+ describes a resource. Note that the top level discovery document
327
+ is considered a resource.
328
+ rootDesc: object, the entire deserialized discovery document.
329
+ schema: object, mapping of schema names to schema descriptions.
330
+
331
+ Returns:
332
+ An instance of Resource with all the methods attached for interacting with
333
+ that resource.
334
+ """
335
+
336
+ class Resource(object):
337
+ """A class for interacting with a resource."""
338
+
339
+ def __init__(self):
340
+ self._http = http
341
+ self._baseUrl = baseUrl
342
+ self._model = model
343
+ self._developerKey = developerKey
344
+ self._requestBuilder = requestBuilder
345
+
346
+ def createMethod(theclass, methodName, methodDesc, rootDesc):
347
+ """Creates a method for attaching to a Resource.
348
+
349
+ Args:
350
+ theclass: type, the class to attach methods to.
351
+ methodName: string, name of the method to use.
352
+ methodDesc: object, fragment of deserialized discovery document that
353
+ describes the method.
354
+ rootDesc: object, the entire deserialized discovery document.
355
+ """
356
+ methodName = fix_method_name(methodName)
357
+ pathUrl = methodDesc['path']
358
+ httpMethod = methodDesc['httpMethod']
359
+ methodId = methodDesc['id']
360
+
361
+ mediaPathUrl = None
362
+ accept = []
363
+ maxSize = 0
364
+ if 'mediaUpload' in methodDesc:
365
+ mediaUpload = methodDesc['mediaUpload']
366
+ # TODO(user) Use URLs from discovery once it is updated.
367
+ parsed = list(urlparse.urlparse(baseUrl))
368
+ basePath = parsed[2]
369
+ mediaPathUrl = '/upload' + basePath + pathUrl
370
+ accept = mediaUpload['accept']
371
+ maxSize = _media_size_to_long(mediaUpload.get('maxSize', ''))
372
+
373
+ if 'parameters' not in methodDesc:
374
+ methodDesc['parameters'] = {}
375
+
376
+ # Add in the parameters common to all methods.
377
+ for name, desc in rootDesc.get('parameters', {}).iteritems():
378
+ methodDesc['parameters'][name] = desc
379
+
380
+ # Add in undocumented query parameters.
381
+ for name in STACK_QUERY_PARAMETERS:
382
+ methodDesc['parameters'][name] = {
383
+ 'type': 'string',
384
+ 'location': 'query'
385
+ }
386
+
387
+ if httpMethod in ['PUT', 'POST', 'PATCH'] and 'request' in methodDesc:
388
+ methodDesc['parameters']['body'] = {
389
+ 'description': 'The request body.',
390
+ 'type': 'object',
391
+ 'required': True,
392
+ }
393
+ if 'request' in methodDesc:
394
+ methodDesc['parameters']['body'].update(methodDesc['request'])
395
+ else:
396
+ methodDesc['parameters']['body']['type'] = 'object'
397
+ if 'mediaUpload' in methodDesc:
398
+ methodDesc['parameters']['media_body'] = {
399
+ 'description': 'The filename of the media request body.',
400
+ 'type': 'string',
401
+ 'required': False,
402
+ }
403
+ if 'body' in methodDesc['parameters']:
404
+ methodDesc['parameters']['body']['required'] = False
405
+
406
+ argmap = {} # Map from method parameter name to query parameter name
407
+ required_params = [] # Required parameters
408
+ repeated_params = [] # Repeated parameters
409
+ pattern_params = {} # Parameters that must match a regex
410
+ query_params = [] # Parameters that will be used in the query string
411
+ path_params = {} # Parameters that will be used in the base URL
412
+ param_type = {} # The type of the parameter
413
+ enum_params = {} # Allowable enumeration values for each parameter
414
+
415
+
416
+ if 'parameters' in methodDesc:
417
+ for arg, desc in methodDesc['parameters'].iteritems():
418
+ param = key2param(arg)
419
+ argmap[param] = arg
420
+
421
+ if desc.get('pattern', ''):
422
+ pattern_params[param] = desc['pattern']
423
+ if desc.get('enum', ''):
424
+ enum_params[param] = desc['enum']
425
+ if desc.get('required', False):
426
+ required_params.append(param)
427
+ if desc.get('repeated', False):
428
+ repeated_params.append(param)
429
+ if desc.get('location') == 'query':
430
+ query_params.append(param)
431
+ if desc.get('location') == 'path':
432
+ path_params[param] = param
433
+ param_type[param] = desc.get('type', 'string')
434
+
435
+ for match in URITEMPLATE.finditer(pathUrl):
436
+ for namematch in VARNAME.finditer(match.group(0)):
437
+ name = key2param(namematch.group(0))
438
+ path_params[name] = name
439
+ if name in query_params:
440
+ query_params.remove(name)
441
+
442
+ def method(self, **kwargs):
443
+ # Don't bother with doc string, it will be over-written by createMethod.
444
+
445
+ for name in kwargs.iterkeys():
446
+ if name not in argmap:
447
+ raise TypeError('Got an unexpected keyword argument "%s"' % name)
448
+
449
+ # Remove args that have a value of None.
450
+ keys = kwargs.keys()
451
+ for name in keys:
452
+ if kwargs[name] is None:
453
+ del kwargs[name]
454
+
455
+ for name in required_params:
456
+ if name not in kwargs:
457
+ raise TypeError('Missing required parameter "%s"' % name)
458
+
459
+ for name, regex in pattern_params.iteritems():
460
+ if name in kwargs:
461
+ if isinstance(kwargs[name], basestring):
462
+ pvalues = [kwargs[name]]
463
+ else:
464
+ pvalues = kwargs[name]
465
+ for pvalue in pvalues:
466
+ if re.match(regex, pvalue) is None:
467
+ raise TypeError(
468
+ 'Parameter "%s" value "%s" does not match the pattern "%s"' %
469
+ (name, pvalue, regex))
470
+
471
+ for name, enums in enum_params.iteritems():
472
+ if name in kwargs:
473
+ # We need to handle the case of a repeated enum
474
+ # name differently, since we want to handle both
475
+ # arg='value' and arg=['value1', 'value2']
476
+ if (name in repeated_params and
477
+ not isinstance(kwargs[name], basestring)):
478
+ values = kwargs[name]
479
+ else:
480
+ values = [kwargs[name]]
481
+ for value in values:
482
+ if value not in enums:
483
+ raise TypeError(
484
+ 'Parameter "%s" value "%s" is not an allowed value in "%s"' %
485
+ (name, value, str(enums)))
486
+
487
+ actual_query_params = {}
488
+ actual_path_params = {}
489
+ for key, value in kwargs.iteritems():
490
+ to_type = param_type.get(key, 'string')
491
+ # For repeated parameters we cast each member of the list.
492
+ if key in repeated_params and type(value) == type([]):
493
+ cast_value = [_cast(x, to_type) for x in value]
494
+ else:
495
+ cast_value = _cast(value, to_type)
496
+ if key in query_params:
497
+ actual_query_params[argmap[key]] = cast_value
498
+ if key in path_params:
499
+ actual_path_params[argmap[key]] = cast_value
500
+ body_value = kwargs.get('body', None)
501
+ media_filename = kwargs.get('media_body', None)
502
+
503
+ if self._developerKey:
504
+ actual_query_params['key'] = self._developerKey
505
+
506
+ model = self._model
507
+ # If there is no schema for the response then presume a binary blob.
508
+ if methodName.endswith('_media'):
509
+ model = MediaModel()
510
+ elif 'response' not in methodDesc:
511
+ model = RawModel()
512
+
513
+ headers = {}
514
+ headers, params, query, body = model.request(headers,
515
+ actual_path_params, actual_query_params, body_value)
516
+
517
+ expanded_url = uritemplate.expand(pathUrl, params)
518
+ url = urlparse.urljoin(self._baseUrl, expanded_url + query)
519
+
520
+ resumable = None
521
+ multipart_boundary = ''
522
+
523
+ if media_filename:
524
+ # Ensure we end up with a valid MediaUpload object.
525
+ if isinstance(media_filename, basestring):
526
+ (media_mime_type, encoding) = mimetypes.guess_type(media_filename)
527
+ if media_mime_type is None:
528
+ raise UnknownFileType(media_filename)
529
+ if not mimeparse.best_match([media_mime_type], ','.join(accept)):
530
+ raise UnacceptableMimeTypeError(media_mime_type)
531
+ media_upload = MediaFileUpload(media_filename, media_mime_type)
532
+ elif isinstance(media_filename, MediaUpload):
533
+ media_upload = media_filename
534
+ else:
535
+ raise TypeError('media_filename must be str or MediaUpload.')
536
+
537
+ # Check the maxSize
538
+ if maxSize > 0 and media_upload.size() > maxSize:
539
+ raise MediaUploadSizeError("Media larger than: %s" % maxSize)
540
+
541
+ # Use the media path uri for media uploads
542
+ expanded_url = uritemplate.expand(mediaPathUrl, params)
543
+ url = urlparse.urljoin(self._baseUrl, expanded_url + query)
544
+ if media_upload.resumable():
545
+ url = _add_query_parameter(url, 'uploadType', 'resumable')
546
+
547
+ if media_upload.resumable():
548
+ # This is all we need to do for resumable, if the body exists it gets
549
+ # sent in the first request, otherwise an empty body is sent.
550
+ resumable = media_upload
551
+ else:
552
+ # A non-resumable upload
553
+ if body is None:
554
+ # This is a simple media upload
555
+ headers['content-type'] = media_upload.mimetype()
556
+ body = media_upload.getbytes(0, media_upload.size())
557
+ url = _add_query_parameter(url, 'uploadType', 'media')
558
+ else:
559
+ # This is a multipart/related upload.
560
+ msgRoot = MIMEMultipart('related')
561
+ # msgRoot should not write out it's own headers
562
+ setattr(msgRoot, '_write_headers', lambda self: None)
563
+
564
+ # attach the body as one part
565
+ msg = MIMENonMultipart(*headers['content-type'].split('/'))
566
+ msg.set_payload(body)
567
+ msgRoot.attach(msg)
568
+
569
+ # attach the media as the second part
570
+ msg = MIMENonMultipart(*media_upload.mimetype().split('/'))
571
+ msg['Content-Transfer-Encoding'] = 'binary'
572
+
573
+ payload = media_upload.getbytes(0, media_upload.size())
574
+ msg.set_payload(payload)
575
+ msgRoot.attach(msg)
576
+ body = msgRoot.as_string()
577
+
578
+ multipart_boundary = msgRoot.get_boundary()
579
+ headers['content-type'] = ('multipart/related; '
580
+ 'boundary="%s"') % multipart_boundary
581
+ url = _add_query_parameter(url, 'uploadType', 'multipart')
582
+
583
+ logger.info('URL being requested: %s' % url)
584
+ return self._requestBuilder(self._http,
585
+ model.response,
586
+ url,
587
+ method=httpMethod,
588
+ body=body,
589
+ headers=headers,
590
+ methodId=methodId,
591
+ resumable=resumable)
592
+
593
+ docs = [methodDesc.get('description', DEFAULT_METHOD_DOC), '\n\n']
594
+ if len(argmap) > 0:
595
+ docs.append('Args:\n')
596
+
597
+ # Skip undocumented params and params common to all methods.
598
+ skip_parameters = rootDesc.get('parameters', {}).keys()
599
+ skip_parameters.append(STACK_QUERY_PARAMETERS)
600
+
601
+ for arg in argmap.iterkeys():
602
+ if arg in skip_parameters:
603
+ continue
604
+
605
+ repeated = ''
606
+ if arg in repeated_params:
607
+ repeated = ' (repeated)'
608
+ required = ''
609
+ if arg in required_params:
610
+ required = ' (required)'
611
+ paramdesc = methodDesc['parameters'][argmap[arg]]
612
+ paramdoc = paramdesc.get('description', 'A parameter')
613
+ if '$ref' in paramdesc:
614
+ docs.append(
615
+ (' %s: object, %s%s%s\n The object takes the'
616
+ ' form of:\n\n%s\n\n') % (arg, paramdoc, required, repeated,
617
+ schema.prettyPrintByName(paramdesc['$ref'])))
618
+ else:
619
+ paramtype = paramdesc.get('type', 'string')
620
+ docs.append(' %s: %s, %s%s%s\n' % (arg, paramtype, paramdoc, required,
621
+ repeated))
622
+ enum = paramdesc.get('enum', [])
623
+ enumDesc = paramdesc.get('enumDescriptions', [])
624
+ if enum and enumDesc:
625
+ docs.append(' Allowed values\n')
626
+ for (name, desc) in zip(enum, enumDesc):
627
+ docs.append(' %s - %s\n' % (name, desc))
628
+ if 'response' in methodDesc:
629
+ if methodName.endswith('_media'):
630
+ docs.append('\nReturns:\n The media object as a string.\n\n ')
631
+ else:
632
+ docs.append('\nReturns:\n An object of the form:\n\n ')
633
+ docs.append(schema.prettyPrintSchema(methodDesc['response']))
634
+
635
+ setattr(method, '__doc__', ''.join(docs))
636
+ setattr(theclass, methodName, method)
637
+
638
+ def createNextMethod(theclass, methodName, methodDesc, rootDesc):
639
+ """Creates any _next methods for attaching to a Resource.
640
+
641
+ The _next methods allow for easy iteration through list() responses.
642
+
643
+ Args:
644
+ theclass: type, the class to attach methods to.
645
+ methodName: string, name of the method to use.
646
+ methodDesc: object, fragment of deserialized discovery document that
647
+ describes the method.
648
+ rootDesc: object, the entire deserialized discovery document.
649
+ """
650
+ methodName = fix_method_name(methodName)
651
+ methodId = methodDesc['id'] + '.next'
652
+
653
+ def methodNext(self, previous_request, previous_response):
654
+ """Retrieves the next page of results.
655
+
656
+ Args:
657
+ previous_request: The request for the previous page.
658
+ previous_response: The response from the request for the previous page.
659
+
660
+ Returns:
661
+ A request object that you can call 'execute()' on to request the next
662
+ page. Returns None if there are no more items in the collection.
663
+ """
664
+ # Retrieve nextPageToken from previous_response
665
+ # Use as pageToken in previous_request to create new request.
666
+
667
+ if 'nextPageToken' not in previous_response:
668
+ return None
669
+
670
+ request = copy.copy(previous_request)
671
+
672
+ pageToken = previous_response['nextPageToken']
673
+ parsed = list(urlparse.urlparse(request.uri))
674
+ q = parse_qsl(parsed[4])
675
+
676
+ # Find and remove old 'pageToken' value from URI
677
+ newq = [(key, value) for (key, value) in q if key != 'pageToken']
678
+ newq.append(('pageToken', pageToken))
679
+ parsed[4] = urllib.urlencode(newq)
680
+ uri = urlparse.urlunparse(parsed)
681
+
682
+ request.uri = uri
683
+
684
+ logger.info('URL being requested: %s' % uri)
685
+
686
+ return request
687
+
688
+ setattr(theclass, methodName, methodNext)
689
+
690
+ # Add basic methods to Resource
691
+ if 'methods' in resourceDesc:
692
+ for methodName, methodDesc in resourceDesc['methods'].iteritems():
693
+ createMethod(Resource, methodName, methodDesc, rootDesc)
694
+ # Add in _media methods. The functionality of the attached method will
695
+ # change when it sees that the method name ends in _media.
696
+ if methodDesc.get('supportsMediaDownload', False):
697
+ createMethod(Resource, methodName + '_media', methodDesc, rootDesc)
698
+
699
+ # Add in nested resources
700
+ if 'resources' in resourceDesc:
701
+
702
+ def createResourceMethod(theclass, methodName, methodDesc, rootDesc):
703
+ """Create a method on the Resource to access a nested Resource.
704
+
705
+ Args:
706
+ theclass: type, the class to attach methods to.
707
+ methodName: string, name of the method to use.
708
+ methodDesc: object, fragment of deserialized discovery document that
709
+ describes the method.
710
+ rootDesc: object, the entire deserialized discovery document.
711
+ """
712
+ methodName = fix_method_name(methodName)
713
+
714
+ def methodResource(self):
715
+ return _createResource(self._http, self._baseUrl, self._model,
716
+ self._requestBuilder, self._developerKey,
717
+ methodDesc, rootDesc, schema)
718
+
719
+ setattr(methodResource, '__doc__', 'A collection resource.')
720
+ setattr(methodResource, '__is_resource__', True)
721
+ setattr(theclass, methodName, methodResource)
722
+
723
+ for methodName, methodDesc in resourceDesc['resources'].iteritems():
724
+ createResourceMethod(Resource, methodName, methodDesc, rootDesc)
725
+
726
+ # Add _next() methods
727
+ # Look for response bodies in schema that contain nextPageToken, and methods
728
+ # that take a pageToken parameter.
729
+ if 'methods' in resourceDesc:
730
+ for methodName, methodDesc in resourceDesc['methods'].iteritems():
731
+ if 'response' in methodDesc:
732
+ responseSchema = methodDesc['response']
733
+ if '$ref' in responseSchema:
734
+ responseSchema = schema.get(responseSchema['$ref'])
735
+ hasNextPageToken = 'nextPageToken' in responseSchema.get('properties',
736
+ {})
737
+ hasPageToken = 'pageToken' in methodDesc.get('parameters', {})
738
+ if hasNextPageToken and hasPageToken:
739
+ createNextMethod(Resource, methodName + '_next',
740
+ resourceDesc['methods'][methodName],
741
+ methodName)
742
+
743
+ return Resource()