Skip to content

Commit 42d6229

Browse files
authored
[TOOLSLIBS-564] Adds Tag List endpoints (#201)
* adds deps to mypy pre-commit * adds tag_lists url * adds TagList class to inits * rm unused imports * adds tag list class * mv payload render to property * adds mock stubs * fixes test init * adds docs strings, renames args * rm delete method, rename list method * adds tests * docstrings
1 parent 2925b50 commit 42d6229

File tree

8 files changed

+218
-1
lines changed

8 files changed

+218
-1
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ repos:
1919
rev: v0.931
2020
hooks:
2121
- id: mypy
22+
additional_dependencies: [types-requests, types-six, types-urllib3, types-mock]

tests/data/tag_list.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
channel_id
2+
c543f3a3-bc1d-4830-8dee-7532c6a23b9a
3+
6ba360a0-1f73-4ee7-861e-95f6c1ed6410
4+
15410d17-687c-46fa-bbd9-f255741a1523
5+
c2c64ef7-8f5c-470e-915f-f5e3da04e1df

tests/devices/test_tags_lists.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import unittest
2+
import json
3+
import mock
4+
5+
import requests
6+
7+
import urbanairship as ua
8+
from tests import TEST_KEY, TEST_SECRET
9+
10+
11+
class TestTagLists(unittest.TestCase):
12+
def setUp(self):
13+
self.airship = ua.Airship(key=TEST_KEY, secret=TEST_SECRET)
14+
self.list_name = "test_tag_list"
15+
self.description = "a list of some tags"
16+
self.extra = {"key": "value"}
17+
self.file_path = "tests/data/tag_list.csv"
18+
self.tag_dict = {"group_name": ["tag1", "tag2"]}
19+
self.tag_list = ua.TagList(
20+
airship=self.airship,
21+
list_name=self.list_name,
22+
description=self.description,
23+
extra=self.extra,
24+
add_tags=self.tag_dict,
25+
remove_tags=self.tag_dict,
26+
set_tags=self.tag_dict,
27+
)
28+
29+
def test_create(self):
30+
with mock.patch.object(ua.Airship, "_request") as mock_request:
31+
response = requests.Response()
32+
response._content = json.dumps({"ok": True}).encode("UTF-8")
33+
mock_request.return_value = response
34+
35+
result = self.tag_list.create()
36+
37+
self.assertEqual(result.json(), {"ok": True})
38+
39+
def test_create_payload_property(self):
40+
self.assertEqual(
41+
self.tag_list._create_payload,
42+
{
43+
"name": self.list_name,
44+
"description": self.description,
45+
"extra": self.extra,
46+
"add": self.tag_dict,
47+
"remove": self.tag_dict,
48+
"set": self.tag_dict,
49+
},
50+
)
51+
52+
def test_upload(self):
53+
with mock.patch.object(ua.Airship, "_request") as mock_request:
54+
response = requests.Response()
55+
response._content = json.dumps({"ok": True}).encode("UTF-8")
56+
mock_request.return_value = response
57+
58+
result = self.tag_list.upload(file_path=self.file_path)
59+
60+
self.assertEqual(result.json(), {"ok": True})
61+
62+
def test_get_errors(self):
63+
with mock.patch.object(ua.Airship, "_request") as mock_request:
64+
response = requests.Response()
65+
response._content = json.dumps({"ok": True}).encode("UTF-8")
66+
mock_request.return_value = response
67+
68+
result = self.tag_list.get_errors()
69+
70+
self.assertEqual(result.json(), {"ok": True})
71+
72+
def test_list(self):
73+
with mock.patch.object(ua.Airship, "_request") as mock_request:
74+
response = requests.Response()
75+
response._content = json.dumps({"ok": True}).encode("UTF-8")
76+
mock_request.return_value = response
77+
78+
result = ua.TagList.list(airship=self.airship)
79+
80+
self.assertEqual(result.json(), {"ok": True})

urbanairship/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
StaticList,
3535
StaticLists,
3636
SubscriptionList,
37+
TagList,
3738
)
3839
from .experiments import ABTest, Experiment, Variant
3940
from .push import (
@@ -214,6 +215,11 @@
214215
KeywordInteraction,
215216
SubscriptionList,
216217
CustomEvent,
218+
SmsCustomResponse,
219+
TagList,
220+
ABTest,
221+
Experiment,
222+
Variant,
217223
]
218224

219225

urbanairship/core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self, location: Optional[str] = None) -> None:
5959
self.experiments_validate = self.experiments_url + "validate/"
6060
self.attachment_url = self.base_url + "attachments/"
6161
self.custom_events_url = self.base_url + "custom-events/"
62+
self.tag_lists_url = self.base_url + "tag-lists/"
6263

6364
def get(self, endpoint: str) -> str:
6465
url: str = getattr(self, endpoint)

