55from StringIO import StringIO
66
77from gcloud .storage .acl import ObjectACL
8+ from gcloud .storage .exceptions import StorageError
89from gcloud .storage .iterator import Iterator
9- from gcloud .storage .iterator import KeyDataIterator
1010
1111
1212class Key (object ):
@@ -170,7 +170,7 @@ def get_contents_to_file(self, file_obj):
170170
171171 :raises: :class:`gcloud.storage.exceptions.NotFoundError`
172172 """
173- for chunk in KeyDataIterator (self ):
173+ for chunk in _KeyDataIterator (self ):
174174 file_obj .write (chunk )
175175
176176 def get_contents_to_filename (self , filename ):
@@ -433,7 +433,7 @@ def make_public(self):
433433 return self
434434
435435
436- class KeyIterator (Iterator ):
436+ class _KeyIterator (Iterator ):
437437 """An iterator listing keys.
438438
439439 You shouldn't have to use this directly, but instead should use the
@@ -444,7 +444,7 @@ class KeyIterator(Iterator):
444444 """
445445 def __init__ (self , bucket ):
446446 self .bucket = bucket
447- super (KeyIterator , self ).__init__ (
447+ super (_KeyIterator , self ).__init__ (
448448 connection = bucket .connection , path = bucket .path + '/o' )
449449
450450 def get_items_from_response (self , response ):
@@ -455,3 +455,97 @@ def get_items_from_response(self, response):
455455 """
456456 for item in response .get ('items' , []):
457457 yield Key .from_dict (item , bucket = self .bucket )
458+
459+
460+ class _KeyDataIterator (object ):
461+ """An iterator listing data stored in a key.
462+
463+ You shouldn't have to use this directly, but instead should use the
464+ helper methods on :class:`gcloud.storage.key.Key` objects.
465+
466+ :type key: :class:`gcloud.storage.key.Key`
467+ :param key: The key from which to list data..
468+ """
469+
470+ def __init__ (self , key ):
471+ self .key = key
472+ # NOTE: These variables will be initialized by reset().
473+ self ._bytes_written = None
474+ self ._total_bytes = None
475+ self .reset ()
476+
477+ def __iter__ (self ):
478+ while self .has_more_data ():
479+ yield self .get_next_chunk ()
480+
481+ def reset (self ):
482+ """Resets the iterator to the beginning."""
483+ self ._bytes_written = 0
484+ self ._total_bytes = None
485+
486+ def has_more_data (self ):
487+ """Determines whether or not this iterator has more data to read.
488+
489+ :rtype: bool
490+ :returns: Whether the iterator has more data or not.
491+ """
492+ if self ._bytes_written == 0 :
493+ return True
494+ elif not self ._total_bytes :
495+ # self._total_bytes **should** be set by this point.
496+ # If it isn't, something is wrong.
497+ raise ValueError ('Size of object is unknown.' )
498+ else :
499+ return self ._bytes_written < self ._total_bytes
500+
501+ def get_headers (self ):
502+ """Gets range header(s) for next chunk of data.
503+
504+ :rtype: dict
505+ :returns: A dictionary of query parameters.
506+ """
507+ start = self ._bytes_written
508+ end = self ._bytes_written + self .key .CHUNK_SIZE - 1
509+
510+ if self ._total_bytes and end > self ._total_bytes :
511+ end = ''
512+
513+ return {'Range' : 'bytes=%s-%s' % (start , end )}
514+
515+ def get_url (self ):
516+ """Gets URL to read next chunk of data.
517+
518+ :rtype: string
519+ :returns: A URL.
520+ """
521+ return self .key .connection .build_api_url (
522+ path = self .key .path , query_params = {'alt' : 'media' })
523+
524+ def get_next_chunk (self ):
525+ """Gets the next chunk of data.
526+
527+ Uses CHUNK_SIZE to determine how much data to get.
528+
529+ :rtype: string
530+ :returns: The chunk of data read from the key.
531+ :raises: :class:`RuntimeError` if no more data or
532+ :class:`gcloud.storage.exceptions.StorageError` in the
533+ case of an unexpected response status code.
534+ """
535+ if not self .has_more_data ():
536+ raise RuntimeError ('No more data in this iterator. Try resetting.' )
537+
538+ response , content = self .key .connection .make_request (
539+ method = 'GET' , url = self .get_url (), headers = self .get_headers ())
540+
541+ if response .status in (200 , 206 ):
542+ self ._bytes_written += len (content )
543+
544+ if 'content-range' in response :
545+ content_range = response ['content-range' ]
546+ self ._total_bytes = int (content_range .rsplit ('/' , 1 )[1 ])
547+
548+ return content
549+
550+ # Expected a 200 or a 206. Got something else, which is unknown.
551+ raise StorageError (response )
0 commit comments