1616)
1717
1818try :
19+ from google .api_core import exceptions as api_exceptions
20+ from google .api_core import retry
21+ from google .auth import exceptions as auth_exceptions
1922 from google .cloud .exceptions import NotFound
2023 from google .cloud .storage import Blob , Client
2124 from google .cloud .storage .blob import _quote
2225 from google .cloud .storage .retry import (
23- DEFAULT_RETRY , DEFAULT_RETRY_IF_GENERATION_SPECIFIED ,
26+ _RETRYABLE_TYPES , ConditionalRetryPolicy , is_generation_specified ,
27+ _ADDITIONAL_RETRYABLE_STATUS_CODES ,
2428 )
29+ import requests .exceptions
2530except ImportError :
2631 raise ImproperlyConfigured ("Could not load Google Cloud Storage bindings.\n "
2732 "See https://github.com/GoogleCloudPlatform/gcloud-python" )
3035CONTENT_ENCODING = 'content_encoding'
3136CONTENT_TYPE = 'content_type'
3237
38+ # TODO(m1): revert the commit that introduced this after a version of
39+ # `google-cloud-storage` with https://github.com/googleapis/python-storage/pull/727
40+ # gets released and ScanForm is updated to use it.
41+ CUSTOM_RETRYABLE_TYPES = _RETRYABLE_TYPES + (
42+ requests .exceptions .Timeout ,
43+ )
44+
45+
46+ def _should_retry (exc ):
47+ """Predicate for determining when to retry."""
48+ if isinstance (exc , CUSTOM_RETRYABLE_TYPES ):
49+ return True
50+ elif isinstance (exc , api_exceptions .GoogleAPICallError ):
51+ return exc .code in _ADDITIONAL_RETRYABLE_STATUS_CODES
52+ elif isinstance (exc , auth_exceptions .TransportError ):
53+ return _should_retry (exc .args [0 ])
54+ else :
55+ return False
56+
57+
58+ RETRY = retry .Retry (predicate = _should_retry )
59+
60+ RETRY_IF_GENERATION_SPECIFIED = ConditionalRetryPolicy (
61+ RETRY , is_generation_specified , ["query_params" ]
62+ )
63+
3364
3465class GoogleCloudFile (CompressedFileMixin , File ):
3566 def __init__ (self , name , mode , storage ):
3667 self .name = name
3768 self .mime_type = mimetypes .guess_type (name )[0 ]
3869 self ._mode = mode
3970 self ._storage = storage
40- self .blob = storage .bucket .get_blob (name , timeout = storage .timeout )
71+ self .blob = storage .bucket .get_blob (name , timeout = storage .timeout , retry = RETRY )
4172 if not self .blob and 'w' in mode :
4273 self .blob = Blob (
4374 self .name , storage .bucket ,
@@ -58,7 +89,8 @@ def _get_file(self):
5889 )
5990 if 'r' in self ._mode :
6091 self ._is_dirty = False
61- self .blob .download_to_file (self ._file , timeout = self ._storage .timeout )
92+ self .blob .download_to_file (self ._file , timeout = self ._storage .timeout ,
93+ retry = RETRY )
6294 self ._file .seek (0 )
6395 if self ._storage .gzip and self .blob .content_encoding == 'gzip' :
6496 self ._file = self ._decompress_file (mode = self ._mode , file = self ._file )
@@ -140,9 +172,9 @@ def get_default_settings(self):
140172 @property
141173 def retry_if_generation_specified_or_immutable (self ):
142174 if self .all_files_immutable :
143- return DEFAULT_RETRY
175+ return RETRY
144176
145- return DEFAULT_RETRY_IF_GENERATION_SPECIFIED
177+ return RETRY_IF_GENERATION_SPECIFIED
146178
147179 @property
148180 def client (self ):
@@ -242,13 +274,13 @@ def delete(self, name):
242274 def exists (self , name ):
243275 if not name : # root element aka the bucket
244276 try :
245- self .client .get_bucket (self .bucket , timeout = self .timeout )
277+ self .client .get_bucket (self .bucket , timeout = self .timeout , retry = RETRY )
246278 return True
247279 except NotFound :
248280 return False
249281
250282 name = self ._normalize_name (clean_name (name ))
251- return bool (self .bucket .get_blob (name , timeout = self .timeout ))
283+ return bool (self .bucket .get_blob (name , timeout = self .timeout , retry = RETRY ))
252284
253285 def listdir (self , name ):
254286 name = self ._normalize_name (clean_name (name ))
@@ -258,7 +290,7 @@ def listdir(self, name):
258290 name += '/'
259291
260292 iterator = self .bucket .list_blobs (prefix = name , delimiter = '/' ,
261- timeout = self .timeout )
293+ timeout = self .timeout , retry = RETRY )
262294 blobs = list (iterator )
263295 prefixes = iterator .prefixes
264296
@@ -276,7 +308,7 @@ def listdir(self, name):
276308
277309 def _get_blob (self , name ):
278310 # Wrap google.cloud.storage's blob to raise if the file doesn't exist
279- blob = self .bucket .get_blob (name , timeout = self .timeout )
311+ blob = self .bucket .get_blob (name , timeout = self .timeout , retry = RETRY )
280312
281313 if blob is None :
282314 raise NotFound ('File does not exist: {}' .format (name ))
0 commit comments