urbanairship/devices/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
from .sms import Sms, KeywordInteraction, SmsCustomResponse
1010
from .static_lists import StaticList, StaticLists
1111
from .subscription_lists import SubscriptionList
12+
from .tag_lists import TagList

urbanairship/devices/static_lists.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import gzip
33
import collections
44
import datetime
5-
from typing import Dict, TypeVar, Optional, Type
5+
from typing import Dict, Optional
66
from io import TextIOWrapper
77

88
from requests import Response

urbanairship/devices/tag_lists.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import json
2+
from typing import Dict, Optional, Any, List
3+
from io import TextIOWrapper
4+
from unicodedata import name
5+
6+
from requests import Response
7+
8+
from urbanairship import Airship
9+
from urbanairship.devices.static_lists import GzipCompressReadStream
10+
11+
12+
class TagList:
13+
"""Create, Upload, Delete, and get information for a tag list.
14+
Please see the Airship API documentation for more information
15+
about CSV formatting, limits, and use of this feature.
16+
17+
;param airship: Required. An urbanairship.Airship instance.
18+
:param list_name: Required. A name for the list this instance represents.
19+
:param description: Optional. A description of the list.
20+
:param extra: Optional. A a dictionary of string mappings associated with the list.
21+
:param add_tags: Optional. A dictionary consisting of a tag group string and list of tag
22+
string to add to uploaded channels.
23+
:param remove_tags: Optional. A dictionary consisting of a tag group string and list of
24+
tag strings to remove from uploaded channels.
25+
:param set_tags: Optional. A dictionary consisting of a tag group string and list of tag
26+
strings to set on uploaded channels. Warning: This action is destructive and will
27+
remove all existing tags associated with channels.
28+
"""
29+
30+
def __init__(
31+
self,
32+
airship: Airship,
33+
list_name: str,
34+
description: Optional[str] = None,
35+
extra: Optional[Dict[str, str]] = None,
36+
add_tags: Optional[Dict[str, List[str]]] = None,
37+
remove_tags: Optional[Dict[str, List[str]]] = None,
38+
set_tags: Optional[Dict[str, List[str]]] = None,
39+
) -> None:
40+
self.airship = airship
41+
self.list_name = list_name
42+
self.description = description
43+
self.extra = extra
44+
self.add_tags = add_tags
45+
self.remove_tags = remove_tags
46+
self.set_tags = set_tags
47+
48+
@property
49+
def _create_payload(self) -> Dict:
50+
payload: Dict[str, Any] = {"name": self.list_name}
51+
52+
if self.description:
53+
payload["description"] = self.description
54+
if self.extra:
55+
payload["extra"] = self.extra
56+
if self.add_tags:
57+
payload["add"] = self.add_tags
58+
if self.remove_tags:
59+
payload["remove"] = self.remove_tags
60+
if self.set_tags:
61+
payload["set"] = self.set_tags
62+
63+
return payload
64+
65+
def create(self) -> Response:
66+
"""Create a new tag list. Channels must be uploaded after creation using
67+
the `upload` method.
68+
69+
:return: Response object
70+
"""
71+
response = self.airship.request(
72+
method="POST",
73+
url=self.airship.urls.get("tag_lists_url"),
74+
body=json.dumps(self._create_payload),
75+
content_type="application/json",
76+
version=3,
77+
)
78+
79+
return response
80+
81+
def upload(self, file_path: str) -> Response:
82+
"""Upload a CSV file of channels. See the Airship API documentation
83+
for information about CSV file formatting requirements and limits.
84+
85+
:param file_path: Path to the CSV file to upload.
86+
87+
:return: Response object
88+
"""
89+
with open(file_path, "rb") as open_file:
90+
response = self.airship.request(
91+
method="PUT",
92+
body=GzipCompressReadStream(open_file),
93+
url=f"{self.airship.urls.get('tag_lists_url')}/{self.list_name}/csv",
94+
content_type="text/csv",
95+
version=3,
96+
encoding="gzip",
97+
)
98+
return response
99+
100+
def get_errors(self) -> Response:
101+
"""Returns a csv of tag list processing errors.
102+
103+
:return: Response object
104+
"""
105+
response = self.airship.request(
106+
method="GET",
107+
body={},
108+
url=f"{self.airship.urls.get('tag_lists_url')}/{self.list_name}/errors",
109+
version=3,
110+
)
111+
return response
112+
113+
@classmethod
114+
def list(cls, airship: Airship) -> Response:
115+
"""Returns a json string with details on all tag lists associated with
116+
an Airship instance / project.
117+
118+
:return: Response object
119+
"""
120+
response = airship.request(
121+
method="GET", body={}, url=airship.urls.get("tag_lists_url"), version=3
122+
)
123+
return response

0 commit comments

Comments
 (0)