Skip to content

Commit 6ada264

Browse files
feat(kno-3081): add idempotency_key header (#19)
Co-authored-by: Meryl Dakin <[email protected]>
1 parent 79dba09 commit 6ada264

File tree

5 files changed

+45
-7
lines changed

5 files changed

+45
-7
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## v0.5.3
2+
3+
* Introduce `options` parameter for `client.notify` and `workflows.trigger` methods
4+
* This parameter can be used to pass in additional options to the request
5+
* Pass in an `idempotency_key` value via options to prevent duplicate notifications from being sent to your service

knockapi/client.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ def __init__(self, api_key, host='https://api.knock.app'):
1414
'User-Agent': 'Knock Python - {}'.format(self.client_version)
1515
}
1616

17-
def request(self, method, endpoint, payload=None):
17+
def request(self, method, endpoint, payload=None, options={}):
1818
url = '{}/v1{}'.format(self.host, endpoint)
1919

20+
extra_headers = {}
21+
if (method in ["post", "put"]) and options.get('idempotency_key') != None:
22+
extra_headers['Idempotency-Key'] = options.get('idempotency_key')
23+
2024
r = requests.request(
2125
method,
2226
url,
2327
params=payload if method == 'get' else None,
2428
json=payload if method != 'get' else None,
25-
headers=self.headers,
29+
headers={**self.headers, **extra_headers}
2630
)
2731

2832
# If we got a successful response, then attempt to deserialize as JSON
@@ -88,7 +92,8 @@ def notify(
8892
data={},
8993
actor=None,
9094
cancellation_key=None,
91-
tenant=None):
95+
tenant=None,
96+
options={}):
9297
"""
9398
Triggers a workflow.
9499
@@ -108,6 +113,10 @@ def notify(
108113
109114
cancellation_key (str): A key used to cancel this notify.
110115
116+
options (dict): An optional dictionary of options to pass to the request.
117+
Can include:
118+
- idempotency_key (str): An optional key that, if passed, will ensure that the same call is not made twice.
119+
111120
Returns:
112121
dict: Response from Knock.
113122
"""
@@ -118,4 +127,5 @@ def notify(
118127
data=data,
119128
actor=actor,
120129
cancellation_key=cancellation_key,
121-
tenant=tenant)
130+
tenant=tenant,
131+
options=options)

knockapi/resources/workflows.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ def trigger(
99
data={},
1010
actor=None,
1111
cancellation_key=None,
12-
tenant=None):
12+
tenant=None,
13+
options={}):
1314
"""
1415
Triggers a workflow.
1516
@@ -29,6 +30,10 @@ def trigger(
2930
3031
cancellation_key (str): A key used to cancel this notify.
3132
33+
options (dict): A dictionary of options to pass to the request (optional).
34+
Can include:
35+
- idempotency_key (str): An optional key that, if passed, will ensure that the same call is not made twice.
36+
3237
Returns:
3338
dict: Response from Knock.
3439
"""
@@ -42,7 +47,7 @@ def trigger(
4247
'tenant': tenant
4348
}
4449

45-
return self.client.request("post", endpoint, payload=params)
50+
return self.client.request("post", endpoint, payload=params, options=options)
4651

4752
def cancel(self, key, cancellation_key, recipients=None):
4853
"""

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import setuptools
22

3-
version = '0.5.2'
3+
version = '0.5.3'
44

55
with open("README.md", "r") as f:
66
long_description = f.read()

tests/test_workflows_class.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from unittest.mock import Mock
2+
3+
from knockapi.resources.workflows import Workflows
4+
5+
# Tests
6+
7+
def test_trigger():
8+
mocked_client = Mock()
9+
workflows = Workflows(mocked_client)
10+
workflows.trigger("workflow-key", ["user-123"], {"foo": "bar"}, actor="actor-123", cancellation_key="cancel-key", tenant="tenant-123", options={"idempotency_key": "123"})
11+
mocked_client.request.assert_called_with("post", "/workflows/workflow-key/trigger", payload={
12+
"actor": "actor-123",
13+
"recipients": ["user-123"],
14+
"data": {"foo": "bar"},
15+
"cancellation_key": "cancel-key",
16+
"tenant": "tenant-123"},
17+
options={'idempotency_key': '123'})
18+

0 commit comments

Comments
 (0)