Skip to content

Commit

Permalink
Merge pull request #248 from tseaver/239-remove_pragma_no_cover
Browse files Browse the repository at this point in the history
Fix #239: remove / document 'pragma: NO COVER'
  • Loading branch information
tseaver committed Oct 17, 2014
2 parents b194e0e + 2338955 commit 906700b
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
omit =
*/demo/*
*/demo.py
exclude_lines =
# Re-enable the standard pragma
pragma: NO COVER
# Ignore debug-only repr
def __repr__
2 changes: 1 addition & 1 deletion gcloud/datastore/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def delete(self):
dataset_id=self.dataset().id(), key_pb=self.key().to_protobuf())
# pylint: enable=maybe-no-member

def __repr__(self): # pragma NO COVER
def __repr__(self):
# An entity should have a key all the time (even if it's partial).
if self.key():
# pylint: disable=maybe-no-member
Expand Down
4 changes: 2 additions & 2 deletions gcloud/datastore/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def id_or_name(self):
"""
return self.id() or self.name()

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

def __repr__(self): # pragma NO COVER
def __repr__(self):
return '<Key%s>' % self.path()
4 changes: 2 additions & 2 deletions gcloud/storage/acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def __str__(self):
else:
return '{self.type}-{self.identifier}'.format(self=self)

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

Expand Down Expand Up @@ -353,7 +353,7 @@ def get_entities(self):

return self.entities.values()

def save(self): # pragma NO COVER
def save(self):
"""A method to be overridden by subclasses.
:raises: NotImplementedError
Expand Down
4 changes: 2 additions & 2 deletions gcloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def from_dict(cls, bucket_dict, connection=None):
return cls(connection=connection, name=bucket_dict['name'],
metadata=bucket_dict)

def __repr__(self): # pragma NO COVER
def __repr__(self):
return '<Bucket: %s>' % self.name

def __iter__(self):
Expand Down Expand Up @@ -126,7 +126,7 @@ def new_key(self, key):
# Support Python 2 and 3.
try:
string_type = basestring
except NameError: # pragma NO COVER PY3k
except NameError: # pragma: NO COVER PY3k
string_type = str

if isinstance(key, string_type):
Expand Down
73 changes: 45 additions & 28 deletions gcloud/storage/connection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Create / interact with gcloud storage connections."""

import base64
import calendar
import datetime
import json
import time
import urllib

from Crypto.Hash import SHA256
Expand All @@ -18,6 +18,14 @@
from gcloud.storage.iterator import BucketIterator


def _utcnow(): # pragma: NO COVER testing replaces
"""Returns current time as UTC datetime.
NOTE: on the module namespace so tests can replace it.
"""
return datetime.datetime.utcnow()


class Connection(connection.Connection):
"""A connection to Google Cloud Storage via the JSON REST API.
Expand Down Expand Up @@ -419,7 +427,7 @@ def new_bucket(self, bucket):
# Support Python 2 and 3.
try:
string_type = basestring
except NameError: # pragma NO COVER PY3k
except NameError: # pragma: NO COVER PY3k
string_type = str

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

def generate_signed_url(self, resource, expiration,
method='GET', content_md5=None,
content_type=None): # pragma NO COVER
content_type=None):
"""Generate signed URL to provide query-string auth'n to a resource.
:type resource: string
Expand All @@ -455,31 +463,7 @@ def generate_signed_url(self, resource, expiration,
until expiration.
"""

# expiration can be an absolute timestamp (int, long),
# an absolute time (datetime.datetime),
# or a relative time (datetime.timedelta).
# We should convert all of these into an absolute timestamp.

# If it's a timedelta, add it to `now` in UTC.
if isinstance(expiration, datetime.timedelta):
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
expiration = now + expiration

