aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2017-10-30 02:20:47 +0000
committerMike Crute <mike@crute.us>2017-10-30 02:27:08 +0000
commit1b20d353dabb63f2c68ee7f5563b5d3f22833a17 (patch)
tree3faa05192fed69a5467c2403a010b0a1765fe80f
parent8d8d3f1418eefe5e0ec179ddd768bb96866ecc8b (diff)
downloadpydora-1b20d353dabb63f2c68ee7f5563b5d3f22833a17.tar.bz2
pydora-1b20d353dabb63f2c68ee7f5563b5d3f22833a17.tar.xz
pydora-1b20d353dabb63f2c68ee7f5563b5d3f22833a17.zip
Add documentation
The complex models don't make sense without a data sample and I always end up looking at the tests. Instead add some docs and a data sample.
-rw-r--r--pandora/models/__init__.py113
1 files changed, 112 insertions, 1 deletions
diff --git a/pandora/models/__init__.py b/pandora/models/__init__.py
index 6a14f45..f799308 100644
--- a/pandora/models/__init__.py
+++ b/pandora/models/__init__.py
@@ -7,13 +7,34 @@ def with_metaclass(meta, *bases):
7 7
8 8
9class Field(namedtuple("Field", ["field", "default", "formatter"])): 9class Field(namedtuple("Field", ["field", "default", "formatter"])):
10 """Model Field
11
12 Model fields represent JSON key/value pairs. When added to a PandoraModel
13 the describe the unpacking logic for the API JSON and will be replaced at
14 runtime with the values from the parsed JSON or their defaults.
15
16 field
17 name of the field from the incoming JSON
18 default
19 default value if key does not exist in the incoming JSON, None if not
20 provided
21 formatter
22 formatter function accepting an API client and the value of the field
23 as arguments, will be called on the value of the data for the field key
24 in the incoming JSON. The return value of this function is used as the
25 value of the field on the model object.
26 model
27 the model class that the value of this field should be constructed into
28 the model construction logic will handle building a list or single
29 model based on the type of data in the JSON
30 """
10 31
11 def __new__(cls, field, default=None, formatter=None): 32 def __new__(cls, field, default=None, formatter=None):
12 return super(Field, cls).__new__(cls, field, default, formatter) 33 return super(Field, cls).__new__(cls, field, default, formatter)
13 34
14 35
15class SyntheticField(namedtuple("SyntheticField", ["field"])): 36class SyntheticField(namedtuple("SyntheticField", ["field"])):
16 """Field That Requires Synthesis 37 """Field Requiring Synthesis
17 38
18 Synthetic fields may exist in the data but generally do not and require 39 Synthetic fields may exist in the data but generally do not and require
19 additional synthesis to arrive ate a sane value. Subclasses must define 40 additional synthesis to arrive ate a sane value. Subclasses must define
@@ -25,6 +46,17 @@ class SyntheticField(namedtuple("SyntheticField", ["field"])):
25 46
26 @staticmethod 47 @staticmethod
27 def formatter(api_client, field, data): # pragma: no cover 48 def formatter(api_client, field, data): # pragma: no cover
49 """Format Value for Model
50
51 The return value of this method is used as a value for the field in the
52 model of which this field is a member
53
54 api_client
55 instance of a Pandora API client
56 data
57 complete JSON data blob for the parent model of which this field is
58 a member
59 """
28 raise NotImplementedError 60 raise NotImplementedError
29 61
30 62
@@ -46,6 +78,14 @@ class ModelMetaClass(type):
46 78
47 79
48class PandoraModel(with_metaclass(ModelMetaClass, object)): 80class PandoraModel(with_metaclass(ModelMetaClass, object)):
81 """Pandora API Model
82
83 A single object representing a Pandora data object. Subclasses are
84 specified declaratively and contain Field objects as well as optionally
85 other methods. The end result object after loading from JSON will be a
86 normal python object with all fields declared in the schema populated and
87 consumers of these instances can ignore all of the details of this class.
88 """
49 89
50 @staticmethod 90 @staticmethod
51 def json_to_date(api_client, data): 91 def json_to_date(api_client, data):
@@ -53,6 +93,8 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)):
53 93
54 @classmethod 94 @classmethod
55 def from_json_list(cls, api_client, data): 95 def from_json_list(cls, api_client, data):
96 """Convert a list of JSON values to a list of models
97 """
56 return [cls.from_json(api_client, item) for item in data] 98 return [cls.from_json(api_client, item) for item in data]
57 99
58 def __init__(self, api_client): 100 def __init__(self, api_client):
@@ -70,6 +112,13 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)):
70 112
71 @staticmethod 113 @staticmethod
72 def populate_fields(api_client, instance, data): 114 def populate_fields(api_client, instance, data):
115 """Populate all fields of a model with data
116
117 Given a model with a PandoraModel superclass will enumerate all
118 declared fields on that model and populate the values of their Field
119 and SyntheticField classes. All declared fields will have a value after
120 this function runs even if they are missing from the incoming JSON.
121 """
73 for key, value in instance.__class__._fields.items(): 122 for key, value in instance.__class__._fields.items():
74 newval = data.get(value.field, value.default) 123 newval = data.get(value.field, value.default)
75 124
@@ -85,11 +134,15 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)):
85 134
86 @classmethod 135 @classmethod
87 def from_json(cls, api_client, data): 136 def from_json(cls, api_client, data):
137 """Convert one JSON value to a model object
138 """
88 self = cls(api_client) 139 self = cls(api_client)
89 PandoraModel.populate_fields(api_client, self, data) 140 PandoraModel.populate_fields(api_client, self, data)
90 return self 141 return self
91 142
92 def _base_repr(self, and_also=None): 143 def _base_repr(self, and_also=None):
144 """Common repr logic for subclasses to hook
145 """
93 items = [ 146 items = [
94 "=".join((key, repr(getattr(self, key)))) 147 "=".join((key, repr(getattr(self, key))))
95 for key in sorted(self._fields.keys())] 148 for key in sorted(self._fields.keys())]
@@ -110,6 +163,31 @@ class PandoraModel(with_metaclass(ModelMetaClass, object)):
110 163
111 164
112class PandoraListModel(PandoraModel, list): 165class PandoraListModel(PandoraModel, list):
166 """Dict-like List of Pandora Models
167
168 Processes a JSON map, expecting a key that contains a list of maps. Will
169 process each item in the list, creating models for each one and a secondary
170 index based on the value in each item. This object behaves like a list and
171 like a dict.
172
173 Example JSON:
174
175 {
176 "__list_key__": [
177 { "__index_key__": "key", "other": "fields" },
178 { "__index_key__": "key", "other": "fields" }
179 ],
180 "other": "fields"
181 }
182
183 __list_key__
184 they key within the parent map containing a list
185 __list_model__
186 model class to use when constructing models for list contents
187 __index_key__
188 key from each object in the model list that will be used as an index
189 within this object
190 """
113 191
114 __list_key__ = None 192 __list_key__ = None
115 __list_model__ = None 193 __list_model__ = None
@@ -159,6 +237,39 @@ class PandoraListModel(PandoraModel, list):
159 237
160 238
161class PandoraDictListModel(PandoraModel, dict): 239class PandoraDictListModel(PandoraModel, dict):
240 """Dict of Models
241
242 Processes a JSON map, expecting a key that contains a list of maps, each of
243 which contain a key and a list of values which are the final models. Will
244 process each item in the list, creating models for each one and storing the
245 constructed models in a map indexed by the dict key. Duplicated sub-maps
246 will be merged into one key for this model.
247
248 Example JSON:
249
250 {
251 "__dict_list_key__": [
252 {
253 "__dict_key__": "key for this model",
254 "__list_key__": [
255 { "model": "fields" },
256 { "model": "fields" }
257 ]
258 }
259 ],
260 "other": "fields"
261 }
262
263 __dict_list_key__
264 the key within the parent map that contains the maps that contain
265 lists of models
266 __dict_key__
267 the key within the nested map that contains the key for this object
268 __list_key__
269 they key within the nested map that contains the list of models
270 __list_model__
271 model class to use when constructing models for list contents
272 """
162 273
163 __dict_list_key__ = None 274 __dict_list_key__ = None
164 __dict_key__ = None 275 __dict_key__ = None