Browse Source

refactor retry mechanism

Arnau Sanchez 10 years ago
parent
commit
71deef0c0b
3 changed files with 25 additions and 29 deletions
  1. 2 2
      README.md
  2. 19 0
      youtube_upload/lib.py
  3. 4 27
      youtube_upload/upload_video.py

+ 2 - 2
README.md

@@ -69,9 +69,9 @@ $ youtube-upload ....
 Caveats
 =======
 
-* The package includes a default ```client_secrets.json``` file, but if you plan to make a heavy use of the script, please [create and use your own OAuth 2.0 file](https://developers.google.com/youtube/registering_an_application).
+* The package includes a default ```client_secrets.json``` file. If you plan to make a heavy use of the script, please [create and use your own OAuth 2.0 file](https://developers.google.com/youtube/registering_an_application).
 
-* If a video does not comply with Youtube size limitations you must split it (using ffmpeg/avconvm, for example). 
+* If a video does not comply with Youtube size limitations you must split it (using ffmpeg/avconv, for example). 
 
 More
 ====

+ 19 - 0
youtube_upload/lib.py

@@ -36,3 +36,22 @@ def string_to_dict(string):
     """Return dictionary from string "key1=value1, key2=value2"."""
     pairs = [s.strip() for s in (string or "").split(",")]
     return dict(pair.split("=") for pair in pairs)
+
+def retriable_exceptions(fun, retriable_exceptions, max_retries=None):
+    """Run function and retry on some exceptions (with exponential backoff)."""
+    retry = 0
+    while 1:
+        try:
+            return fun()
+        except tuple(retriable_exceptions) as exc:
+            retry += 1
+            if type(exc) not in retriable_exceptions:
+                raise exc
+            elif max_retries is not None and retry > max_retries:
+                lib.debug("Retry limit reached, time to give up")
+                raise exc
+            else:
+                seconds = random.uniform(0, 2**retry)
+                lib.debug("Retryable error {}/{}: {}. Waiting {} seconds".
+                    format(retry, max_retries or "-", type(exc).__name__, seconds))
+                time.sleep(seconds)

+ 4 - 27
youtube_upload/upload_video.py

@@ -14,29 +14,6 @@ RETRIABLE_EXCEPTIONS = [
     httplib.ResponseNotReady, httplib.BadStatusLine,
 ]
 
-def with_retriable_exceptions(retriable_exceptions, max_retries=None):
-    """Decorate a funcion with a a retry mechanism (exponential backoff)."""
-    def _decorator(f):
-        def _wrapper(*args, **kwargs):
-            retry = 0
-            while 1:
-                try:
-                    return f(*args, **kwargs)
-                except tuple(retriable_exceptions) as exc:
-                    retry += 1
-                    if type(exc) not in retriable_exceptions:
-                        raise exc
-                    elif max_retries is not None and retry > max_retries:
-                        lib.debug("Retry limit reached, time to give up")
-                        raise exc
-                    seconds = random.uniform(0, 2**retry)
-                    lib.debug("Retryable error {}/{}: {}. Waiting {} seconds".
-                        format(retry, max_retries or "inf", type(exc).__name__, seconds))
-                    time.sleep(seconds)
-        return _wrapper
-    return _decorator
-
-@with_retriable_exceptions(RETRIABLE_EXCEPTIONS, max_retries=10)
 def _upload_to_request(request, progress_callback):
     """Upload a video to a Youtube request. Return video ID."""
     while 1:
@@ -45,7 +22,7 @@ def _upload_to_request(request, progress_callback):
             if "id" in response:
                 return response['id']
             else:
-                raise KeyError("Response has no 'id' field")
+                raise KeyError("The response has no 'id' field")
         elif status and progress_callback:
             progress_callback(status.total_size, status.resumable_progress)
         
@@ -53,6 +30,6 @@ def upload(resource, path, body, chunksize=int(1e6), progress_callback=None):
     """Upload video to Youtube. Return video ID."""
     body_keys = ",".join(body.keys())
     media = apiclient.http.MediaFileUpload(path, chunksize=chunksize, resumable=True)
-    videos = resource.videos()
-    request = videos.insert(part=body_keys, body=body, media_body=media)
-    return _upload_to_request(request, progress_callback)
+    request = resource.videos().insert(part=body_keys, body=body, media_body=media)
+    upload_fun = lambda: _upload_to_request(request, progress_callback)
+    return lib.retriable_exceptions(upload_fun, RETRIABLE_EXCEPTIONS, max_retries=10)