diff --git a/emma/model/__init__.py b/emma/model/__init__.py index 2410ebc..ff4cd90 100644 --- a/emma/model/__init__.py +++ b/emma/model/__init__.py @@ -5,6 +5,7 @@ SERIALIZED_DATETIME_FORMAT = "@D:%Y-%m-%dT%H:%M:%S" +SERIALIZED_DATETIME_ALT_FORMAT = "%Y-%m-%d %H:%M:%S.%f" def str_fields_to_datetime(fields, raw): @@ -13,6 +14,12 @@ def str_fields_to_datetime(fields, raw): for x in raw.items() if x[0] in fields and x[1] is not None) +def str_fields_to_datetime_alt(fields, raw): + """Parses Emma date fields to :class:`datetime` objects""" + return dict((x[0], datetime.strptime(x[1], SERIALIZED_DATETIME_ALT_FORMAT)) + for x in raw.items() if x[0] in fields and x[1] is not None) + + class BaseApiModel(collections.MutableMapping): """Creates a model with dictionary access""" def __init__(self, raw=None): diff --git a/emma/model/account.py b/emma/model/account.py index 9898eb7..118ea22 100644 --- a/emma/model/account.py +++ b/emma/model/account.py @@ -12,6 +12,7 @@ import emma.model.search import emma.model.trigger import emma.model.webhook +import emma.model.automation class Account(object): @@ -54,6 +55,7 @@ def __init__(self, account_id, public_key, private_key): self.searches = AccountSearchCollection(self) self.triggers = AccountTriggerCollection(self) self.webhooks = AccountWebHookCollection(self) + self.workflows = AccountWorkflowCollect(self) class AccountFieldCollection(BaseApiModel): @@ -1132,3 +1134,85 @@ def list_events(self): """ path = '/webhooks/events' return self.account.adapter.get(path) + + +class AccountWorkflowCollect(BaseApiModel): + """ + Encapsulates operations for the set of :class:`Workflow` objects of an + :class:`account` + + :param account: The Account which owns this collection + :type account: :class:`Account` + """ + + def __init__(self, account): + self.account = account + super(AccountWorkflowCollect, self).__init__() + + def factory(self, raw=None): + """ + New :class:`Workflow` factory + + :param raw: Raw data with which to populate class + :type raw: :class:`dict` + :rtype: :class:`Workflow` + + Usage:: + + >>> from emma.model.account import Account + >>> acct = Account(1234, "08192a3b4c5d6e7f", "f7e6d5c4b3a29180") + >>> acct.workflows.factory() + + >>> acct.triggers.factory({'id': u"test-automation-id", ...}) + + """ + return emma.model.automation.Workflow(self.account, raw) + + def fetch_all(self): + """ + Lazy-loads the full set of :class:`Workflow` objects + + :rtype: :class:`dict` of :class:`Workflow` objects + + Usage:: + + >>> from emma.model.account import Account + >>> acct = Account(1234, "08192a3b4c5d6e7f", "f7e6d5c4b3a29180") + >>> acct.workflows.fetch_all() + {'adfasdfasdf123123': , 'afadf23324': , ...} + """ + automation = emma.model.automation + path = '/automation/workflows' + if not self._dict: + self._dict = dict( + (x['workflow_id'], automation.Workflow(self.account, x)) + for x in self.account.adapter.paginated_get(path)) + return self._dict + + def find_one_by_workflow_id(self, workflow_id): + """ + Lazy-loads a single :class:`WorkFlow` by ID + + :param workflow_id: The workflow identifier + :type workflow_id: :class:`int` + :rtype: :class:`Field` or :class:`None` + + Usage:: + + >>> from emma.model.account import Account + >>> acct = Account(1234, "08192a3b4c5d6e7f", "f7e6d5c4b3a29180") + >>> acct.workflows.find_one_by_workflow_id('1kj131hj31239231') # does not exist + raises + >>> acct.workflows.find_one_by_workflow_id('21123123jnl123') + + >>> acct.workflows[123] + + """ + path = '/automation/workflows/%s' % workflow_id + if workflow_id not in self._dict: + automation = emma.model.automation + raw = self.account.adapter.get(path) + if raw: + self._dict[workflow_id] = automation.Workflow(self.account, raw) + + return (workflow_id in self._dict) and self._dict[workflow_id] or None diff --git a/emma/model/automation.py b/emma/model/automation.py new file mode 100644 index 0000000..a23e5f0 --- /dev/null +++ b/emma/model/automation.py @@ -0,0 +1,36 @@ +"""Automation models""" + +from emma.model import BaseApiModel, str_fields_to_datetime_alt + + +class Workflow(BaseApiModel): + """ + Encapsulates operations for a :class:`Workflow` + + :param account: The Account which owns this Workflow + :type account: :class:`Account` + :param raw: The raw values of this :class:`Workflow` + :type raw: :class:`dict` + + Usage:: + + >>> from emma.model.account import Account + >>> acct = Account(1234, "08192a3b4c5d6e7f", "f7e6d5c4b3a29180") + >>> auto = acct.workflows[123] + >>> auto + + """ + def __init__(self, account, raw=None): + self.account = account + super(Workflow, self).__init__(raw) + + # def __repr__(self): + # return ''.format(self.workflow_id) + + def _parse_raw(self, raw): + raw.update( + str_fields_to_datetime_alt( + ['created_at', 'updated_at'], raw + ) + ) + return raw diff --git a/tests/model/account_test.py b/tests/model/account_test.py index 68320ca..71f5c81 100644 --- a/tests/model/account_test.py +++ b/tests/model/account_test.py @@ -18,6 +18,7 @@ from emma.model.search import Search from emma.model.trigger import Trigger from emma.model.webhook import WebHook +from emma.model.automation import Workflow from tests.model import MockAdapter @@ -2027,3 +2028,52 @@ def test_can_list_events(self): self.webhooks.account.adapter.call, ('GET', '/webhooks/events', {})) self.assertEquals(0, len(self.webhooks)) + + +class AccountWorkflowTest(unittest.TestCase): + def setUp(self): + Account.default_adapter = MockAdapter + self.workflows = Account( + account_id="100", + public_key="xxx", + private_key="yyy").workflows + + def test_fetch_all_returns_a_dictionary(self): + MockAdapter.expected = [{'workflow_id': 201}] + self.assertIsInstance(self.workflows.fetch_all(), dict) + self.assertEquals(self.workflows.account.adapter.called, 1) + self.assertEquals( + self.workflows.account.adapter.call, + ('GET', '/automation/workflows', {})) + + def test_fetch_all_returns_a_dictionary(self): + MockAdapter.expected = [{'workflow_id': 201}] + self.assertIsInstance(self.workflows.fetch_all(), dict) + self.assertEquals(self.workflows.account.adapter.called, 1) + self.assertEquals( + self.workflows.account.adapter.call, + ('GET', '/automation/workflows', {})) + + def test_find_one_by_workflow_id_returns_an_import_object(self): + MockAdapter.expected = {'workflow_id': 201} + workflow = self.workflows.find_one_by_workflow_id(201) + self.assertIsInstance(workflow, Workflow) + self.assertEquals(workflow['workflow_id'], 201) + self.assertEquals(self.workflows.account.adapter.called, 1) + self.assertEquals( + self.workflows.account.adapter.call, + ('GET', '/automation/workflows/201', {})) + + def test_find_one_by_workflow_id_populates_collection(self): + MockAdapter.expected = {'workflow_id': 201} + self.workflows.find_one_by_workflow_id(201) + self.assertIn(201, self.workflows) + self.assertIsInstance(self.workflows[201], Workflow) + self.assertEquals(self.workflows[201]['workflow_id'], 201) + + def test_find_one_by_workflow_id_caches_result(self): + MockAdapter.expected = {'workflow_id': 201} + self.workflows.find_one_by_workflow_id(201) + self.workflows.find_one_by_workflow_id(201) + self.assertEquals(self.workflows.account.adapter.called, 1) + diff --git a/tests/model/automation_test.py b/tests/model/automation_test.py new file mode 100644 index 0000000..1d75c31 --- /dev/null +++ b/tests/model/automation_test.py @@ -0,0 +1,36 @@ +from datetime import datetime +import unittest + +from emma.model import SERIALIZED_DATETIME_ALT_FORMAT +from emma import exceptions as ex +from emma.model.account import Account +from emma.model.automation import Workflow + +from tests.model import MockAdapter + + +class WorkflowTest(unittest.TestCase): + """ + Tests for the Workflow model + """ + + def setUp(self): + """ + Set up tasks for our tests + """ + Account.default_adapter = MockAdapter + self.workflow = Workflow( + Account(account_id="100", public_key="xxx", private_key="yyy"), + { + 'workflow_id': '22048a49-9533-4014-ae03-2af3598ed9a7', + 'status': 'active', + 'name': 'Test', + 'created_at': datetime.now().strftime(SERIALIZED_DATETIME_ALT_FORMAT), + 'updated_at': datetime.now().strftime(SERIALIZED_DATETIME_ALT_FORMAT), + } + ) + + def test_can_represent_workflow(self): + self.assertEquals( + u"", + repr(self.workflow))