Skip to content

Commit 92bb9dd

Browse files
Add Tasks API support to Python Client [sc-66753] (#109)
* [sc-66753] Added support for Tasks API * Adjusted Task resource defintion to specify 'allow_none=True' for completed_at field * Dropped support for Python 3.8 since it has passed the EOL period * Bumped version to 4.6.0
1 parent 0517187 commit 92bb9dd

10 files changed

Lines changed: 288 additions & 22 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
strategy:
3232
fail-fast: false
3333
matrix:
34-
python-versions: ["3.8", "3.9", "3.10", "3.11", "3.12"]
34+
python-versions: ["3.9", "3.10", "3.11", "3.12"]
3535
steps:
3636
- uses: actions/checkout@v2
3737
- name: Set up Python ${{ matrix.python-versions }}

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning].
88
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
99
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
1010

11+
## [4.6.0] - 2025-04-25
12+
- Adds support for Tasks (https://dev.chartmogul.com/reference/tasks)
13+
1114
## [4.5.1] - 2025-04-07
1215
- Update urllib3 dependency to use >=2.2.3 to allow for future minor updates
1316

README.md

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
## Installation
3131

32-
This library requires Python 3.8 to 3.12. It was last tested against Python 2.7 in version 1.3.0.
32+
This library requires Python 3.9 to 3.12. It was last tested against Python 2.7 in version 1.3.0.
3333

3434
```sh
3535
pip3 install chartmogul
@@ -106,7 +106,7 @@ except Exception as ex:
106106

107107
Available methods in Import API:
108108

109-
#### [Data Sources](https://dev.chartmogul.com/docs/data-sources)
109+
#### [Data Sources](https://dev.chartmogul.com/reference/sources/)
110110

111111
```python
112112
chartmogul.DataSource.create(config, data={'name': 'In-house billing'})
@@ -115,7 +115,7 @@ chartmogul.DataSource.all(config)
115115
chartmogul.DataSource.destroy(config, uuid='ds_5915ee5a-babd-406b-b8ce-d207133fb4cb')
116116
```
117117

118-
#### [Customers](https://dev.chartmogul.com/docs/customers)
118+
#### [Customers](https://dev.chartmogul.com/reference/customers/)
119119

120120
```python
121121
chartmogul.Customer.create(config, data={})
@@ -166,11 +166,13 @@ chartmogul.Customer.contacts(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb
166166
chartmogul.Customer.createContact(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
167167
chartmogul.Customer.notes(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
168168
chartmogul.Customer.createNote(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
169-
chartmogul.Customer.opporunities(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
169+
chartmogul.Customer.opportunities(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
170170
chartmogul.Customer.createOpportunity(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
171+
chartmogul.Customer.tasks(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', cursor='aabbcc', per_page=20)
172+
chartmogul.Customer.createTask(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={})
171173
```
172174

173-
#### [Contacts](https://dev.chartmogul.com/docs/contacts)
175+
#### [Contacts](https://dev.chartmogul.com/reference/contacts/)
174176

175177
```python
176178
chartmogul.Contact.create(config, data={})
@@ -183,7 +185,7 @@ chartmogul.Contact.modify(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb
183185
chartmogul.Contact.destroy(config, uuid='con_5915ee5a-babd-406b-b8ce-d207133fb4cb')
184186
```
185187

186-
#### [Customer Notes](https://dev.chartmogul.com/docs/customer_notes)
188+
#### [Customer Notes](https://dev.chartmogul.com/reference/notes-and-call-logs/)
187189
```python
188190
chartmogul.CustomerNote.create(config, data={})
189191
chartmogul.CustomerNote.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
@@ -192,7 +194,7 @@ chartmogul.CustomerNote.patch(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133
192194
chartmogul.CustomerNote.destroy(config, uuid='note_5915ee5a-babd-406b-b8ce-d207133fb4cb')
193195
```
194196

195-
#### [Opportunities](https://dev.chartmogul.com/docs/opportunities)
197+
#### [Opportunities](https://dev.chartmogul.com/reference/opportunities/)
196198

197199
```python
198200
chartmogul.Opportunity.create(config, data={})
@@ -202,15 +204,25 @@ chartmogul.Opportunity.patch(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb'
202204
chartmogul.Opportunity.destroy(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')
203205
```
204206

205-
#### [Customer Attributes](https://dev.chartmogul.com/docs/customer-attributes)
207+
#### [Tasks](https://dev.chartmogul.com/reference/tasks/)
208+
209+
```python
210+
chartmogul.Task.create(config, data={})
211+
chartmogul.Task.all(config, cursor='aabbcc', per_page=20, customer_uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
212+
chartmogul.Task.retrieve(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')
213+
chartmogul.Task.patch(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')
214+
chartmogul.Task.destroy(config, uuid='5915ee5a-babd-406b-b8ce-d207133fb4cb')
215+
```
216+
217+
#### [Customer Attributes](https://dev.chartmogul.com/reference/customers/attributes/)
206218

207219
Note that the returned attributes of type date are not parsed and stay in string.
208220

209221
```python
210222
chartmogul.Attributes.retrieve(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb')
211223
```
212224

213-
#### [Tags](https://dev.chartmogul.com/docs/tags)
225+
#### [Tags](https://dev.chartmogul.com/reference/customers/tags/)
214226

215227
```python
216228
chartmogul.Tags.add(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
@@ -225,7 +237,7 @@ chartmogul.Tags.remove(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb',
225237
})
226238
```
227239

228-
#### [Custom Attributes](https://dev.chartmogul.com/docs/custom-attributes)
240+
#### [Custom Attributes](https://dev.chartmogul.com/reference/customers/attributes/)
229241

230242
```python
231243
chartmogul.CustomAttributes.add(config, uuid='cus_5915ee5a-babd-406b-b8ce-d207133fb4cb', data={
@@ -250,7 +262,7 @@ chartmogul.CustomAttributes.remove(config, uuid='cus_5915ee5a-babd-406b-b8ce-d20
250262
})
251263
```
252264

253-
#### [Plans](https://dev.chartmogul.com/docs/plans)
265+
#### [Plans](https://dev.chartmogul.com/reference/plans/)
254266

255267
```python
256268
chartmogul.Plan.create(config, data={})
@@ -262,7 +274,7 @@ chartmogul.Plan.all(config, cursor='cursor==', external_id='')
262274
chartmogul.Plan.destroy(config, uuid='')
263275
```
264276

265-
#### [Plan Groups](https://dev.chartmogul.com/docs/plan_groups)
277+
#### [Plan Groups](https://dev.chartmogul.com/reference/plan-groups/)
266278

267279
```python
268280
chartmogul.PlanGroup.create(config, data={})
@@ -273,7 +285,7 @@ chartmogul.PlanGroup.all(config, uuid='plg_5915ee5a-babd-406b-b8ce-d207133fb4cb'
273285
chartmogul.PlanGroup.destroy(config, uuid='')
274286
```
275287

276-
#### [Invoices](https://dev.chartmogul.com/docs/invoices)
288+
#### [Invoices](https://dev.chartmogul.com/reference/invoices/)
277289

278290
```python
279291
import chartmogul
@@ -284,15 +296,15 @@ chartmogul.Invoice.all(config, customer_uuid='cus_f466e33d-ff2b-4a11-8f85-417eb0
284296
chartmogul.Invoice.retrieve(config, uuid='inv_22910fc6-c931-48e7-ac12-90d2cb5f0059')
285297
```
286298

287-
#### [Transactions](https://dev.chartmogul.com/docs/transactions)
299+
#### [Transactions](https://dev.chartmogul.com/reference/transactions/)
288300

289301
```python
290302
import chartmogul
291303

292304
chartmogul.Transaction.create(config, uuid='inv_745df1d4-819f-48ee-873d-b5204801e021', data={})
293305
```
294306

295-
#### [SubscriptionEvents](https://dev.chartmogul.com/docs/subscription_events)
307+
#### [SubscriptionEvents](https://dev.chartmogul.com/reference/subscription-events/)
296308

297309
```python
298310
import chartmogul
@@ -323,7 +335,7 @@ chartmogul.SubscriptionEvent.destroy_with_params(config, data={
323335
}})
324336
```
325337

326-
#### [Subscriptions](https://dev.chartmogul.com/docs/subscriptions)
338+
#### [Subscriptions](https://dev.chartmogul.com/reference/subscriptions/)
327339

328340
```python
329341
import chartmogul
@@ -333,7 +345,7 @@ chartmogul.Subscription.cancel(config, uuid='sub_3995ee5a-bbdb-406b-a8ca-d207133
333345
chartmogul.Subscription.modify(config, uuid='sub_3995ee5a-bbdb-406b-a8ca-d207133fb9bb' data={'cancellation_dates': []})
334346
```
335347

336-
### [Metrics API](https://dev.chartmogul.com/docs/introduction-metrics-api)
348+
### [Metrics API](https://dev.chartmogul.com/reference/metrics/)
337349

338350
Available methods in Metrics API:
339351

chartmogul/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .api.invoice import Invoice
1818
from .api.metrics import Metrics
1919
from .api.opportunity import Opportunity
20+
from .api.task import Task
2021
from .api.ping import Ping
2122
from .api.plan import Plan
2223
from .api.plan_group import PlanGroup

chartmogul/api/customer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .contact import Contact
1313
from .customer_note import CustomerNote
1414
from .opportunity import Opportunity
15+
from .task import Task
1516

1617

1718
class Address(DataObject):
@@ -93,3 +94,5 @@ def make(self, data, **kwargs):
9394
Customer.createNote = CustomerNote._method("create", "post", "/customer_notes", useCallerClass=True, useUUIDFor="customer_uuid")
9495
Customer.opportunities = Opportunity._method("all", "get", "/opportunities?customer_uuid={uuid}", useCallerClass=True)
9596
Customer.createOpportunity = Opportunity._method("create", "post", "/opportunities", useCallerClass=True, useUUIDFor="customer_uuid")
97+
Customer.tasks = Task._method("all", "get", "/tasks?customer_uuid={uuid}", useCallerClass=True)
98+
Customer.createTask = Task._method("create", "post", "/tasks", useCallerClass=True, useUUIDFor="customer_uuid")

chartmogul/api/task.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from marshmallow import Schema, fields, post_load, EXCLUDE
2+
from ..resource import Resource
3+
from collections import namedtuple
4+
5+
6+
class Task(Resource):
7+
"""
8+
https://dev.chartmogul.com/reference/tasks
9+
"""
10+
11+
_path = "/tasks{/uuid}"
12+
_root_key = "entries"
13+
_many = namedtuple("Tasks", [_root_key, "has_more", "cursor"])
14+
15+
class _Schema(Schema):
16+
17+
uuid = fields.String()
18+
customer_uuid = fields.String()
19+
assignee = fields.String()
20+
task_details = fields.String()
21+
due_date = fields.DateTime()
22+
completed_at = fields.DateTime(allow_none=True)
23+
created_at = fields.DateTime()
24+
updated_at = fields.DateTime()
25+
26+
@post_load
27+
def make(self, data, **kwargs):
28+
return Task(**data)
29+
30+
_schema = _Schema(unknown=EXCLUDE)

chartmogul/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.5.1"
1+
__version__ = "4.6.0"

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
classifiers=[
6868
"Programming Language :: Python",
6969
"Programming Language :: Python :: 3",
70-
"Programming Language :: Python :: 3.8",
7170
"Programming Language :: Python :: 3.9",
7271
"Programming Language :: Python :: 3.10",
7372
"Programming Language :: Python :: 3.11",

test/api/test_customer.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from chartmogul import Customer, Contact, Config, CustomerNote, Opportunity
2+
from chartmogul import Customer, Contact, Config, CustomerNote, Opportunity, Task
33
from chartmogul.api.customer import Attributes, Address
44
from datetime import datetime
55
from chartmogul import APIError
@@ -362,6 +362,38 @@
362362

363363
allOpportunities = {"entries": [opportunityEntry], "cursor": "cursor==", "has_more": True}
364364

365+
task = {
366+
"uuid": "00000000-0000-0000-0000-000000000000",
367+
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
368+
"assignee": "customer@example.com",
369+
"task_details": "This is some task details text.",
370+
"due_date": "2025-04-30T00:00:00Z",
371+
"completed_at": "2025-04-20T00:00:00Z",
372+
"created_at": "2025-04-01T12:00:00.000Z",
373+
"updated_at": "2025-04-01T12:00:00.000Z"
374+
}
375+
376+
createTask = {
377+
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
378+
"assignee": "customer@example.com",
379+
"task_details": "This is some task details text.",
380+
"due_date": "2025-04-30T00:00:00Z",
381+
"completed_at": None,
382+
}
383+
384+
taskEntry = {
385+
"uuid": "00000000-0000-0000-0000-000000000000",
386+
"customer_uuid": "cus_00000000-0000-0000-0000-000000000000",
387+
"assignee": "customer@example.com",
388+
"task_details": "This is some task details text.",
389+
"due_date": "2025-04-30T00:00:00Z",
390+
"completed_at": "2025-04-20T00:00:00Z",
391+
"created_at": "2025-04-01T12:00:00.000Z",
392+
"updated_at": "2025-04-01T12:00:00.000Z"
393+
}
394+
395+
allTasks = {"entries": [taskEntry], "cursor": "cursor==", "has_more": False}
396+
365397

366398
class CustomerTestCase(unittest.TestCase):
367399
"""
@@ -453,7 +485,7 @@ def test_merge(self, mock_requests):
453485
self.assertEqual(mock_requests.last_request.qs, {})
454486
self.assertEqual(mock_requests.last_request.json(), jsonRequest)
455487
self.assertEqual(result, None)
456-
488+
457489
@requests_mock.mock()
458490
def test_unmerge(self, mock_requests):
459491
mock_requests.register_uri(
@@ -701,3 +733,48 @@ def test_createOpportunity(self, mock_requests):
701733
self.assertEqual(mock_requests.last_request.qs, {})
702734
self.assertEqual(mock_requests.last_request.json(), createOpportunity)
703735
self.assertTrue(isinstance(expected, Opportunity))
736+
737+
@requests_mock.mock()
738+
def test_tasks(self, mock_requests):
739+
mock_requests.register_uri(
740+
"GET",
741+
"https://api.chartmogul.com/v1/tasks?customer_uuid=cus_00000000-0000-0000-0000-000000000000&cursor=df431387&per_page=1",
742+
status_code=200,
743+
json=allTasks,
744+
)
745+
746+
config = Config("token")
747+
tasks = Customer.tasks(
748+
config,
749+
uuid="cus_00000000-0000-0000-0000-000000000000",
750+
cursor="df431387",
751+
per_page=1,
752+
).get()
753+
expected = Customer._many(**allTasks)
754+
755+
self.assertEqual(mock_requests.call_count, 1, "expected call")
756+
self.assertEqual(mock_requests.last_request.qs, {'customer_uuid': ['cus_00000000-0000-0000-0000-000000000000'], 'cursor': ['df431387'], 'per_page': ['1']})
757+
self.assertEqual(mock_requests.last_request.text, None)
758+
self.assertEqual(sorted(dir(tasks)), sorted(dir(expected)))
759+
self.assertTrue(isinstance(tasks.entries[0], Task))
760+
self.assertEqual(tasks.cursor, "cursor==")
761+
self.assertFalse(tasks.has_more)
762+
763+
@requests_mock.mock()
764+
def test_createTask(self, mock_requests):
765+
mock_requests.register_uri(
766+
"POST",
767+
"https://api.chartmogul.com/v1/tasks",
768+
status_code=200,
769+
json=task,
770+
)
771+
772+
config = Config("token")
773+
expected = Customer.createTask(
774+
config, uuid="cus_00000000-0000-0000-0000-000000000000", data=createTask
775+
).get()
776+
777+
self.assertEqual(mock_requests.call_count, 1, "expected call")
778+
self.assertEqual(mock_requests.last_request.qs, {})
779+
self.assertEqual(mock_requests.last_request.json(), createTask)
780+
self.assertTrue(isinstance(expected, Task))

0 commit comments

Comments
 (0)