gam7 7.3.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gam7 might be problematic. Click here for more details.
- gam/__init__.py +77555 -0
- gam/__main__.py +40 -0
- gam/atom/__init__.py +1460 -0
- gam/atom/auth.py +41 -0
- gam/atom/client.py +214 -0
- gam/atom/core.py +535 -0
- gam/atom/data.py +327 -0
- gam/atom/http.py +354 -0
- gam/atom/http_core.py +599 -0
- gam/atom/http_interface.py +144 -0
- gam/atom/mock_http.py +123 -0
- gam/atom/mock_http_core.py +313 -0
- gam/atom/mock_service.py +235 -0
- gam/atom/service.py +723 -0
- gam/atom/token_store.py +105 -0
- gam/atom/url.py +130 -0
- gam/cacerts.pem +1130 -0
- gam/cbcm-v1.1beta1.json +593 -0
- gam/contactdelegation-v1.json +249 -0
- gam/datastudio-v1.json +486 -0
- gam/gamlib/__init__.py +17 -0
- gam/gamlib/glaction.py +308 -0
- gam/gamlib/glapi.py +837 -0
- gam/gamlib/glcfg.py +616 -0
- gam/gamlib/glclargs.py +1184 -0
- gam/gamlib/glentity.py +831 -0
- gam/gamlib/glgapi.py +817 -0
- gam/gamlib/glgdata.py +98 -0
- gam/gamlib/glglobals.py +307 -0
- gam/gamlib/glindent.py +46 -0
- gam/gamlib/glmsgs.py +547 -0
- gam/gamlib/glskus.py +246 -0
- gam/gamlib/gluprop.py +279 -0
- gam/gamlib/glverlibs.py +33 -0
- gam/gamlib/yubikey.py +202 -0
- gam/gdata/__init__.py +825 -0
- gam/gdata/alt/__init__.py +20 -0
- gam/gdata/alt/app_engine.py +101 -0
- gam/gdata/alt/appengine.py +321 -0
- gam/gdata/apps/__init__.py +526 -0
- gam/gdata/apps/audit/__init__.py +1 -0
- gam/gdata/apps/audit/service.py +278 -0
- gam/gdata/apps/contacts/__init__.py +874 -0
- gam/gdata/apps/contacts/service.py +355 -0
- gam/gdata/apps/service.py +544 -0
- gam/gdata/apps/sites/__init__.py +283 -0
- gam/gdata/apps/sites/service.py +246 -0
- gam/gdata/service.py +1714 -0
- gam/gdata/urlfetch.py +247 -0
- gam/googleapiclient/__init__.py +27 -0
- gam/googleapiclient/_auth.py +167 -0
- gam/googleapiclient/_helpers.py +207 -0
- gam/googleapiclient/channel.py +315 -0
- gam/googleapiclient/discovery.py +1662 -0
- gam/googleapiclient/discovery_cache/__init__.py +78 -0
- gam/googleapiclient/discovery_cache/appengine_memcache.py +55 -0
- gam/googleapiclient/discovery_cache/base.py +46 -0
- gam/googleapiclient/discovery_cache/file_cache.py +145 -0
- gam/googleapiclient/errors.py +197 -0
- gam/googleapiclient/http.py +1962 -0
- gam/googleapiclient/mimeparse.py +183 -0
- gam/googleapiclient/model.py +429 -0
- gam/googleapiclient/schema.py +317 -0
- gam/googleapiclient/version.py +15 -0
- gam/iso8601/__init__.py +28 -0
- gam/iso8601/iso8601.py +160 -0
- gam/serviceaccountlookup-v1.json +141 -0
- gam/six.py +982 -0
- gam7-7.3.4.dist-info/METADATA +69 -0
- gam7-7.3.4.dist-info/RECORD +72 -0
- gam7-7.3.4.dist-info/WHEEL +4 -0
- gam7-7.3.4.dist-info/licenses/LICENSE +201 -0
gam/atom/service.py
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2006, 2007, 2008 Google Inc.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License 2.0;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
"""AtomService provides CRUD ops. in line with the Atom Publishing Protocol.
|
|
9
|
+
|
|
10
|
+
AtomService: Encapsulates the ability to perform insert, update and delete
|
|
11
|
+
operations with the Atom Publishing Protocol on which GData is
|
|
12
|
+
based. An instance can perform query, insertion, deletion, and
|
|
13
|
+
update.
|
|
14
|
+
|
|
15
|
+
HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request
|
|
16
|
+
to the specified end point. An AtomService object or a subclass can be
|
|
17
|
+
used to specify information about the request.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# __author__ = 'api.jscudder (Jeff Scudder)'
|
|
21
|
+
|
|
22
|
+
import base64
|
|
23
|
+
import http.client
|
|
24
|
+
import os
|
|
25
|
+
import socket
|
|
26
|
+
import urllib.error
|
|
27
|
+
import urllib.parse
|
|
28
|
+
import urllib.request
|
|
29
|
+
import warnings
|
|
30
|
+
|
|
31
|
+
import atom.http
|
|
32
|
+
import atom.http_interface
|
|
33
|
+
import atom.token_store
|
|
34
|
+
import atom.url
|
|
35
|
+
|
|
36
|
+
import lxml.etree as ElementTree
|
|
37
|
+
import atom
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AtomService(object):
|
|
41
|
+
"""Performs Atom Publishing Protocol CRUD operations.
|
|
42
|
+
|
|
43
|
+
The AtomService contains methods to perform HTTP CRUD operations.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Default values for members
|
|
47
|
+
port = 80
|
|
48
|
+
ssl = False
|
|
49
|
+
# Set the current_token to force the AtomService to use this token
|
|
50
|
+
# instead of searching for an appropriate token in the token_store.
|
|
51
|
+
current_token = None
|
|
52
|
+
auto_store_tokens = True
|
|
53
|
+
auto_set_current_token = True
|
|
54
|
+
|
|
55
|
+
def _get_override_token(self):
|
|
56
|
+
return self.current_token
|
|
57
|
+
|
|
58
|
+
def _set_override_token(self, token):
|
|
59
|
+
self.current_token = token
|
|
60
|
+
|
|
61
|
+
override_token = property(_get_override_token, _set_override_token)
|
|
62
|
+
|
|
63
|
+
# @atom.v1_deprecated('Please use atom.client.AtomPubClient instead.')
|
|
64
|
+
def __init__(self, server=None, additional_headers=None,
|
|
65
|
+
application_name='', http_client=None, token_store=None):
|
|
66
|
+
"""Creates a new AtomService client.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
server: string (optional) The start of a URL for the server
|
|
70
|
+
to which all operations should be directed. Example:
|
|
71
|
+
'www.google.com'
|
|
72
|
+
additional_headers: dict (optional) Any additional HTTP headers which
|
|
73
|
+
should be included with CRUD operations.
|
|
74
|
+
http_client: An object responsible for making HTTP requests using a
|
|
75
|
+
request method. If none is provided, a new instance of
|
|
76
|
+
atom.http.ProxiedHttpClient will be used.
|
|
77
|
+
token_store: Keeps a collection of authorization tokens which can be
|
|
78
|
+
applied to requests for a specific URLs. Critical methods are
|
|
79
|
+
find_token based on a URL (atom.url.Url or a string), add_token,
|
|
80
|
+
and remove_token.
|
|
81
|
+
"""
|
|
82
|
+
self.http_client = http_client or atom.http.ProxiedHttpClient()
|
|
83
|
+
self.token_store = token_store or atom.token_store.TokenStore()
|
|
84
|
+
self.server = server
|
|
85
|
+
self.additional_headers = additional_headers or {}
|
|
86
|
+
self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
|
|
87
|
+
application_name,)
|
|
88
|
+
# If debug is True, the HTTPConnection will display debug information
|
|
89
|
+
self._set_debug(False)
|
|
90
|
+
|
|
91
|
+
__init__ = atom.v1_deprecated(
|
|
92
|
+
'Please use atom.client.AtomPubClient instead.')(
|
|
93
|
+
__init__)
|
|
94
|
+
|
|
95
|
+
def _get_debug(self):
|
|
96
|
+
return self.http_client.debug
|
|
97
|
+
|
|
98
|
+
def _set_debug(self, value):
|
|
99
|
+
self.http_client.debug = value
|
|
100
|
+
|
|
101
|
+
debug = property(_get_debug, _set_debug,
|
|
102
|
+
doc='If True, HTTP debug information is printed.')
|
|
103
|
+
|
|
104
|
+
def use_basic_auth(self, username, password, scopes=None):
|
|
105
|
+
if username is not None and password is not None:
|
|
106
|
+
if scopes is None:
|
|
107
|
+
scopes = [atom.token_store.SCOPE_ALL]
|
|
108
|
+
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
|
109
|
+
token = BasicAuthToken('Basic %s' % base_64_string.strip(),
|
|
110
|
+
scopes=[atom.token_store.SCOPE_ALL])
|
|
111
|
+
if self.auto_set_current_token:
|
|
112
|
+
self.current_token = token
|
|
113
|
+
if self.auto_store_tokens:
|
|
114
|
+
return self.token_store.add_token(token)
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def UseBasicAuth(self, username, password, for_proxy=False):
|
|
119
|
+
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
|
120
|
+
|
|
121
|
+
Deprecated, use use_basic_auth instead.
|
|
122
|
+
|
|
123
|
+
The username and password are base64 encoded and added to an HTTP header
|
|
124
|
+
which will be included in each request. Note that your username and
|
|
125
|
+
password are sent in plaintext.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
username: str
|
|
129
|
+
password: str
|
|
130
|
+
"""
|
|
131
|
+
self.use_basic_auth(username, password)
|
|
132
|
+
|
|
133
|
+
# @atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.')
|
|
134
|
+
def request(self, operation, url, data=None, headers=None,
|
|
135
|
+
url_params=None):
|
|
136
|
+
if isinstance(url, str):
|
|
137
|
+
if url.startswith('http:') and self.ssl:
|
|
138
|
+
# Force all requests to be https if self.ssl is True.
|
|
139
|
+
url = atom.url.parse_url('https:' + url[5:])
|
|
140
|
+
elif not url.startswith('http') and self.ssl:
|
|
141
|
+
url = atom.url.parse_url('https://%s%s' % (self.server, url))
|
|
142
|
+
elif not url.startswith('http'):
|
|
143
|
+
url = atom.url.parse_url('http://%s%s' % (self.server, url))
|
|
144
|
+
else:
|
|
145
|
+
url = atom.url.parse_url(url)
|
|
146
|
+
|
|
147
|
+
if url_params:
|
|
148
|
+
for name, value in url_params.items():
|
|
149
|
+
url.params[name] = value
|
|
150
|
+
|
|
151
|
+
all_headers = self.additional_headers.copy()
|
|
152
|
+
if headers:
|
|
153
|
+
all_headers.update(headers)
|
|
154
|
+
|
|
155
|
+
# If the list of headers does not include a Content-Length, attempt to
|
|
156
|
+
# calculate it based on the data object.
|
|
157
|
+
if data and 'Content-Length' not in all_headers:
|
|
158
|
+
content_length = CalculateDataLength(data)
|
|
159
|
+
if content_length:
|
|
160
|
+
all_headers['Content-Length'] = str(content_length)
|
|
161
|
+
|
|
162
|
+
# Find an Authorization token for this URL if one is available.
|
|
163
|
+
if self.override_token:
|
|
164
|
+
auth_token = self.override_token
|
|
165
|
+
else:
|
|
166
|
+
auth_token = self.token_store.find_token(url)
|
|
167
|
+
return auth_token.perform_request(self.http_client, operation, url,
|
|
168
|
+
data=data, headers=all_headers)
|
|
169
|
+
|
|
170
|
+
request = atom.v1_deprecated(
|
|
171
|
+
'Please use atom.client.AtomPubClient for requests.')(
|
|
172
|
+
request)
|
|
173
|
+
|
|
174
|
+
# CRUD operations
|
|
175
|
+
def Get(self, uri, extra_headers=None, url_params=None, escape_params=True):
|
|
176
|
+
"""Query the APP server with the given URI
|
|
177
|
+
|
|
178
|
+
The uri is the portion of the URI after the server value
|
|
179
|
+
(server example: 'www.google.com').
|
|
180
|
+
|
|
181
|
+
Example use:
|
|
182
|
+
To perform a query against Google Base, set the server to
|
|
183
|
+
'base.google.com' and set the uri to '/base/feeds/...', where ... is
|
|
184
|
+
your query. For example, to find snippets for all digital cameras uri
|
|
185
|
+
should be set to: '/base/feeds/snippets?bq=digital+camera'
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
uri: string The query in the form of a URI. Example:
|
|
189
|
+
'/base/feeds/snippets?bq=digital+camera'.
|
|
190
|
+
extra_headers: dicty (optional) Extra HTTP headers to be included
|
|
191
|
+
in the GET request. These headers are in addition to
|
|
192
|
+
those stored in the client's additional_headers property.
|
|
193
|
+
The client automatically sets the Content-Type and
|
|
194
|
+
Authorization headers.
|
|
195
|
+
url_params: dict (optional) Additional URL parameters to be included
|
|
196
|
+
in the query. These are translated into query arguments
|
|
197
|
+
in the form '&dict_key=value&...'.
|
|
198
|
+
Example: {'max-results': '250'} becomes &max-results=250
|
|
199
|
+
escape_params: boolean (optional) If false, the calling code has already
|
|
200
|
+
ensured that the query will form a valid URL (all
|
|
201
|
+
reserved characters have been escaped). If true, this
|
|
202
|
+
method will escape the query and any URL parameters
|
|
203
|
+
provided.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
httplib.HTTPResponse The server's response to the GET request.
|
|
207
|
+
"""
|
|
208
|
+
return self.request('GET', uri, data=None, headers=extra_headers,
|
|
209
|
+
url_params=url_params)
|
|
210
|
+
|
|
211
|
+
def Post(self, data, uri, extra_headers=None, url_params=None,
|
|
212
|
+
escape_params=True, content_type='application/atom+xml'):
|
|
213
|
+
"""Insert data into an APP server at the given URI.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
data: string, ElementTree._Element, or something with a __str__ method
|
|
217
|
+
The XML to be sent to the uri.
|
|
218
|
+
uri: string The location (feed) to which the data should be inserted.
|
|
219
|
+
Example: '/base/feeds/items'.
|
|
220
|
+
extra_headers: dict (optional) HTTP headers which are to be included.
|
|
221
|
+
The client automatically sets the Content-Type,
|
|
222
|
+
Authorization, and Content-Length headers.
|
|
223
|
+
url_params: dict (optional) Additional URL parameters to be included
|
|
224
|
+
in the URI. These are translated into query arguments
|
|
225
|
+
in the form '&dict_key=value&...'.
|
|
226
|
+
Example: {'max-results': '250'} becomes &max-results=250
|
|
227
|
+
escape_params: boolean (optional) If false, the calling code has already
|
|
228
|
+
ensured that the query will form a valid URL (all
|
|
229
|
+
reserved characters have been escaped). If true, this
|
|
230
|
+
method will escape the query and any URL parameters
|
|
231
|
+
provided.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
httplib.HTTPResponse Server's response to the POST request.
|
|
235
|
+
"""
|
|
236
|
+
if extra_headers is None:
|
|
237
|
+
extra_headers = {}
|
|
238
|
+
if content_type:
|
|
239
|
+
extra_headers['Content-Type'] = content_type
|
|
240
|
+
return self.request('POST', uri, data=data, headers=extra_headers,
|
|
241
|
+
url_params=url_params)
|
|
242
|
+
|
|
243
|
+
def Put(self, data, uri, extra_headers=None, url_params=None,
|
|
244
|
+
escape_params=True, content_type='application/atom+xml'):
|
|
245
|
+
"""Updates an entry at the given URI.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The
|
|
249
|
+
XML containing the updated data.
|
|
250
|
+
uri: string A URI indicating entry to which the update will be applied.
|
|
251
|
+
Example: '/base/feeds/items/ITEM-ID'
|
|
252
|
+
extra_headers: dict (optional) HTTP headers which are to be included.
|
|
253
|
+
The client automatically sets the Content-Type,
|
|
254
|
+
Authorization, and Content-Length headers.
|
|
255
|
+
url_params: dict (optional) Additional URL parameters to be included
|
|
256
|
+
in the URI. These are translated into query arguments
|
|
257
|
+
in the form '&dict_key=value&...'.
|
|
258
|
+
Example: {'max-results': '250'} becomes &max-results=250
|
|
259
|
+
escape_params: boolean (optional) If false, the calling code has already
|
|
260
|
+
ensured that the query will form a valid URL (all
|
|
261
|
+
reserved characters have been escaped). If true, this
|
|
262
|
+
method will escape the query and any URL parameters
|
|
263
|
+
provided.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
httplib.HTTPResponse Server's response to the PUT request.
|
|
267
|
+
"""
|
|
268
|
+
if extra_headers is None:
|
|
269
|
+
extra_headers = {}
|
|
270
|
+
if content_type:
|
|
271
|
+
extra_headers['Content-Type'] = content_type
|
|
272
|
+
return self.request('PUT', uri, data=data, headers=extra_headers,
|
|
273
|
+
url_params=url_params)
|
|
274
|
+
|
|
275
|
+
def Delete(self, uri, extra_headers=None, url_params=None,
|
|
276
|
+
escape_params=True):
|
|
277
|
+
"""Deletes the entry at the given URI.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
uri: string The URI of the entry to be deleted. Example:
|
|
281
|
+
'/base/feeds/items/ITEM-ID'
|
|
282
|
+
extra_headers: dict (optional) HTTP headers which are to be included.
|
|
283
|
+
The client automatically sets the Content-Type and
|
|
284
|
+
Authorization headers.
|
|
285
|
+
url_params: dict (optional) Additional URL parameters to be included
|
|
286
|
+
in the URI. These are translated into query arguments
|
|
287
|
+
in the form '&dict_key=value&...'.
|
|
288
|
+
Example: {'max-results': '250'} becomes &max-results=250
|
|
289
|
+
escape_params: boolean (optional) If false, the calling code has already
|
|
290
|
+
ensured that the query will form a valid URL (all
|
|
291
|
+
reserved characters have been escaped). If true, this
|
|
292
|
+
method will escape the query and any URL parameters
|
|
293
|
+
provided.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
httplib.HTTPResponse Server's response to the DELETE request.
|
|
297
|
+
"""
|
|
298
|
+
return self.request('DELETE', uri, data=None, headers=extra_headers,
|
|
299
|
+
url_params=url_params)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class BasicAuthToken(atom.http_interface.GenericToken):
|
|
303
|
+
def __init__(self, auth_header, scopes=None):
|
|
304
|
+
"""Creates a token used to add Basic Auth headers to HTTP requests.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
auth_header: str The value for the Authorization header.
|
|
308
|
+
scopes: list of str or atom.url.Url specifying the beginnings of URLs
|
|
309
|
+
for which this token can be used. For example, if scopes contains
|
|
310
|
+
'http://example.com/foo', then this token can be used for a request to
|
|
311
|
+
'http://example.com/foo/bar' but it cannot be used for a request to
|
|
312
|
+
'http://example.com/baz'
|
|
313
|
+
"""
|
|
314
|
+
self.auth_header = auth_header
|
|
315
|
+
self.scopes = scopes or []
|
|
316
|
+
|
|
317
|
+
def perform_request(self, http_client, operation, url, data=None,
|
|
318
|
+
headers=None):
|
|
319
|
+
"""Sets the Authorization header to the basic auth string."""
|
|
320
|
+
if headers is None:
|
|
321
|
+
headers = {'Authorization': self.auth_header}
|
|
322
|
+
else:
|
|
323
|
+
headers['Authorization'] = self.auth_header
|
|
324
|
+
return http_client.request(operation, url, data=data, headers=headers)
|
|
325
|
+
|
|
326
|
+
def __str__(self):
|
|
327
|
+
return self.auth_header
|
|
328
|
+
|
|
329
|
+
def valid_for_scope(self, url):
|
|
330
|
+
"""Tells the caller if the token authorizes access to the desired URL.
|
|
331
|
+
"""
|
|
332
|
+
if isinstance(url, str):
|
|
333
|
+
url = atom.url.parse_url(url)
|
|
334
|
+
for scope in self.scopes:
|
|
335
|
+
if scope == atom.token_store.SCOPE_ALL:
|
|
336
|
+
return True
|
|
337
|
+
if isinstance(scope, str):
|
|
338
|
+
scope = atom.url.parse_url(scope)
|
|
339
|
+
if scope == url:
|
|
340
|
+
return True
|
|
341
|
+
# Check the host and the path, but ignore the port and protocol.
|
|
342
|
+
elif scope.host == url.host and not scope.path:
|
|
343
|
+
return True
|
|
344
|
+
elif scope.host == url.host and scope.path and not url.path:
|
|
345
|
+
continue
|
|
346
|
+
elif scope.host == url.host and url.path.startswith(scope.path):
|
|
347
|
+
return True
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def PrepareConnection(service, full_uri):
|
|
352
|
+
"""Opens a connection to the server based on the full URI.
|
|
353
|
+
|
|
354
|
+
This method is deprecated, instead use atom.http.HttpClient.request.
|
|
355
|
+
|
|
356
|
+
Examines the target URI and the proxy settings, which are set as
|
|
357
|
+
environment variables, to open a connection with the server. This
|
|
358
|
+
connection is used to make an HTTP request.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
service: atom.AtomService or a subclass. It must have a server string which
|
|
362
|
+
represents the server host to which the request should be made. It may also
|
|
363
|
+
have a dictionary of additional_headers to send in the HTTP request.
|
|
364
|
+
full_uri: str Which is the target relative (lacks protocol and host) or
|
|
365
|
+
absolute URL to be opened. Example:
|
|
366
|
+
'https://www.google.com/accounts/ClientLogin' or
|
|
367
|
+
'base/feeds/snippets' where the server is set to www.google.com.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
A tuple containing the httplib.HTTPConnection and the full_uri for the
|
|
371
|
+
request.
|
|
372
|
+
"""
|
|
373
|
+
deprecation('calling deprecated function PrepareConnection')
|
|
374
|
+
(server, port, ssl, partial_uri) = ProcessUrl(service, full_uri)
|
|
375
|
+
if ssl:
|
|
376
|
+
# destination is https
|
|
377
|
+
proxy = os.environ.get('https_proxy')
|
|
378
|
+
if proxy:
|
|
379
|
+
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True)
|
|
380
|
+
proxy_username = os.environ.get('proxy-username')
|
|
381
|
+
if not proxy_username:
|
|
382
|
+
proxy_username = os.environ.get('proxy_username')
|
|
383
|
+
proxy_password = os.environ.get('proxy-password')
|
|
384
|
+
if not proxy_password:
|
|
385
|
+
proxy_password = os.environ.get('proxy_password')
|
|
386
|
+
if proxy_username:
|
|
387
|
+
user_auth = base64.encodestring('%s:%s' % (proxy_username,
|
|
388
|
+
proxy_password))
|
|
389
|
+
proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % (
|
|
390
|
+
user_auth.strip()))
|
|
391
|
+
else:
|
|
392
|
+
proxy_authorization = ''
|
|
393
|
+
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port)
|
|
394
|
+
user_agent = 'User-Agent: %s\r\n' % (
|
|
395
|
+
service.additional_headers['User-Agent'])
|
|
396
|
+
proxy_pieces = (proxy_connect + proxy_authorization + user_agent
|
|
397
|
+
+ '\r\n')
|
|
398
|
+
|
|
399
|
+
# now connect, very simple recv and error checking
|
|
400
|
+
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
401
|
+
p_sock.connect((p_server, p_port))
|
|
402
|
+
p_sock.sendall(proxy_pieces)
|
|
403
|
+
response = ''
|
|
404
|
+
|
|
405
|
+
# Wait for the full response.
|
|
406
|
+
while response.find("\r\n\r\n") == -1:
|
|
407
|
+
response += p_sock.recv(8192)
|
|
408
|
+
|
|
409
|
+
p_status = response.split()[1]
|
|
410
|
+
if p_status != str(200):
|
|
411
|
+
raise atom.http.ProxyError('Error status=%s' % p_status)
|
|
412
|
+
|
|
413
|
+
# Trivial setup for ssl socket.
|
|
414
|
+
ssl = socket.ssl(p_sock, None, None)
|
|
415
|
+
fake_sock = http.client.FakeSocket(p_sock, ssl)
|
|
416
|
+
|
|
417
|
+
# Initalize httplib and replace with the proxy socket.
|
|
418
|
+
connection = http.client.HTTPConnection(server)
|
|
419
|
+
connection.sock = fake_sock
|
|
420
|
+
full_uri = partial_uri
|
|
421
|
+
|
|
422
|
+
else:
|
|
423
|
+
connection = http.client.HTTPSConnection(server, port)
|
|
424
|
+
full_uri = partial_uri
|
|
425
|
+
|
|
426
|
+
else:
|
|
427
|
+
# destination is http
|
|
428
|
+
proxy = os.environ.get('http_proxy')
|
|
429
|
+
if proxy:
|
|
430
|
+
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True)
|
|
431
|
+
proxy_username = os.environ.get('proxy-username')
|
|
432
|
+
if not proxy_username:
|
|
433
|
+
proxy_username = os.environ.get('proxy_username')
|
|
434
|
+
proxy_password = os.environ.get('proxy-password')
|
|
435
|
+
if not proxy_password:
|
|
436
|
+
proxy_password = os.environ.get('proxy_password')
|
|
437
|
+
if proxy_username:
|
|
438
|
+
UseBasicAuth(service, proxy_username, proxy_password, True)
|
|
439
|
+
connection = http.client.HTTPConnection(p_server, p_port)
|
|
440
|
+
if not full_uri.startswith("http://"):
|
|
441
|
+
if full_uri.startswith("/"):
|
|
442
|
+
full_uri = "http://%s%s" % (service.server, full_uri)
|
|
443
|
+
else:
|
|
444
|
+
full_uri = "http://%s/%s" % (service.server, full_uri)
|
|
445
|
+
else:
|
|
446
|
+
connection = http.client.HTTPConnection(server, port)
|
|
447
|
+
full_uri = partial_uri
|
|
448
|
+
|
|
449
|
+
return (connection, full_uri)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def UseBasicAuth(service, username, password, for_proxy=False):
|
|
453
|
+
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
|
454
|
+
|
|
455
|
+
Deprecated, use AtomService.use_basic_auth insread.
|
|
456
|
+
|
|
457
|
+
The username and password are base64 encoded and added to an HTTP header
|
|
458
|
+
which will be included in each request. Note that your username and
|
|
459
|
+
password are sent in plaintext. The auth header is added to the
|
|
460
|
+
additional_headers dictionary in the service object.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
service: atom.AtomService or a subclass which has an
|
|
464
|
+
additional_headers dict as a member.
|
|
465
|
+
username: str
|
|
466
|
+
password: str
|
|
467
|
+
"""
|
|
468
|
+
deprecation('calling deprecated function UseBasicAuth')
|
|
469
|
+
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
|
470
|
+
base_64_string = base_64_string.strip()
|
|
471
|
+
if for_proxy:
|
|
472
|
+
header_name = 'Proxy-Authorization'
|
|
473
|
+
else:
|
|
474
|
+
header_name = 'Authorization'
|
|
475
|
+
service.additional_headers[header_name] = 'Basic %s' % (base_64_string,)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def ProcessUrl(service, url, for_proxy=False):
|
|
479
|
+
"""Processes a passed URL. If the URL does not begin with https?, then
|
|
480
|
+
the default value for server is used
|
|
481
|
+
|
|
482
|
+
This method is deprecated, use atom.url.parse_url instead.
|
|
483
|
+
"""
|
|
484
|
+
if not isinstance(url, atom.url.Url):
|
|
485
|
+
url = atom.url.parse_url(url)
|
|
486
|
+
|
|
487
|
+
server = url.host
|
|
488
|
+
ssl = False
|
|
489
|
+
port = 80
|
|
490
|
+
|
|
491
|
+
if not server:
|
|
492
|
+
if hasattr(service, 'server'):
|
|
493
|
+
server = service.server
|
|
494
|
+
else:
|
|
495
|
+
server = service
|
|
496
|
+
if not url.protocol and hasattr(service, 'ssl'):
|
|
497
|
+
ssl = service.ssl
|
|
498
|
+
if hasattr(service, 'port'):
|
|
499
|
+
port = service.port
|
|
500
|
+
else:
|
|
501
|
+
if url.protocol == 'https':
|
|
502
|
+
ssl = True
|
|
503
|
+
elif url.protocol == 'http':
|
|
504
|
+
ssl = False
|
|
505
|
+
if url.port:
|
|
506
|
+
port = int(url.port)
|
|
507
|
+
elif port == 80 and ssl:
|
|
508
|
+
port = 443
|
|
509
|
+
|
|
510
|
+
return (server, port, ssl, url.get_request_uri())
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def DictionaryToParamList(url_parameters, escape_params=True):
|
|
514
|
+
"""Convert a dictionary of URL arguments into a URL parameter string.
|
|
515
|
+
|
|
516
|
+
This function is deprcated, use atom.url.Url instead.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
url_parameters: The dictionaty of key-value pairs which will be converted
|
|
520
|
+
into URL parameters. For example,
|
|
521
|
+
{'dry-run': 'true', 'foo': 'bar'}
|
|
522
|
+
will become ['dry-run=true', 'foo=bar'].
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
A list which contains a string for each key-value pair. The strings are
|
|
526
|
+
ready to be incorporated into a URL by using '&'.join([] + parameter_list)
|
|
527
|
+
"""
|
|
528
|
+
# Choose which function to use when modifying the query and parameters.
|
|
529
|
+
# Use quote_plus when escape_params is true.
|
|
530
|
+
transform_op = [str, urllib.parse.quote_plus][bool(escape_params)]
|
|
531
|
+
# Create a list of tuples containing the escaped version of the
|
|
532
|
+
# parameter-value pairs.
|
|
533
|
+
parameter_tuples = [(transform_op(param), transform_op(value))
|
|
534
|
+
for param, value in list((url_parameters or {}).items())]
|
|
535
|
+
# Turn parameter-value tuples into a list of strings in the form
|
|
536
|
+
# 'PARAMETER=VALUE'.
|
|
537
|
+
return ['='.join(x) for x in parameter_tuples]
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def BuildUri(uri, url_params=None, escape_params=True):
|
|
541
|
+
"""Converts a uri string and a collection of parameters into a URI.
|
|
542
|
+
|
|
543
|
+
This function is deprcated, use atom.url.Url instead.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
uri: string
|
|
547
|
+
url_params: dict (optional)
|
|
548
|
+
escape_params: boolean (optional)
|
|
549
|
+
uri: string The start of the desired URI. This string can alrady contain
|
|
550
|
+
URL parameters. Examples: '/base/feeds/snippets',
|
|
551
|
+
'/base/feeds/snippets?bq=digital+camera'
|
|
552
|
+
url_parameters: dict (optional) Additional URL parameters to be included
|
|
553
|
+
in the query. These are translated into query arguments
|
|
554
|
+
in the form '&dict_key=value&...'.
|
|
555
|
+
Example: {'max-results': '250'} becomes &max-results=250
|
|
556
|
+
escape_params: boolean (optional) If false, the calling code has already
|
|
557
|
+
ensured that the query will form a valid URL (all
|
|
558
|
+
reserved characters have been escaped). If true, this
|
|
559
|
+
method will escape the query and any URL parameters
|
|
560
|
+
provided.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
string The URI consisting of the escaped URL parameters appended to the
|
|
564
|
+
initial uri string.
|
|
565
|
+
"""
|
|
566
|
+
# Prepare URL parameters for inclusion into the GET request.
|
|
567
|
+
parameter_list = DictionaryToParamList(url_params, escape_params)
|
|
568
|
+
|
|
569
|
+
# Append the URL parameters to the URL.
|
|
570
|
+
if parameter_list:
|
|
571
|
+
if uri.find('?') != -1:
|
|
572
|
+
# If there are already URL parameters in the uri string, add the
|
|
573
|
+
# parameters after a new & character.
|
|
574
|
+
full_uri = '&'.join([uri] + parameter_list)
|
|
575
|
+
else:
|
|
576
|
+
# The uri string did not have any URL parameters (no ? character)
|
|
577
|
+
# so put a ? between the uri and URL parameters.
|
|
578
|
+
full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list)))
|
|
579
|
+
else:
|
|
580
|
+
full_uri = uri
|
|
581
|
+
|
|
582
|
+
return full_uri
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def HttpRequest(service, operation, data, uri, extra_headers=None,
|
|
586
|
+
url_params=None, escape_params=True, content_type='application/atom+xml'):
|
|
587
|
+
"""Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
|
|
588
|
+
|
|
589
|
+
This method is deprecated, use atom.http.HttpClient.request instead.
|
|
590
|
+
|
|
591
|
+
Usage example, perform and HTTP GET on http://www.google.com/:
|
|
592
|
+
import atom.service
|
|
593
|
+
client = atom.service.AtomService()
|
|
594
|
+
http_response = client.Get('http://www.google.com/')
|
|
595
|
+
or you could set the client.server to 'www.google.com' and use the
|
|
596
|
+
following:
|
|
597
|
+
client.server = 'www.google.com'
|
|
598
|
+
http_response = client.Get('/')
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
service: atom.AtomService object which contains some of the parameters
|
|
602
|
+
needed to make the request. The following members are used to
|
|
603
|
+
construct the HTTP call: server (str), additional_headers (dict),
|
|
604
|
+
port (int), and ssl (bool).
|
|
605
|
+
operation: str The HTTP operation to be performed. This is usually one of
|
|
606
|
+
'GET', 'POST', 'PUT', or 'DELETE'
|
|
607
|
+
data: ElementTree, filestream, list of parts, or other object which can be
|
|
608
|
+
converted to a string.
|
|
609
|
+
Should be set to None when performing a GET or PUT.
|
|
610
|
+
If data is a file-like object which can be read, this method will read
|
|
611
|
+
a chunk of 100K bytes at a time and send them.
|
|
612
|
+
If the data is a list of parts to be sent, each part will be evaluated
|
|
613
|
+
and sent.
|
|
614
|
+
uri: The beginning of the URL to which the request should be sent.
|
|
615
|
+
Examples: '/', '/base/feeds/snippets',
|
|
616
|
+
'/m8/feeds/contacts/default/base'
|
|
617
|
+
extra_headers: dict of strings. HTTP headers which should be sent
|
|
618
|
+
in the request. These headers are in addition to those stored in
|
|
619
|
+
service.additional_headers.
|
|
620
|
+
url_params: dict of strings. Key value pairs to be added to the URL as
|
|
621
|
+
URL parameters. For example {'foo':'bar', 'test':'param'} will
|
|
622
|
+
become ?foo=bar&test=param.
|
|
623
|
+
escape_params: bool default True. If true, the keys and values in
|
|
624
|
+
url_params will be URL escaped when the form is constructed
|
|
625
|
+
(Special characters converted to %XX form.)
|
|
626
|
+
content_type: str The MIME type for the data being sent. Defaults to
|
|
627
|
+
'application/atom+xml', this is only used if data is set.
|
|
628
|
+
"""
|
|
629
|
+
deprecation('call to deprecated function HttpRequest')
|
|
630
|
+
full_uri = BuildUri(uri, url_params, escape_params)
|
|
631
|
+
(connection, full_uri) = PrepareConnection(service, full_uri)
|
|
632
|
+
|
|
633
|
+
if extra_headers is None:
|
|
634
|
+
extra_headers = {}
|
|
635
|
+
|
|
636
|
+
# Turn on debug mode if the debug member is set.
|
|
637
|
+
if service.debug:
|
|
638
|
+
connection.debuglevel = 1
|
|
639
|
+
|
|
640
|
+
connection.putrequest(operation, full_uri)
|
|
641
|
+
|
|
642
|
+
# If the list of headers does not include a Content-Length, attempt to
|
|
643
|
+
# calculate it based on the data object.
|
|
644
|
+
if (data and 'Content-Length' not in service.additional_headers and
|
|
645
|
+
'Content-Length' not in extra_headers):
|
|
646
|
+
content_length = CalculateDataLength(data)
|
|
647
|
+
if content_length:
|
|
648
|
+
extra_headers['Content-Length'] = str(content_length)
|
|
649
|
+
|
|
650
|
+
if content_type:
|
|
651
|
+
extra_headers['Content-Type'] = content_type
|
|
652
|
+
|
|
653
|
+
# Send the HTTP headers.
|
|
654
|
+
if isinstance(service.additional_headers, dict):
|
|
655
|
+
for header in service.additional_headers:
|
|
656
|
+
connection.putheader(header, service.additional_headers[header])
|
|
657
|
+
if isinstance(extra_headers, dict):
|
|
658
|
+
for header in extra_headers:
|
|
659
|
+
connection.putheader(header, extra_headers[header])
|
|
660
|
+
connection.endheaders()
|
|
661
|
+
|
|
662
|
+
# If there is data, send it in the request.
|
|
663
|
+
if data:
|
|
664
|
+
if isinstance(data, list):
|
|
665
|
+
for data_part in data:
|
|
666
|
+
__SendDataPart(data_part, connection)
|
|
667
|
+
else:
|
|
668
|
+
__SendDataPart(data, connection)
|
|
669
|
+
|
|
670
|
+
# Return the HTTP Response from the server.
|
|
671
|
+
return connection.getresponse()
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def __SendDataPart(data, connection):
|
|
675
|
+
"""This method is deprecated, use atom.http._send_data_part"""
|
|
676
|
+
deprecated('call to deprecated function __SendDataPart')
|
|
677
|
+
if isinstance(data, str):
|
|
678
|
+
# TODO add handling for unicode.
|
|
679
|
+
connection.send(data)
|
|
680
|
+
return
|
|
681
|
+
elif ElementTree.iselement(data):
|
|
682
|
+
connection.send(ElementTree.tostring(data))
|
|
683
|
+
return
|
|
684
|
+
# Check to see if data is a file-like object that has a read method.
|
|
685
|
+
elif hasattr(data, 'read'):
|
|
686
|
+
# Read the file and send it a chunk at a time.
|
|
687
|
+
while 1:
|
|
688
|
+
binarydata = data.read(100000)
|
|
689
|
+
if binarydata == '': break
|
|
690
|
+
connection.send(binarydata)
|
|
691
|
+
return
|
|
692
|
+
else:
|
|
693
|
+
# The data object was not a file.
|
|
694
|
+
# Try to convert to a string and send the data.
|
|
695
|
+
connection.send(str(data))
|
|
696
|
+
return
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def CalculateDataLength(data):
|
|
700
|
+
"""Attempts to determine the length of the data to send.
|
|
701
|
+
|
|
702
|
+
This method will respond with a length only if the data is a string or
|
|
703
|
+
and ElementTree element.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
data: object If this is not a string or ElementTree element this funtion
|
|
707
|
+
will return None.
|
|
708
|
+
"""
|
|
709
|
+
if isinstance(data, str):
|
|
710
|
+
return len(data)
|
|
711
|
+
elif isinstance(data, list):
|
|
712
|
+
return None
|
|
713
|
+
elif ElementTree.iselement(data):
|
|
714
|
+
return len(ElementTree.tostring(data))
|
|
715
|
+
elif hasattr(data, 'read'):
|
|
716
|
+
# If this is a file-like object, don't try to guess the length.
|
|
717
|
+
return None
|
|
718
|
+
else:
|
|
719
|
+
return len(str(data).encode('utf-8'))
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def deprecation(message):
|
|
723
|
+
warnings.warn(message, DeprecationWarning, stacklevel=2)
|