Skip to content

Commit 906700b

Browse files
committed
Merge pull request #248 from tseaver/239-remove_pragma_no_cover
Fix #239: remove / document 'pragma: NO COVER'
2 parents b194e0e + 2338955 commit 906700b

12 files changed

Lines changed: 286 additions & 44 deletions

File tree

.coveragerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
omit =
33
*/demo/*
44
*/demo.py
5+
exclude_lines =
6+
# Re-enable the standard pragma
7+
pragma: NO COVER
8+
# Ignore debug-only repr
9+
def __repr__

gcloud/datastore/entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def delete(self):
230230
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf())
231231
# pylint: enable=maybe-no-member
232232

233-
def __repr__(self): # pragma NO COVER
233+
def __repr__(self):
234234
# An entity should have a key all the time (even if it's partial).
235235
if self.key():
236236
# pylint: disable=maybe-no-member

gcloud/datastore/key.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def id_or_name(self):
274274
"""
275275
return self.id() or self.name()
276276

277-
def parent(self): # pragma NO COVER
277+
def parent(self):
278278
"""Getter: return a new key for the next highest element in path.
279279
280280
:rtype: :class:`gcloud.datastore.key.Key`
@@ -286,5 +286,5 @@ def parent(self): # pragma NO COVER
286286
return None
287287
return self.path(self.path()[:-1])
288288

289-
def __repr__(self): # pragma NO COVER
289+
def __repr__(self):
290290
return '<Key%s>' % self.path()

gcloud/storage/acl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def __str__(self):
110110
else:
111111
return '{self.type}-{self.identifier}'.format(self=self)
112112

113-
def __repr__(self): # pragma NO COVER
113+
def __repr__(self):
114114
return '<ACL Entity: {self} ({roles})>'.format(
115115
self=self, roles=', '.join(self.roles))
116116

@@ -353,7 +353,7 @@ def get_entities(self):
353353

354354
return self.entities.values()
355355

356-
def save(self): # pragma NO COVER
356+
def save(self):
357357
"""A method to be overridden by subclasses.
358358
359359
:raises: NotImplementedError

gcloud/storage/bucket.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def from_dict(cls, bucket_dict, connection=None):
4242
return cls(connection=connection, name=bucket_dict['name'],
4343
metadata=bucket_dict)
4444

45-
def __repr__(self): # pragma NO COVER
45+
def __repr__(self):
4646
return '<Bucket: %s>' % self.name
4747

4848
def __iter__(self):
@@ -126,7 +126,7 @@ def new_key(self, key):
126126
# Support Python 2 and 3.
127127
try:
128128
string_type = basestring
129-
except NameError: # pragma NO COVER PY3k
129+
except NameError: # pragma: NO COVER PY3k
130130
string_type = str
131131

132132
if isinstance(key, string_type):

gcloud/storage/connection.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Create / interact with gcloud storage connections."""
22

33
import base64
4+
import calendar
45
import datetime
56
import json
6-
import time
77
import urllib
88

99
from Crypto.Hash import SHA256
@@ -18,6 +18,14 @@
1818
from gcloud.storage.iterator import BucketIterator
1919

2020

