factor out guts of StringifyMixin into a separate module.
add methods to convert to/from json.loads/dumps-compatible dictionary.
this is mainly for json representation of of-wire (OFPxxx) classes.

Signed-off-by: YAMAMOTO Takashi <[email protected]>
---
 ryu/lib/stringify.py          | 210 ++++++++++++++++++++++++++++++++++++++++++
 ryu/ofproto/ofproto_parser.py |  50 +++++-----
 2 files changed, 236 insertions(+), 24 deletions(-)
 create mode 100644 ryu/lib/stringify.py

diff --git a/ryu/lib/stringify.py b/ryu/lib/stringify.py
new file mode 100644
index 0000000..3e1b41b
--- /dev/null
+++ b/ryu/lib/stringify.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co jp>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import collections
+import inspect
+
+
+# Some arguments to __init__ is mungled in order to avoid name conflicts
+# with builtin names.
+# The standard mangling is to append '_' in order to avoid name clashes
+# with reserved keywords.
+#
+# PEP8:
+# Function and method arguments
+#   If a function argument's name clashes with a reserved keyword,
+#   it is generally better to append a single trailing underscore
+#   rather than use an abbreviation or spelling corruption. Thus
+#   class_ is better than clss. (Perhaps better is to avoid such
+#   clashes by using a synonym.)
+#
+# grep __init__ *.py | grep '[^_]_\>' showed that
+# 'len', 'property', 'set', 'type'
+# A bit more generic way is adopted
+import __builtin__
+_RESERVED_KEYWORD = dir(__builtin__)
+
+
+_mapdict = lambda f, d: dict([(k, f(v)) for k, v in d.items()])
+_mapdict_key = lambda f, d: dict([(f(k), v) for k, v in d.items()])
+
+
+class StringifyMixin(object):
+    _class_prefixes = []
+
+    def stringify_attrs(self):
+        """an override point for sub classes"""
+        return obj_python_attrs(self)
+
+    def __str__(self):
+        buf = ''
+        sep = ''
+        for k, v in self.stringify_attrs():
+            buf += sep
+            buf += "%s=%s" % (k, repr(v))  # repr() to escape binaries
+            sep = ','
+        return self.__class__.__name__ + '(' + buf + ')'
+    __repr__ = __str__  # note: str(list) uses __repr__ for elements
+
+    @classmethod
+    def _is_class(cls, dict_):
+        # we distinguish a dict like OFPSwitchFeatures.ports
+        # from OFPxxx classes using heuristics.
+        # exmples of OFP classes:
+        #   {"OFPMatch": { ... }}
+        #   {"MTIPv6SRC": { ... }}
+        assert isinstance(dict_, dict)
+        if len(dict_) != 1:
+            return False
+        k = dict_.keys()[0]
+        if not isinstance(k, (bytes, unicode)):
+            return False
+        for p in cls._class_prefixes:
+            if k.startswith(p):
+                return True
+        return False
+
+    @classmethod
+    def _encode_value(cls, v, encode_string=base64.b64encode):
+        encode = lambda x: cls._encode_value(x, encode_string)
+        if isinstance(v, (bytes, unicode)):
+            json_value = encode_string(v)
+        elif isinstance(v, list):
+            json_value = map(encode, v)
+        elif isinstance(v, dict):
+            json_value = _mapdict(encode, v)
+            # while a python dict key can be any hashable object,
+            # a JSON object key should be a string.
+            json_value = _mapdict_key(str, json_value)
+            assert not cls._is_class(json_value)
+        else:
+            try:
+                json_value = v.to_jsondict()
+            except:
+                json_value = v
+        return json_value
+
+    def to_jsondict(self, encode_string=base64.b64encode):
+        """returns an object to feed json.dumps()
+        """
+        dict_ = {}
+        encode = lambda x: self._encode_value(x, encode_string)
+        for k, v in obj_attrs(self):
+            dict_[k] = encode(v)
+        return {self.__class__.__name__: dict_}
+
+    @classmethod
+    def cls_from_jsondict_key(cls, k):
+        # find a class with the given name from our class' module.
+        import sys
+        mod = sys.modules[cls.__module__]
+        return getattr(mod, k)
+
+    @classmethod
+    def obj_from_jsondict(cls, jsondict):
+        assert len(jsondict) == 1
+        for k, v in jsondict.iteritems():
+            obj_cls = cls.cls_from_jsondict_key(k)
+            return obj_cls.from_jsondict(v)
+
+    @classmethod
+    def _decode_value(cls, json_value, decode_string=base64.b64decode):
+        decode = lambda x: cls._decode_value(x, decode_string)
+        if isinstance(json_value, (bytes, unicode)):
+            v = decode_string(json_value)
+        elif isinstance(json_value, list):
+            v = map(decode, json_value)
+        elif isinstance(json_value, dict):
+            if cls._is_class(json_value):
+                v = cls.obj_from_jsondict(json_value)
+            else:
+                v = _mapdict(decode, json_value)
+                # XXXhack
+                # try to restore integer keys used by OFPSwitchFeatures.ports.
+                try:
+                    v = _mapdict_key(int, v)
+                except ValueError:
+                    pass
+        else:
+            v = json_value
+        return v
+
+    @staticmethod
+    def _restore_args(dict_):
+        def restore(k):
+            if k in _RESERVED_KEYWORD:
+                return k + '_'
+            return k
+        return _mapdict_key(restore, dict_)
+
+    @classmethod
+    def from_jsondict(cls, dict_, decode_string=base64.b64decode,
+                      **additional_args):
+        """create an instance from a result of json.loads()
+        """
+        decode = lambda x: cls._decode_value(x, decode_string)
+        kwargs = cls._restore_args(_mapdict(decode, dict_))
+        try:
+            return cls(**dict(kwargs, **additional_args))
+        except TypeError:
+            #debug
+            print "CLS", cls
+            print "ARG", dict_
+            print "KWARG", kwargs
+            raise
+
+
+def obj_python_attrs(msg_):
+    """iterate object attributes for stringify purposes
+    """
+
+    # a special case for namedtuple which seems widely used in
+    # ofp parser implementations.
+    if hasattr(msg_, '_fields'):
+        for k in msg_._fields:
+            yield(k, getattr(msg_, k))
+        return
+    base = getattr(msg_, '_base_attributes', [])
+    for k, v in inspect.getmembers(msg_):
+        if k.startswith('_'):
+            continue
+        if callable(v):
+            continue
+        if k in base:
+            continue
+        if hasattr(msg_.__class__, k):
+            continue
+        yield (k, v)
+
+
+def obj_attrs(msg_):
+    """similar to obj_python_attrs() but deals with python reserved keywords
+    """
+
+    if isinstance(msg_, StringifyMixin):
+        iter = msg_.stringify_attrs()
+    else:
+        # probably called by msg_str_attr
+        iter = obj_python_attrs(msg_)
+    for k, v in iter:
+        if k.endswith('_') and k[:-1] in _RESERVED_KEYWORD:
+            # XXX currently only StringifyMixin has restoring logic
+            assert isinstance(msg_, StringifyMixin)
+            k = k[:-1]
+        yield (k, v)
diff --git a/ryu/ofproto/ofproto_parser.py b/ryu/ofproto/ofproto_parser.py
index 73a6d9e..8cb0402 100644
--- a/ryu/ofproto/ofproto_parser.py
+++ b/ryu/ofproto/ofproto_parser.py
@@ -14,12 +14,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import collections
 import logging
 import struct
