Skip to content

Commit a53d2c8

Browse files
committed
Adding lazy loading __user_agent__ to main package.
Fixes #566.
1 parent d858ff0 commit a53d2c8

8 files changed

Lines changed: 110 additions & 21 deletions

File tree

gcloud/__init__.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,87 @@
1616

1717
from pkg_resources import get_distribution
1818

19+
import httplib
20+
import socket
21+
22+
try:
23+
from google import appengine
24+
except ImportError:
25+
appengine = None
26+
27+
1928
__version__ = get_distribution('gcloud').version
29+
30+
31+
class _LazyUserAgent(object):
32+
"""Helper for extra environment information."""
33+
34+
_curr_environ = None
35+
_user_agent = None
36+
37+
def __init__(self):
38+
self._curr_environ = self.environ_at_init()
39+
40+
@staticmethod
41+
def environ_at_init():
42+
"""Checks environment variables during instance initialization.
43+
44+
Intended to infer as much as possible from the environment without
45+
running code.
46+
47+
:rtype: string or ``NoneType``
48+
:returns: Either ``'-GAE'`` if on App Engine else ``None``
49+
"""
50+
if appengine is not None:
51+
return '-GAE'
52+
53+
def environ_post_init(self):
54+
"""Checks environment variables after instance initialization.
55+
56+
This is meant for checks which can't be performed instantaneously.
57+
58+
:rtype: string
59+
:returns: Either ``'-GCE'`` if on Compute Engine else an empty string.
60+
"""
61+
gce_environ = self.check_compute_engine()
62+
if gce_environ is not None:
63+
return gce_environ
64+
65+
return ''
66+
67+
@staticmethod
68+
def check_compute_engine():
69+
"""Checks if the current environment is Compute Engine.
70+
71+
:rtype: string or ``NoneType``
72+
:returns: The string ``'-GCE'`` if on Compute Engine else ``None``.
73+
"""
74+
host = '169.254.169.254'
75+
uri_path = '/computeMetadata/v1/project/project-id'
76+
headers = {'Metadata-Flavor': 'Google'}
77+
connection = httplib.HTTPConnection(host, timeout=0.1)
78+
try:
79+
connection.request('GET', uri_path, headers=headers)
80+
response = connection.getresponse()
81+
if response.status == 200:
82+
return '-GCE'
83+
except socket.error: # Expect timeout or host is down
84+
pass
85+
finally:
86+
connection.close()
87+
88+
def __str__(self):
89+
if self._curr_environ is None:
90+
self._curr_environ = self.environ_post_init()
91+
92+
if self._user_agent is None:
93+
self._user_agent = "gcloud-python/{0}{1}".format(
94+
__version__, self._curr_environ)
95+
96+
return self._user_agent
97+
98+
def __repr__(self):
99+
return '<_LazyUserAgent: %r>' % (self.__str__(),)
100+
101+
102+
__user_agent__ = _LazyUserAgent()

gcloud/connection.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414

1515
""" Shared implementation of connections to API servers."""
1616

17-
from pkg_resources import get_distribution
18-
1917
import httplib2
2018

19+
import gcloud
20+
2121

