Skip to content

Commit

Permalink
Merge pull request #15 from industrydive/TECH-3763-add-update-job-sup…
Browse files Browse the repository at this point in the history
…port

Sailthru update_job() function that accepts file name
  • Loading branch information
elidickinson authored Mar 2, 2018
2 parents 854aee4 + e9c1f7e commit 6ad201b
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pythonqa:
docker run --rm -v ${PWD}:/code dbarbar/pythonqa:latest
test:
docker run --rm -v ${PWD}:/code -w /code python:2.7 python setup.py test
docker run -e SAILTHRU_API_KEY -e SAILTHRU_API_SECRET --rm -v ${PWD}:/code -w /code python:2.7 python setup.py test
36 changes: 36 additions & 0 deletions dive_sailthru_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,42 @@ def export_list(self, list_name, fields=None, sailthru_vars=None, block_until_co
raise SailthruApiError("Job '%s' ended with unexpected status '%s'", job_id, job_result_json['status'])
return job_result_json

def update_job(self, update_file_name=None, update_file_stream=None, block_until_complete=True):
"""
Perform an 'update' job request, which bulk updates changes to a list of users typically
in a file in JSON-lines format. See https://getstarted.sailthru.com/developers/api/job/#update
Should pass in a file name (which will be opened for you) or a file-like object but not both!
@param update_file_name: file name of update file to send; should be None if update_file_stream is set
@param update_file_stream: an already opened stream to a file-link object of stuff to update
@param block_until_complete: whether to simply request the job and return or to block until it's done
"""
job_params = {
'job': 'update',
}
assert not (update_file_name and update_file_stream)
if update_file_name:
job_params['file'] = update_file_name
job_result_json = self.api_post('job', job_params).json
else: # update_file_stream
job_result_json = self.api_post_with_binary_stream('job', job_params, update_file_stream).json
job_id = job_result_json['job_id']
if block_until_complete:
job_result_json = self._block_until_job_complete(job_id)
if job_result_json['status'] not in ('pending', 'completed'):
raise SailthruApiError("Job '%s' ended with unexpected status '%s'", job_id, job_result_json['status'])
return job_result_json

def api_post_with_binary_stream(self, action, data, binary_stream):
"""
A wrapper around _http_request that lets you pass in opened streams and doesn't assume
it needs to open() them itself
"""
request_type = 'POST'
file_data = {'file': binary_stream}
response = self._http_request(action, self._prepare_json_payload(data), request_type, file_data)
self.raise_exception_if_error(response)
return response

def _block_until_job_complete(self, job_id, seconds_between_checks=1, max_wait_seconds=600):
""" returns result of the job; raises exception if job not complete in max_wait_seconds """
max_iterations = int(max_wait_seconds / seconds_between_checks) + 1
Expand Down
78 changes: 78 additions & 0 deletions dive_sailthru_client/tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from unittest import TestCase
from nose.plugins.attrib import attr
from dive_sailthru_client.client import DiveSailthruClient
import os
import datetime
import tempfile
import StringIO


@attr('external')
class TestDiveSailthruClientExternalIntegration(TestCase):
def setUp(self):
sailthru_key = os.getenv("SAILTHRU_API_KEY")
sailthru_secret = os.getenv("SAILTHRU_API_SECRET")
assert sailthru_key, "SAILTHRU_API_KEY env var must be defined"
assert sailthru_secret, "SAILTHRU_API_SECRET env var must be defined"
assert sailthru_key.lower().startswith("bc8"), "This test requires the DEV account Sailthru API key"
self.sailthru_client = DiveSailthruClient(sailthru_key, sailthru_secret)
self.test_email = 'eli+sailthru-client-integration-test@industrydive.com'
self.test_var_key = "sailthruclienttestvalue"

def _get_user_var(self, userid, key):
response = self.sailthru_client.get_user(userid).response
self.assertTrue(response.ok)
json = response.json()
if not json.get("vars"):
return None
return json["vars"].get(key)

def _set_user_var(self, userid, key, value):
response = self.sailthru_client.save_user(userid, options={"vars": {key: value}}).response
self.assertTrue(response.ok)

def test_get_set_var(self):
""" Make sure the _get_user_var and _set_user_var functions work with the API as expected """
new_value = str(datetime.datetime.now())
value = self._get_user_var(self.test_email, self.test_var_key)
self.assertNotEqual(value, new_value)
self._set_user_var(self.test_email, self.test_var_key, new_value)
value = self._get_user_var(self.test_email, self.test_var_key)
self.assertEqual(value, new_value)

def test_update_job_with_filename(self):
""" Test that the update_job() function actually updates from a filename """
# first set a known value to the variable using set_var
start_value = "start value %s" % datetime.datetime.now()
self._set_user_var(self.test_email, self.test_var_key, start_value)
# create temp file and stick our update string in it, then call update_job with the
# temp file's name. we set delete=False so that it isn't auto deleted when f.close()
# is called.
f = tempfile.NamedTemporaryFile(delete=False)
try:
updated_value = "updated value %s" % datetime.datetime.now()
update_line = '{"id":"%s", "key": "email", "vars":{"%s":"%s"}}\n' % \
(self.test_email, self.test_var_key, updated_value)
f.write(update_line)
f.close()
self.sailthru_client.update_job(update_file_name=f.name)
finally:
# since we set delete=False we need to clean up after ourselves manually
os.unlink(f.name)
# now check if it really updated
test_updated_var = self._get_user_var(self.test_email, self.test_var_key)
self.assertEqual(test_updated_var, updated_value)

def test_update_job_with_stream(self):
""" Test that the update_job() function actually updates from a opened file-like object """
# first set a known value to the variable using set_var
start_value = "start value %s" % datetime.datetime.now()
self._set_user_var(self.test_email, self.test_var_key, start_value)
# now let's set up the update "file" and turn it into a stream we can pass to API
updated_value = "updated value %s" % datetime.datetime.now()
update_line = '{"id":"%s", "vars":{"%s":"%s"}}\n' % (self.test_email, self.test_var_key, updated_value)
stream = StringIO.StringIO(update_line)
self.sailthru_client.update_job(update_file_stream=stream)
# now check if it really updated
test_updated_var = self._get_user_var(self.test_email, self.test_var_key)
self.assertEqual(test_updated_var, updated_value)
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="dive_sailthru_client",
version="0.0.13-dev",
version="0.0.14",
description="Industry Dive abstraction of the Sailthru API client",
author='Industry Dive',
author_email='tech.team@industrydive.com',
Expand All @@ -12,6 +12,7 @@
packages=['dive_sailthru_client'],
install_requires=[
'sailthru-client==2.3.3',
# Note that sailthru-client installs requests and simplejson
],
test_suite='nose.collector',
tests_require=['nose', 'mock']
Expand Down

0 comments on commit 6ad201b

Please sign in to comment.