aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pandora/models/__init__.py20
-rw-r--r--pandora/models/pandora.py12
-rw-r--r--tests/test_pandora/test_models.py25
3 files changed, 41 insertions, 16 deletions
diff --git a/pandora/models/__init__.py b/pandora/models/__init__.py
index 5b25de2..141a64b 100644
--- a/pandora/models/__init__.py
+++ b/pandora/models/__init__.py
@@ -3,7 +3,7 @@ from collections import namedtuple
3from ..py2compat import with_metaclass 3from ..py2compat import with_metaclass
4 4
5 5
6class Field(namedtuple("Field", ["field", "default", "formatter"])): 6class Field(namedtuple("Field", ["field", "default", "formatter", "model"])):
7 """Model Field 7 """Model Field
8 8
9 Model fields represent JSON key/value pairs. When added to a PandoraModel 9 Model fields represent JSON key/value pairs. When added to a PandoraModel
@@ -26,8 +26,8 @@ class Field(namedtuple("Field", ["field", "default", "formatter"])):
26 model based on the type of data in the JSON 26 model based on the type of data in the JSON
27 """ 27 """
28 28
29 def __new__(cls, field, default=None, formatter=None): 29 def __new__(cls, field, default=None, formatter=None, model=None):
30 return super(Field, cls).__new__(cls, field, default, formatter) 30 return super(Field, cls).__new__(cls, field, default, formatter, model)
31 31
32 32
33class SyntheticField(namedtuple("SyntheticField", ["field"])): 33class SyntheticField(namedtuple("SyntheticField", ["field"])):
@@ -39,8 +39,6 @@ class SyntheticField(namedtuple("SyntheticField", ["field"])):
39 payload. 39 payload.
40 """ 40 """
41 41
42 default = None
43
44 @staticmethod 42 @staticmethod
45 def formatter(api_client, field, data): # pragma: no cover 43 def formatter(api_client, field, data): # pragma: no cover
46 """Format Value for Model 44 """Format Value for Model
@@ -100,7 +98,7 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)):
100 safe_types = (type(None), str, bytes, int, bool) 98 safe_types = (type(None), str, bytes, int, bool)
101 99
102 for key, value in self._fields.items(): 100 for key, value in self._fields.items():
103 default = value.default 101 default = getattr(value, "default", None)
104 102
105 if not isinstance(default, safe_types): 103 if not isinstance(default, safe_types):
106 default = type(default)() 104 default = type(default)()
@@ -117,13 +115,21 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)):
117 this function runs even if they are missing from the incoming JSON. 115 this function runs even if they are missing from the incoming JSON.
118 """ 116 """
119 for key, value in instance.__class__._fields.items(): 117 for key, value in instance.__class__._fields.items():
120 newval = data.get(value.field, value.default) 118 default = getattr(value, "default", None)
119 newval = data.get(value.field, default)
121 120
122 if isinstance(value, SyntheticField): 121 if isinstance(value, SyntheticField):
123 newval = value.formatter(api_client, value.field, data, newval) 122 newval = value.formatter(api_client, value.field, data, newval)
124 setattr(instance, key, newval) 123 setattr(instance, key, newval)
125 continue 124 continue
126 125
126 model_class = getattr(value, "model", None)
127 if newval and model_class:
128 if isinstance(newval, list):
129 newval = model_class.from_json_list(api_client, newval)
130 else:
131 newval = model_class.from_json(api_client, newval)
132
127 if newval and value.formatter: 133 if newval and value.formatter:
128 newval = value.formatter(api_client, newval) 134 newval = value.formatter(api_client, newval)
129 135
diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py
index d7d2bf1..71a1d51 100644
--- a/pandora/models/pandora.py
+++ b/pandora/models/pandora.py
@@ -253,8 +253,8 @@ class Bookmark(PandoraModel):
253 253
254class BookmarkList(PandoraModel): 254class BookmarkList(PandoraModel):
255 255
256 songs = Field("songs", formatter=Bookmark.from_json_list) 256 songs = Field("songs", model=Bookmark)
257 artists = Field("artists", formatter=Bookmark.from_json_list) 257 artists = Field("artists", model=Bookmark)
258 258
259 259
260class SearchResultItem(PandoraModel): 260class SearchResultItem(PandoraModel):
@@ -344,11 +344,9 @@ class SearchResult(PandoraModel):
344 344
345 nearest_matches_available = Field("nearMatchesAvailable") 345 nearest_matches_available = Field("nearMatchesAvailable")
346 explanation = Field("explanation") 346 explanation = Field("explanation")
347 songs = Field("songs", formatter=SongSearchResultItem.from_json_list) 347 songs = Field("songs", model=SongSearchResultItem)
348 artists = Field("artists", formatter=ArtistSearchResultItem.from_json_list) 348 artists = Field("artists", model=ArtistSearchResultItem)
349 genre_stations = Field( 349 genre_stations = Field("genreStations", model=GenreStationSearchResultItem)
350 "genreStations",
351 formatter=GenreStationSearchResultItem.from_json_list)
352 350
353 351
354class GenreStationList(PandoraDictListModel): 352class GenreStationList(PandoraDictListModel):
diff --git a/tests/test_pandora/test_models.py b/tests/test_pandora/test_models.py
index 914f09e..1310d71 100644
--- a/tests/test_pandora/test_models.py
+++ b/tests/test_pandora/test_models.py
@@ -37,15 +37,26 @@ class TestModelMetaClass(TestCase):
37 37
38class TestPandoraModel(TestCase): 38class TestPandoraModel(TestCase):
39 39
40 JSON_DATA = {"field2": ["test2"], "field3": 41} 40 JSON_DATA = {
41 "field2": ["test2"],
42 "field3": 41,
43 "field4": {"field1": "foo"},
44 "field5": [{"field1": "foo"}, {"field1": "bar"}],
45 }
41 46
42 class TestModel(m.PandoraModel): 47 class TestModel(m.PandoraModel):
43 48
49 class SubModel(m.PandoraModel):
50
51 field1 = m.Field("field1")
52
44 THE_LIST = [] 53 THE_LIST = []
45 54
46 field1 = m.Field("field1", default="a string") 55 field1 = m.Field("field1", default="a string")
47 field2 = m.Field("field2", default=THE_LIST) 56 field2 = m.Field("field2", default=THE_LIST)
48 field3 = m.Field("field3", formatter=lambda c, x: x + 1) 57 field3 = m.Field("field3", formatter=lambda c, x: x + 1)
58 field4 = m.Field("field4", model=SubModel)
59 field5 = m.Field("field5", model=SubModel)
49 60
50 class NoFieldsModel(m.PandoraModel): 61 class NoFieldsModel(m.PandoraModel):
51 pass 62 pass
@@ -76,6 +87,14 @@ class TestPandoraModel(TestCase):
76 self.assertEqual("a string", result.field1) 87 self.assertEqual("a string", result.field1)
77 self.assertEqual(["test2"], result.field2) 88 self.assertEqual(["test2"], result.field2)
78 89
90 def test_it_creates_sub_models(self):
91 result = self.TestModel.from_json(None, self.JSON_DATA)
92 self.assertIsInstance(result.field4, self.TestModel.SubModel)
93 self.assertEqual("foo", result.field4.field1)
94 self.assertEqual(2, len(result.field5))
95 self.assertEqual("foo", result.field5[0].field1)
96 self.assertEqual("bar", result.field5[1].field1)
97
79 def test_populate_fields_calls_formatter(self): 98 def test_populate_fields_calls_formatter(self):
80 result = self.TestModel.from_json(None, self.JSON_DATA) 99 result = self.TestModel.from_json(None, self.JSON_DATA)
81 self.assertEqual(42, result.field3) 100 self.assertEqual(42, result.field3)
@@ -87,7 +106,9 @@ class TestPandoraModel(TestCase):
87 self.assertEqual("a string", result[1].field1) 106 self.assertEqual("a string", result[1].field1)
88 107
89 def test_repr(self): 108 def test_repr(self):
90 expected = "TestModel(field1='a string', field2=['test2'], field3=42)" 109 expected = ("TestModel(field1='a string', field2=['test2'], field3=42,"
110 " field4=SubModel(field1='foo'), "
111 "field5=[SubModel(field1='foo'), SubModel(field1='bar')])")
91 result = self.TestModel.from_json(None, self.JSON_DATA) 112 result = self.TestModel.from_json(None, self.JSON_DATA)
92 self.assertEqual(expected, repr(result)) 113 self.assertEqual(expected, repr(result))
93 114