Skip to content

Commit

Permalink
Finish mutability/protection of configurables
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanstraten committed Jul 24, 2019
1 parent 680c988 commit cb300c1
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 7 deletions.
96 changes: 95 additions & 1 deletion vhdmmio/config/listconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,107 @@ def deserialize(self, dictionary, parent):
"""`ListConfig` deserializer. See `Loader.deserialize()` for more
info."""
with ParseError.wrap(self.key):
return list(self._handle_list(dictionary.pop(self.key, []), parent))
return ProtectedList(
self._configurable,
self._handle_list(dictionary.pop(self.key, []), parent))

def serialize(self, dictionary, value):
"""`ListConfig` serializer. See `Loader.serialize()` for more info."""
dictionary[self.key] = [item.serialize() for item in value]


class ProtectedList:
"""Wrapper for Python list that ensures that the type of objects added to
it match the expected type."""

def __init__(self, configurable, initial=None):
super().__init__()
self._configurable = configurable
self._list = []
if initial is not None:
self.extend(initial)

def validate(self, value):
"""Checks the type of the given value to make sure it can go into the
list."""

# Note: an exact typecheck is used in order to ensure that
# serialization followed by deserialization results in the same value.
if type(value) is not self._configurable: #pylint: disable=C0123
raise TypeError(
'cannot insert value of type %s into list, must be %s'
% (type(value).__name__, self._configurable.__name__))

def __len__(self):
return len(self._list)

def __getitem__(self, index):
return self._list[index]

def __setitem__(self, index, value):
self.validate(value)
self._list[index] = value

def __delitem__(self, index):
del self._list[index]

def __iter__(self):
return iter(self._list)

def __reversed__(self):
return reversed(self._list)

def __contains__(self, value):
return value in self._list

def append(self, value):
"""Appends to the internal list, ensuring value is acceptable."""
self.validate(value)
self._list.append(value)

def extend(self, iterable):
"""Extends the internal list, ensuring value is acceptable."""
for value in iterable:
self.append(value)

def insert(self, index, value):
"""Inserts into the internal list, ensuring value is acceptable."""
self.validate(value)
self._list.insert(index, value)

def remove(self, value):
"""Removes a value from the list."""
self._list.remove(value)

def pop(self, index=-1):
"""Pops a value from the list."""
return self._list.pop(index)

def clear(self):
"""Clears the list."""
self._list.clear()

def index(self, *args):
"""Chains to list.index()."""
return self._list.index(*args)

def count(self, value):
"""Chains to list.count()."""
return self._list.count(value)

def sort(self, *args, **kwargs):
"""Chains to list.sort()."""
return self._list.sort(*args, **kwargs)

def reverse(self):
"""Chains to list.reverse()."""
return self._list.reverse()

def copy(self):
"""Returns a shallow copy of this protected list."""
return ProtectedList(self._configurable, self._list)


def listconfig(method):
"""Method decorator for configuring a list of `configurable`-annotated
objects. The annotated method is called with zero arguments (not even
Expand Down
39 changes: 34 additions & 5 deletions vhdmmio/config/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,42 @@ def deserialize(self, dictionary, parent):
self.key, selection,
*map(friendly_yaml_value, self._config_options))
item = configurable(parent, dictionary)
return selection, item
assert type(item) is configurable #pylint: disable=C0123
return item

def serialize(self, dictionary, value):
"""`Select` serializer. See `Loader.serialize()` for more info."""
selection, item = value
selection = None
for selection, configurable in self._config_options.items():
if type(value) is configurable: #pylint: disable=C0123
break
else:
selection = None
assert selection is not None
dictionary[self.key] = selection
item.serialize(dictionary)
value.serialize(dictionary)

def mutable(self):
"""Returns whether the value managed by this loader can be mutated. If
this is overridden to return `True`, the loader must implement
`validate()`."""
return True

def validate(self, value):
"""Checks that the given value is valid for this loader, raising an
appropriate ParseError if not. This function only needs to work if
`mutable()` returns `True`."""
for configurable in self._config_options.values():
# Note: an exact typecheck is used in order to ensure that
# serialization followed by deserialization results in the same
# value. Also, the configurables in the mapping could be instances
# of each other.
if type(value) is configurable: #pylint: disable=C0123
break
else:
raise TypeError('type of value must be one of the configurable options')
if value.parent is not self:
raise ValueError('value must have been initialized with us as the parent')


def select(method):
Expand All @@ -67,8 +96,8 @@ def select(method):
`(value, configurable, doc)` three-tuples, where `value` is the unique
value that the user must specify to select `configurable`, and `doc` is the
documentation associated with this option. The method is transformed to a
property that allows the object type and the constructed configurable
instance to be accessed as a `(selection, instance)` two-tuple."""
property that allows the constructed configurable instance to be
accessed."""
return Select(
method.__name__, method.__doc__, OrderedDict((
(value, (configurable, doc))
Expand Down
5 changes: 4 additions & 1 deletion vhdmmio/config/subconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ def validate(self, value):
"""Checks that the given value is valid for this loader, raising an
appropriate ParseError if not. This function only needs to work if
`mutable()` returns `True`."""
if not isinstance(value, self._configurable):

# Note: an exact typecheck is used in order to ensure that
# serialization followed by deserialization results in the same value.
if type(value) is not self._configurable: #pylint: disable=C0123
raise TypeError('value must be an instance of %s' % self._configurable.__name__)
if value.parent is not self:
raise ValueError('value must have been initialized with us as the parent')
Expand Down

0 comments on commit cb300c1

Please # to comment.