Skip to content

Commit ed311d0

Browse files
committed
Retry logic on 404s in some text-analytics API calls
1 parent 08d6e71 commit ed311d0

File tree

9 files changed

+84
-79
lines changed

9 files changed

+84
-79
lines changed

ravenpackapi/core.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33

44
from ravenpackapi import Dataset
5-
from ravenpackapi.exceptions import APIException
5+
from ravenpackapi.exceptions import get_exception
66
from ravenpackapi.models.dataset_list import DatasetList
77
from ravenpackapi.models.job import Job
88
from ravenpackapi.models.mapping import RPMappingResults
@@ -15,7 +15,7 @@
1515
from ravenpackapi.utils.dynamic_sessions import DynamicSession
1616

1717
_VALID_METHODS = ('get', 'post', 'put', 'delete', 'patch')
18-
VERSION = '1.0.43'
18+
VERSION = '1.0.44'
1919

2020
logger = logging.getLogger("ravenpack.core")
2121

@@ -88,11 +88,7 @@ def request(self, endpoint, data=None, json=None,
8888
logger.info("API query to %s" % to_curl(response.request))
8989
if except_on_fail and response.status_code != 200:
9090
logger.error("Error calling the API, we tried: %s" % to_curl(response.request))
91-
raise APIException(
92-
'Got an error {status}: body was \'{error_message}\''.format(
93-
status=response.status_code,
94-
error_message=response.text
95-
), response=response)
91+
raise get_exception(response)
9692
return response
9793

9894
def list_datasets(self, scope=None, tags=None):

ravenpackapi/exceptions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,31 @@
33
import requests
44

55

6+
def get_exception(response):
7+
""" Return an appropriate APIException given an API response """
8+
if response.status_code == 404:
9+
Exception_class = APIException404
10+
else:
11+
Exception_class = APIException
12+
13+
return Exception_class(
14+
'Got an error {status}: body was \'{error_message}\''.format(
15+
status=response.status_code,
16+
error_message=response.text
17+
), response=response)
18+
19+
620
class APIException(Exception):
721
def __init__(self, *args, **kwargs):
822
response = kwargs.pop('response', None)
923
super(APIException, self).__init__(*args)
1024
self.response = response
1125

1226

27+
class APIException404(APIException):
28+
pass
29+
30+
1331
class MissingAPIException(Exception):
1432
def __str__(self):
1533
return "Define the api parameter with your own APIKEY " \

ravenpackapi/tests/test_job_cancellation.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

ravenpackapi/upload/models.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from time import sleep
22

33
from ravenpackapi.exceptions import api_method
4+
from ravenpackapi.upload.upload_utils import retry_404
45

56
FILE_FIELDS = (
67
'file_id', 'file_name', 'folder_id',
@@ -64,33 +65,36 @@ def get_metadata(self, force_refresh=False):
6465

6566
@api_method
6667
def save_original(self, filename):
67-
response = self.api.request('%s/files/%s' % (self.api._UPLOAD_BASE_URL, self.file_id),
68-
stream=True)
68+
response = retry_404(self.api.request,
69+
'%s/files/%s' % (self.api._UPLOAD_BASE_URL, self.file_id),
70+
stream=True)
6971
with open(filename, 'wb') as f:
7072
for chunk in response.iter_content(chunk_size=self.api._CHUNK_SIZE):
7173
f.write(chunk)
7274

7375
@api_method
7476
def save_analytics(self, filename, output_format='application/json'):
7577
self.wait_for_completion()
76-
response = self.api.request('%s/files/%s/analytics' % (self.api._UPLOAD_BASE_URL, self.file_id,),
77-
headers=dict(
78-
Accept=output_format,
79-
**self.api.headers
80-
),
81-
stream=True)
78+
response = retry_404(self.api.request,
79+
'%s/files/%s/analytics' % (self.api._UPLOAD_BASE_URL, self.file_id,),
80+
headers=dict(
81+
Accept=output_format,
82+
**self.api.headers
83+
),
84+
stream=True)
8285
with open(filename, 'wb') as f:
8386
for chunk in response.iter_content(chunk_size=self.api._CHUNK_SIZE):
8487
f.write(chunk)
8588

8689
@api_method
8790
def get_analytics(self, output_format='application/json'):
8891
self.wait_for_completion()
89-
response = self.api.request('%s/files/%s/analytics' % (self.api._UPLOAD_BASE_URL, self.file_id,),
90-
headers=dict(
91-
Accept=output_format,
92-
**self.api.headers
93-
))
92+
response = retry_404(self.api.request,
93+
'%s/files/%s/analytics' % (self.api._UPLOAD_BASE_URL, self.file_id,),
94+
headers=dict(
95+
Accept=output_format,
96+
**self.api.headers
97+
))
9498
if output_format == 'application/json':
9599
return response.json()
96100
else:
@@ -99,22 +103,27 @@ def get_analytics(self, output_format='application/json'):
99103
@api_method
100104
def save_annotated(self, filename):
101105
self.wait_for_completion()
102-
response = self.api.request('%s/files/%s/annotated' % (self.api._UPLOAD_BASE_URL, self.file_id),
103-
stream=True)
106+
response = retry_404(self.api.request,
107+
'%s/files/%s/annotated' % (self.api._UPLOAD_BASE_URL, self.file_id),
108+
stream=True)
104109
with open(filename, 'wb') as f:
105110
for chunk in response.iter_content(chunk_size=self.api._CHUNK_SIZE):
106111
f.write(chunk)
107112

108113
@api_method
109114
def get_annotated(self):
110115
self.wait_for_completion()
111-
response = self.api.request('%s/files/%s/annotated' % (self.api._UPLOAD_BASE_URL, self.file_id))
116+
response = retry_404(self.api.request,
117+
'%s/files/%s/annotated' % (self.api._UPLOAD_BASE_URL, self.file_id)
118+
)
112119
return response.text
113120

114121
@api_method
115122
def delete(self):
116-
response = self.api.request('%s/files/%s' % (self.api._UPLOAD_BASE_URL, self.file_id),
117-
method='delete')
123+
response = retry_404(self.api.request,
124+
'%s/files/%s' % (self.api._UPLOAD_BASE_URL, self.file_id),
125+
method='delete'
126+
)
118127
return response
119128

120129
@api_method
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import os
2+
3+
from ravenpackapi import RPApi
4+
5+
6+
class TestRecentAnalyticsRetried:
7+
api = RPApi()
8+
9+
def test_upload_delete_retry(self):
10+
""" When we delete immediately after creation we get a 404
11+
The API should silently retry for some time
12+
"""
13+
api = self.api
14+
filename = "upload_sample.txt"
15+
f = api.upload.file(os.path.join(os.path.dirname(__file__), filename))
16+
f.delete()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Apple Inc.
2+
IBM
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import logging
2+
3+
from retry.api import retry_call
4+
5+
from ravenpackapi.exceptions import APIException404
6+
7+
logger = logging.getLogger('ravenpack.upload.retry')
8+
9+
10+
def retry_404(func, *args, **kwargs):
11+
response = retry_call(func, fargs=args, fkwargs=kwargs,
12+
exceptions=(APIException404,),
13+
delay=3, backoff=2, jitter=(1, 5), tries=10,
14+
logger=logger)
15+
return response

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ requests[security]
22
future
33
six
44
python-dateutil
5+
retry

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup, find_packages
22

3-
VERSION = '1.0.43'
3+
VERSION = '1.0.44'
44

55
with open('README.rst') as readme_file:
66
readme = readme_file.read()
@@ -35,5 +35,5 @@
3535
],
3636

3737
keywords='python analytics api rest news data',
38-
install_requires=['requests[security]', 'future', 'python-dateutil', 'six'],
38+
install_requires=['requests[security]', 'future', 'python-dateutil', 'six', 'retry'],
3939
)

0 commit comments

Comments
 (0)