21+
def _utcnow(): # pragma: NO COVER testing replaces
22+
"""Returns current time as UTC datetime.
23+
24+
NOTE: on the module namespace so tests can replace it.
25+
"""
26+
return datetime.datetime.utcnow()
27+
28+
2129
class Connection(connection.Connection):
2230
"""A connection to Google Cloud Storage via the JSON REST API.
2331
@@ -419,7 +427,7 @@ def new_bucket(self, bucket):
419427
# Support Python 2 and 3.
420428
try:
421429
string_type = basestring
422-
except NameError: # pragma NO COVER PY3k
430+
except NameError: # pragma: NO COVER PY3k
423431
string_type = str
424432

425433
if isinstance(bucket, string_type):
@@ -429,7 +437,7 @@ def new_bucket(self, bucket):
429437

430438
def generate_signed_url(self, resource, expiration,
431439
method='GET', content_md5=None,
432-
content_type=None): # pragma NO COVER
440+
content_type=None):
433441
"""Generate signed URL to provide query-string auth'n to a resource.
434442
435443
:type resource: string
@@ -455,31 +463,7 @@ def generate_signed_url(self, resource, expiration,
455463
until expiration.
456464
"""
457465

458-
# expiration can be an absolute timestamp (int, long),
459-
# an absolute time (datetime.datetime),
460-
# or a relative time (datetime.timedelta).
461-
# We should convert all of these into an absolute timestamp.
462-
463-
# If it's a timedelta, add it to `now` in UTC.
464-
if isinstance(expiration, datetime.timedelta):
465-
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
466-
expiration = now + expiration
467-
468-
# If it's a datetime, convert to a timestamp.
469-
if isinstance(expiration, datetime.datetime):
470-
# Make sure the timezone on the value is UTC
471-
# (either by converting or replacing the value).
472-
if expiration.tzinfo:
473-
expiration = expiration.astimezone(pytz.utc)
474-
else:
475-
expiration = expiration.replace(tzinfo=pytz.utc)
476-
477-
# Turn the datetime into a timestamp (seconds, not microseconds).
478-
expiration = int(time.mktime(expiration.timetuple()))
479-
480-
if not isinstance(expiration, (int, long)):
481-
raise ValueError('Expected an integer timestamp, datetime, or '
482-
'timedelta. Got %s' % type(expiration))
466+
expiration = _get_expiration_seconds(expiration)
483467

484468
# Generate the string to sign.
485469
signature_string = '\n'.join([
@@ -514,3 +498,36 @@ def generate_signed_url(self, resource, expiration,
514498
return '{endpoint}{resource}?{querystring}'.format(
515499
endpoint=self.API_ACCESS_ENDPOINT, resource=resource,
516500
querystring=urllib.urlencode(query_params))
501+
502+
503+
def _get_expiration_seconds(expiration):
504+
"""Convert 'expiration' to a number of seconds in the future.
505+
506+
:type expiration: int, long, datetime.datetime, datetime.timedelta
507+
:param expiration: When the signed URL should expire.
508+
509+
:rtype: int
510+
:returns: a timestamp as an absolute number of seconds.
511+
"""
512+
513+
# If it's a timedelta, add it to `now` in UTC.
514+
if isinstance(expiration, datetime.timedelta):
515+
now = _utcnow().replace(tzinfo=pytz.utc)
516+
expiration = now + expiration
517+
518+
# If it's a datetime, convert to a timestamp.
519+
if isinstance(expiration, datetime.datetime):
520+
# Make sure the timezone on the value is UTC
521+
# (either by converting or replacing the value).
522+
if expiration.tzinfo:
523+
expiration = expiration.astimezone(pytz.utc)
524+
else:
525+
expiration = expiration.replace(tzinfo=pytz.utc)
526+
527+
# Turn the datetime into a timestamp (seconds, not microseconds).
528+
expiration = int(calendar.timegm(expiration.timetuple()))
529+
530+
if not isinstance(expiration, (int, long)):
531+
raise TypeError('Expected an integer timestamp, datetime, or '
532+
'timedelta. Got %s' % type(expiration))
533+
return expiration

gcloud/storage/iterator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def reset(self):
112112
self.page_number = 0
113113
self.next_page_token = None
114114

115-
def get_items_from_response(self, response): # pragma NO COVER
115+
def get_items_from_response(self, response):
116116
"""Factory method called while iterating. This should be overriden.
117117
118118
This method should be overridden by a subclass.

gcloud/storage/key.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def from_dict(cls, key_dict, bucket=None):
5858

5959
return cls(bucket=bucket, name=key_dict['name'], metadata=key_dict)
6060

61-
def __repr__(self): # pragma NO COVER
61+
def __repr__(self):
6262
if self.bucket:
6363
bucket_name = self.bucket.name
6464
else:
@@ -102,8 +102,7 @@ def public_url(self):
102102
storage_base_url='http://commondatastorage.googleapis.com',
103103
self=self)
104104

105-
def generate_signed_url(self, expiration,
106-
method='GET'): # pragma NO COVER
105+
def generate_signed_url(self, expiration, method='GET'):
107106
"""Generates a signed URL for this key.
108107
109108
If you have a key that you want to allow access to
@@ -181,11 +180,7 @@ def get_contents_to_file(self, fh):
181180
"""
182181

183182
for chunk in KeyDataIterator(self):
184-
try:
185-
fh.write(chunk)
186-
except IOError, e: # pragma NO COVER
187-
if e.errno == errno.ENOSPC:
188-
raise Exception('No space left on device.')
183+
fh.write(chunk)
189184

190185
def get_contents_to_filename(self, filename):
191186
"""Get the contents of this key to a file by name.

gcloud/storage/test_acl.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,21 @@ def test_all_authenticated(self):
376376
self.assertEqual(list(acl),
377377
[{'entity': 'allAuthenticatedUsers', 'role': ROLE}])
378378

379+
def test_get_entities_empty(self):
380+
acl = self._makeOne()
381+
self.assertEqual(acl.get_entities(), [])
382+
383+
def test_get_entities_nonempty(self):
384+
TYPE = 'type'
385+
ID = 'id'
386+
acl = self._makeOne()
387+
entity = acl.entity(TYPE, ID)
388+
self.assertEqual(acl.get_entities(), [entity])
389+
390+
def test_save_raises_NotImplementedError(self):
391+
acl = self._makeOne()
392+
self.assertRaises(NotImplementedError, acl.save)
393+
379394

380395
class Test_BucketACL(unittest2.TestCase):
381396

0 commit comments

Comments
 (0)