Skip to content

Commit

Permalink
Refactored from_dict and to_dict
Browse files Browse the repository at this point in the history
  • Loading branch information
coretl committed Jul 25, 2016
1 parent c79fbee commit c9baf4e
Show file tree
Hide file tree
Showing 31 changed files with 617 additions and 803 deletions.
16 changes: 5 additions & 11 deletions malcolm/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,16 @@ def put(self, value):
self.put_func(value)

def set_value(self, value, notify=True):
self.value = self.meta.validate(value)
self.on_changed([["value"], self.value], notify)

def to_dict(self):
"""Create ordered dictionary representing class instance"""
return super(Attribute, self).to_dict(meta=self.meta.to_dict())
self.set_endpoint("value", self.meta.validate(value), notify)

@classmethod
def from_dict(cls, name, d):
def from_dict(cls, d, name):
"""Create an Attribute instance from a serialized version of itself
Args:
name (str): Attribute instance name
d (dict): Output of self.to_dict()
"""
meta = Serializable.deserialize("meta", d["meta"])
attribute = cls(name, meta)
attribute.value = d["value"]
return attribute
meta_cls = Serializable.lookup_subclass(d)
meta = meta_cls.from_dict("meta", d.pop("meta"))
return super(Attribute, cls).from_dict(d, name, meta)
33 changes: 16 additions & 17 deletions malcolm/core/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from malcolm.core.notifier import Notifier
from malcolm.core.serializable import Serializable
from malcolm.core.request import Request, Put, Post
from malcolm.core.request import Put, Post
from malcolm.core.response import Return
from malcolm.core.attribute import Attribute
from malcolm.core.method import Method
Expand Down Expand Up @@ -46,7 +46,6 @@ def __init__(self, name):
name (str): Block name e.g. "BL18I:ZEBRA1"
"""
super(Block, self).__init__(name=name)
self.name = name
self.methods = OrderedDict()
self.attributes = OrderedDict()
self.lock = DummyLock()
Expand Down Expand Up @@ -86,27 +85,27 @@ def _where_child_stored(self, child):
elif isinstance(child, Attribute):
return self.attributes

def update(self, change):
"""Update block given a single change.
Delegates to children update methods if possible.
def handle_change(self, change):
"""
Set a given attribute to a new value
Args:
change [[path], new value]: Path to changed element and new value
change(tuple): Attribute path and value e.g. (["value"], 5)
"""
name = change[0][0]
if hasattr(self, name):
# sub-structure exists in block - delegate down
# TODO: handle removal?
getattr(self, name).update([change[0][1:], change[1]])
else:
# sub-structure does not exist - create and add
if len(change[0]) > 1:
raise ValueError("Missing substructure at %s" % name)
child = Serializable.deserialize(name, change[1])
endpoint, value = change
child_name = endpoint[0]
if not hasattr(self, child_name):
# Child doesn't exist, create it
if len(endpoint) > 1:
raise ValueError("Missing substructure at %s" % child_name)
child_cls = Serializable.lookup_subclass(value)
child = child_cls.from_dict(child_name, value)
d = self._where_child_stored(child)
assert d is not None, \
"Change %s deserialized to unknown object %s" % (change, child)
self.add_child(child, d)
else:
# Let super class set child attr
super(Block, self).handle_change(change)

def replace_children(self, children, notify=True):
for method_name in self.methods:
Expand Down
68 changes: 23 additions & 45 deletions malcolm/core/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,22 @@

@Serializable.register_subclass("malcolm:core/Map:1.0")
class Map(Serializable):
# real data stored as attributes
# dictionary type view supported

def __init__(self, meta, d=None):
self.meta = meta
d = {} if d is None else d
for key, value in d.items():
if key in meta.elements:
self.__setattr__(key, value)
setattr(self, key, value)
else:
raise ValueError("%s is not a valid key for given meta" % key)

@property
def endpoints(self):
return [e for e in self.meta.elements if hasattr(self, e)]

def to_dict(self):
overrides = {}
for e in self.endpoints:
a = getattr(self, e)
if hasattr(a, "to_dict"):
overrides[e] = a.to_dict()
return super(Map, self).to_dict(**overrides)

