Skip to content

Commit 34d7479

Browse files
authored
[TOOLSLIBS-18] Add Custom Events (#190)
* adds custom event module * more py2 rms * properly wires class * adds custom events url * adds CustomEvent class * fixes payload builder * adds custom events tests * cleanup for doc generation * adds docs * adds sphinx docs build to test run * typo fixes
1 parent ec55a7b commit 34d7479

File tree

10 files changed

+257
-10
lines changed

10 files changed

+257
-10
lines changed

.github/workflows/test_runner.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ jobs:
2424
- name: Test with pytest
2525
run: |
2626
nosetests -v --with-doctest
27+
- name: Build Sphinx Docs
28+
run: |
29+
cd docs/
30+
make clean html

docs/custom_events.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Custom Events
2+
*************
3+
4+
.. autoclass:: urbanairship.custom_events.custom_events.CustomEvent
5+
:members:

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Contents
104104
devices.rst
105105
audience.rst
106106
reports.rst
107+
custom_events.rst
107108

108109

109110
Indices and tables

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"urbanairship.reports",
2424
"urbanairship.automation",
2525
"urbanairship.experiments",
26+
"urbanairship.custom_events",
2627
],
2728
license="BSD License",
2829
classifiers=[

tests/custom_events/__init__.py

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import datetime
2+
import json
3+
import unittest
4+
import uuid
5+
6+
import urbanairship as ua
7+
from tests import TEST_KEY, TEST_TOKEN
8+
9+
10+
class TestCustomEvent(unittest.TestCase):
11+
def setUp(self):
12+
airship = ua.Airship(key=TEST_KEY, token=TEST_TOKEN)
13+
14+
self.event = ua.CustomEvent(
15+
airship=airship, name="test_event", user=ua.named_user("test_named_user")
16+
)
17+
18+
def test_minimum_custom_event(self):
19+
self.assertEqual(
20+
self.event._payload,
21+
{
22+
"body": {"name": "test_event"},
23+
"user": {"named_user_id": "test_named_user"},
24+
},
25+
)
26+
27+
def test_minimum_event_channel(self):
28+
self.event.user = ua.channel("0617a35e-b0c2-4b1c-9c41-586ca6b081d6")
29+
30+
self.assertEqual(
31+
self.event._payload,
32+
{
33+
"body": {"name": "test_event"},
34+
"user": {"channel": "0617a35e-b0c2-4b1c-9c41-586ca6b081d6"},
35+
},
36+
)
37+
38+
def test_full_custom_event(self):
39+
properties = {"key": "value", "nested": {"another": "pair"}}
40+
41+
self.event.occurred = datetime.datetime(2022, 1, 11, 11, 30, 00)
42+
self.event.session_id = "0617a35e-b0c2-4b1c-9c41-586ca6b081d6"
43+
self.event.interaction_id = "test_interaction_id"
44+
self.event.interaction_type = "test_interaction_type"
45+
self.event.value = 1234.56
46+
self.event.transaction = "test_transaction"
47+
self.event.properties = properties
48+
49+
self.maxDiff = 10000
50+
51+
self.assertEqual(
52+
self.event._payload,
53+
{
54+
"occurred": "2022-01-11T11:30:00",
55+
"user": {"named_user_id": "test_named_user"},
56+
"body": {
57+
"name": "test_event",
58+
"session_id": "0617a35e-b0c2-4b1c-9c41-586ca6b081d6",
59+
"interaction_id": "test_interaction_id",
60+
"interaction_type": "test_interaction_type",
61+
"value": 1234.56,
62+
"transaction": "test_transaction",
63+
"properties": properties,
64+
},
65+
},
66+
)

urbanairship/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .automation import Automation, Pipeline
55
from .common import AirshipFailure, Unauthorized
66
from .core import Airship
7+
from .custom_events import CustomEvent
78
from .devices import (
89
APIDList,
910
Attribute,
@@ -210,6 +211,7 @@
210211
ExperimentReport,
211212
KeywordInteraction,
212213
SubscriptionList,
214+
CustomEvent,
213215
]
214216

215217

urbanairship/core.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import logging
22
import re
3-
import sys
4-
import warnings
53

64
import requests
75

@@ -36,34 +34,27 @@ def __init__(self, location=None):
3634
self.attributes_list_url = self.base_url + "attribute-lists/"
3735
self.message_center_delete_url = self.base_url + "user/messages/"
3836
self.subscription_lists_url = self.channel_url + "subscription_lists/"
39-
4037
self.templates_url = self.base_url + "templates/"
4138
self.schedule_template_url = self.templates_url + "schedules/"
42-
4339
self.pipelines_url = self.base_url + "pipelines/"
44-
4540
self.named_user_url = self.base_url + "named_users/"
4641
self.named_user_tag_url = self.named_user_url + "tags/"
4742
self.named_user_disassociate_url = self.named_user_url + "disassociate/"
4843
self.named_user_associate_url = self.named_user_url + "associate/"
4944
self.named_user_uninstall_url = self.named_user_url + "uninstall/"
50-
5145
self.sms_url = self.channel_url + "sms/"
5246
self.sms_opt_out_url = self.sms_url + "opt-out/"
5347
self.sms_uninstall_url = self.sms_url + "uninstall/"
54-
5548
self.email_url = self.channel_url + "email/"
5649
self.email_tags_url = self.email_url + "tags/"
5750
self.email_uninstall_url = self.email_url + "uninstall/"
58-
5951
self.create_and_send_url = self.base_url + "create-and-send/"
6052
self.schedule_create_and_send_url = self.schedules_url + "create-and-send/"
61-
6253
self.experiments_url = self.base_url + "experiments/"
6354
self.experiments_schedule_url = self.experiments_url + "scheduled/"
6455
self.experiments_validate = self.experiments_url + "validate/"
65-
6656
self.attachment_url = self.base_url + "attachments/"
57+
self.custom_events_url = self.base_url + "custom-events/"
6758

6859
def get(self, endpoint):
6960
url = getattr(self, endpoint, None)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .custom_events import CustomEvent
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import datetime
2+
from typing import Dict, Optional, Union
3+
import json
4+
5+
from urbanairship import Airship
6+
7+
8+
class CustomEvent:
9+
def __init__(
10+
self,
11+
airship: Airship,
12+
name: Optional[str] = None,
13+
user: Optional[Dict] = None,
14+
interaction_type: Optional[str] = None,
15+
interaction_id: Optional[str] = None,
16+
properties: Optional[Dict] = None,
17+
session_id: Optional[str] = None,
18+
transaction: Optional[str] = None,
19+
value: Optional[Union[int, float]] = None,
20+
occurred: Optional[datetime.datetime] = None,
21+
) -> None:
22+
"""
23+
A class representing an Airship custom event. Please see the
24+
documentation at https://docs.airship.com/api/ua/?http#tag-custom-events for
25+
details on Custom Event usage.
26+
27+
:param Airship: [required] An urbanairship.Airship instance initialized with
28+
bearer token authentication.
29+
:param name: [required] A plain-text name for the event. Airship's analytics
30+
systems will roll up events with the same name, providing counts and total
31+
value associated with the event. This value cannot contain upper-case
32+
characters. If the name contains upper-case characters, you will receive a
33+
400 response.
34+
:param user: [required] An Airship channel identifier or named user
35+
for the user who triggered the event.
36+
:param interaction_id: [optional] The identifier defining where the event
37+
occurred.
38+
:param interaction_type: [optional] Describes the type of interaction that
39+
triggered the event
40+
:param properties: [optional] A dict containing custom event properties.
41+
:param session_id: [optional] The user session during which the event occurred.
42+
You must supply and maintain session identifiers.
43+
:param transaction: [optional] If the event is one in a series representing a
44+
single transaction, use the transaction field to tie events together.
45+
:param value: [optional] If the event is associated with a count or amount,
46+
the 'value' field carries that information.
47+
:param occurred: [optional The date and time when the event occurred. Events
48+
must have occurred within the past 90 days. You cannot provide
49+
a future datetime.
50+
"""
51+
self.airship = airship
52+
self.name = name
53+
self.user = user
54+
self.interaction_type = interaction_type
55+
self.interaction_id = interaction_id
56+
self.properties = properties
57+
self.session_id = session_id
58+
self.transaction = transaction
59+
self.value = value
60+
self.occurred = occurred
61+
62+
@property
63+
def name(self) -> str:
64+
return self._name
65+
66+
@name.setter
67+
def name(self, value: str) -> None:
68+
self._name = value
69+
70+
@property
71+
def user(self) -> Dict:
72+
if "named_user" in self._user.keys():
73+
return {"named_user_id": self._user["named_user"]}
74+
75+
return self._user
76+
77+
@user.setter
78+
def user(self, value: Dict) -> None:
79+
self._user = value
80+
81+
@property
82+
def interaction_id(self) -> Optional[str]:
83+
return self._interaction_id
84+
85+
@interaction_id.setter
86+
def interaction_id(self, value: str) -> None:
87+
self._interaction_id = value
88+
89+
@property
90+
def interaction_type(self) -> Optional[str]:
91+
return self._interaction_type
92+
93+
@interaction_type.setter
94+
def interaction_type(self, value: str) -> None:
95+
self._interaction_type = value
96+
97+
@property
98+
def properties(self) -> Optional[Dict]:
99+
return self._properties
100+
101+
@properties.setter
102+
def properties(self, value: Optional[Dict]) -> None:
103+
self._properties = value
104+
105+
@property
106+
def session_id(self) -> Optional[str]:
107+
return self._session_id
108+
109+
@session_id.setter
110+
def session_id(self, value: Optional[str]) -> None:
111+
self._session_id = value
112+
113+
@property
114+
def transaciton(self) -> Optional[str]:
115+
return self._transaction
116+
117+
@transaciton.setter
118+
def transaction(self, value: Optional[str]) -> None:
119+
self._transaction = value
120+
121+
@property
122+
def value(self) -> Optional[Union[int, float]]:
123+
return self._value
124+
125+
@value.setter
126+
def value(self, value: Optional[Union[int, float]]):
127+
self._value = value
128+
129+
@property
130+
def occurred(self) -> Optional[str]:
131+
if not isinstance(self._occurred, datetime.datetime):
132+
return self._occurred
133+
return self._occurred.strftime("%Y-%m-%dT%H:%M:%S")
134+
135+
@occurred.setter
136+
def occurred(self, value: Optional[datetime.datetime]) -> None:
137+
self._occurred = value
138+
139+
@property
140+
def _payload(self) -> Dict:
141+
event_payload = {"user": self.user}
142+
body = {"name": self.name}
143+
144+
for payload_attr in ["occurred"]:
145+
if getattr(self, payload_attr) is not None:
146+
event_payload[payload_attr] = getattr(self, payload_attr)
147+
148+
for body_attr in [
149+
"value",
150+
"transaction",
151+
"interaction_id",
152+
"interaction_type",
153+
"properties",
154+
"session_id",
155+
]:
156+
if getattr(self, body_attr) is not None:
157+
body[body_attr] = getattr(self, body_attr)
158+
159+
event_payload["body"] = body
160+
161+
return event_payload
162+
163+
def send(self) -> Dict:
164+
"""Send the Custom Event to Airship systems
165+
166+
:returns: API response dict
167+
"""
168+
response = self.airship.request(
169+
method="POST",
170+
body=json.dumps(self._payload),
171+
url=self.airship.urls.get("custom_events_url"),
172+
content_type="application/json",
173+
version=3,
174+
)
175+
176+
return response.json()

0 commit comments

Comments
 (0)