2 This module's purpose is to enable us to present internals of objects
3 in well-defined way to operator. To do this we can define "views"
4 on some objects. View is a definition of how to present object
5 and relations to other objects which also have their views defined.
7 By using views we can avoid making all interesting internal values
8 public. They will stay private and only "view" will access them
9 (think friend-class from C++)
13 from ryu.services.protocols.bgp.operator.views import fields
15 LOG = logging.getLogger('bgpspeaker.operator.views.base')
18 class RdyToFlattenCollection(object):
22 class RdyToFlattenList(list, RdyToFlattenCollection):
26 class RdyToFlattenDict(dict, RdyToFlattenCollection):
30 class OperatorAbstractView(object):
31 """Abstract base class for operator views. It isn't meant to be
35 def __init__(self, obj, filter_func=None):
38 :param obj: data model for view. In other words object we
39 are creating view for. In case of ListView it should be
40 a list and in case of DictView it should be a dict.
41 :param filter_func: function to filter models
43 self._filter_func = filter_func
44 self._fields = self._collect_fields()
48 def _collect_fields(cls):
49 names = [attr for attr in dir(cls)
50 if isinstance(getattr(cls, attr), fields.Field)]
51 return dict([(name, getattr(cls, name)) for name in names])
53 def combine_related(self, field_name):
54 """Combines related views. In case of DetailView it just returns
55 one-element list containing related view wrapped in
58 In case of ListView and DictView it returns a list of related views
59 for every element of model collection also wrapped
60 in CombinedViewsWrapper.
62 :param field_name: field name of related view
63 :returns: vectorized form of related views. You can access them
64 as if you had only one view and you will receive flattened list
65 of responses from related views. Look at docstring of
68 raise NotImplementedError()
70 def c_rel(self, *args, **kwargs):
71 """Shortcut for combine_related. Look above
73 return self.combine_related(*args, **kwargs)
75 def get_field(self, field_name):
76 """Get value of data field.
78 :return: value of data-field of this view
80 raise NotImplementedError()
83 """Representation of view which is using only python standard types.
85 :return: dict representation of this views data. However it
86 doesn't have to be a dict. In case of ListView it would
87 return a list. It should return wrapped types
88 for list - RdyToFlattenList, for dict - RdyToFlattenDict
90 raise NotImplementedError()
94 """Getter for data model being presented by this view. Every view is
95 associated with some data model.
97 :return: underlaying data of this view
99 raise NotImplementedError()
101 def apply_filter(self, filter_func):
102 """Sets filter function to apply on model
104 :param filter_func: function which takes the model and returns it
107 self._filter_func = filter_func
109 def clear_filter(self):
110 self._filter_func = None
113 class OperatorDetailView(OperatorAbstractView):
114 def combine_related(self, field_name):
115 f = self._fields[field_name]
116 return CombinedViewsWrapper([f.retrieve_and_wrap(self._obj)])
118 def get_field(self, field_name):
119 f = self._fields[field_name]
120 return f.get(self._obj)
124 for field_name, field in self._fields.items():
125 if isinstance(field, fields.DataField):
126 encoded[field_name] = field.get(self._obj)
129 def rel(self, field_name):
130 f = self._fields[field_name]
131 return f.retrieve_and_wrap(self._obj)
138 class OperatorListView(OperatorAbstractView):
139 def __init__(self, obj, filter_func=None):
140 assert isinstance(obj, list)
141 obj = RdyToFlattenList(obj)
142 super(OperatorListView, self).__init__(obj, filter_func)
144 def combine_related(self, field_name):
145 f = self._fields[field_name]
146 return CombinedViewsWrapper(RdyToFlattenList(
147 [f.retrieve_and_wrap(obj) for obj in self.model]
150 def get_field(self, field_name):
151 f = self._fields[field_name]
152 return RdyToFlattenList([f.get(obj) for obj in self.model])
156 for obj in self.model:
158 for field_name, field in self._fields.items():
159 if isinstance(field, fields.DataField):
160 encoded_item[field_name] = field.get(obj)
161 encoded_list.append(encoded_item)
162 return RdyToFlattenList(encoded_list)
166 if self._filter_func is not None:
167 return RdyToFlattenList(filter(self._filter_func, self._obj))
172 class OperatorDictView(OperatorAbstractView):
173 def __init__(self, obj, filter_func=None):
174 assert isinstance(obj, dict)
175 obj = RdyToFlattenDict(obj)
176 super(OperatorDictView, self).__init__(obj, filter_func)
178 def combine_related(self, field_name):
179 f = self._fields[field_name]
180 return CombinedViewsWrapper(RdyToFlattenList(
181 [f.retrieve_and_wrap(obj) for obj in self.model.values()])
184 def get_field(self, field_name):
185 f = self._fields[field_name]
187 for key, obj in self.model.items():
188 dict_to_flatten[key] = f.get(obj)
189 return RdyToFlattenDict(dict_to_flatten)
192 outer_dict_to_flatten = {}
193 for key, obj in self.model.items():
194 inner_dict_to_flatten = {}
195 for field_name, field in self._fields.items():
196 if isinstance(field, fields.DataField):
197 inner_dict_to_flatten[field_name] = field.get(obj)
198 outer_dict_to_flatten[key] = inner_dict_to_flatten
199 return RdyToFlattenDict(outer_dict_to_flatten)
203 if self._filter_func is not None:
204 new_model = RdyToFlattenDict()
205 for k, v in self._obj.items():
206 if self._filter_func(k, v):
213 class CombinedViewsWrapper(RdyToFlattenList):
214 """List-like wrapper for views. It provides same interface as any other
215 views but enables as to access all views in bulk.
216 It wraps and return responses from all views as a list. Be aware that
217 in case of DictViews wrapped in CombinedViewsWrapper you loose
218 information about dict keys.
221 def __init__(self, obj):
222 super(CombinedViewsWrapper, self).__init__(obj)
225 def combine_related(self, field_name):
226 return CombinedViewsWrapper(
228 [obj.combine_related(field_name) for obj in self._obj]
232 def c_rel(self, *args, **kwargs):
233 return self.combine_related(*args, **kwargs)
236 return list(_flatten([obj.encode() for obj in self._obj]))
238 def get_field(self, field_name):
239 return list(_flatten([obj.get_field(field_name) for obj in self._obj]))
243 return list(_flatten([obj.model for obj in self._obj]))
245 def apply_filter(self, filter_func):
246 for obj in self._obj:
247 obj.apply_filter(filter_func)
249 def clear_filter(self):
250 for obj in self._obj:
254 def _flatten(l, max_level=10):
255 """Generator function going deep in tree-like structures
256 (i.e. dicts in dicts or lists in lists etc.) and returning all elements as
257 a flat list. It's flattening only lists and dicts which are subclasses of
258 RdyToFlattenCollection. Regular lists and dicts are treated as a
261 :param l: some iterable to be flattened
262 :return: flattened iterator
265 _iter = l.values() if isinstance(l, dict) else l
267 if isinstance(el, RdyToFlattenCollection):
268 for sub in _flatten(el, max_level=max_level - 1):
276 def _create_collection_view(detail_view_class, name, encode=None,
278 assert issubclass(detail_view_class, OperatorDetailView)
279 class_fields = detail_view_class._collect_fields()
280 if encode is not None:
281 class_fields.update({'encode': encode})
282 return type(name, (view_class,), class_fields)
285 # function creating ListView from DetailView
286 def create_dict_view_class(detail_view_class, name):
288 if 'encode' in dir(detail_view_class):
291 for key, obj in self.model.items():
292 dict_to_flatten[key] = detail_view_class(obj).encode()
293 return RdyToFlattenDict(dict_to_flatten)
295 return _create_collection_view(
296 detail_view_class, name, encode, OperatorDictView
300 # function creating DictView from DetailView
301 def create_list_view_class(detail_view_class, name):
303 if 'encode' in dir(detail_view_class):
305 return RdyToFlattenList([detail_view_class(obj).encode()
306 for obj in self.model])
308 return _create_collection_view(
309 detail_view_class, name, encode, OperatorListView