2222
class Connection(object):
2323
"""A generic connection to Google Cloud Platform.
@@ -29,10 +29,7 @@ class Connection(object):
2929
API_BASE_URL = 'https://www.googleapis.com'
3030
"""The base of the API call URL."""
3131

32-
_EMPTY = object()
33-
"""A pointer to represent an empty value for default arguments."""
34-
35-
USER_AGENT = "gcloud-python/{0}".format(get_distribution('gcloud').version)
32+
USER_AGENT = gcloud.__user_agent__
3633
"""The user agent for gcloud-python requests."""
3734

3835
def __init__(self, credentials=None):

gcloud/datastore/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def _request(self, dataset_id, method, data):
5959
headers = {
6060
'Content-Type': 'application/x-protobuf',
6161
'Content-Length': str(len(data)),
62-
'User-Agent': self.USER_AGENT,
62+
'User-Agent': str(self.USER_AGENT),
6363
}
6464
headers, content = self.http.request(
6565
uri=self.build_api_url(dataset_id=dataset_id, method=method),

gcloud/datastore/test_connection.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,17 @@ def _make_query_pb(self, kind):
3838
def _makeOne(self, *args, **kw):
3939
return self._getTargetClass()(*args, **kw)
4040

41+
def _user_agent(self):
42+
import gcloud
43+
return "gcloud-python/{0}".format(gcloud.__version__)
44+
4145
def _verifyProtobufCall(self, called_with, URI, conn):
4246
self.assertEqual(called_with['uri'], URI)
4347
self.assertEqual(called_with['method'], 'POST')
4448
self.assertEqual(called_with['headers']['Content-Type'],
4549
'application/x-protobuf')
4650
self.assertEqual(called_with['headers']['User-Agent'],
47-
conn.USER_AGENT)
51+
self._user_agent())
4852

4953
def test_ctor_defaults(self):
5054
conn = self._makeOne()
@@ -404,7 +408,7 @@ def test_lookup_multiple_keys_w_deferred(self):
404408
self.assertEqual(cw['method'], 'POST')
405409
self.assertEqual(cw['headers']['Content-Type'],
406410
'application/x-protobuf')
407-
self.assertEqual(cw['headers']['User-Agent'], conn.USER_AGENT)
411+
self.assertEqual(cw['headers']['User-Agent'], self._user_agent())
408412
rq_class = datastore_pb.LookupRequest
409413
request = rq_class()
410414
request.ParseFromString(cw['body'])

gcloud/storage/blob.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
292292
headers = {
293293
'Accept': 'application/json',
294294
'Accept-Encoding': 'gzip, deflate',
295-
'User-Agent': conn.USER_AGENT,
295+
'User-Agent': str(conn.USER_AGENT),
296296
}
297297

298298
upload = transfer.Upload(file_obj,

gcloud/storage/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def make_request(self, method, url, data=None, content_type=None,
252252
if content_type:
253253
headers['Content-Type'] = content_type
254254

255-
headers['User-Agent'] = self.USER_AGENT
255+
headers['User-Agent'] = str(self.USER_AGENT)
256256

257257
return self.http.request(uri=url, method=method, headers=headers,
258258
body=data)

gcloud/storage/test_connection.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ def test_build_api_url_w_upload(self):
176176
])
177177
self.assertEqual(conn.build_api_url('/foo', upload=True), URI)
178178

179+
def _user_agent(self):
180+
import gcloud
181+
return "gcloud-python/{0}".format(gcloud.__version__)
182+
179183
def test_make_request_no_data_no_content_type_no_headers(self):
180184
PROJECT = 'project'
181185
conn = self._makeOne(PROJECT)
@@ -194,7 +198,7 @@ def test_make_request_no_data_no_content_type_no_headers(self):
194198
expected_headers = {
195199
'Accept-Encoding': 'gzip',
196200
'Content-Length': 0,
197-
'User-Agent': conn.USER_AGENT,
201+
'User-Agent': self._user_agent(),
198202
}
199203
self.assertEqual(http._called_with['headers'], expected_headers)
200204

@@ -214,7 +218,7 @@ def test_make_request_w_data_no_extra_headers(self):
214218
'Accept-Encoding': 'gzip',
215219
'Content-Length': 0,
216220
'Content-Type': 'application/json',
217-
'User-Agent': conn.USER_AGENT,
221+
'User-Agent': self._user_agent(),
218222
}
219223
self.assertEqual(http._called_with['headers'], expected_headers)
220224

@@ -234,7 +238,7 @@ def test_make_request_w_extra_headers(self):
234238
'Accept-Encoding': 'gzip',
235239
'Content-Length': 0,
236240
'X-Foo': 'foo',
237-
'User-Agent': conn.USER_AGENT,
241+
'User-Agent': self._user_agent(),
238242
}
239243
self.assertEqual(http._called_with['headers'], expected_headers)
240244

@@ -258,7 +262,7 @@ def test_api_request_defaults(self):
258262
expected_headers = {
259263
'Accept-Encoding': 'gzip',
260264
'Content-Length': 0,
261-
'User-Agent': conn.USER_AGENT,
265+
'User-Agent': self._user_agent(),
262266
}
263267
self.assertEqual(http._called_with['headers'], expected_headers)
264268

@@ -305,7 +309,7 @@ def test_api_request_w_query_params(self):
305309
expected_headers = {
306310
'Accept-Encoding': 'gzip',
307311
'Content-Length': 0,
308-
'User-Agent': conn.USER_AGENT,
312+
'User-Agent': self._user_agent(),
309313
}
310314
self.assertEqual(http._called_with['headers'], expected_headers)
311315

@@ -333,7 +337,7 @@ def test_api_request_w_data(self):
333337
'Accept-Encoding': 'gzip',
334338
'Content-Length': len(DATAJ),
335339
'Content-Type': 'application/json',
336-
'User-Agent': conn.USER_AGENT,
340+
'User-Agent': self._user_agent(),
337341
}
338342
self.assertEqual(http._called_with['headers'], expected_headers)
339343

gcloud/test_connection.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ def authorize(self, http):
5858
self.assertTrue(isinstance(creds._called_with, Http))
5959

6060
def test_user_agent_format(self):
61-
from pkg_resources import get_distribution
62-
expected_ua = 'gcloud-python/{0}'.format(
63-
get_distribution('gcloud').version)
61+
import gcloud
62+
63+
expected_ua = 'gcloud-python/{0}'.format(gcloud.__version__)
6464
conn = self._makeOne()
65-
self.assertEqual(conn.USER_AGENT, expected_ua)
65+
self.assertTrue(isinstance(conn.USER_AGENT, gcloud._LazyUserAgent))
66+
self.assertEqual(str(conn.USER_AGENT), expected_ua)

0 commit comments

Comments
 (0)