@classmethod
def from_dict(cls, meta, d):
m = cls(meta)
for k, v in d.items():
try:
# check if this is something that needs deserializing
if "typeid" in v:
v = Serializable.deserialize(k, v)
except TypeError:
# not a dictionary - pass
pass
setattr(m, k, v)
return m

def __repr__(self):
return self.to_dict().__repr__()

Expand All @@ -54,25 +34,29 @@ def __ne__(self, rhs):
return not self.__eq__(rhs)

def __setitem__(self, key, val):
if key not in self.meta.elements:
raise ValueError("%s is not a valid key for given meta" % key)
setattr(self, key, val)

def __setattr__(self, attr, val):
if hasattr(self, "meta"):
if attr not in self.meta.elements:
raise ValueError("%s is not a valid key for given meta" % attr)
val = self.meta.elements[attr].validate(val)
object.__setattr__(self, attr, val)

def __getitem__(self, key):
if key not in self.meta.elements or not hasattr(self, key):
if key == "meta" or not hasattr(self, key):
raise KeyError
return getattr(self, key)

def __contains__(self, key):
return key in self.meta.elements and hasattr(self, key)
return key != "meta" and hasattr(self, key)

def __len__(self):
return len([e for e in self.meta.elements if hasattr(self, e)])
return len(self.endpoints)

def __iter__(self):
for e in self.meta.elements:
if hasattr(self, e):
yield e
for e in self.endpoints:
yield e

def update(self, d):
if not set(d).issubset(self.meta.elements):
Expand All @@ -86,21 +70,15 @@ def clear(self):
delattr(self, e)

def keys(self):
return [e for e in self.meta.elements if hasattr(self, e)]
return self.endpoints

def values(self):
return [getattr(self, e)
for e in self.meta.elements if hasattr(self, e)]
return [getattr(self, e) for e in self.endpoints]

def items(self):
return [(e, getattr(self, e))
for e in self.meta.elements if hasattr(self, e)]

def setdefault(self, key, default=None):
if key in self:
return self[key]
else:
if key not in self.meta.elements:
raise ValueError("%s is not a valid key for given meta" % key)
self[key] = default
return default
return [(e, getattr(self, e)) for e in self.endpoints]

@classmethod
def from_dict(cls, d, meta):
d.pop("typeid")
return cls(meta, d)
28 changes: 14 additions & 14 deletions malcolm/core/meta.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
from collections import OrderedDict

from malcolm.core.notifier import Notifier
from malcolm.compat import base_string


class Meta(Notifier):
"""Meta base class"""

endpoints = ["description", "tags"]

def __init__(self, name, description, *args):
super(Meta, self).__init__(name, *args)
def __init__(self, name, description=""):
super(Meta, self).__init__(name)
self.description = description
self.tags = []

def set_description(self, description, notify=True):
self.description = description
self.on_changed([["description"], description], notify)
"""Set the description string"""
assert isinstance(description, base_string), \
"Expected descroption to be a string, got %s" % (description,)
self.set_endpoint("description", description, notify)

def set_tags(self, tags, notify=True):
self.tags = tags
self.on_changed([["tags"], tags], notify)

@classmethod
def from_dict(cls, name, d, *args):
meta = cls(name, d["description"], *args)
meta.tags = d["tags"]
return meta
"""Set the tags list"""
assert isinstance(tags, list), \
"Expected tags to be a list, got %s" % (tags,)
for tag in tags:
assert isinstance(tag, base_string), \
"Expected tag to be string, got %s" % (tag,)
self.set_endpoint("tags", tags, notify)
53 changes: 12 additions & 41 deletions malcolm/core/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ class Method(Notifier):
endpoints = ["takes", "defaults", "description", "tags", "writeable",
"returns"]

