On Tue, Jul 02, 2013 at 11:13:42AM +0900, YAMAMOTO Takashi wrote:
> 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 = ','

','.join("%s=%s" % (k, repr(v)) for k, v in self.stringify_attrs())


> +        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
> 

-- 
yamahata

------------------------------------------------------------------------------
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