+import sys
 import functools
-import inspect
 
 from ryu import exception
+from ryu.lib import stringify
 
 from . import ofproto_common
 
@@ -61,16 +63,23 @@ def create_list_of_base_attributes(f):
     return wrapper
 
 
-class StringifyMixin(object):
-    def __str__(self):
-        buf = ''
-        sep = ''
-        for k, v in ofp_attrs(self):
-            buf += sep
-            buf += "%s=%s" % (k, repr(v))  # repr() to escape binaries
-            sep = ','
-        return self.__class__.__name__ + '(' + buf + ')'
-    __repr__ = __str__  # note: str(list) uses __repr__ for elements
+def ofp_msg_from_jsondict(dp, jsondict):
+    parser = dp.ofproto_parser
+    assert len(jsondict) == 1
+    for k, v in jsondict.iteritems():
+        cls = getattr(parser, k)
+        assert issubclass(cls, MsgBase)
+        return cls.from_jsondict(v, datapath=dp)
+
+
+class StringifyMixin(stringify.StringifyMixin):
+    _class_prefixes = ["OFP", "MT"]
+
+    @classmethod
+    def cls_from_jsondict_key(cls, k):
+        obj_cls = super(StringifyMixin, cls).cls_from_jsondict_key(k)
+        assert not issubclass(obj_cls, MsgBase)
+        return obj_cls
 
 
 class MsgBase(StringifyMixin):
@@ -161,23 +170,16 @@ def msg_pack_into(fmt, buf, offset, *args):
     struct.pack_into(fmt, buf, offset, *args)
 
 
-def ofp_attrs(msg_):
-    base = getattr(msg_, '_base_attributes', [])
-    for k, v in inspect.getmembers(msg_):
-        if k.startswith('_'):
-            continue
-        if callable(v):
-            continue
-        if k in base:
-            continue
-        if hasattr(msg_.__class__, k):
-            continue
-        yield (k, v)
+def namedtuple(typename, fields, **kwargs):
+    class _namedtuple(StringifyMixin,
+                      collections.namedtuple(typename, fields, **kwargs)):
+        pass
+    return _namedtuple
 
 
 def msg_str_attr(msg_, buf, attr_list=None):
     if attr_list is None:
-        attr_list = ofp_attrs(msg_)
+        attr_list = stringify.obj_attrs(msg_)
     for attr in attr_list:
         val = getattr(msg_, attr, None)
         if val is not None:
-- 
1.8.1.5


------------------------------------------------------------------------------
This SF.net email is sponsored by Windows:

Build for Windows Store.

http://p.sf.net/sfu/windows-dev2dev
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to