backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / lib / stringify.py
1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
2 # Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co jp>
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #    http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 from __future__ import print_function
18
19 import base64
20 import inspect
21
22 import six
23
24 # Some arguments to __init__ is mangled in order to avoid name conflicts
25 # with builtin names.
26 # The standard mangling is to append '_' in order to avoid name clashes
27 # with reserved keywords.
28 #
29 # PEP8:
30 # Function and method arguments
31 #   If a function argument's name clashes with a reserved keyword,
32 #   it is generally better to append a single trailing underscore
33 #   rather than use an abbreviation or spelling corruption. Thus
34 #   class_ is better than clss. (Perhaps better is to avoid such
35 #   clashes by using a synonym.)
36 #
37 # grep __init__ *.py | grep '[^_]_\>' showed that
38 # 'len', 'property', 'set', 'type'
39 # A bit more generic way is adopted
40
41 _RESERVED_KEYWORD = dir(six.moves.builtins)
42
43 _mapdict = lambda f, d: dict([(k, f(v)) for k, v in d.items()])
44 _mapdict_key = lambda f, d: dict([(f(k), v) for k, v in d.items()])
45 _mapdict_kv = lambda f, d: dict([(k, f(k, v)) for k, v in d.items()])
46
47
48 class TypeDescr(object):
49     pass
50
51
52 class AsciiStringType(TypeDescr):
53     @staticmethod
54     def encode(v):
55         # TODO: AsciiStringType data should probably be stored as
56         # text_type in class data.  This isinstance() check exists
57         # because OFPDescStats violates this.
58         if six.PY3 and isinstance(v, six.text_type):
59             return v
60         return six.text_type(v, 'ascii')
61
62     @staticmethod
63     def decode(v):
64         if six.PY3:
65             return v
66         return v.encode('ascii')
67
68
69 class Utf8StringType(TypeDescr):
70     @staticmethod
71     def encode(v):
72         return six.text_type(v, 'utf-8')
73
74     @staticmethod
75     def decode(v):
76         return v.encode('utf-8')
77
78
79 class AsciiStringListType(TypeDescr):
80     @staticmethod
81     def encode(v):
82         return [AsciiStringType.encode(x) for x in v]
83
84     @staticmethod
85     def decode(v):
86         return [AsciiStringType.decode(x) for x in v]
87
88
89 class NXFlowSpecFieldType(TypeDescr):
90     # ("field_name", 0) <-> ["field_name", 0]
91
92     @staticmethod
93     def encode(v):
94         if not isinstance(v, tuple):
95             return v
96         field, ofs = v
97         return [field, ofs]
98
99     @staticmethod
100     def decode(v):
101         if not isinstance(v, list):
102             return v
103         field, ofs = v
104         return field, ofs
105
106
107 _types = {
108     'ascii': AsciiStringType,
109     'utf-8': Utf8StringType,
110     'asciilist': AsciiStringListType,
111     'nx-flow-spec-field': NXFlowSpecFieldType,  # XXX this should not be here
112 }
113
114
115 class StringifyMixin(object):
116
117     _TYPE = {}
118     """_TYPE class attribute is used to annotate types of attributes.
119
120     This type information is used to find an appropriate conversion for
121     a JSON style dictionary.
122
123     Currently the following types are implemented.
124
125     ========= =============
126     Type      Description
127     ========= =============
128     ascii     US-ASCII
129     utf-8     UTF-8
130     asciilist list of ascii
131     ========= =============
132
133     Example::
134         _TYPE = {
135             'ascii': [
136                 'hw_addr',
137             ],
138             'utf-8': [
139                 'name',
140             ]
141         }
142     """
143
144     _class_prefixes = []
145     _class_suffixes = []
146
147     # List of attributes ignored in the str and json representations.
148     _base_attributes = []
149
150     # Optional attributes included in the str and json representations.
151     # e.g.) In case of attributes are property, the attributes will be
152     # skipped in the str and json representations.
153     # Then, please specify the attributes into this list.
154     _opt_attributes = []
155
156     def stringify_attrs(self):
157         """an override point for sub classes"""
158         return obj_python_attrs(self)
159
160     def __str__(self):
161         # repr() to escape binaries
162         return self.__class__.__name__ + '(' + \
163             ','.join("%s=%s" % (k, repr(v)) for k, v in
164                      self.stringify_attrs()) + ')'
165     __repr__ = __str__  # note: str(list) uses __repr__ for elements
166
167     @classmethod
168     def _is_class(cls, dict_):
169         # we distinguish a dict like OFPSwitchFeatures.ports
170         # from OFPxxx classes using heuristics.
171         # Examples of OFP classes:
172         #   {"OFPMatch": { ... }}
173         #   {"MTIPv6SRC": { ... }}
174         assert isinstance(dict_, dict)
175         if len(dict_) != 1:
176             return False
177         k = list(dict_.keys())[0]
178         if not isinstance(k, (bytes, six.text_type)):
179             return False
180         for p in cls._class_prefixes:
181             if k.startswith(p):
182                 return True
183         for p in cls._class_suffixes:
184             if k.endswith(p):
185                 return True
186         return False
187
188     @classmethod
189     def _get_type(cls, k):
190         if hasattr(cls, '_TYPE'):
191             for t, attrs in cls._TYPE.items():
192                 if k in attrs:
193                     return _types[t]
194         return None
195
196     @classmethod
197     def _get_encoder(cls, k, encode_string):
198         t = cls._get_type(k)
199         if t:
200             return t.encode
201         return cls._get_default_encoder(encode_string)
202
203     @classmethod
204     def _encode_value(cls, k, v, encode_string=base64.b64encode):
205         return cls._get_encoder(k, encode_string)(v)
206
207     @classmethod
208     def _get_default_encoder(cls, encode_string):
209         def _encode(v):
210             if isinstance(v, (bytes, six.text_type)):
211                 if isinstance(v, six.text_type):
212                     v = v.encode('utf-8')
213                 json_value = encode_string(v)
214                 if six.PY3:
215                     json_value = json_value.decode('ascii')
216             elif isinstance(v, list):
217                 json_value = [_encode(ve) for ve in v]
218             elif isinstance(v, dict):
219                 json_value = _mapdict(_encode, v)
220                 # while a python dict key can be any hashable object,
221                 # a JSON object key should be a string.
222                 json_value = _mapdict_key(str, json_value)
223                 assert not cls._is_class(json_value)
224             else:
225                 try:
226                     json_value = v.to_jsondict()
227                 except Exception:
228                     json_value = v
229             return json_value
230         return _encode
231
232     def to_jsondict(self, encode_string=base64.b64encode):
233         """
234         This method returns a JSON style dict to describe this object.
235
236         The returned dict is compatible with json.dumps() and json.loads().
237
238         Suppose ClassName object inherits StringifyMixin.
239         For an object like the following::
240
241             ClassName(Param1=100, Param2=200)
242
243         this method would produce::
244
245             { "ClassName": {"Param1": 100, "Param2": 200} }
246
247         This method takes the following arguments.
248
249         .. tabularcolumns:: |l|L|
250
251         =============  =====================================================
252         Argument       Description
253         =============  =====================================================
254         encode_string  (Optional) specify how to encode attributes which has
255                        python 'str' type.
256                        The default is base64.
257                        This argument is used only for attributes which don't
258                        have explicit type annotations in _TYPE class attribute.
259         =============  =====================================================
260         """
261         dict_ = {}
262         encode = lambda key, val: self._encode_value(key, val, encode_string)
263         for k, v in obj_attrs(self):
264             dict_[k] = encode(k, v)
265         return {self.__class__.__name__: dict_}
266
267     @classmethod
268     def cls_from_jsondict_key(cls, k):
269         # find a class with the given name from our class' module.
270         import sys
271         mod = sys.modules[cls.__module__]
272         return getattr(mod, k)
273
274     @classmethod
275     def obj_from_jsondict(cls, jsondict, **additional_args):
276         assert len(jsondict) == 1
277         for k, v in jsondict.items():
278             obj_cls = cls.cls_from_jsondict_key(k)
279             return obj_cls.from_jsondict(v, **additional_args)
280
281     @classmethod
282     def _get_decoder(cls, k, decode_string):
283         t = cls._get_type(k)
284         if t:
285             return t.decode
286         return cls._get_default_decoder(decode_string)
287
288     @classmethod
289     def _decode_value(cls, k, json_value, decode_string=base64.b64decode,
290                       **additional_args):
291         # Note: To avoid passing redundant arguments (e.g. 'datapath' for
292         # non OFP classes), we omit '**additional_args' here.
293         return cls._get_decoder(k, decode_string)(json_value)
294
295     @classmethod
296     def _get_default_decoder(cls, decode_string):
297         def _decode(json_value, **additional_args):
298             if isinstance(json_value, (bytes, six.text_type)):
299                 v = decode_string(json_value)
300             elif isinstance(json_value, list):
301                 v = [_decode(jv) for jv in json_value]
302             elif isinstance(json_value, dict):
303                 if cls._is_class(json_value):
304                     v = cls.obj_from_jsondict(json_value, **additional_args)
305                 else:
306                     v = _mapdict(_decode, json_value)
307                     # XXX: Hack
308                     # try to restore integer keys used by
309                     # OFPSwitchFeatures.ports.
310                     try:
311                         v = _mapdict_key(int, v)
312                     except ValueError:
313                         pass
314             else:
315                 v = json_value
316             return v
317         return _decode
318
319     @staticmethod
320     def _restore_args(dict_):
321         def restore(k):
322             if k in _RESERVED_KEYWORD:
323                 return k + '_'
324             return k
325         return _mapdict_key(restore, dict_)
326
327     @classmethod
328     def from_jsondict(cls, dict_, decode_string=base64.b64decode,
329                       **additional_args):
330         r"""Create an instance from a JSON style dict.
331
332         Instantiate this class with parameters specified by the dict.
333
334         This method takes the following arguments.
335
336         .. tabularcolumns:: |l|L|
337
338         =============== =====================================================
339         Argument        Descrpition
340         =============== =====================================================
341         dict\_          A dictionary which describes the parameters.
342                         For example, {"Param1": 100, "Param2": 200}
343         decode_string   (Optional) specify how to decode strings.
344                         The default is base64.
345                         This argument is used only for attributes which don't
346                         have explicit type annotations in _TYPE class
347                         attribute.
348         additional_args (Optional) Additional kwargs for constructor.
349         =============== =====================================================
350         """
351         decode = lambda k, x: cls._decode_value(k, x, decode_string,
352                                                 **additional_args)
353         kwargs = cls._restore_args(_mapdict_kv(decode, dict_))
354         try:
355             return cls(**dict(kwargs, **additional_args))
356         except TypeError:
357             # debug
358             print("CLS %s" % cls)
359             print("ARG %s" % dict_)
360             print("KWARG %s" % kwargs)
361             raise
362
363     @classmethod
364     def set_classes(cls, registered_dict):
365         cls._class_prefixes.extend([v.__name__ for v in
366                                     registered_dict.values()])
367
368
369 def obj_python_attrs(msg_):
370     """iterate object attributes for stringify purposes
371     """
372
373     # a special case for namedtuple which seems widely used in
374     # ofp parser implementations.
375     if hasattr(msg_, '_fields'):
376         for k in msg_._fields:
377             yield(k, getattr(msg_, k))
378         return
379     base = getattr(msg_, '_base_attributes', [])
380     opt = getattr(msg_, '_opt_attributes', [])
381     for k, v in inspect.getmembers(msg_):
382         if k in opt:
383             pass
384         elif k.startswith('_'):
385             continue
386         elif callable(v):
387             continue
388         elif k in base:
389             continue
390         elif hasattr(msg_.__class__, k):
391             continue
392         yield (k, v)
393
394
395 def obj_attrs(msg_):
396     """similar to obj_python_attrs() but deals with python reserved keywords
397     """
398
399     if isinstance(msg_, StringifyMixin):
400         itr = msg_.stringify_attrs()
401     else:
402         # probably called by msg_str_attr
403         itr = obj_python_attrs(msg_)
404     for k, v in itr:
405         if k.endswith('_') and k[:-1] in _RESERVED_KEYWORD:
406             # XXX currently only StringifyMixin has restoring logic
407             assert isinstance(msg_, StringifyMixin)
408             k = k[:-1]
409         yield (k, v)