Skip to content

Commit 89d3168

Browse files
authored
[TOOLSLIBS-447] Attribute lists (#183)
* wires up AttributeList class * adds attr list class * adds attr list url * adds test data and test class stub * adds attribute list tests * adds static list download classmethod * rm name
1 parent 40c2657 commit 89d3168

File tree

7 files changed

+182
-6
lines changed

7 files changed

+182
-6
lines changed

tests/data/attribute_list.csv

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

tests/devices/test_attributes.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import datetime
2-
from time import time
32
import unittest
3+
import mock
4+
import json
5+
6+
import requests
47

58
import urbanairship as ua
69
from tests import TEST_KEY, TEST_SECRET
710

11+
812
class TestAttribute(unittest.TestCase):
913
def setUp(self):
1014
self.test_time = datetime.datetime.utcnow()
@@ -136,8 +140,7 @@ def test_attribute_alone(self):
136140
def test_no_devices(self):
137141
with self.assertRaises(ValueError) as err_ctx:
138142
ua.ModifyAttributes(
139-
airship=self.test_airship,
140-
attributes=[self.set_attribute],
143+
airship=self.test_airship, attributes=[self.set_attribute]
141144
)
142145

143146
self.assertEqual(
@@ -157,3 +160,48 @@ def test_both_devices(self):
157160
err_ctx.message,
158161
"Either channel or named_user must be included, not both",
159162
)
163+
164+
165+
class TestAttributeList(unittest.TestCase):
166+
def setUp(self):
167+
self.airship = ua.Airship(TEST_KEY, TEST_SECRET)
168+
self.list_name = "my_test_list"
169+
self.description = "this is my cool list"
170+
self.extra = {"key": "value"}
171+
self.file_path = "tests/data/attribute_list.csv"
172+
self.attr_list = ua.AttributeList(
173+
airship=self.airship,
174+
list_name=self.list_name,
175+
description=self.description,
176+
extra=self.extra,
177+
)
178+
179+
def test_create(self):
180+
with mock.patch.object(ua.Airship, "_request") as mock_request:
181+
response = requests.Response()
182+
response._content = json.dumps({"ok": True}).encode("UTF-8")
183+
mock_request.return_value = response
184+
185+
result = self.attr_list.create()
186+
187+
self.assertEqual(result.json(), {"ok": True})
188+
189+
def test_upload(self):
190+
with mock.patch.object(ua.Airship, "_request") as mock_request:
191+
response = requests.Response()
192+
response._content = json.dumps({"ok": True}).encode("UTF-8")
193+
mock_request.return_value = response
194+
195+
result = self.attr_list.upload(file_path=self.file_path)
196+
197+
self.assertEqual(result.json(), {"ok": True})
198+
199+
def test_create_payload_property(self):
200+
self.assertEqual(
201+
self.attr_list._create_payload,
202+
{
203+
"name": self.list_name,
204+
"description": self.description,
205+
"extra": self.extra,
206+
},
207+
)

urbanairship/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
APIDList,
99
Attribute,
1010
AttributeResponse,
11+
AttributeList,
1112
ChannelInfo,
1213
ChannelList,
1314
ChannelTags,
@@ -210,6 +211,7 @@
210211
OpenChannelTags,
211212
Attribute,
212213
AttributeResponse,
214+
AttributeList,
213215
ModifyAttributes,
214216
WebResponseReport,
215217
ExperimentReport,

urbanairship/core.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__(self, location=None):
3434
self.lists_url = self.base_url + "lists/"
3535
self.location_url = self.base_url + "location/"
3636
self.attributes_url = self.channel_url + "attributes/"
37+
self.attributes_list_url = self.base_url + "attribute-lists/"
3738
self.message_center_delete_url = self.base_url + "user/messages/"
3839
self.subscription_lists_url = self.channel_url + "subscription_lists/"
3940

@@ -131,8 +132,17 @@ def secret(self, value):
131132
raise ValueError("secrets must be 22 characters")
132133
self._secret = value
133134

134-
def request(self, method, body, url, content_type=None, version=None, params=None):
135-
return self._request(method, body, url, content_type, version, params)
135+
def request(
136+
self,
137+
method,
138+
body,
139+
url,
140+
content_type=None,
141+
version=None,
142+
params=None,
143+
encoding=None,
144+
):
145+
return self._request(method, body, url, content_type, version, params, encoding)
136146

137147
def _request(
138148
self,

urbanairship/devices/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .tag import ChannelTags, OpenChannelTags
2-
from .attributes import Attribute, AttributeResponse, ModifyAttributes
2+
from .attributes import Attribute, AttributeResponse, ModifyAttributes, AttributeList
33
from .channel_uninstall import ChannelUninstall
44
from .devicelist import APIDList, ChannelInfo, ChannelList, DeviceInfo, DeviceTokenList
55
from .email import Email, EmailAttachment, EmailTags

urbanairship/devices/attributes.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from datetime import datetime
44
import re
55

6+
from .static_lists import GzipCompressReadStream
7+
68
logger = logging.getLogger("urbanairship")
79

810

@@ -146,6 +148,96 @@ def send(self):
146148
return AttributeResponse(response=response)
147149

148150

151+
class AttributeList(object):
152+
"""
153+
Define and manage attribute lists; upload corresponding attribute data in CSV format.
154+
155+
:param airship: Required. An unbanairship.Airship instance.
156+
:param list_name: Required. The name of your list. Must be prefixed
157+
with "ua_attributes_"
158+
:param description: Required. A description of your list.
159+
:param extra: Optional. An optional dict of up to 100 key-value (string-to-string)
160+
pairs associated with the list.
161+
"""
162+
163+
def __init__(self, airship, list_name, description, extra=None):
164+
self.airship = airship
165+
self.list_name = list_name
166+
self.description = description
167+
self.extra = extra
168+
169+
@property
170+
def _create_payload(self):
171+
payload = {"name": self.list_name, "description": self.description}
172+
173+
if self.extra:
174+
payload["extra"] = self.extra
175+
176+
return payload
177+
178+
def create(self):
179+
response = self.airship.request(
180+
method="POST",
181+
url=self.airship.urls.get("attributes_list_url"),
182+
body=json.dumps(self._create_payload),
183+
content_type="application/json",
184+
version=3,
185+
)
186+
187+
return response
188+
189+
def upload(self, file_path):
190+
"""
191+
Upload a CSV that will set attribute values on the specified channels or
192+
named users. Please see the documentation at
193+
https://docs.airship.com/api/ua/#operation-api-attribute-lists-list_name-csv-put
194+
for details about list formatting, size limits, and error responses.
195+
196+
:param file_path: Required. Local path to the csv file to be uploaded.
197+
"""
198+
with open(file_path, "rb") as open_file:
199+
response = self.airship._request(
200+
method="PUT",
201+
body=GzipCompressReadStream(open_file),
202+
url=self.airship.urls.get("attributes_list_url")
203+
+ self.list_name
204+
+ "/csv/",
205+
content_type="text/csv",
206+
version=3,
207+
encoding="gzip",
208+
)
209+
210+
return response
211+
212+
def get_errors(self):
213+
"""
214+
Returns csv of attribute list processing errors. During processing, after a
215+
list is uploaded, errors can occur. Depending on the type of list
216+
processing, an error file may be created, showing a user exactly what
217+
went wrong.
218+
"""
219+
response = self.airship.request(
220+
method="GET",
221+
body={},
222+
url=self.airship.urls.get("attributes_list_url")
223+
+ self.list_name
224+
+ "/errors/",
225+
)
226+
227+
return response
228+
229+
@classmethod
230+
def list(cls, airship):
231+
response = airship._request(
232+
method="GET",
233+
url=airship.urls.get("attributes_list_url"),
234+
body={},
235+
version=3,
236+
)
237+
238+
return response
239+
240+
149241
class AttributeResponse(object):
150242
def __init__(self, response):
151243
self.response = response

urbanairship/devices/static_lists.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ def update(self):
7676

7777
return response.json()
7878

79+
@classmethod
80+
def download(cls, airship, list_name):
81+
"""
82+
Allows you to download the contents of a static list. Alias and named_user
83+
values are resolved to channels.
84+
85+
:param airship: Required. An urbanairship.Airship instance.
86+
:param list_name: Required. Name of an existing list to download.
87+
88+
:return: csv list data
89+
"""
90+
response = airship._request(
91+
method="GET",
92+
url=airship.urls.get("lists_url") + list_name + "/csv/",
93+
body={},
94+
)
95+
96+
return response
97+
7998
@classmethod
8099
def from_payload(cls, payload, airship):
81100
obj = cls(airship, payload["name"])

0 commit comments

Comments
 (0)