def __init__(self, name, description):
super(Method, self).__init__(name=name)
def __init__(self, name, description=""):
super(Method, self).__init__(name, description)
self.func = None
self.description = description
self.takes = MapMeta("takes", "Method arguments")
self.returns = MapMeta("returns", "Method output structure")
self.defaults = OrderedDict()
Expand All @@ -38,35 +37,29 @@ def set_function(self, func):
"""
self.func = func

def set_function_takes(self, arg_meta, defaults=None):
def set_takes(self, takes, defaults=None, notify=True):
"""Set the arguments and default values for the method
Args:
arg_meta (MapMeta): Arguments to the function
default (dict): Default values for arguments (default None)
takes (MapMeta): Arguments to the function
defaults (dict): Dict {str name: value} of default values for args
"""
self.takes = arg_meta
if defaults is not None:
self.defaults = OrderedDict(defaults)
else:
self.defaults = OrderedDict()
self.set_endpoint("takes", takes, notify)

def set_function_returns(self, return_meta):
def set_returns(self, returns, notify=True):
"""Set the return parameters for the method to validate against"""
self.returns = return_meta
self.set_endpoint("returns", returns, notify)

def set_writeable(self, writeable):
def set_writeable(self, writeable, notify=True):
"""Set writeable property to enable or disable calling method"""
self.writeable = writeable
self.on_changed([["writeable"], writeable])
self.set_endpoint("writeable", writeable, notify)

def set_tags(self, tags):
self.tags = tags
self.on_changed([["tags"], tags])

def set_label(self, label):
self.label = label
self.on_changed([["label"], label])
def set_label(self, label, notify=True):
self.set_endpoint("label", label, notify)

def __call__(self, *args, **kwargs):
"""Call the exposed function using regular keyword argument parameters.
Expand Down Expand Up @@ -147,28 +140,6 @@ def get_response(self, request):
self.log_debug("Returning result %s", result)
return Return(request.id_, request.context, value=result)

def to_dict(self):
"""Return ordered dictionary representing Method object."""
return super(Method, self).to_dict(
takes=self.takes.to_dict(), returns=self.returns.to_dict())

@classmethod
def from_dict(cls, name, d):
"""Create a Method instance from the serialized version of itself
Args:
name (str): Method instance name
d (dict): Something that self.to_dict() would create
"""
method = cls(name, d["description"])
takes = MapMeta.from_dict("takes", d["takes"])
method.set_function_takes(takes, d["defaults"])
returns = MapMeta.from_dict("returns", d["returns"])
method.set_function_returns(returns)
method.writeable = d["writeable"]
method.tags = d["tags"]
return method

@classmethod
def wrap_method(cls, func):
"""
Expand Down
53 changes: 51 additions & 2 deletions malcolm/core/notifier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from malcolm.core.loggable import Loggable
from malcolm.core.serializable import Serializable
from malcolm.core.serializable import Serializable, serialize_object


class Notifier(Loggable, Serializable):
def __init__(self, name):
Expand All @@ -26,4 +27,52 @@ def on_changed(self, change, notify=True):

def set_endpoint(self, name, value, notify=True):
setattr(self, name, value)
self.on_changed([[name], value], notify)
self.on_changed([[name], serialize_object(value)], notify)

@classmethod
def from_dict(cls, d, name, *args, **kwargs):
"""
Base method to create a serializable instance from a dictionary
Args:
d(dict): Class instance attributes to set
Returns:
Instance of subclass given in d
"""

inst = cls(name, *args, **kwargs)
for k, v in d.items():
# attribute_assignment e.g. [attribute, value]
if k != "typeid":
inst.handle_change(([k], v))

return inst

def handle_change(self, change):
"""
Set a given attribute to a new value
Args:
change(tuple): Attribute path and value e.g. (["value"], 5)
"""
endpoint, value = change

if len(endpoint) == 0:
# Replacing ourself, not allowed
raise ValueError("Cannot replace ourself in change %s" % (change,))
elif len(endpoint) == 1:
# Update an attribute on ourself
attr_name = endpoint[0]
try:
setter = getattr(self, "set_%s" % attr_name)
except AttributeError:
raise ValueError("Cannot get setter for change %s" % (change,))
setter(value)
else:
# Updating a child of ourself
child_name = endpoint[0]
try:
child = getattr(self, child_name)
except AttributeError:
raise ValueError("Cannot get child for change %s" % (change,))
child.update((endpoint[1:], value))

0 comments on commit c9baf4e

Please sign in to comment.