# If it's a datetime, convert to a timestamp.
if isinstance(expiration, datetime.datetime):
# Make sure the timezone on the value is UTC
# (either by converting or replacing the value).
if expiration.tzinfo:
expiration = expiration.astimezone(pytz.utc)
else:
expiration = expiration.replace(tzinfo=pytz.utc)

# Turn the datetime into a timestamp (seconds, not microseconds).
expiration = int(time.mktime(expiration.timetuple()))

if not isinstance(expiration, (int, long)):
raise ValueError('Expected an integer timestamp, datetime, or '
'timedelta. Got %s' % type(expiration))
expiration = _get_expiration_seconds(expiration)

# Generate the string to sign.
signature_string = '\n'.join([
Expand Down Expand Up @@ -514,3 +498,36 @@ def generate_signed_url(self, resource, expiration,
return '{endpoint}{resource}?{querystring}'.format(
endpoint=self.API_ACCESS_ENDPOINT, resource=resource,
querystring=urllib.urlencode(query_params))


def _get_expiration_seconds(expiration):
"""Convert 'expiration' to a number of seconds in the future.
:type expiration: int, long, datetime.datetime, datetime.timedelta
:param expiration: When the signed URL should expire.
:rtype: int
:returns: a timestamp as an absolute number of seconds.
"""

# If it's a timedelta, add it to `now` in UTC.
if isinstance(expiration, datetime.timedelta):
now = _utcnow().replace(tzinfo=pytz.utc)
expiration = now + expiration

# If it's a datetime, convert to a timestamp.
if isinstance(expiration, datetime.datetime):
# Make sure the timezone on the value is UTC
# (either by converting or replacing the value).
if expiration.tzinfo:
expiration = expiration.astimezone(pytz.utc)
else:
expiration = expiration.replace(tzinfo=pytz.utc)

# Turn the datetime into a timestamp (seconds, not microseconds).
expiration = int(calendar.timegm(expiration.timetuple()))

if not isinstance(expiration, (int, long)):
raise TypeError('Expected an integer timestamp, datetime, or '
'timedelta. Got %s' % type(expiration))
return expiration
2 changes: 1 addition & 1 deletion gcloud/storage/iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def reset(self):
self.page_number = 0
self.next_page_token = None

def get_items_from_response(self, response): # pragma NO COVER
def get_items_from_response(self, response):
"""Factory method called while iterating. This should be overriden.
This method should be overridden by a subclass.
Expand Down
11 changes: 3 additions & 8 deletions gcloud/storage/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def from_dict(cls, key_dict, bucket=None):

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

def __repr__(self): # pragma NO COVER
def __repr__(self):
if self.bucket:
bucket_name = self.bucket.name
else:
Expand Down Expand Up @@ -102,8 +102,7 @@ def public_url(self):
storage_base_url='http://commondatastorage.googleapis.com',
self=self)

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

for chunk in KeyDataIterator(self):
try:
fh.write(chunk)
except IOError, e: # pragma NO COVER
if e.errno == errno.ENOSPC:
raise Exception('No space left on device.')
fh.write(chunk)

def get_contents_to_filename(self, filename):
"""Get the contents of this key to a file by name.
Expand Down
15 changes: 15 additions & 0 deletions gcloud/storage/test_acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,21 @@ def test_all_authenticated(self):
self.assertEqual(list(acl),
[{'entity': 'allAuthenticatedUsers', 'role': ROLE}])

def test_get_entities_empty(self):
acl = self._makeOne()
self.assertEqual(acl.get_entities(), [])

def test_get_entities_nonempty(self):
TYPE = 'type'
ID = 'id'
acl = self._makeOne()
entity = acl.entity(TYPE, ID)
self.assertEqual(acl.get_entities(), [entity])

def test_save_raises_NotImplementedError(self):
acl = self._makeOne()
self.assertRaises(NotImplementedError, acl.save)


class Test_BucketACL(unittest2.TestCase):

Expand Down
Loading

0 comments on commit 906700b

Please sign in to comment.