class RecipeContainer(Observable, Configurable, Validatable): """Base class for organizing pieces of a FitRecipe. RecipeContainers are hierarchical organizations of Parameters and other RecipeContainers. This class provides attribute-access to these contained objects. Parameters and other RecipeContainers can be found within the hierarchy with the _locateManagedObject method. A RecipeContainer can manage dictionaries for that store various objects. These dictionaries can be added to the RecipeContainer using the _manage method. RecipeContainer methods that add, remove or retrieve objects will work with any managed dictionary. This makes it easy to add new types of objects to be contained by a RecipeContainer. By default, the RecipeContainer is configured to manage an OrderedDict of Parameter objects. RecipeContainer is an Observable, and observes its managed objects and Parameters. This allows hierarchical calculation elements, such as ProfileGenerator, to detect changes in Parameters and Restraints on which it may depend. Attributes name -- A name for this RecipeContainer. Names should be unique within a RecipeContainer and should be valid attribute names. _parameters -- A managed OrderedDict of contained Parameters. __managed -- A list of managed dictionaries. This is used for attribute access, addition and removal. _configobjs -- A set of configurable objects that must know of configuration changes within this object. Properties names -- Variable names (read only). See getNames. values -- Variable values (read only). See getValues. """ names = property(lambda self: self.getNames()) values = property(lambda self: self.getValues()) def __init__(self, name): Observable.__init__(self) Configurable.__init__(self) validateName(name) self.name = name self._parameters = OrderedDict() self.__managed = [] self._manage(self._parameters) return def _manage(self, d): """Manage a dictionary of objects. This adds the dictionary to the __managed list. Dictionaries in __managed are used for attribute access, addition, and removal. """ self.__managed.append(d) return def _iterManaged(self): """Get iterator over managed objects.""" return chain(*(d.values() for d in self.__managed)) def iterPars(self, name=".", recurse=True): """Iterate over Parameters. name -- Select parameters with this name (regular expression, default "."). recurse -- Recurse into managed objects (default True) """ for par in self._parameters.itervalues(): if re.match(name, par.name): yield par if not recurse: return # Iterate over objects within the managed dictionaries. managed = self.__managed[:] managed.remove(self._parameters) for m in managed: for obj in m.values(): if hasattr(obj, "iterPars"): for par in obj.iterPars(name=name): yield par return def __iter__(self): """Iterate over top-level parameters.""" return self._parameters.itervalues() def __len__(self): """Get number of top-level parameters.""" return len(self._parameters) def __getitem__(self, idx): """Get top-level parameters by index.""" return self._parameters.values()[idx] def __getattr__(self, name): """Gives access to the contained objects as attributes.""" arg = self.get(name) if arg is None: raise AttributeError(name) return arg # Needed by __setattr__ _parameters = {} __managed = {} def __setattr__(self, name, value): """Parameter access and object checking.""" if name in self._parameters: par = self._parameters[name] if isinstance(value, Parameter): par.value = value.value else: par.value = value return m = self.get(name) if m is not None: raise AttributeError("Cannot set '%s'" % name) super(RecipeContainer, self).__setattr__(name, value) return def __delattr__(self, name): """Delete parameters with del. This does not allow deletion of non-parameters, as this may require configuration changes that are not yet handled in a general way. """ if name in self._parameters: self._removeParameter(self._parameters[name]) return m = self.get(name) if m is not None: raise AttributeError("Cannot delete '%s'" % name) super(RecipeContainer, self).__delattr__(name) return def get(self, name, default=None): """Get a managed object.""" for d in self.__managed: arg = d.get(name) if arg is not None: return arg return default def getNames(self): """Get the names of managed parameters.""" return [p.name for p in self._parameters.values()] def getValues(self): """Get the values of managed parameters.""" return [p.value for p in self._parameters.values()] def _addObject(self, obj, d, check=True): """Add an object to a managed dictionary. obj -- The object to be stored. d -- The managed dictionary to store the object in. check -- If True (default), a ValueError is raised an object of the given name already exists. Raises ValueError if the object has no name. Raises ValueError if the object has the same name as some other managed object. """ # Check name if not obj.name: message = "%s has no name" % obj.__class__.__name__ raise ValueError(message) # Check for extant object in d with same name oldobj = d.get(obj.name) if check and oldobj is not None: message = "%s with name '%s' already exists" % (obj.__class__.__name__, obj.name) raise ValueError(message) # Check for object with same name in other dictionary. if oldobj is None and self.get(obj.name) is not None: message = "Non-%s with name '%s' already exists" % (obj.__class__.__name__, obj.name) raise ValueError(message) # Detach the old object, if there is one if oldobj is not None: oldobj.removeObserver(self._flush) # Add the object d[obj.name] = obj # Observe the object obj.addObserver(self._flush) # Store this as a configurable object self._storeConfigurable(obj) return def _removeObject(self, obj, d): """Remove an object from a managed dictionary. Raises ValueError if obj is not part of the dictionary. """ if obj not in d.values(): m = "'%s' is not part of the %s" % (obj, self.__class__.__name__) raise ValueError(m) del d[obj.name] obj.removeObserver(self._flush) return def _locateManagedObject(self, obj): """Find the location a managed object within the hierarchy. obj -- The object to find. Returns a list of objects. The first member of the list is this object, and each subsequent member is a sub-object of the previous one. The last entry in the list is obj. If obj cannot be found, the list is empty. """ loc = [self] # This handles the case that an object is asked to locate itself. if obj is self: return loc for m in self._iterManaged(): # Check locally for the object if m is obj: loc.append(obj) return loc # Check within managed objects if hasattr(m, "_locateManagedObject"): subloc = m._locateManagedObject(obj) if subloc: return loc + subloc return [] def _flush(self, other): """Invalidate cached state. This will force any observer to invalidate its state. By default this does nothing. """ self.notify() return def _validate(self): """Validate my state. This validates that contained Parameters and managed objects are valid. Raises AttributeError if validation fails. """ iterable = chain(self.__iter__(), self._iterManaged()) self._validateOthers(iterable) return
class RecipeContainer(Observable, Configurable, Validatable): """Base class for organizing pieces of a FitRecipe. RecipeContainers are hierarchical organizations of Parameters and other RecipeContainers. This class provides attribute-access to these contained objects. Parameters and other RecipeContainers can be found within the hierarchy with the _locateManagedObject method. A RecipeContainer can manage dictionaries for that store various objects. These dictionaries can be added to the RecipeContainer using the _manage method. RecipeContainer methods that add, remove or retrieve objects will work with any managed dictionary. This makes it easy to add new types of objects to be contained by a RecipeContainer. By default, the RecipeContainer is configured to manage an OrderedDict of Parameter objects. RecipeContainer is an Observable, and observes its managed objects and Parameters. This allows hierarchical calculation elements, such as ProfileGenerator, to detect changes in Parameters and Restraints on which it may depend. Attributes name -- A name for this RecipeContainer. Names should be unique within a RecipeContainer and should be valid attribute names. _parameters -- A managed OrderedDict of contained Parameters. __managed -- A list of managed dictionaries. This is used for attribute access, addition and removal. _configobjs -- A set of configurable objects that must know of configuration changes within this object. Properties names -- Variable names (read only). See getNames. values -- Variable values (read only). See getValues. """ names = property(lambda self: self.getNames()) values = property(lambda self: self.getValues()) def __init__(self, name): Observable.__init__(self) Configurable.__init__(self) validateName(name) self.name = name self._parameters = OrderedDict() self.__managed = [] self._manage(self._parameters) return def _manage(self, d): """Manage a dictionary of objects. This adds the dictionary to the __managed list. Dictionaries in __managed are used for attribute access, addition, and removal. """ self.__managed.append(d) return def _iterManaged(self): """Get iterator over managed objects.""" return chain(*(d.values() for d in self.__managed)) def iterPars(self, name=".", recurse=True): """Iterate over Parameters. name -- Select parameters with this name (regular expression, default "."). recurse -- Recurse into managed objects (default True) """ for par in self._parameters.itervalues(): if re.match(name, par.name): yield par if not recurse: return # Iterate over objects within the managed dictionaries. managed = self.__managed[:] managed.remove(self._parameters) for m in managed: for obj in m.values(): if hasattr(obj, "iterPars"): for par in obj.iterPars(name=name): yield par return def __iter__(self): """Iterate over top-level parameters.""" return self._parameters.itervalues() def __len__(self): """Get number of top-level parameters.""" return len(self._parameters) def __getitem__(self, idx): """Get top-level parameters by index.""" return self._parameters.values()[idx] def __getattr__(self, name): """Gives access to the contained objects as attributes.""" arg = self.get(name) if arg is None: raise AttributeError(name) return arg # Ensure there is no __dir__ override in the base class. assert (getattr(Observable, '__dir__', None) is getattr( Configurable, '__dir__', None) is getattr(Validatable, '__dir__', None) is getattr(object, '__dir__', None)) def __dir__(self): "Return sorted list of attributes for this object." rv = set(dir(type(self))) rv.update(self.__dict__) # self.get fetches looks up for items in all managed dictionaries. # Add keys from each dictionary in self.__managed. rv.update(*self.__managed) rv = sorted(rv) return rv # Needed by __setattr__ _parameters = {} __managed = {} def __setattr__(self, name, value): """Parameter access and object checking.""" if name in self._parameters: par = self._parameters[name] if isinstance(value, Parameter): par.value = value.value else: par.value = value return m = self.get(name) if m is not None: raise AttributeError("Cannot set '%s'" % name) super(RecipeContainer, self).__setattr__(name, value) return def __delattr__(self, name): """Delete parameters with del. This does not allow deletion of non-parameters, as this may require configuration changes that are not yet handled in a general way. """ if name in self._parameters: self._removeParameter(self._parameters[name]) return m = self.get(name) if m is not None: raise AttributeError("Cannot delete '%s'" % name) super(RecipeContainer, self).__delattr__(name) return def get(self, name, default=None): """Get a managed object.""" for d in self.__managed: arg = d.get(name) if arg is not None: return arg return default def getNames(self): """Get the names of managed parameters.""" return [p.name for p in self._parameters.values()] def getValues(self): """Get the values of managed parameters.""" return [p.value for p in self._parameters.values()] def _addObject(self, obj, d, check=True): """Add an object to a managed dictionary. obj -- The object to be stored. d -- The managed dictionary to store the object in. check -- If True (default), a ValueError is raised an object of the given name already exists. Raises ValueError if the object has no name. Raises ValueError if the object has the same name as some other managed object. """ # Check name if not obj.name: message = "%s has no name" % obj.__class__.__name__ raise ValueError(message) # Check for extant object in d with same name oldobj = d.get(obj.name) if check and oldobj is not None: message = "%s with name '%s' already exists"%\ (obj.__class__.__name__, obj.name) raise ValueError(message) # Check for object with same name in other dictionary. if oldobj is None and self.get(obj.name) is not None: message = "Non-%s with name '%s' already exists"%\ (obj.__class__.__name__, obj.name) raise ValueError(message) # Detach the old object, if there is one if oldobj is not None: oldobj.removeObserver(self._flush) # Add the object d[obj.name] = obj # Observe the object obj.addObserver(self._flush) # Store this as a configurable object self._storeConfigurable(obj) return def _removeObject(self, obj, d): """Remove an object from a managed dictionary. Raises ValueError if obj is not part of the dictionary. """ if obj not in d.values(): m = "'%s' is not part of the %s" % (obj, self.__class__.__name__) raise ValueError(m) del d[obj.name] obj.removeObserver(self._flush) return def _locateManagedObject(self, obj): """Find the location a managed object within the hierarchy. obj -- The object to find. Returns a list of objects. The first member of the list is this object, and each subsequent member is a sub-object of the previous one. The last entry in the list is obj. If obj cannot be found, the list is empty. """ loc = [self] # This handles the case that an object is asked to locate itself. if obj is self: return loc for m in self._iterManaged(): # Check locally for the object if m is obj: loc.append(obj) return loc # Check within managed objects if hasattr(m, "_locateManagedObject"): subloc = m._locateManagedObject(obj) if subloc: return loc + subloc return [] def _flush(self, other): """Invalidate cached state. This will force any observer to invalidate its state. By default this does nothing. """ self.notify(other) return def _validate(self): """Validate my state. This validates that contained Parameters and managed objects are valid. Raises AttributeError if validation fails. """ iterable = chain(self.__iter__(), self._iterManaged()) self._validateOthers(iterable) return