Skip to content

Commit

Permalink
Support for Contacts (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
briwa authored Mar 15, 2023
1 parent 3164370 commit 0bb9b01
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[flake8]
ignore = E501
ignore = E501,W503
exclude = __init__.py
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ chartmogul.Customer.connectSubscriptions(config, uuid='cus_5915ee5a-babd-406b-b8
}
]
})
chartmogul.Customer.contacts(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
chartmogul.Customer.createContact(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
```

#### [Contacts](https://dev.chartmogul.com/docs/contacts)

```python
chartmogul.Contact.create(config, data={})
chartmogul.Contact.all(config, cursor='aabbcc', per_page=20)
chartmogul.Contact.retrieve(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb')
chartmogul.Contact.merge(config, into_uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', from_uuid='con_2123290f-09c8-4628-a205-db5596bd58f7')
chartmogul.Contact.modify(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
"email": "test@example.com"
})
chartmogul.Contact.destroy(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb')
```

#### [Customer Attributes](https://dev.chartmogul.com/docs/customer-attributes)
Expand Down
1 change: 1 addition & 0 deletions chartmogul/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .api.attributes import Attributes
from .api.custom_attrs import CustomAttributes
from .api.customer import Customer
from .api.contact import Contact
from .api.data_source import DataSource
from .api.invoice import Invoice
from .api.metrics import Metrics
Expand Down
37 changes: 37 additions & 0 deletions chartmogul/api/contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from marshmallow import Schema, fields, post_load, EXCLUDE
from ..resource import Resource
from collections import namedtuple


class Contact(Resource):
"""
https://dev.chartmogul.com/v1.0/reference#contacts
"""
_path = "/contacts{/uuid}"
_root_key = "entries"
_many = namedtuple("Contacts",
[_root_key, "has_more", "cursor"])

class _Schema(Schema):
customer_uuid = fields.String(allow_none=True)
data_source_uuid = fields.String(allow_none=True)
customer_external_id = fields.String(allow_none=True)
first_name = fields.String(allow_none=True)
last_name = fields.String(allow_none=True)
position = fields.Int(allow_none=True)
email = fields.String(allow_none=True)
title = fields.String(allow_none=True)
notes = fields.String(allow_none=True)
phone = fields.String(allow_none=True)
linked_in = fields.String(allow_none=True)
twitter = fields.String(allow_none=True)
custom = fields.Dict(allow_none=True)

@post_load
def make(self, data, **kwargs):
return Contact(**data)

_schema = _Schema(unknown=EXCLUDE)


Contact.merge = Contact._method("merge", "post", "/contacts/{into_uuid}/merge/{from_uuid}")
3 changes: 3 additions & 0 deletions chartmogul/api/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..resource import DataObject, Resource
from collections import namedtuple
from .attributes import Attributes
from .contact import Contact


class Address(DataObject):
Expand Down Expand Up @@ -76,3 +77,5 @@ def make(self, data, **kwargs):
Customer.search = Customer._method('all', 'get', '/customers/search')
Customer.merge = Customer._method('merge', 'post', '/customers/merges')
Customer.connectSubscriptions = Customer._method('create', 'post', '/customers/{uuid}/connect_subscriptions')
Customer.contacts = Contact._method('all', 'get', '/customers/{uuid}/contacts', useCallerClass=True)
Customer.createContact = Contact._method('create', 'post', '/customers/{uuid}/contacts', useCallerClass=True)
8 changes: 6 additions & 2 deletions chartmogul/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"page",
"summary",
"customer_uuid",
"data_source_uuid",
"cursor",
"meta",
]
ESCAPED_QUERY_KEYS = {"start_date": "start-date", "end_date": "end-date"}
Expand Down Expand Up @@ -198,14 +200,16 @@ def _validate_arguments(cls, method, kwargs):
)

@classmethod
def _method(cls, method, http_verb, path=None):
def _method(callerClass, method, http_verb, path=None, useCallerClass=False):
@classmethod
def fc(cls, config, **kwargs):
def fc(calleeClass, config, **kwargs):
if config is None or not isinstance(config, Config):
raise ConfigurationError(
"First argument should be" " instance of chartmogul.Config class!"
)

cls = callerClass if useCallerClass else calleeClass

pathTemp = path # due to Python closure
if pathTemp is None:
pathTemp = cls._path
Expand Down
2 changes: 1 addition & 1 deletion chartmogul/retry_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def _retry_adapter(retries, backoff_factor):
read=retries,
connect=retries,
status=retries,
method_whitelist=METHOD_WHITELIST,
allowed_methods=METHOD_WHITELIST,
status_forcelist=STATUS_FORCELIST,
backoff_factor=backoff_factor,
)
Expand Down
161 changes: 161 additions & 0 deletions test/api/test_contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import unittest
from chartmogul import Contact, Config
import requests_mock

from pprint import pprint

contact = {
"uuid": "con_00000000-0000-0000-0000-000000000000",
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
"data_source_uuid": "ds_00000000-0000-0000-0000-000000000000",
"customer_external_id": "external_001",
"first_name": "First name",
"last_name": "Last name",
"position": 9,
"title": "Title",
"email": "test@example.com",
"phone": "+1234567890",
"linked_in": "https://linkedin.com/not_found",
"twitter": "https://twitter.com/not_found",
"notes": "Heading\nBody\nFooter",
"custom": {
"MyStringAttribute": "Test",
"MyIntegerAttribute": 123
}
}

createContact = {
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
"data_source_uuid": "ds_00000000-0000-0000-0000-000000000000",
"first_name": "First name",
"last_name": "Last name",
"position": 9,
"title": "Title",
"email": "test@example.com",
"phone": "+1234567890",
"linked_in": "https://linkedin.com/not_found",
"twitter": "https://twitter.com/not_found",
"notes": "Heading\nBody\nFooter",
"custom": [
{ "key": "MyStringAttribute", "value": "Test" },
{ "key": "MyIntegerAttribute", "value": 123 }
]
}

allContacts = {
"entries": [contact],
"cursor": "MjAyMy0wMy0xMFQwMzo1MzoxNS44MTg1MjUwMDArMDA6MDAmY29uXzE2NDcwZjk4LWJlZjctMTFlZC05MjA4LTdiMDhhNDBmMzA0OQ==",
"has_more": False
}


class ContactTestCase(unittest.TestCase):
"""
Tests complex nested structure & assymetric create/retrieve schema.
"""

@requests_mock.mock()
def test_all(self, mock_requests):
mock_requests.register_uri(
"GET",
"https://api.chartmogul.com/v1/contacts?cursor=Ym9veWFo&per_page=1&data_source_uuid=ds_00000000-0000-0000-0000-000000000000",
status_code=200,
json=allContacts
)

config = Config("token")
contacts = Contact.all(config, data_source_uuid="ds_00000000-0000-0000-0000-000000000000", cursor="Ym9veWFo", per_page=1).get()
expected = Contact._many(**allContacts)

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {
"cursor": ["ym9vewfo"],
"per_page": ["1"],
"data_source_uuid": ["ds_00000000-0000-0000-0000-000000000000"]
})
self.assertEqual(mock_requests.last_request.text, None)
self.assertEqual(dir(contacts), dir(expected))
self.assertTrue(isinstance(contacts.entries[0], Contact))

@requests_mock.mock()
def test_create(self, mock_requests):
mock_requests.register_uri(
"POST",
"https://api.chartmogul.com/v1/contacts",
status_code=200,
json=contact
)

config = Config("token")
Contact.create(config, data=createContact).get()
self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertEqual(mock_requests.last_request.json(), createContact)

@requests_mock.mock()
def test_merge(self, mock_requests):
mock_requests.register_uri(
"POST",
"https://api.chartmogul.com/v1/contacts/con_00000000-0000-0000-0000-000000000000/merge/con_00000000-0000-0000-0000-000000000001",
status_code=200,
json=contact
)

config = Config("token")
expected = Contact.merge(config, into_uuid="con_00000000-0000-0000-0000-000000000000", from_uuid="con_00000000-0000-0000-0000-000000000001").get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertTrue(isinstance(expected, Contact))

@requests_mock.mock()
def test_modify(self, mock_requests):
mock_requests.register_uri(
"PATCH",
"https://api.chartmogul.com/v1/contacts/con_00000000-0000-0000-0000-000000000000",
status_code=200,
json=contact
)

jsonRequest = {
"email": "test2@example.com"
}
config = Config("token")
expected = Contact.modify(config, uuid="con_00000000-0000-0000-0000-000000000000", data=jsonRequest).get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertEqual(mock_requests.last_request.json(), jsonRequest)
self.assertTrue(isinstance(expected, Contact))

@requests_mock.mock()
def test_retrieve(self, mock_requests):
mock_requests.register_uri(
"GET",
"https://api.chartmogul.com/v1/contacts/con_00000000-0000-0000-0000-000000000000",
status_code=200,
json=contact
)

config = Config("token")
expected = Contact.retrieve(config, uuid="con_00000000-0000-0000-0000-000000000000").get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertTrue(isinstance(expected, Contact))

@requests_mock.mock()
def test_destroy(self, mock_requests):
mock_requests.register_uri(
"DELETE",
"https://api.chartmogul.com/v1/contacts/con_00000000-0000-0000-0000-000000000000",
status_code=200,
json={}
)

config = Config("token")
expected = Contact.destroy(config, uuid="con_00000000-0000-0000-0000-000000000000").get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertTrue(expected, {})
83 changes: 81 additions & 2 deletions test/api/test_customer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from chartmogul import Customer, Config
from chartmogul import Customer, Contact, Config
from chartmogul.api.customer import Attributes, Address
from datetime import datetime
from chartmogul import APIError
Expand Down Expand Up @@ -283,6 +283,49 @@
'zip': '0185128'
}

contact = {
"uuid": "con_00000000-0000-0000-0000-000000000000",
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
"data_source_uuid": "ds_00000000-0000-0000-0000-000000000000",
"customer_external_id": "external_001",
"first_name": "First name",
"last_name": "Last name",
"position": 9,
"title": "Title",
"email": "test@example.com",
"phone": "+1234567890",
"linked_in": "https://linkedin.com/not_found",
"twitter": "https://twitter.com/not_found",
"notes": "Heading\nBody\nFooter",
"custom": {
"MyStringAttribute": "Test",
"MyIntegerAttribute": 123
}
}

createContact = {
"data_source_uuid": "ds_00000000-0000-0000-0000-000000000000",
"first_name": "First name",
"last_name": "Last name",
"position": 9,
"title": "Title",
"email": "test@example.com",
"phone": "+1234567890",
"linked_in": "https://linkedin.com/not_found",
"twitter": "https://twitter.com/not_found",
"notes": "Heading\nBody\nFooter",
"custom": [
{ "key": "MyStringAttribute", "value": "Test" },
{ "key": "MyIntegerAttribute", "value": 123 }
]
}

allContacts = {
"entries": [contact],
"cursor": "MjAyMy0wMy0xMFQwMzo1MzoxNS44MTg1MjUwMDArMDA6MDAmY29uXzE2NDcwZjk4LWJlZjctMTFlZC05MjA4LTdiMDhhNDBmMzA0OQ==",
"has_more": False
}


class CustomerTestCase(unittest.TestCase):
"""
Expand Down Expand Up @@ -444,4 +487,40 @@ def test_modify(self, mock_requests):

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertEqual(mock_requests.last_request.json(), jsonRequest)
self.assertEqual(mock_requests.last_request.json(), jsonRequest)

@requests_mock.mock()
def test_contacts(self, mock_requests):
mock_requests.register_uri(
"GET",
"https://api.chartmogul.com/v1/customers/cus_00000000-0000-0000-0000-000000000000/contacts",
status_code=200,
json=allContacts
)

config = Config("token")
contacts = Customer.contacts(config, uuid="cus_00000000-0000-0000-0000-000000000000").get()
expected = Contact._many(**allContacts)

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertEqual(mock_requests.last_request.text, None)
self.assertEqual(sorted(dir(contacts)), sorted(dir(expected)))
self.assertTrue(isinstance(contacts.entries[0], Contact))

@requests_mock.mock()
def test_createContact(self, mock_requests):
mock_requests.register_uri(
"POST",
"https://api.chartmogul.com/v1/customers/cus_00000000-0000-0000-0000-000000000000/contacts",
status_code=200,
json=contact
)

config = Config("token")
expected = Customer.createContact(config, uuid="cus_00000000-0000-0000-0000-000000000000", data=createContact).get()

self.assertEqual(mock_requests.call_count, 1, "expected call")
self.assertEqual(mock_requests.last_request.qs, {})
self.assertEqual(mock_requests.last_request.json(), createContact)
self.assertTrue(isinstance(expected, Contact))

0 comments on commit 0bb9b01

Please sign in to comment.