diff -r 87cfa131b535 -r e7e0ae78773e configurationengine/source/cone/public/api.py --- a/configurationengine/source/cone/public/api.py Fri Mar 12 08:30:17 2010 +0200 +++ b/configurationengine/source/cone/public/api.py Tue Aug 10 14:29:28 2010 +0300 @@ -1,3 +1,4 @@ +from __future__ import with_statement # # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). # All rights reserved. @@ -18,14 +19,15 @@ The core interface to the ConE functionality. """ -import os import re import sys import logging -import copy -import sets - -import exceptions, utils, container, mapping +import mimetypes + +from cone.public import exceptions, utils, container, mapping + +def get_file_logger(): + return logger class Base(container.ObjectContainer): """ @@ -38,14 +40,34 @@ raise exceptions.InvalidRef("Invalid reference for Base object %s!" % ref) self.ref = ref container.ObjectContainer.__init__(self, ref) - for arg in kwargs.keys(): - if kwargs.get(arg) != None: - setattr(self, arg, kwargs.get(arg)) - + try: + for arg in kwargs.keys(): + if kwargs.get(arg) != None: + setattr(self, arg, kwargs.get(arg)) + except AttributeError,e: + raise e + def __repr__(self): dict = self._dict() return "%s(%s)" % (self.__class__.__name__, dict) +# def __reduce_ex__(self, protocol_version): +# tpl = super(Base, self).__reduce_ex__(protocol_version) +# return tpl + +# def __getstate__(self): +# state = self._dict(internals=True, ignore_empty=True) +# # pop out the _name so that it wont appear as redundant data (ref is the same) +# state.pop('_name', None) +# state.pop('_parent', None) +# return state +# +# def __setstate__(self, state): +# super(Base, self).__setstate__(state) +# self.ref = state.get('ref','') +# for arg in state.keys(): +# self.__dict__.setdefault(arg, state.get(arg)) + def _get_mapper(self,modelname): """ Return a instance of appropriate mapper for given model. @@ -108,16 +130,23 @@ obj._add(child._clone(**kwargs), container.APPEND) return obj - def _dict(self): + def _dict(self, **kwargs): """ Return the public variables in a dictionary """ dict = {} - for key in self.__dict__.keys(): - if key.startswith('_'): + # loop through the items in this object internal __dict__ + # and add all except internal variables and function overrides + for (key,value) in self.__dict__.items(): + if not kwargs.get('internals', False) and key.startswith('_'): continue + elif not kwargs.get('callables', False) and callable(value): + continue + elif kwargs.get('ignore_empty') and not value: + # ignore empty values + pass else: - dict[key] = self.__dict__[key] + dict[key] = value return dict def _default_object(self, name): @@ -152,6 +181,27 @@ paths.reverse() return utils.dottedref.join_refs(paths) + def path(self, toparent=None): + """ + Get the path to this Base object.. + @param toparent: the _parent object up to which the path is relative. Default value is None., + which gives the fully qualified path in the topology. + @return: The path to this Base object from toparent + """ + return self._path(toparent) + + def parent_path(self, toparent=None): + """ + Get the path to the parent of this Base object.. + @param toparent: the _parent object up to which the path is relative. Default value is None., + which gives the fully qualified path in the topology. + @return: The path to this Base object from toparent + """ + if self._parent != None: + return self._parent.path(toparent) + else: + return '' + def get_fullref(self): """ Return a full reference, reference including a @@ -240,6 +290,23 @@ else: return None + def get_configuration(self): + """ + Return the containing configuration of this object. + """ + parent = self._find_parent_or_default(type=Configuration) + return parent + + def get_configuration_path(self): + """ + Return the path of containing configuration of this object. + """ + parent = self._find_parent_or_default(type=Configuration) + try: + return parent.get_full_path() + except AttributeError: + return None + def get_index(self): """ @return : the index of the data element for sequential data defined inside the same configuration. @@ -293,6 +360,27 @@ """ return None + def get_store_interface(self): + # if the project cannot be retrieved return None + try: + return self.get_project() + except exceptions.NotFound: + return None + + def get_id(self): + try: + return self._id + except AttributeError: + return None + + def set_id(self,value): + self._id = value + + def del_id(self): + delattr(self,'_id') + + """ The id as a property """ + id = property(get_id,set_id, del_id) class Project(Base): """ @@ -309,10 +397,17 @@ self._model = storage.persistentmodule.MODEL except AttributeError: self._model = None - + self.loaded = {} self.set_storage(storage) self.update() - self.loaded = {} + + def __getstate__(self): + state = {} + state['storage'] = self.storage + return state + + def __setstate__(self, state): + self.__init__(state['storage']) def __add_loaded__(self, ref, obj): """ @@ -353,8 +448,10 @@ return True else: return False - else: - return True + else: + # Return False in case the object is loaded at all in this project + # increases performance as unloading is not done on unchanged objects + return False def _supported_type(self, obj): if isinstance(obj, Configuration) \ @@ -383,10 +480,9 @@ """ Set the Storage instance of this Project. """ - if isinstance(storage, Storage): - self.storage = storage - else: + if storage != None and not isinstance(storage, Storage): raise exceptions.StorageException("The given storage is not a instance of Storage!") + self.storage = storage def list_configurations(self, filter_or_filters=None): """ @@ -418,7 +514,9 @@ List all configuration objects of the project (all configurations) @return: a list for configuration file paths """ - return [obj.get_path() for obj in self._traverse(type=(Configuration, ConfigurationProxy))] + # TODO + # huge performance problem + return [obj.get_full_path() for obj in self._traverse(type=(Configuration, ConfigurationProxy))] def get_configuration(self, path): """ @@ -445,27 +543,44 @@ """ # Changed from list_all_configurations to list_configurations # (list_all_configurations causes a insane performance problem with _traverse) - return path in self.list_configurations() - - def add_configuration(self, config): + # + try: + return self.storage.is_resource(path) + except exceptions.NotSupportedException: + return path in self.list_configurations() + + def add_configuration(self, config, overwrite_existing=False): """ Add a Configuration object to this project - """ + @param config: The configuration object to add + @param overwrite_existing: When this is set true any existing configuration is + overwritten. + """ if isinstance(config, Configuration): - if self.is_configuration(config.get_path()): + if not overwrite_existing and self.is_configuration(config.get_path()): raise exceptions.AlreadyExists("%s" % config.get_path()) - self._add(config) + + proxy = ConfigurationProxy(config.path) + proxy._set_obj(config) + self._add(proxy) + #self._add(config) self.__add_loaded__(config.get_path(), config) self.__loaded__(config.get_path()) else: raise exceptions.IncorrectClassError("Only Configuration instance can be added to Project!") - def create_configuration(self, path, namespace=""): + def create_configuration(self, path, overwrite_existing=False, **kwargs): """ Create a Configuration object to this project - """ - config = self.get_configuration_class()(utils.resourceref.norm(path), namespace=namespace) - self.add_configuration(config) + @param path: The path of the new configuration file + @param overwrite_existing: When this is set true any existing configuration is + overwritten. + @param **kwargs: normal keyword arguments that are passed on to the newly + created Configuration object. See Configuration object constructor description on what + you can pass on here. + """ + config = self.get_configuration_class()(utils.resourceref.norm(path), **kwargs) + self.add_configuration(config, overwrite_existing) return config def remove_configuration(self, path): @@ -487,7 +602,7 @@ self.storage.import_resources(configuration.list_resources(), configuration.get_storage()) return - def export_configuration(self, configuration, export_storage, empty_folders=False): + def export_configuration(self, configuration, export_storage, **kwargs): """ Export a configuration object to another storage """ @@ -504,8 +619,9 @@ #l = [] cpath = utils.resourceref.get_path(configuration.get_path()) resr = [utils.resourceref.join_refs([cpath,related]) \ - for related in configuration.get_layer().list_all_related(empty_folders)] - self.storage.export_resources(resr ,export_storage, empty_folders) + for related in configuration.get_layer().list_all_related(**kwargs)] + + self.storage.export_resources(resr ,export_storage, kwargs.get("empty_folders", False)) return def get_configuration_class(self): @@ -545,7 +661,7 @@ if not self.__get_loaded__(path): configuration = self.get_storage().load(path) if configuration.get_ref() == 'unknown': - configuration.set_ref(utils.resourceref.to_dref(path)) + configuration.set_ref(utils.resourceref.to_dref(path)) self.__add_loaded__(path, configuration) """ increase the ref counter """ self.__loaded__(path) @@ -560,6 +676,15 @@ """ if self.__unloaded__(path): self.get_storage().unload(path, object) + # remove the configuration from this this project, + # with proxy set the _obj reference to None + try: + conf = self.get_configuration(path) + if isinstance(conf, ConfigurationProxy): + conf._set_obj(None) + except exceptions.NotFound: + # if the configuration is not found at all then ignore the resetting + pass def get_path(self): """ @@ -578,6 +703,9 @@ super(CompositeConfiguration, self).__init__(ref, **kwargs) self.container = True + def _configuration_class(self): + return Configuration + def add_configuration(self, config): """ Add an existing Configuration to this configuration @@ -587,9 +715,23 @@ """ Merge the default view features from added config to this configs _default_view. """ - self._add(config) - - def include_configuration(self, configref): + # if the Configuration has a separate resource path, add it automatically behind proxy + if utils.resourceref.is_path(config.path) and isinstance(config, Configuration): + proxy = ConfigurationProxy(config.path) + proxy._set_obj(config) + self._add(proxy) + # Add the new configuration to the list of "modified/loaded" configurations + try: + prj = self.get_project() + prj.__add_loaded__(config.get_full_path(), config) + prj.__loaded__(config.get_full_path()) + except exceptions.NotFound: + # if the parent is not found this configuration is not (yet) a part of project and cant be stored + pass + else: + self._add(config) + + def include_configuration(self, configref, policy=0): """ Add an existing Configuration to this configuration by its resource reference @param config: A Configuration instance: @@ -597,7 +739,7 @@ """ # add the configuration load proxy to this configuration instead # adding the configuration directly - self._add(ConfigurationProxy(configref)) + self._add(ConfigurationProxy(configref), policy) def create_configuration(self, path): """ @@ -611,11 +753,9 @@ """ # normalise the path normpath = utils.resourceref.norm(path) - cklass = self.get_configuration_class() + cklass = self._configuration_class() conf = cklass(normpath, namespace=self.namespace) - proxy = ConfigurationProxy(normpath) - self.add_configuration(proxy) - proxy._set_obj(conf) + self.add_configuration(conf) return conf def remove_configuration(self, path): @@ -636,9 +776,7 @@ List all Layer objects in the Configuration @return: a copy array of layer references. """ - # TODO - # huge performance problem - return [config.get_path() for config in self._traverse(type=(Configuration, ConfigurationProxy))] + return [config.get_path_for_parent(self) for config in self._traverse(type=(Configuration, ConfigurationProxy))] def get_configuration(self, path): """ @@ -667,17 +805,6 @@ except IndexError: return self - def get_configuration_class(self): - """ - return the default configuration class retrieved from the project if it is found. - Otherwise return cone.public.api.Configuration. - """ - try: - return self.get_project().get_configuration_class() - # catch the Parent/Project NotFound exception - except exceptions.NotFound: - return Configuration - def add(self, child, policy=container.REPLACE): """ A generic add function to add child objects. The function is intended to act as @@ -690,18 +817,41 @@ if isinstance(child, Configuration): self.add_configuration(child) elif isinstance(child, ConfigurationProxy): - self.add_configuration(child) + self._add(child) elif isinstance(child, Base): self._add(child) else: raise exceptions.IncorrectClassError("Cannot add %s to %s" % (child, self)) - def layered_content(self, layers=None): - """ - fetch content from first to last and override content - if it is found from a later layer - Create an array of the layers based on the layer indexes. - """ + def layered_resources(self, layers=None, empty_folders=False, folder=None, resource_type=None): + """ + Fetch resource paths by layers so that if a resource with the same name + exists on multiple layers, the one on the latest layer is the active one. + @param layers: List of layer indices to specify the layer to use, None + for all layers. + @param empty_folders: If True, empty folders are returned also. + @param folder: Name of a specific folder from which to get resources, or None. + If None, resource_type must be specified. + @param resource_type: Type of the resources to find. Must be one of + ('confml', 'implml', 'content', 'doc') or None. + If None, folder must be specified. + @return: A container.DataContainer instance containing the resource paths. + For example: {'foo.txt': ['layer1/content/foo.txt', + 'layer2/content/foo.txt'], + 'bar.txt': ['layer1/content/bar.txt']} + """ + MAPPING = {'confml': lambda layer: layer.confml_folder(), + 'implml': lambda layer: layer.implml_folder(), + 'content': lambda layer: layer.content_folder(), + 'doc': lambda layer: layer.doc_folder()} + if resource_type is not None and resource_type not in MAPPING: + raise ValueError("Invalid resource type %r, should be one of %r" % (resource_type, MAPPING.keys())) + + if folder and resource_type: + raise ValueError('Only one of folder and resource_type must be specified!') + if not folder and not resource_type: + raise ValueError('Either folder or resource_type must be specified!') + configuration_array = [] if layers == None: configuration_array = self.list_configurations() @@ -709,18 +859,43 @@ all = self.list_configurations() for i in layers: configuration_array.append(all[i]) - - content = container.DataContainer() + + # Add the current configuration as the last one in the list, in case + # the current configuration happens to be a layer root itself + configuration_array.append('') + + # Set up the get_folder function based on the parameters + if resource_type: + get_folder = MAPPING[resource_type] + else: + def get_folder(layer): + cpath = layer.get_current_path() + return Folder(layer.storage, utils.resourceref.join_refs([cpath, folder])) + + result = container.DataContainer() for configuration_path in configuration_array: - content_folder = self.get_configuration(configuration_path).get_layer().content_folder() - content_path = content_folder.get_current_path() - for content_file in content_folder.list_resources("", True): - source_file = utils.resourceref.join_refs([content_path, content_file]) - content.add_value(content_file, source_file) - - return content - - + folder_obj = get_folder(self.get_configuration(configuration_path).get_layer()) + folder_path = folder_obj.get_current_path() + for res_path in folder_obj.list_resources("", recurse=True, empty_folders=empty_folders): + if res_path == '': continue # ZipStorage sometimes returns empty paths + res_fullpath = utils.resourceref.join_refs([folder_path, res_path]) + result.add_value(res_path, res_fullpath) + return result + + def layered_confml(self, layers=None, empty_folders=False): + return self.layered_resources(layers, empty_folders, resource_type='confml') + + def layered_implml(self, layers=None, empty_folders=False): + return self.layered_resources(layers, empty_folders, resource_type='implml') + + def layered_content(self, layers=None, empty_folders=False): + return self.layered_resources(layers, empty_folders, resource_type='content') + + def layered_doc(self, layers=None, empty_folders=False): + return self.layered_resources(layers, empty_folders, resource_type='doc') + + + class Configuration(CompositeConfiguration): """ A Configuration is a container that can hold several Layer objects. @@ -729,12 +904,33 @@ def __init__(self, ref="", **kwargs): self.path = kwargs.get('path') or ref self.namespace = kwargs.get('namespace', '') - self.name = utils.resourceref.to_objref(self.path) - super(Configuration, self).__init__(utils.resourceref.to_objref(self.path)) + self.name = kwargs.get('name',utils.resourceref.to_objref(self.path)) + self.version = kwargs.get('version') + super(Configuration, self).__init__(utils.resourceref.to_objref(self.path), **kwargs) self.container = True + def __reduce_ex__(self, protocol_version): + """ + Make the Configuration pickle a ConfigurationProxy object that would load this configuration + """ + proxy = ConfigurationProxy(self.path, store_interface = self.get_project()) + tpl = proxy.__reduce_ex__(protocol_version) + return tpl + + def __getstate__(self): + return None + def _default_object(self, name): - return Feature(name) + return self._default_class()(name) + + def _default_class(self): + return self._feature_class() + + def _feature_class(self): + return Feature + + def _view_class(self): + return View def _supported_type(self, obj): if isinstance(obj, Configuration) \ @@ -747,16 +943,11 @@ else: return False - def _dict(self): + def _dict(self, **kwargs): """ Return the public variables in a dictionary """ - dict = {} - for key in self.__dict__.keys(): - if key.startswith('_'): - continue - else: - dict[key] = self.__dict__[key] + dict = super(Configuration, self)._dict(**kwargs) dict['namespace'] = self.namespace return dict @@ -783,10 +974,8 @@ Set the path of the configuration resource, and update the name and ref to correspond """ self.path = path - #self.name = utils.resourceref.to_objref(self.path) self.set_ref(utils.resourceref.to_objref(self.path)) - #@property def get_full_path(self): """ Return the path of the configuration resource @@ -796,7 +985,19 @@ parent_path = utils.resourceref.get_path(parentconfig.get_path()) except exceptions.NotFound: parent_path = "" - + return utils.resourceref.join_refs([parent_path, self.path]) + + def get_path_for_parent(self, parent): + """ + Return the path to this configuration for a defined parent Configuration object. + """ + parent_path = "" + try: + parentconfig = self._find_parent(type=Configuration) + if parent != parentconfig: + parent_path = utils.resourceref.get_path(parentconfig.get_path_for_parent(parent)) + except exceptions.NotFound: + pass return utils.resourceref.join_refs([parent_path, self.path]) def get_layer(self): @@ -821,7 +1022,6 @@ @param namespace: The new namespace of the object """ self._namespace = namespace - #self.root.set_namespace(namespace) def get_namespace(self): """ @@ -836,7 +1036,7 @@ self._namespace = None namespace = property(get_namespace, set_namespace, del_namespace) - def list_resources(self, empty_folders=False): + def list_resources(self, **kwargs): """ List all resources used in this configuration """ @@ -848,10 +1048,10 @@ resources = [self.get_full_path()] - for config in self._traverse(type=Configuration): + for config in self._traverse(type=(Configuration,ConfigurationProxy)): resources.append(config.get_full_path()) layer = self.get_layer() - for resref in layer.list_all_resources(empty_folders): + for resref in layer.list_all_resources(): resources.append(utils.resourceref.join_refs([layer.get_current_path(), resref])) return utils.distinct_array(resources) @@ -891,6 +1091,18 @@ """ return self._get(ref) + def create_feature(self, ref, **kwargs): + """ + Create a feature object to the configuration. + @param ref: The ref for the Feature object. + @param **kwargs: keyword arguments + e.g. to add fea2 under fea1 add_feature(fea2, 'fea1') + @return: the new feature object. + """ + fea = self._feature_class()(ref, **kwargs) + self._add(fea) + return fea + def add_feature(self, feature, namespace=""): """ Add a feature object to the configuration. @@ -899,7 +1111,11 @@ e.g. to add fea2 under fea1 add_feature(fea2, 'fea1') @return: None """ - self._add_to_path(namespace, feature) + if namespace and self._has(namespace): + # Add the feature directly with an existing feature's add_feature functionality + self.get_feature(namespace).add_feature(feature) + else: + self._add_to_path(namespace, feature) def remove_feature(self, ref): """ @@ -927,13 +1143,27 @@ def add_data(self, data, policy=container.REPLACE): """ Add a data object to this configuration object. - @param data: The Data object to add. + @param data: The Data object or list of Data objects to add. @return: None - """ - if not self._has(data.attr): - self._add(DataContainer(data.attr, container=True)) - (namespace, name) = utils.dottedref.psplit_ref(data.get_fearef()) - self._get(data.attr)._add_to_path(namespace, data, policy) + """ + data_objs = utils.get_list(data) + + if policy == container.PREPEND: + data_objs = reversed(data_objs) + policy_first = container.PREPEND + policy_rest = container.PREPEND + else: + policy_first = policy + policy_rest = container.APPEND + + for i, data_obj in enumerate(data_objs): + if not self._has(data_obj.attr): + self._add(DataContainer(data_obj.attr, container=True)) + (namespace, name) = utils.dottedref.psplit_ref(data_obj.get_fearef()) + + if i == 0: p = policy_first + else: p = policy_rest + self._get(data_obj.attr)._add_to_path(namespace, data_obj, p) def get_data(self, ref): """ @@ -1009,13 +1239,24 @@ view.populate() return view - def add_view(self, viewname): + def create_view(self, viewname): + """ + Create a view object to the configuration. + @param viewname: The name of the view to add. + @return: view object + """ + viewobj = self._view_class()(viewname) + self.add_view(viewobj) + return viewobj + + def add_view(self, viewobj): """ Add a view object to the configuration. - @param viewname: The name of the view to add. + @param viewobj: The existing view object to add. @return: None """ - return self._add(View(viewname)) + assert(isinstance(viewobj, View)) + return self._add(viewobj) def remove_view(self, ref): """ @@ -1038,10 +1279,13 @@ Save the object to the permanent Storage object. Calls the save operation of all the children. """ + # Change the recursion order so that the current object + # is saved first and then its childen. + # This increases performance in cases where this object requires information about its childen (no unload -> load cases) + self.get_project().unload(self.get_full_path(), self) for child in self._objects(): if isinstance(child, (Configuration,ConfigurationProxy)): child.save() - self.get_project().unload(self.get_full_path(), self) def close(self): """ @@ -1077,13 +1321,13 @@ This returns always the view from the Root configuration point of view. """ try: - parent = self._find_parent_or_default() + parent = self._find_parent_or_default() if parent and isinstance(parent, Configuration): return parent.get_default_view() else: - if not hasattr(self, '_default_view'): + if not self._has('?default_view'): self._create_default_view() - return self._default_view + return self._get('?default_view') except exceptions.NotFound, e: raise e # raise exceptions.NotFound("Default View is not found! No root configuration?") @@ -1101,17 +1345,19 @@ def _create_default_view(self): # Rebuild the default view for this Configuration - self._default_view = View("_default_view", data=True) - self._default_view._parent= self + default_view = View("?default_view", data=True) + #self._default_view._parent= self + self._add(default_view) # First add all features of the configuration to the view. # Then add all data elements under the features for child in self._traverse(type=Feature): - self._default_view.add_feature(child, child.namespace) + # TODO print "Adding : %s -> %s" % (child.namespace, child) + default_view.add_feature(child, child.namespace) for child in self._traverse(type=Data): #parent_config = child._find_parent_or_default(type=Configuration) #print "Adding data %s: fqr: %s from file %s." % (child.get_value(), child.fqr, parent_config.get_path()) try: - fea = self._default_view.get_feature(child.fqr) + fea = default_view.get_feature(child.fqr) fea.add_data(child) except exceptions.NotFound, e: data_parent_config = child._find_parent_or_default(type=Configuration) @@ -1128,9 +1374,15 @@ The ConfigurationProxy trust to get the store_interface from the parent object with get_storage() function. """ - container.LoadProxy.__init__(self, path) + super(ConfigurationProxy,self).__init__(path, **kwargs) self.set('_name', utils.resourceref.to_objref(path)) + def __reduce_ex__(self, protocol_version): + """ + Make the Configuration pickle a ConfigurationProxy object that would load this configuration + """ + return super(ConfigurationProxy, self).__reduce_ex__(protocol_version) + def _clone(self, **kwargs): """ A ConfigurationProxy specific implementation for cloning. @@ -1152,7 +1404,7 @@ newobj = self._get_obj()._clone(**kwargs) obj._set_obj(newobj) return obj - + def _dict(self): """ Return the public variables in a dictionary @@ -1164,7 +1416,7 @@ else: dict[key] = self.__dict__[key] return dict - + def _get_mapper(self,modelname): """ Return a instance of appropriate mapper for given model. @@ -1177,32 +1429,27 @@ """ def __init__(self, ref="", **kwargs): super(Group, self).__init__(ref, **kwargs) - self.name = ref + self.name = kwargs.get('name', ref) self.support_data = kwargs.get("data", False) def _supported_type(self, obj): if isinstance(obj, (Group, \ Base, \ _FeatureProxy, \ - FeatureLink)): + FeatureLink, \ + ConfigurationProxy)): return True else: return False def _default_object(self, name): - return Group(name) - - def get_name(self): - """ - Return the name of the configuration - """ - return self.name - - def set_name(self, name): - """ - Set the name - """ - self.name + return self._group_class()(name) + + def _group_class(self): + return Group + + def _featurelink_class(self): + return FeatureLink def add(self, child, policy=container.REPLACE): """ @@ -1213,10 +1460,7 @@ @param child: the child object to add @raise IncorrectClassError: if the given class cannot be added to this object. """ - if isinstance(child, (Group, \ - Base, \ - _FeatureProxy, \ - FeatureLink)): + if self._supported_type(child): self._add(child) else: raise exceptions.IncorrectClassError("Cannot add %s to %s" % (child, self)) @@ -1233,6 +1477,21 @@ """ self.name = name + def create_featurelink(self, feature_ref, **kwargs): + """ + create a feature link object to this element, with the given ref + @param feature_ref: the reference for the featurelink which should + point to a exising feature in the configuration. + @param **kwargs: keyword arguments are passed to the featurelink object + directly. + """ + fealink = self._featurelink_class()(feature_ref, **kwargs) + self.add(fealink) + return fealink + + def get_featurelink(self, ref): + return self._get(FeatureLink.get_featurelink_ref(ref)) + def add_feature(self, feature, path=""): """ Add feature to this Group. @@ -1260,26 +1519,65 @@ except exceptions.NotFound: raise exceptions.NotFound("Feature '%s' not found." % ref) - def get_features(self, ref, **kwargs): + def get_features(self, refs, **kwargs): """ Get a list of features that match the ref. + + @param refs: The paths (refs) to the given feature or xpath like expression. The refs + argument can be a single reference or a list of references to features. + @return: A list of features. + + NOTE! the invalid references will not raise an exception. + Example1: get_features('foo.bar') would be the same as get_feature('foo.bar'), but this returns always a list []. Example2: get_features('foo.*') would try to retrieve a list of all foo children. Example3: get_features('foo.*', type='') would try to retrieve a list of all foo children, that have a defined type. - @param path: The path (ref) to the given feature or xpath like expression + Example4: get_features(['foo','bar.set1']) would try to retrieve a foo and then bar.set1. + + """ + + if utils.is_list(refs): + features = [] + for ref in refs: + features += self.get_matching_features(ref, **kwargs) + return features + else: + return self.get_matching_features(refs, **kwargs) + + def get_matching_features(self, ref, **kwargs): + """ + Get a list of features that match the ref. + + @param refs: The paths (refs) to the given feature or xpath like expression. The refs + argument can be a single reference or a list of references to features. @return: A list of features. - """ - (startref, last) = utils.dottedref.psplit_ref(ref) - startelem = self._get(startref) - if last == '**': - return [fea for fea in startelem._traverse(**kwargs)] - elif last == '*': - return [fea for fea in startelem._objects(**kwargs)] - else: - return [self._get(ref)] - + + NOTE! the invalid references will not raise an exception but return an empty list. + + Example1: get_features('foo.bar') would be the same as get_feature('foo.bar'), but this returns + always a list []. + Example2: get_features('foo.*') would try to retrieve a list of all foo children. + Example3: get_features('foo.*', type='') would try to retrieve a list of all foo children, + that have a defined type. + + """ + try: + (startref, last) = utils.dottedref.psplit_ref(ref) + startelem = self._get(startref) + kwargs['type'] = _FeatureProxy + if last == '**': + return [fea for fea in startelem._traverse(**kwargs)] + elif last == '*': + return [fea for fea in startelem._objects(**kwargs)] + elif ref != "": + return [self._get(ref)] + else: + return [] + except exceptions.NotFound: + return [] + def list_features(self): """ Return a array of all Feature children references under this object. @@ -1292,10 +1590,20 @@ """ return [fea.fqr for fea in self._traverse(type=(_FeatureProxy))] - def add_group(self, groupname): - """ - """ - self._add(Group(groupname)) + def create_group(self, groupname, **kwargs): + """ + create a group object to this element with given group name. + @param groupname: the name for the new group + @param **kwargs: keyword arguments are passed on to the new group object. + """ + grp = self._group_class()(groupname, **kwargs) + self.add_group(grp) + return grp + + def add_group(self, grp): + """ + """ + self._add(grp) def remove_group(self, ref): """ @@ -1313,7 +1621,7 @@ def list_groups(self): """ """ - return [group.get_name() for group in self._objects(type=Group)] + return [group.ref for group in self._objects(type=Group)] def populate(self): """ @@ -1332,7 +1640,6 @@ """ def __init__(self, ref="", **kwargs): super(View, self).__init__(self.to_ref(ref), **kwargs) - self.name = ref self.container = True @classmethod @@ -1350,8 +1657,10 @@ PROPERTIES = ['value'] def __init__(self, ref="", **kwargs): super(Feature, self).__init__(ref, **kwargs) - self.name = kwargs.get('name', ref) + self.name = kwargs.get('name', None) self.type = kwargs.get('type', None) + self.relevant = kwargs.get('relevant', None) + self.constraint = kwargs.get('constraint', None) self._dataproxy = None def __copy__(self): @@ -1364,7 +1673,17 @@ fea = self.__class__(self.ref, **dict) return fea - + def __getstate__(self): + state = super(Feature, self).__getstate__() + # remove the dataproxy value so that it is not stored in serializings + state.pop('_dataproxy', None) + return state + + def __setstate__(self, state): + super(Feature, self).__setstate__(state) + self._dataproxy = None + + def _supported_type(self, obj): # For now support added for desc element via support for Base if isinstance(obj, (Feature, Option, Base)): @@ -1372,6 +1691,9 @@ else: return False + def _feature_class(self): + return Feature + def add(self, child, policy=container.REPLACE): """ A generic add function to add child objects. The function is intended to act as @@ -1387,6 +1709,8 @@ self._add(child, policy) elif isinstance(child, Base): self._add(child, policy) + elif isinstance(child, Property): + self._add(child, policy) else: raise exceptions.IncorrectClassError("Cannot add %s to %s" % (child, self)) @@ -1402,22 +1726,53 @@ """ self.name = name + def get_relevant(self): + """ + Return the relevant attribute of the feature + """ + return self.relevant + + def set_relevant(self, relevant): + """ + Set the relevant attribute + """ + self.relevant = relevant + + def get_constraint(self): + """ + Return the constraint attribute of the feature + """ + return self.constraint + + def set_constraint(self, constraint): + """ + Set the constraint attribute + """ + self.constraint = constraint + def get_type(self): return self.type def set_type(self, type): self.type = type + def create_feature(self, ref, **kwargs): + """ + Create a feature object to the configuration. + @param ref: The ref for the Feature object. + @param **kwargs: keyword arguments + e.g. to add fea2 under fea1 add_feature(fea2, 'fea1') + @return: the new feature object. + """ + fea = self._feature_class()(ref, **kwargs) + self.add_feature(fea) + return fea + def add_feature(self, feature, path=""): """ @param feature: The Feature object to add """ - configuration = self.find_parent(type=Configuration) - if configuration: - feapath = utils.dottedref.join_refs([self._path(configuration), path]) - configuration.add_feature(feature, feapath) - else: - self._add_to_path(path, feature) + self._add_to_path(path, feature) def get_feature(self, path): """ @@ -1430,12 +1785,7 @@ remove a given feature from this view by reference. @param ref: """ - configuration = self.find_parent(type=Configuration) - if configuration: - fullfqr = utils.dottedref.join_refs([self._path(configuration), ref]) - configuration.remove_feature(fullfqr) - else: - self._remove(ref) + self._remove(ref) def list_features(self): """ @@ -1491,40 +1841,107 @@ # Return option refs without the leading 'opt_' return [opt.ref[4:] for opt in self._objects(type=Option)] + def add_property(self, property): + """ + @param property: property object to add + """ + if not isinstance(property, Property): + raise TypeError("%r is not an instance of Property!" % property) + self._add(property) + + def create_property(self, **kwargs): + """ + @param name=str: property name + @param value=str: property value + @param unit=str: property unit, e.g. kB + """ + self._add(Property(**kwargs)) + + + def get_property(self, ref): + """ + @param ref: The ref of the property + """ + obj = self._get(Property.to_propertyref(ref)) + + if not isinstance(obj, Property): + raise TypeError('Object %r is not an instance of Property (%r instead)' % (Property.to_propertyref(ref), type(obj))) + return obj + + def remove_property(self, ref): + """ + remove a given property from this feature by ref. + @param ref: + """ + obj = self._get(Property.to_propertyref(ref)) + if not isinstance(obj, Property): + raise TypeError('Trying to remove property with ref %r, but object with ref %r is not an instance of Property (%s instead)' % (ref, Property.to_propertyref(ref), type(obj))) + self._remove(Property.to_propertyref(ref)) + + def list_properties(self): + """ + Return a array of all Feature properties under this object. + """ + + return [Property.to_normref(property.ref) for property in self._objects(type=Property)] + def get_value(self, attr=None): """ - Get the current value of the feature + Get the current value of the feature. @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' """ - # Do not allow getting of setting of sequence values directly with Feature object - if not self.is_sequence(): - return self.get_value_cast(self.dataproxy._get_value(attr), attr) - else: - """ get the feature specific data from sequence => a column of data table """ - coldata = [] - feasequence = self.get_sequence_parent() - feapath = self._path(feasequence) - for row in feasequence.data: - feadata = row.get_feature(feapath) - coldata.append(feadata.value) - return coldata + return self.convert_data_to_value(self.dataproxy._get_datas(attr=attr), cast=True, attr=attr) def set_value(self, value, attr=None): """ Set the current value for this feature. Set the value on the topmost layer. @param value: the value to set """ - # Do not allow setting of setting of sequence values directly with Feature object - if not self.is_sequence(): - value = self.set_value_cast(value, attr) - self.dataproxy._set_value(value, attr) - + data_objs = self.convert_value_to_data(value, attr) + + # Set the created data objects to the dataproxy and the + # last configuration, overriding any existing elements + self.dataproxy._set_datas(data_objs, attr) + last_config = self.get_root_configuration().get_last_configuration() + last_config.add_data(data_objs, container.REPLACE) + + def convert_data_to_value(self, data_objects, cast=True, attr=None): + """ + Convert the given list of Data objects into a suitable value + for this setting. + @param data_objects: The Data object list. + @param cast: If True, the value should be cast to its correct Python type + (e.g. int), if False, the value should remain in the string form + it was in the data objects. + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + @return: The converted value. + """ + if not data_objects: return None + + data_obj = data_objects[-1] + if data_obj.map: + value = self._resolve_name_id_mapped_value(data_obj.map, cast_value=cast) + else: + value = data_obj.value + if cast: value = self.get_value_cast(value, attr) + return value + + def convert_value_to_data(self, value, attr=None): + """ + Convert the given value to a list of Data objects that can be placed + in the configuration's last layer's data section (DataContainer object). + @param value: The value to convert. + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + @return: The converted Data objects. + """ + value = self.set_value_cast(value, attr) + return [Data(fqr=self.fqr, value=value, attr=attr)] + def del_value(self, attr=None): """ Delete the topmost value for this feature. """ - if not self.is_sequence(): - self.dataproxy._del_value(attr) + self.dataproxy._del_value(attr) def get_value_cast(self, value, attr=None): """ @@ -1547,18 +1964,7 @@ Get the current value of the feature @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' """ - # Do not allow getting of setting of sequence values directly with Feature object - if not self.is_sequence(): - return self.dataproxy._get_value(attr) - else: - """ get the feature specific data from sequence => a column of data table """ - coldata = [] - feasequence = self.get_sequence_parent() - feapath = self._path(feasequence.data) - for row in feasequence.data: - feadata = row.get_feature(feapath) - coldata.append(feadata.value) - return coldata + return self.convert_data_to_value(self.dataproxy._get_datas(attr=attr), cast=False, attr=attr) def add_data(self, data): """ @@ -1616,6 +2022,10 @@ except AttributeError: return False + def is_sequence_root(self): + """ Return true if this feature is a sequence object it self """ + return False + def get_sequence_parent(self): """ Try to get a FeatureSequence object for this Feature if it is found """ try: @@ -1630,7 +2040,163 @@ def setdataproxy(self, value): self._dataproxy = value def deldataproxy(self): self._dataproxy = None dataproxy = property(getdataproxy, setdataproxy, deldataproxy) - value = property(get_value, set_value, del_value) + """ Use custom OProperty to enable overriding value methods in subclasses """ + value = utils.OProperty(get_value, set_value, del_value) + + def get_column_value(self, attr=None): + """ + Get the value of the featuresequence column + @param ref: the reference to the column + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + """ get the feature specific data from sequence => a column of data table """ + seq_parent = self.get_sequence_parent() + if seq_parent._has_empty_sequence_marker(): + return [] + + coldata = [] + colref = self.path(seq_parent) + for row in seq_parent.data: + feadata = row.get_feature(colref) + coldata.append(feadata.get_value(attr)) + return coldata + + def get_column_original_value(self, attr=None): + """ + Get the value of the featuresequence column + @param feasequence: the feature sequence object + @param ref: the reference to the column + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + """ get the feature specific data from sequence => a column of data table """ + seq_parent = self.get_sequence_parent() + if seq_parent._has_empty_sequence_marker(): + return [] + + coldata = [] + colref = self.path(seq_parent) + for row in seq_parent.data: + feadata = row.get_feature(colref) + coldata.append(feadata.get_original_value(attr)) + return coldata + + def set_column_value(self, value, attr=None): + """ + Get the value of the featuresequence column + @param feasequence: the feature sequence object + @param ref: the reference to the column + @param value: the value to set. This must be a list instance. + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + seq_parent = self.get_sequence_parent() + colref = self.path(seq_parent) + + if not isinstance(value,list): + raise exceptions.ConeException("The value for feature sequence '%s' column '%s' must be a list instance. Got %r" % (self.get_sequence_parent().fqr, colref, value)) + + # Handle the special case where the sequence is marked as empty + # with the empty sequence marker (single empty data element) + if seq_parent._has_empty_sequence_marker(): + seqrows = [] + else: + seqrows = seq_parent.data + + if len(seqrows) < len(value): + raise exceptions.ConeException("Too many values for feature sequence '%s' column '%s'. Sequence holds only %d rows. Got %d data values in %r" % (self.get_sequence_parent().fqr, colref, len(seqrows), len(value), value)) + for i in range(0, len(value)): + feadata = seqrows[i].get_feature(colref) + feadata.set_value(value[i]) + + def add_sequence_feature(self, feature, path=""): + """ + Override of the add_feature function in sequence to set the sequence childs to act + as columns of the feature sequence + @param feature: The Feature object to add + @param path: path to feature if it not added directly under parent_fea + """ + # modify all possible children of feature + for fea in feature._traverse(type=Feature): + to_sequence_feature(fea) + + # Finally modify and add this feature to parent_feat + to_sequence_feature(feature) + self._add_to_path(path, feature) + + def _resolve_name_id_mapped_value(self, mapping_string, cast_value=True): + """ + Resolve the name-ID mapped value based on the given mapping string. + @param mapping_string: The name-ID mapping string in the data element, e.g. + "FooFeature/FooSequence[@key='123']" + @param cast_value: If True, the resolved value will be cast to the corresponding + Python type, otherwise the raw string representation of the value in the + data element will be returned. + @return: The resolved value. + """ + def fail(msg): raise exceptions.NameIdMappingError(msg) + + pattern = r"^([\w/]+)\[@key='(.*)'\]$" + m = re.match(pattern, mapping_string) + if m is None: fail("Malformed mapping expression: %s" % mapping_string) + + source_seq_ref = m.group(1).replace('/', '.') + mapping_key = m.group(2) + + dview = self.get_root_configuration().get_default_view() + + try: + source_seq = dview.get_feature(source_seq_ref) + except exceptions.NotFound: + fail("Mapping source sequence '%s' does not exist" % source_seq_ref) + + if source_seq.type != 'sequence': + fail("Mapping source setting '%s' is not a sequence setting" % source_seq_ref) + if not source_seq.mapKey or not source_seq.mapValue: + fail("Source sequence '%s' must have both mapKey and mapValue specified" % source_seq_ref) + + def get_subsetting(ref): + """ + Return the sub-setting by the given mapKey or mapValue ref from the + source sequence. + @param ref: The reference in the format it is in the ConfML file. + E.g. 'SubSetting', 'FileSubSetting/localPath', 'FileSubSetting/targetPath' + """ + subsetting = source_seq.get_feature(ref.replace('/', '.')) + # Use localPath for file and folder settings by default + if subsetting.type in ('file', 'folder'): + subsetting = subsetting.get_feature('localPath') + return subsetting + + try: + key_subsetting = get_subsetting(source_seq.mapKey) + except exceptions.NotFound: + fail("Invalid mapKey in source sequence '%s': no sub-setting with ref '%s'" % (source_seq_ref, source_seq.mapKey)) + + + # Get possible override for mapValue from options + value_subsetting_ref = source_seq.mapValue + value_subsetting_ref_overridden = False + for opt in self._objects(type=Option): + if not opt.map or not opt.map_value: continue + if opt.map.replace('/', '.') == source_seq_ref: + value_subsetting_ref = opt.map_value + value_subsetting_ref_overridden = True + + try: + value_subsetting = get_subsetting(value_subsetting_ref) + except exceptions.NotFound: + if value_subsetting_ref_overridden: + fail("Invalid mapValue override in option: sub-setting '%s' does not exist under source sequence '%s'" % (value_subsetting_ref, source_seq_ref)) + else: + fail("Invalid mapValue in source sequence '%s': no sub-setting with ref '%s'" % (source_seq_ref, value_subsetting_ref)) + + key_list = key_subsetting.get_original_value() + if mapping_key not in key_list: + fail("No item-setting in source sequence '%s' matches key '%s'" % (source_seq_ref, mapping_key)) + + if cast_value: value_list = value_subsetting.get_value() + else: value_list = value_subsetting.get_original_value() + return value_list[key_list.index(mapping_key)] + class FeatureSequence(Feature): POLICY_REPLACE = 0 @@ -1642,11 +2208,9 @@ dataelem_name = '?datarows' template_name = '?template' def __init__(self, ref="", **kwargs): - super(FeatureSequence, self).__init__(ref) + super(FeatureSequence, self).__init__(ref, **kwargs) self.name = kwargs.get('name', ref) self.type = 'sequence' - self.mapKey = kwargs.get('mapKey') - self.mapValue = kwargs.get('mapValue') self._templatedata = None def _get_policy(self, data): @@ -1665,8 +2229,8 @@ elif firstdata.policy == 'prefix': return self.POLICY_PREPEND elif firstdata == data: - # otherwise the policy is either replace or undefined - # (firstdata.policy == 'replace' or firstdata.policy == ''): + # otherwise the policy is either replace or undefined + # (firstdata.policy == 'replace' or firstdata.policy == ''): return self.POLICY_REPLACE else: return self.POLICY_APPEND @@ -1675,7 +2239,6 @@ """ Set the template of the feature sequence """ - # If template data is not existing, create it if data != None: self._templatedata = data for feaname in self.list_features(): @@ -1690,11 +2253,19 @@ """ Add a feature data row for a new data in this sequence """ + create_sub_data_objs = True if dataobj == None: dataobj = Data(fqr=self.fqr) elif dataobj.attr != 'data': # Add data rows only for data objects (not e.g. RFS) return + else: + # If the data object is given, but it doesn't contain any child + # elements, don't add them automatically. This is to account for the + # case where there is only one empty data element that specifies + # that the sequence is set to be empty + if len(dataobj._order) == 0: + create_sub_data_objs = False fea = FeatureSequenceSub(self.dataelem_name) rowproxy = _FeatureDataProxy(fea._name, fea) """ the imaginary features share the parent relation of the proxy objects """ @@ -1707,13 +2278,32 @@ # add a data element under each feature. for feaname in self.list_all_features(): (pathto_fea, fearef) = utils.dottedref.psplit_ref(feaname) - rowproxy.add_feature(FeatureSequenceSub(fearef), pathto_fea) + subfea = self.get_feature(feaname) + cellfea = FeatureSequenceSub(fearef) + cellfea.set_value_cast = subfea.set_value_cast + cellfea.get_value_cast = subfea.get_value_cast + cellfea.convert_data_to_value = subfea.convert_data_to_value + cellfea.convert_value_to_data = subfea.convert_value_to_data + rowproxy.add_feature(cellfea, pathto_fea) subproxy = rowproxy.get_feature(feaname) - subproxy._obj._parent = subproxy._parent - if not dataobj._has(feaname): + subproxy._obj._parent = subproxy._parent + if create_sub_data_objs and not dataobj._has(feaname): dataobj._add_to_path(pathto_fea, Data(ref=fearef)) subproxy._add_data(dataobj._get(feaname)) - + return dataobj + + def _has_empty_sequence_marker(self): + """ + Return True if the sequence setting has the empty sequence marker (a single + empty data element), which denotes that the sequence is set to empty. + """ + datatable = self.get_data() + if len(datatable) == 1: + data_elem = datatable[0].get_datas()[0] + if len(data_elem._order) == 0: + return True + return False + def add(self, child, policy=container.REPLACE): """ A generic add function to add child objects. The function is intended to act as @@ -1731,38 +2321,83 @@ self._add(child) else: raise exceptions.IncorrectClassError("Cannot add %s to %s" % (child, self)) + + def add_feature(self, feature, path=""): + """ + Override of the add_feature function in sequence to set the sequence childs to act + as columns of the feature sequence + @param feature: The Feature object to add + """ + add_sequence_feature(self, feature, path) def add_sequence(self, data=None, policy=POLICY_APPEND): """ Add a feature data row for a new data in this sequence """ - self._add_datarow(None, policy) + if self._has_empty_sequence_marker(): + # We currently have the empty sequence marker (single empty data + # element), so this one that we are adding should replace it + policy = self.POLICY_REPLACE + + datarow = self._add_datarow(None, policy) + # add the new data sequence/row to the last configuration layer + last_config = self.get_root_configuration().get_last_configuration() + + container_policy = {self.POLICY_REPLACE: container.REPLACE, + self.POLICY_APPEND: container.APPEND, + self.POLICY_PREPEND: container.PREPEND}[policy] + last_config.add_data(datarow, container_policy) + # set the initial data if it is given rowproxy = utils.get_list(self.dataproxy._get(self.dataelem_name))[-1] if data != None: for index in range(len(data)): - rowproxy[index].set_value(data[index]) - # add the new data sequence/row to the last configuration layer - dataobj = rowproxy._get_data() - last_config = self.get_root_configuration().get_last_configuration() - last_config.add_data(dataobj, container.APPEND) - return dataobj + if data[index] != None: + rowproxy[index].set_value(data[index]) def set_template(self, data=None): """ Set the template of the feature sequence """ - # If template data is not existing, create it - if self._templatedata == None: - self._set_template_data(Data(ref=self.ref, template=True)) - # Add the template data to parent config - pconfig = self.find_parent(type=Configuration) - pconfig.add_data(self._templatedata) - - if data != None: - templdatas = self._templatedata._objects() - for index in range(len(data)): - templdatas[index].set_value(data[index]) + if data is None: + self._templatedata = None + return + + if not isinstance(data, list): + raise TypeError('data must be a list (got %r)' % data) + + # Create the new template data object + templatedata = Data(fqr=self.fqr, template=True) + + # Add all sub-objects to the data object + def add_data_objects(feature, data_obj, value_list): + refs = feature.list_features() + if len(refs) != len(value_list): + raise ValueError("Data value list is invalid") + for i, ref in enumerate(refs): + value = value_list[i] + subfea = feature.get_feature(ref) + if isinstance(value, list): + subdata = Data(ref=ref) + data_obj.add(subdata) + add_data_objects(feature.get_feature(ref), subdata, value) + else: + if value is not None: + subdata = Data(ref=ref, value=subfea.set_value_cast(value)) + data_obj.add(subdata) + add_data_objects(self, templatedata, data) + + self._set_template_data(templatedata) + + # Remove any existing template data + pconfig = self.find_parent(type=Configuration) + dataobjs = pconfig._traverse(type=Data, filters=[lambda x: x.template and x.fqr == self.fqr]) + if dataobjs: + for dataobj in dataobjs: + dataobj._parent._remove(dataobj.get_fullref()) + + # Add the template data to the parent config (beginning of the data section) + pconfig.add_data(self._templatedata, policy=container.PREPEND) def get_template(self): """ @@ -1771,7 +2406,19 @@ #self._set_template(None) # set the initial data if it is given if self._templatedata: - return [data.get_value() for data in self._templatedata._objects()] + def get_data_items(feature, data_obj): + refs = feature.list_features() + if refs: + result = [] + for ref in refs: + if data_obj._has(ref): + result.append(get_data_items(feature.get_feature(ref), data_obj._get(ref))) + else: + result.append(None) + return result + else: + return data_obj.value + return get_data_items(self, self._templatedata) else: return None @@ -1796,69 +2443,73 @@ # Get the data index self._add_datarow(data, self._get_policy(data)) return - - def get_map_key(self): - """ - Returns the setting that corresponds to mapKey attribute of this sequence feature. - """ - if self.mapKey != None: - mapkey = self.get_feature(self.mapKey) - return mapkey - else: - return None - - def get_map_key_value(self,key): - """ - Returns the setting that corresponds to mapKey attribute of this sequence feature. - """ - value = None - if self.mapKey != None and self.mapValue != None: - data = self.get_data() - for item in data: - kv = item.get_feature(self.mapKey).get_value() - if kv == key: - value = item.get_feature(self.mapValue).get_value() - return value - - def get_map_value(self): - """ - Returns the setting that corresponds to mapValue attribute of this sequence feature. - """ - if self.mapValue != None: - mapvalue = self.get_feature(self.mapValue) - return mapvalue - else: - return None def get_value(self, attr=None): """ Helper function to get the topmost data value from the default view. """ + if self._has_empty_sequence_marker(): + return [] + datatable = self.get_data() - rettable = [] + rettable = [] for row in datatable: rowvalues = row.value rettable.append(rowvalues) return rettable + def get_original_value(self, attr=None): + """ + Get the current value of the feature + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + if self._has_empty_sequence_marker(): + return [] + + datatable = self.get_data() + rettable = [] + for row in datatable: + rowvalues = row.get_original_value() + rettable.append(rowvalues) + return rettable + def set_value(self, value, attr=None): """ Set the current value for this feature. Set the value on the topmost layer. @param value: the value to set. The value must be a two dimensional array (e.g. matrix) """ - # sets the first data element to replace policy - try: - self.add_sequence(value.pop(0), self.POLICY_REPLACE) - # ignore the index error of an empty list - except IndexError: - pass - for row in value: - self.add_sequence(row) + if value: + # Add the first item with replace policy + self.add_sequence(value[0], self.POLICY_REPLACE) + for row in value[1:]: + self.add_sequence(row) + else: + # Setting the sequence to empty, so add one empty item-setting + # to signify that + self.add_sequence(None, self.POLICY_REPLACE) + + # Strip all sub-elements from the data element just created, + # since the ConfML spec says that an empty sequence is denoted + # by a single empty data element + data_elem = self.get_data()[0].get_datas()[0] + for r in list(data_elem._order): + data_elem._remove(r) def is_sequence(self): """ Return always true from a sequence object """ return True + def is_sequence_root(self): + """ Return true if this feature is a sequence object it self """ + return True + + def get_column_features(self): + """ Return a list of sequence subfeature, which are the columns of the sequence """ + columns = [] + for subref in self.list_features(): + columns.append(self.get_feature(subref)) + return columns + def get_sequence_parent(self): """ Return this object as a sequence parent """ return self @@ -1866,12 +2517,84 @@ value = property(get_value, set_value) data = property(get_data) + +def add_sequence_feature(parent_feature, feature, path=""): + """ + Override of the add_feature function in sequence to set the sequence childs to act + as columns of the feature sequence + @param parent_feature: The parent feature where the feature object is added + @param feature: The Feature object to add + @param path: path to feature if it not added directly under parent_fea + """ + # modify all possible children of feature + for fea in feature._traverse(type=Feature): + to_sequence_feature(fea) + + # Finally modify and add this feature to parent_feat + to_sequence_feature(feature) + parent_feature._add_to_path(path, feature) + +def to_sequence_feature(feature): + """ + modify a Feature object to sequence feature that will return column like data from a sequence. + @param feature: The Feature object for which is modified. + """ + feature.get_value = feature.get_column_value + feature.get_original_value = feature.get_column_original_value + feature.set_value = feature.set_column_value + feature.add_feature = feature.add_sequence_feature + +def get_column_value(feasequence, ref, attr=None): + """ + Get the value of the featuresequence column + @param feasequence: the feature sequence object + @param ref: the reference to the column + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + """ get the feature specific data from sequence => a column of data table """ + coldata = [] + for row in feasequence.data: + feadata = row.get_feature(ref) + coldata.append(feadata.get_value(attr)) + return coldata + +def get_column_original_value(feasequence, ref, attr=None): + """ + Get the value of the featuresequence column + @param feasequence: the feature sequence object + @param ref: the reference to the column + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + """ get the feature specific data from sequence => a column of data table """ + coldata = [] + for row in feasequence.data: + feadata = row.get_feature(ref) + coldata.append(feadata.get_original_value(attr)) + return coldata + +def set_column_value(feasequence, ref, value, attr=None): + """ + Get the value of the featuresequence column + @param feasequence: the feature sequence object + @param ref: the reference to the column + @param value: the value to set. This must be a list instance. + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + if not isinstance(value,list): + raise exceptions.ConeException("The value for feature sequence '%s' column '%s' must be a list instance. Got %r" % (feasequence.fqr, ref, value)) + seqrows = feasequence.data + if len(seqrows) < len(value): + raise exceptions.ConeException("Too many values for feature sequence '%s' column '%s'. Sequence holds only %d rows. Got %d data values in %r" % (feasequence.fqr, ref, len(seqrows), len(value), value)) + for i in range(0, len(value)): + feadata = seqrows[i].get_feature(ref) + feadata.set_value(value[i]) + class FeatureSequenceCell(Feature): """ A Feature class. Feature is the base for all Configurable items in a Configuration. """ def __init__(self, ref="", **kwargs): - super(Feature, self).__init__(ref) + super(FeatureSequenceCell, self).__init__(ref) self.name = kwargs.get('name', ref) self.type = 'seqcell' @@ -1897,11 +2620,16 @@ A Feature class. Feature is the base for all Configurable items in a Configuration. """ def __init__(self, ref="", **kwargs): - super(Feature, self).__init__(ref) + super(FeatureSequenceSub, self).__init__(ref) self.name = kwargs.get('name', ref) self.type = 'subseq' self._index = 0 + def __getstate__(self): + state = super(FeatureSequenceSub,self).__getstate__() + state['_children'].pop('?datarows', None) + return state + def get_index(self): """ @return : the index of the data element for sequential data defined inside the same configuration. @@ -1915,24 +2643,46 @@ @param value: the value row to set """ if utils.is_list(value): - for subindex in range(0, len(value)): + for subindex in range(0, len(value)): self.dataproxy[subindex].get_data().set_value(value[subindex]) - else: - self.dataproxy.get_data().set_value(value) + else: + data_objs = self.convert_value_to_data(value) + data_object_where_to_add = self._parent._get_data() + + self.dataproxy._set_datas(data_objs, attr) + data_object_where_to_add._add(data_objs, container.REPLACE) def get_value(self, attr=None): """ Set the current value for this feature. Set the value on the topmost layer. @param value: the value to set """ - # dataproxy = self.get_default_view().get_feature(self.get_fullfqr()) + # Handle empty sequences + if self.get_sequence_parent()._has_empty_sequence_marker(): + return [] + # The sequence cell only updates the latest value in the proxy childdatas = self.dataproxy._objects() if len(childdatas) > 0: return [subdata.value for subdata in childdatas] else: - return self.dataproxy._get_value(attr=attr) - + return super(FeatureSequenceSub,self).get_value(attr) + + def get_original_value(self, attr=None): + """ + Get the current value of the feature + @param attr: The attribute name of the data. E.g. attr='data', attr='rfs' + """ + # Handle empty sequences + if self.get_sequence_parent()._has_empty_sequence_marker(): + return [] + + childdatas = self.dataproxy._objects() + if len(childdatas) > 0: + return [subdata.get_original_value() for subdata in childdatas] + else: + return self.dataproxy._get_value(attr) + value = property(get_value, set_value) @@ -1941,14 +2691,49 @@ A _FeatureProxy class. _FeatureProxy is the object that is added to View as a link to the actual Feature object. """ - def __init__(self, link="", **kwargs): + """ class variable for defining the override attributes""" + override_attributes = ['name'] + ref_prefix = 'link_' + PROXYREF_PREFIX = 'proxy_' + + def __init__(self, ref="", **kwargs): # Store the fully qualified reference to this object - self.link = link - ref = link.replace('.', '_') - super(FeatureLink, self).__init__(ref) + self.link = kwargs.get('link', ref) + self.name = kwargs.get('name', None) + ref = self.get_featurelink_ref(self.link) + # the reference of this particular object + super(FeatureLink, self).__init__(ref, **kwargs) self._obj = None self._populated = False + def add(self, child, policy=container.REPLACE): + """ + Add an override to enable adding any override attribute to a featurelink object. + + A generic add function to add child objects. The function is intended to act as + proxy function that call the correct add function based on the child objects class. + + Example: obj.add(Feature("test")), actually obj.add_feature(Feature("test")) + @param child: the child object to add + @raise IncorrectClassError: if the given class cannot be added to this object. + """ + if isinstance(child, Base): + self._add(child, policy) + else: + raise exceptions.IncorrectClassError("Cannot add %s to %s" % (child, self)) + + def get_name(self): + """ + Return the name of the featurelink + """ + return self.name + + def set_name(self, name): + """ + Set the name + """ + self.name = name + @property def fqr(self): return self.link @@ -1962,14 +2747,58 @@ try: if not self._populated: feas = self.get_default_view().get_features(self.link) + # get the non wildcard part of ref + static_ref = utils.dottedref.get_static_ref(self.link) # add the found features to the parent for fea in feas: - self._get_parent().add_feature(fea._obj) + override_attrs = {} + # override the FeatureProxy object with exactly same reference + # (in feat/* case dont override the children features) + if fea.fqr == static_ref: + override_attrs = self.get_attributes() + feature = fea._obj + proxy_ref = self.get_featureproxy_ref(feature.fqr) + proxy = _FeatureProxy(proxy_ref, feature, **override_attrs) + self._get_parent()._add(proxy) + except exceptions.NotFound, e: parent_view = self._find_parent_or_default(type=View) view_name = parent_view.get_name() logging.getLogger('cone').info("Warning: Feature '%s' in view '%s' not found." % (self.link, view_name)) + def get_attributes(self): + """ + Returns a list of FeatureLink attributes that override settings of the original feature. + @return: a dictionary of attribute key : value pairs. + """ + attrs = {} + for attr in self.override_attributes: + # try to get the attribute from this object + # and set it to the attribute list if it not None + try: + value = getattr(self, attr) + if value != None: attrs[attr] = value + except AttributeError: + pass + return attrs + + @classmethod + def get_featurelink_ref(cls, ref): + """ + return a featurelink ref from a feature ref. + This is needed to make the featurelink object refs unique in a container + that has Features. + """ + return cls.ref_prefix + ref.replace('.', '_').replace('/','_') + + @classmethod + def get_featureproxy_ref(cls, ref): + """ + Return a ref for a given setting fqr to be used under a group. + This is needed to make the featureproxy object refs unique in a container + that has Features. + """ + return cls.PROXYREF_PREFIX + ref.replace('.', '_').replace('/','_') class _FeatureProxy(container.ObjectProxyContainer, Base): """ @@ -1977,10 +2806,10 @@ link to the actual Feature object. """ def __init__(self, ref="", obj=None, **kwargs): - super(_FeatureProxy, self).__init__(obj, ref) - Base.__init__(self, ref) + container.ObjectProxyContainer.__init__(self, obj, ref) + Base.__init__(self, ref, **kwargs) self.support_data = False - + def __getattr__(self, name): """ First check if the requested attr is a children then @@ -1995,7 +2824,7 @@ return self._objects()[index] def __setitem__(self, index, value): - raise exceptions.NotSupported() + raise exceptions.NotSupportedException() def __delitem__(self, index): item = self.__getitem__(index) @@ -2020,6 +2849,12 @@ """ self._parent = newparent + def get_proxied_obj(self): + """ + @return: Returns proxied object. + """ + return self._obj + def add_feature(self, feature, path=""): """ """ @@ -2061,7 +2896,65 @@ """ pass - + def has_attribute(self, name): + """ + Perform a check whether an attribute with given name is stored inside the + _FeatureProxy. The check does not extend to the proxied (_obj) insanses or + children of this proxy. + + @return: True when an attribute is a real attribute in this _FeatureProxy object. + """ + return self.__dict__.has_key(name) + + def get_option(self, ref): + """ + @param name: The option reference of the option (as returned by list_options()) + """ + real_ref = 'opt_' + ref + for op in self.options.values(): + if op.ref == real_ref: + return op + else: + + obj = self.get_proxied_obj()._get(real_ref) + if not isinstance(obj, Option): + raise TypeError('Object %r is not an instance of Option (%r instead)' % (real_ref, type(obj))) + return obj + + def list_options(self): + """ + Return a array of all Option children references under this object. + """ + opts = self.get_proxied_obj().list_options() + + for opt in self.options: + opts.append(self.options[opt].ref[4:]) + + return opts + + def get_property(self, ref): + """ + @param name: The property reference of the property (as returned by list_properties()) + """ + for prop in self.properties.values(): + if prop.ref == Property.to_propertyref(ref): + return prop + else: + obj = self.get_proxied_obj()._get(Property.to_propertyref(ref)) + return obj + + def list_properties(self): + """ + Return a array of all Property children references under this object. + """ + props = self.get_proxied_obj().list_properties() + + for pr in self.properties: + props.append(Property.to_normref(self.properties[pr].ref)) + + return props + + class _FeatureDataProxy(_FeatureProxy): """ A Feature class. Feature is the base for all Configurable items in a Configuration. @@ -2085,10 +2978,10 @@ """ """ if object.__getattribute__(self, '_obj') is not None: - self._obj.dataproxy = self + self.get_proxied_obj().dataproxy = self if name in Feature.PROPERTIES: - return getattr(self._obj, name) + return getattr(self.get_proxied_obj(), name) else: return super(_FeatureDataProxy, self).__getattr__(name) @@ -2096,10 +2989,10 @@ """ """ if object.__getattribute__(self, '_obj') is not None: - self._obj.dataproxy = self + self.get_proxied_obj().dataproxy = self if name in Feature.PROPERTIES: - return setattr(self._obj, name, value) + return setattr(self.get_proxied_obj(), name, value) else: super(_FeatureDataProxy, self).__setattr__(name, value) @@ -2107,15 +3000,19 @@ """ """ if name in Feature.PROPERTIES: - return delattr(self._obj, name) + return delattr(self.get_proxied_obj(), name) else: return super(_FeatureDataProxy, self).__delattr__(name) def _add_data(self, data): """ - Add a data value. + Add a data value or a list of data values. @param data: A Data object """ + if isinstance(data, list): + for d in data: self._add_data(d) + return + try: self.datas[data.attr].append(data) except KeyError: @@ -2142,7 +3039,14 @@ Get the entire data array. """ dataattr = attr or self.defaultkey - return self.datas[dataattr] + return self.datas.get(dataattr, []) + + def _set_datas(self, datas, attr=None): + """ + Set the entire data array. + """ + dataattr = attr or self.defaultkey + self.datas[dataattr] = list(datas) def _get_value(self, attr=None): """ @@ -2152,7 +3056,7 @@ return self._get_data(attr).get_value() else: return None - + def _set_value(self, datavalue, attr=None): """ Set the value for the feature the last configuration in the current hierarchy @@ -2253,6 +3157,18 @@ self.policy = kwargs.get('policy', '') self.template = kwargs.get('template', False) self.map = kwargs.get('map') + self.empty = kwargs.get('empty', False) + self.lineno = None + + def __setstate__(self, state): + super(Data, self).__setstate__(state) + self.value = state.get('value', None) + self.attr = state.get('attr', None) + self.policy = state.get('policy', '') + self.map = state.get('map', None) + self.template = state.get('template', False) + self.lineno = state.get('lineno', None) + self.fearef = state.get('fearef', None) def get_fearef(self): if self.fearef: @@ -2261,14 +3177,7 @@ return self.fqr def get_value(self): - if self.map != None: - ref = utils.resourceref.to_dref(self.get_map_ref()) - key = self.get_map_key_value() - dview = self.get_root_configuration().get_default_view() - fea = dview.get_feature(ref) - return fea.get_map_key_value(key) - else: - return self.value + return self.value def get_map(self): return self.map @@ -2279,18 +3188,6 @@ #Either value or mapping can be defined. Not both. self.value = None - def get_map_ref(self): - if self.map != None: - return utils.DataMapRef.get_feature_ref(self.map) - else: - return None - - def get_map_key_value(self): - if self.map != None: - return utils.DataMapRef.get_key_value(self.map) - else: - return None - def set_value(self, value): self.value = value if self.map: @@ -2303,7 +3200,7 @@ policy = property(get_policy, set_policy, del_policy) -class ValueSet(sets.Set): +class ValueSet(set): """ A value set object to indicate a set of possible values for a feature. e.g. A boolean feature ValueSet([True, False]) @@ -2337,6 +3234,51 @@ return False +class Property(Base): + """ + Confml property class + """ + def __init__(self, **kwargs): + """ + @param name=str: name string (mandatory) + @param value=str: value for the property, string + @param unit=str: unit of the property + """ + if kwargs.get('name',None) == None: + raise ValueError("Property name cannot be None!") + super(Property,self).__init__(Property.to_propertyref(kwargs.get('name',None))) + self.name = kwargs.get('name',None) + self.value = kwargs.get('value',None) + self.unit = kwargs.get('unit',None) + + @classmethod + def to_propertyref(cls, name): + """ + @param name: name of the property + @return: A property reference. + """ + if name is not None: + return "property_%s" % name + else: + raise ValueError("Property name cannot be None!") + + @classmethod + def to_normref(cls, ref): + """ + @param ref: a property reference + @return: normalized property reference + """ + return ref[9:] + + def get_name(self): + return self.name + + def get_value(self): + return self.value + + def get_unit(self): + return self.unit + class Option(Base): """ Confml option class. @@ -2347,6 +3289,8 @@ self.value = value self.map = kwargs.get('map', None) self.relevant = kwargs.get('relevant', None) + self.map_value = kwargs.get('map_value', None) + self.display_name = kwargs.get('display_name', None) @classmethod def to_optref(cls, value, map): @@ -2391,7 +3335,7 @@ MODE_APPEND = 3 MODE_DELETE = 4 - def __init__(self, path): + def __init__(self, path, mode=''): """ @param path: the reference to the root of the storage. """ @@ -2399,7 +3343,16 @@ self.curpath = "" self.container = True self.__opened_res__ = {} - + self.mode = mode + self.cpath_stack = [] + + def __reduce_ex__(self, protocol_version): + return (open_storage, + (self.path, self.mode), + None, + None, + None) + def __opened__(self, res): """ Internal function to add a newly opened Resource object to the list of open resources. @@ -2496,6 +3449,34 @@ """ return self.rootpath + def push(self, path): + """ + Set the current path under the Storage to the given path and push the possible existing path to a stack. + The current path can be reverted with pop method. + + @return: None + @param path: The path which is set as current path. + """ + self.cpath_stack.append(self.curpath) + self.curpath = path + + def pop(self): + """ + Pop a path from path stack and set the current path to the popped element. The path can be pushed to the + current path stack with push. + + NOTE! if the pop is called when the current path stack is empty, the path will just remain is empty path + keeping the active path in the storages root path. + + @return: The new path. + """ + try: + path = self.cpath_stack.pop() + self.curpath = path + except IndexError: + pass + return self.curpath + def set_current_path(self, path): """ @param path: the current path under the Storage. @@ -2556,7 +3537,7 @@ """ raise exceptions.NotSupportedException() - def list_resources(self, path, recurse=False): + def list_resources(self, path, **kwargs): """ find the resources under certain ref/path @param ref : reference to path where resources are searched @@ -2581,14 +3562,6 @@ """ raise exceptions.NotSupportedException() - def close_resource(self, path): - """ - Close a given resource instance. Normally this is called by the Resource object - in its own close. - @param ref the reference to the resource to close. - """ - raise exceptions.NotSupportedException() - def save_resource(self, path): """ Flush the changes of a given resource instance. Normally this is called by the Resource object @@ -2691,13 +3664,13 @@ Trunkate this resource data to the given size. @param size: The size to trunkate. Default value is zero, which make the resource empty. """ - raise NotSupportedException() + raise exceptions.NotSupportedException() def save(self, size=0): """ Save all changes to data to storage. """ - raise NotSupportedException() + raise exceptions.NotSupportedException() def get_mode(self): return self.storage.get_mode(self.mode) @@ -2768,27 +3741,208 @@ except Exception, e: self.logger.warning("Invalid BMP-file: %s" % resource.get_path()) + +class currentdir(object): + def __init__(self, storage, curdir): + self.storage = storage + # make sure that the curdir does not contain path prefix + self.curdir = curdir.lstrip('/') + + def __enter__(self): + self.storage.push(self.curdir) + + def __exit__(self, type, value, tb): + self.storage.pop() + + class Folder(object): """ A Folder object is a subfolder of a Storage, offering access to part of the Storages resources. """ - def __init__(self, storage, path): + def __init__(self, storage, path, **kwargs): """ Create a layer folder to the storage if it does not exist. """ - #if not storage.is_folder(path): - # storage.create_folder(path) - self.storage = copy.copy(storage) - self.storage.set_current_path(path) - - def __getattr__(self, name): - return getattr(self.storage, name) - -class CompositeLayer(object): + self.curpath = path + self.storage = storage + + def set_path(self, path): + """ + """ + self.curpath = path + + def get_path(self): + """ + """ + return self.curpath + + def set_current_path(self, path): + """ + @param path: the current path under the Storage. + """ + self.curpath = utils.resourceref.remove_end_slash(utils.resourceref.remove_begin_slash(path)) + + def get_current_path(self): + """ + get the current path under the Storage. + """ + return self.curpath + + def close(self): + """ + Close the repository, which will save and close all open resources. + """ + self.storage.close() + + def save(self): + """ + Flush changes from all resources to the repository. + """ + return self.storage.save() + + def open_resource(self, path, mode="r"): + """ + Open the given resource and return a File object. + @param path : reference to the resource + @param mode : the mode in which to open. Can be one of r = read, w = write, a = append. + raises a NotResource exception if the ref item is not a resource. + """ + with currentdir(self.storage, self.curpath): + res = self.storage.open_resource(path, mode) + return res + + def delete_resource(self, path): + """ + Delete the given resource from storage + @param path: reference to the resource + raises a NotSupportedException exception if delete operation is not supported by the storage + """ + with currentdir(self.storage, self.curpath): + res = self.storage.delete_resource(path) + return res + + def close_resource(self, path): + """ + Close a given resource instance. Normally this is called by the Resource object + in its own close. + @param path the reference to the resource to close. + """ + with currentdir(self.storage, self.curpath): + res = self.storage.close_resource(path) + return res + + def is_resource(self, path): + """ + Return true if the ref is a resource + @param ref : reference to path where resources are searched + """ + with currentdir(self.storage, self.curpath): + res = self.storage.is_resource(path) + return res + + def list_resources(self, path, **kwargs): + """ + find the resources under certain ref/path + @param ref : reference to path where resources are searched + @param recurse : defines whether to return resources directly under the path or does the listing recurse to subfolders. + Default value is False. Set to True to enable recursion. + """ + with currentdir(self.storage, self.curpath): + res = self.storage.list_resources(path, **kwargs) + return res + + def import_resources(self, paths, storage): + """ + import resources from a list of resources to this storage + @param paths : a list of Resourse objects. + @param storage : the external storage from which files are imported. + """ + with currentdir(self.storage, self.curpath): + res = self.storage.import_resources(paths, storage) + return res + + def export_resources(self, paths, storage): + """ + export resources from this storage based on a list of reference to this storage + @param path : a list of resource paths in this storage (references). + @param storage : the external storage where to export. + """ + with currentdir(self.storage, self.curpath): + res = self.storage.export_resources(paths, storage) + return res + + def save_resource(self, path): + """ + Flush the changes of a given resource instance. Normally this is called by the Resource object + in its own save. + @param ref the reference to the resource to close. + """ + with currentdir(self.storage, self.curpath): + res = self.storage.save_resource(path) + return res + + def create_folder(self, path): + """ + Create a folder entry to a path + @param path : path to the folder + """ + with currentdir(self.storage, self.curpath): + res = self.storage.create_folder(path) + return res + + def delete_folder(self, path): + """ + Delete a folder entry from a path. The path must be empty. + @param path : path to the folder + """ + with currentdir(self.storage, self.curpath): + res = self.storage.delete_folder(path) + return res + + def is_folder(self, path): + """ + Check if the given path is an existing folder in the storage + @param path : path to the folder + """ + with currentdir(self.storage, self.curpath): + res = self.storage.is_folder(path) + return res + + def get_mode(self, mode_str): + return self.storage.get_mode() + + def unload(self, path, object): + """ + Dump a given object to the storage + @param object: The object to dump to the storage, which is expected to be an instance + of Base class. + @param path: The reference where to store the object + @param object: The object instance to dump + @raise StorageException: if the given object cannot be dumped to this storage + """ + with currentdir(self.storage, self.curpath): + res = self.storage.unload(path, object) + return res + + def load(self, path): + """ + Load an object from a reference. + @param path: The reference where to load the object + @raise StorageException: if the given object cannot be loaded as an object from this storage + """ + with currentdir(self.storage, self.curpath): + res = self.storage.load(path) + return res + + path = property(get_path, set_path) + + +class CompositeLayer(Folder): """ A base class for composite Configuration objects. """ - def __init__(self, path="", **kwargs): + def __init__(self, storage, path="", **kwargs): + super(CompositeLayer, self).__init__(storage, path, **kwargs) self.layers = kwargs.get('layers', []) self.path = path @@ -2850,18 +4004,29 @@ lres.append(utils.resourceref.join_refs([layerpath, respath])) return lres - def list_all_resources(self, empty_folders=False): + def list_all_resources(self, **kwargs): """ Returns a list of all layer related resource paths with full path in the storage. """ lres = [] for layerpath in self.list_layers(): sublayer = self.get_layer(layerpath) - for respath in sublayer.list_all_resources(empty_folders): + for respath in sublayer.list_all_resources(**kwargs): lres.append(utils.resourceref.join_refs([layerpath, respath])) - return lres - + + def list_all_related(self, **kwargs): + """ + Returns a list of all (non confml) layer related resource paths with full path in the storage. + """ + lres = [] + for layerpath in self.list_layers(): + sublayer = self.get_layer(layerpath) + for respath in sublayer.list_all_related(**kwargs): + lres.append(utils.resourceref.join_refs([layerpath, respath])) + + return lres + class Layer(CompositeLayer): """ A Layer object is a subfolder of a Storage, offering access to part of the Storages resources. @@ -2876,11 +4041,9 @@ @param content_path: optional parameter for content files path (give in content_path="something") @param doc_path: optional parameter for doc files path (give in doc_path="something") """ - super(Layer, self).__init__(path, **kwargs) + super(Layer, self).__init__(storage, path, **kwargs) #if not storage.is_folder(path): # storage.create_folder(path) - self.storage = copy.copy(storage) - self.storage.set_current_path(path) self.predefined = {'confml_path' : 'confml', 'implml_path' : 'implml', 'content_path' : 'content', @@ -2894,11 +4057,26 @@ def __getattr__(self, name): return getattr(self.storage, name) + def __getstate__(self): + state = {} + state['predefined'] = self.predefined + state['path'] = self.path + state['layers'] = self.layers + return state + + def __setstate__(self, state): + state = {} + self.predefined = state.get('predefined',{}) + self.path = state.get('path','') + self.layers = state.get('layers',[]) + + return state + def list_confml(self): """ @return: array of confml file references. """ - res = self.storage.list_resources(self.predefined['confml_path'], True) + res = self.list_resources(self.predefined['confml_path'], recurse=True) res += super(Layer, self).list_confml() return res @@ -2906,7 +4084,7 @@ """ @return: array of implml file references. """ - res = self.storage.list_resources(self.predefined['implml_path'], True) + res = self.list_resources(self.predefined['implml_path'], recurse=True) res += super(Layer, self).list_implml() return res @@ -2914,7 +4092,7 @@ """ @return: array of content file references. """ - res = self.storage.list_resources(self.predefined['content_path'], True) + res = self.list_resources(self.predefined['content_path'], recurse=True) res += super(Layer, self).list_content() return res @@ -2922,59 +4100,85 @@ """ @return: array of document file references. """ - res = self.storage.list_resources(self.predefined['doc_path'], True) + res = self.list_resources(self.predefined['doc_path'], recurse=True) res += super(Layer, self).list_doc() return res def confml_folder(self): - cpath = self.storage.get_current_path() + cpath = self.get_current_path() spath = self.predefined['confml_path'] return Folder(self.storage, utils.resourceref.join_refs([cpath, spath])) def implml_folder(self): - cpath = self.storage.get_current_path() + cpath = self.get_current_path() spath = self.predefined['implml_path'] return Folder(self.storage, utils.resourceref.join_refs([cpath, spath])) def content_folder(self): - cpath = self.storage.get_current_path() + cpath = self.get_current_path() spath = self.predefined['content_path'] return Folder(self.storage, utils.resourceref.join_refs([cpath, spath])) def doc_folder(self): - cpath = self.storage.get_current_path() + cpath = self.get_current_path() spath = self.predefined['doc_path'] return Folder(self.storage, utils.resourceref.join_refs([cpath, spath])) - def list_all_resources(self, empty_folders=False): + def list_all_resources(self, **kwargs): """ Returns a list of all layer related resource paths with full path in the storage. """ lres = [] - mypath = self.get_current_path() - for folderpath in sorted(self.predefined.values()): - lres += self.storage.list_resources(folderpath, recurse=True, empty_folders=empty_folders) + lres += self.list_resources(folderpath, recurse=True) - lres += super(Layer, self).list_all_resources(empty_folders) - + lres += super(Layer, self).list_all_resources() return lres - def list_all_related(self, empty_folders=False): + def list_all_related(self, **kwargs): """ Returns a list of all (non confml) layer related resource paths with full path in the storage. """ + lres = [] + exclude_filters = kwargs.get('exclude_filters', {}) + kwargs['recurse'] = True predef = self.predefined.copy() del predef['confml_path'] - mypath = self.get_current_path() for folderpath in sorted(predef.values()): - lres += self.storage.list_resources(folderpath, recurse=True, empty_folders=empty_folders) - lres += super(Layer, self).list_all_resources(empty_folders=empty_folders) + filter = exclude_filters.get(folderpath, None) + resources = self.list_resources(folderpath, **kwargs) + if filter: + lres += [res for res in resources if not re.search(filter, res, re.IGNORECASE)] + else: + lres += resources + lres += super(Layer, self).list_all_related(**kwargs) return lres +class Include(Base, container.LoadLink): + """ + A common include element that automatically loads a resource + and its object under this include element. + """ + def __init__(self, ref="", **kwargs): + path = kwargs.get('path') or ref + store_interface = kwargs.get('store_interface',None) + ref = utils.resourceref.to_objref(path) + container.LoadLink.__init__(self, path, store_interface) + Base.__init__(self, ref) + + def get_store_interface(self): + if not self._storeint and self._parent: + try: + self._storeint = self._parent.get_store_interface() + except exceptions.NotFound: + # If project is not found, let the store interface be None + pass + return self._storeint + + class Rule(object): """ Base class for Rules in the system. @@ -3007,7 +4211,104 @@ return mapmodule.public.mapping.BaseMapper() -################################################################## +class Problem(object): + SEVERITY_ERROR = "error" + SEVERITY_WARNING = "warning" + SEVERITY_INFO = "info" + + def __init__(self, msg, **kwargs): + self.msg = msg + self.type = kwargs.get('type', '') + self.line = kwargs.get('line', None) + self.file = kwargs.get('file', None) + self.severity = kwargs.get('severity', self.SEVERITY_ERROR) + self.traceback = kwargs.get('traceback', None) + # A slot for any problem specific data + self.problem_data = kwargs.get('problem_data', None) + + def log(self, logger, current_file=None): + """ + Log this problem with the given logger. + """ + file = self.file or current_file + if self.line is None: + msg = "(%s) %s" % (file, self.msg) + else: + msg = "(%s:%d) %s" % (file, self.line, self.msg) + + mapping = {self.SEVERITY_ERROR: logging.ERROR, + self.SEVERITY_WARNING: logging.WARNING, + self.SEVERITY_INFO: logging.INFO} + level = mapping.get(self.severity, logging.ERROR) + logger.log(level, msg) + + if self.traceback: + logger.debug(self.traceback) + + @classmethod + def from_exception(cls, ex): + """ + Create a Problem object from an exception instance. + + If the exception is a sub-class of ConeException, then it may contain + extra information (like a line number) for the problem. + """ + if isinstance(ex, exceptions.ConeException): + return Problem(msg = ex.problem_msg or unicode(ex), + type = ex.problem_type or '', + line = ex.problem_lineno, + severity = cls.SEVERITY_ERROR) + else: + return Problem(msg = unicode(ex), + severity = cls.SEVERITY_ERROR) + + def __repr__(self): + var_data = [] + for varname in ('msg', 'type', 'line', 'file', 'severity'): + var_data.append("%s=%r" % (varname, getattr(self, varname))) + return "%s(%s)" % (self.__class__.__name__, ', '.join(var_data)) + + def __eq__(self, other): + if not isinstance(other, Problem): + return False + for varname in ('msg', 'type', 'line', 'file', 'severity'): + self_val = getattr(self, varname) + other_val = getattr(other, varname) + if self_val != other_val: + return False + return True + + def __ne__(self, other): + return self == other + + def __lt__(self, other): + if not isinstance(other, Problem): + return False + return (self.file, self.line) < (other.file, other.line) + +def make_content_info(resource, data): + """ + Factory for ContentInfo + """ + cnt_inf = None + + if resource != None: + guessed_type = mimetypes.guess_type(resource.get_path()) + mimetype = None + mimesubtype = None + + if guessed_type != None: + mimetype, mimesubtype = guessed_type[0].split('/') + + if mimetype == 'image' and mimesubtype == 'x-ms-bmp': + cnt_inf = BmpImageContentInfo(resource, data) + else: + cnt_inf = ContentInfo(mimetype, mimesubtype) + return cnt_inf + +def open_storage(path, mode="r", **kwargs): + return Storage.open(path, mode="r", **kwargs) + class NullHandler(logging.Handler): """ Default handler that does not do anything.