Skip to content
This repository has been archived by the owner on Feb 24, 2020. It is now read-only.

Commit

Permalink
add support for event log API (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
anneFly authored Dec 5, 2018
1 parent d2b6430 commit 1be6b3d
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 1 deletion.
11 changes: 10 additions & 1 deletion closeio/closeio.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

from closeio.exceptions import CloseIOError
from closeio.utils import (
DummyCookieJar, convert, handle_errors, paginate, parse_response
DummyCookieJar, convert, handle_errors, paginate, paginate_via_cursor,
parse_response
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -507,3 +508,11 @@ def create_lead_export(self, query='*', format='json', fields=(),
@handle_errors
def get_export(self, id):
return self._api.export(id).get()

@parse_response
@handle_errors
def get_event_logs(self, **kwargs):
return paginate_via_cursor(
self._api.event.get,
**kwargs
)
87 changes: 87 additions & 0 deletions closeio/contrib/testing_stub.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
import itertools
import threading
import uuid
from contextlib import contextmanager
from datetime import datetime, timezone

from dateutil.parser import parse

from closeio.utils import CloseIOError, Item, parse_response

threadlocal = threading.local()


class CloseIOStub(object):
record_event_logs = False

def __init__(self, user_emails=None):
users = self._data('users', [])
if user_emails:
Expand All @@ -36,6 +41,54 @@ def _data(self, attr, default=None):

return storage[attr]

def _create_task_log(self, task):
return {
'id': 'ev_{}'.format(uuid.uuid4().hex),
'object_type': 'task.lead',
'object_id': task['id'],
'lead_id': task['lead_id'],
'user_id': task['assigned_to'],
'date_updated': datetime.utcnow().isoformat(),
}

def _create_task_log_created(self, task):
logs = self._data('event_logs', [])
log = self._create_task_log(task)
log.update({
'action': 'created',
'data': task,
'previous_data': {},
})
logs.insert(0, log)

def _create_task_log_deleted(self, task):
logs = self._data('event_logs', [])
log = self._create_task_log(task)
log.update({
'action': 'deleted',
'data': {},
'previous_data': task,
})
logs.insert(0, log)

@contextmanager
def record_logs(self):
"""Create event log data for certain operations.
This context manager can be used to automatically create
event logs when calling certain methods on the stub.
Currently only event log data for task creation and deletion
is supported.
Example usage::
with stub.record_logs():
task = stub.create_task(lead_id='x', assigned_to='y', text='z')
stub.delete_task(task_id=task['id'])
"""
self.record_event_logs = True
yield
self.record_event_logs = False

@parse_response
def find_opportunity_status(self, label):
opportunity_status = self._data('opportunity_status', [])
Expand Down Expand Up @@ -233,6 +286,8 @@ def delete_task(self, task_id):
for t in tasks_list:
if t['id'] == task_id:
tasks[lead_id].remove(t)
if self.record_event_logs:
self._create_task_log_deleted(t)
return True
raise CloseIOError()

Expand Down Expand Up @@ -352,6 +407,9 @@ def create_task(self, lead_id, assigned_to, text, due_date=None,

tasks[lead_id].append(task)

if self.record_event_logs:
self._create_task_log_created(task)

return task

def _get_task(self, task_id):
Expand Down Expand Up @@ -552,6 +610,35 @@ def delete_opportunity(self, opportunity_id):

del opportunities[opportunity_id]

@parse_response
def get_event_logs(self, **kwargs):
logs = self._data('event_logs', [])

for param in ['action', 'object_type', 'object_id', 'lead_id', 'user_id']:
if param in kwargs:
logs = [log for log in logs if log[param] == kwargs[param]]

if 'date_updated__gt' in kwargs:
date_updated = parse(kwargs['date_updated__gt'])
logs = [log for log in logs if parse(log['date_updated']) > date_updated]

if 'date_updated__gte' in kwargs:
date_updated = parse(kwargs['date_updated__gte'])
logs = [log for log in logs if parse(log['date_updated']) >= date_updated]

if 'date_updated__lt' in kwargs:
date_updated = parse(kwargs['date_updated__lt'])
logs = [log for log in logs if parse(log['date_updated']) < date_updated]

if 'date_updated__lte' in kwargs:
date_updated = parse(kwargs['date_updated__lte'])
logs = [log for log in logs if parse(log['date_updated']) <= date_updated]

if '_limit' in kwargs:
logs = logs[:kwargs['_limit']]

return logs

@parse_response
def get_export(self, id):
exports = self._data('exports', [])
Expand Down
26 changes: 26 additions & 0 deletions closeio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,32 @@ def paginate(func, *args, **kwargs):
skip += limit


def paginate_via_cursor(func, *args, **kwargs):
cursor = ''
limit = 50

while True:
kwargs['_cursor'] = cursor
kwargs['_limit'] = limit

with convert_errors():
response = func(*args, **kwargs)

if not isinstance(response, dict):
raise CloseIOError(
'close.io response is not a dict, '
'so most likely could not be parsed. \n'
'body "{}"'.format(response)
)

for item in response['data']:
yield item

cursor = response['cursor_next']
if not cursor:
break


class DummyCookieJar(object):
def __init__(self, policy=None):
pass
Expand Down
20 changes: 20 additions & 0 deletions tests/test_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,26 @@ def test_delete_activity_note(self, client, lead):

assert list(client.get_activity_note(lead['id'])) == []

def test_get_event_logs(self, client, task):
task_id = task['id']

logs = client.get_event_logs(object_type='task.lead', action='created')
logs = list(logs)
assert len(logs) >= 1
assert logs[0]['object_id'] == task_id
assert all(log['action'] == 'created' for log in logs)

iso_now = datetime.datetime.utcnow().isoformat()
client.delete_task(task['id'])
time.sleep(1) # give closeio some time to create the event log on their system

logs = client.get_event_logs(date_updated__gte=iso_now, object_type='task.lead',
action='deleted')
logs = list(logs)
assert len(logs) == 1
assert logs[0]['object_id'] == task_id
assert logs[0]['action'] == 'deleted'

def test_create_lead_export(self, client, random_string):
export = client.create_lead_export(random_string)
assert export
Expand Down
49 changes: 49 additions & 0 deletions tests/test_testing_stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import datetime

from closeio.contrib.testing_stub import CloseIOStub


class TestCloseIOStub:
def test_record_and_retrieve_event_logs(self):
client = CloseIOStub()

task1 = client.create_task(lead_id='x1', assigned_to='y1', text='z1')
client.delete_task(task_id=task1['id'])

assert list(client.get_event_logs()) == []

with client.record_logs():
task2 = client.create_task(lead_id='x2', assigned_to='y2', text='z2')
client.delete_task(task_id=task2['id'])
pause = datetime.datetime.utcnow().isoformat()
task3 = client.create_task(lead_id='x3', assigned_to='y3', text='z3')
client.delete_task(task_id=task3['id'])

logs = list(client.get_event_logs())
assert len(logs) == 4
assert logs[0]['action'] == 'deleted'
assert logs[0]['object_id'] == task3['id']
assert logs[1]['action'] == 'created'
assert logs[1]['object_id'] == task3['id']
assert logs[2]['action'] == 'deleted'
assert logs[2]['object_id'] == task2['id']
assert logs[3]['action'] == 'created'
assert logs[3]['object_id'] == task2['id']

logs = list(client.get_event_logs(action='created'))
assert len(logs) == 2

logs = list(client.get_event_logs(action='deleted'))
assert len(logs) == 2

logs = list(client.get_event_logs(object_type='task.lead'))
assert len(logs) == 4
assert logs[0]['object_id'] == task3['id']
assert logs[1]['object_id'] == task3['id']
assert logs[2]['object_id'] == task2['id']
assert logs[3]['object_id'] == task2['id']

logs = list(client.get_event_logs(object_type='task.lead', date_updated__gt=pause))
assert len(logs) == 2
assert logs[0]['object_id'] == task3['id']
assert logs[1]['object_id'] == task3['id']
16 changes: 16 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from closeio.utils import paginate_via_cursor


def test_paginate_via_cursor():
responses = {
'': {'cursor_next': '11111', 'data': [{'id': 1}, {'id': 2}]},
'11111': {'cursor_next': '22222', 'data': [{'id': 3}, {'id': 4}]},
'22222': {'cursor_next': '', 'data': [{'id': 5}, {'id': 6}]}
}

def test_function(**kwargs):
cursor = kwargs.get('_cursor')
return responses[cursor]

response = paginate_via_cursor(test_function)
assert list(response) == [{'id': 1}, {'id': 2}, {'id': 3}, {'id': 4}, {'id': 5}, {'id': 6}]

0 comments on commit 1be6b3d

Please sign in to comment.