11"""Create / interact with gcloud storage connections."""
22
33import base64
4+ import calendar
45import datetime
56import json
6- import time
77import urllib
88
99from Crypto .Hash import SHA256
1818from 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+
2129class 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
0 commit comments