Source code for smo.model.model

import fields
from collections import OrderedDict
from smo.model.fields import FieldGroup, BasicGroup, ModelView, SuperGroup
from smo.web.blocks import HtmlBlock, JsBlock
import smo.web.exceptions as E
from smo.model.actions import ServerAction

# Global registry of numerical models
modelRegistry = OrderedDict()

[docs]class NumericalModelMeta(type): """Metaclass facilitating the creation of a numerical model class. Collects all declared fields, submodels, basic groups, supergroups and model views in the class in respective dictionaries""" def __new__(cls, name, bases, attrs): # Label if ('label' not in attrs): attrs['label'] = name if ('showOnHome' not in attrs): attrs['showOnHome'] = True if ('async' not in attrs): attrs['async'] = False if (attrs['async'] == True): if ('progressOptions' not in attrs): attrs['progressOptions'] = {'suffix': '%', 'fractionOutput': False} if ('abstract' not in attrs): attrs['abstract'] = False # Collect fields from current class. current_fields = [] current_submodels = [] current_basicGroups = {} current_superGroups = {} current_modelViews = {} current_ports = {} for key, value in list(attrs.items()): if isinstance(value, fields.SubModelGroup): current_submodels.append((key, value)) if (isinstance(value.group, BasicGroup)): current_basicGroups[key] = value elif (isinstance(value.group, SuperGroup)): current_superGroups[key] = value else: raise TypeError ('The submodel group {} in class {} must be either field or view group'.format(key, name)) value.name = key attrs.pop(key) elif isinstance(value, fields.Port): current_ports[key] = value value.name = key attrs.pop(key) elif isinstance(value, fields.Field): current_fields.append((key, value)) value.name = key attrs.pop(key) elif isinstance(value, fields.BasicGroup): value.name = key current_basicGroups[key] = value attrs.pop(key) elif isinstance(value, fields.SuperGroup): value.name = key current_superGroups[key] = value attrs.pop(key) elif isinstance(value, fields.Group): # value.name = key # attrs.pop(key) raise TypeError('Unknown group type') elif isinstance(value, ModelView): value.name = key current_modelViews[key] = value attrs.pop(key) elif isinstance(value, HtmlBlock) or isinstance(value, JsBlock): value.name = key current_fields.sort(key=lambda x: x[1].creation_counter) current_submodels.sort(key=lambda x: x[1].creation_counter) # Fields attrs['declared_fields'] = OrderedDict(current_fields) attrs['declared_submodels'] = OrderedDict(current_submodels) attrs['declared_attrs'] = {} attrs['declared_attrs'].update(attrs['declared_fields']) attrs['declared_attrs'].update(attrs['declared_submodels']) attrs['declared_ports'] = current_ports # Groups attrs['declared_basicGroups'] = current_basicGroups attrs['declared_superGroups'] = current_superGroups attrs['declared_modelViews'] = current_modelViews # Create the class type klass = (super(NumericalModelMeta, cls) .__new__(cls, name, bases, attrs)) # Collect fields from base classes base_fields = OrderedDict() base_submodels = OrderedDict() base_basicGroups = {} base_superGroups = {} base_modelViews = {} base_ports = {} # It should not be necessary to walk the complete MRO. Just the bases should be enough # as they have already collected all the fields from their ancesstors #for base in reversed(klass.__mro__[1:]): for base in klass.__bases__: if not (hasattr(base, 'declared_attrs')): continue # Collect declared fields from base classes for key in base.declared_attrs: if key in klass.declared_attrs: raise AttributeError("Base class {0} defines attribute '{1}', which class {2} attempts to redefine".format(base.__name__, key, name)) elif (key in base_fields or key in base_submodels): raise AttributeError("Attribute {} in class {} already inherited. Attempt to inherit it again from another base class {}.".format(key, name, base.__name__)) base_fields.update(base.declared_fields) base_submodels.update(base.declared_submodels) # Collect declared ports from base classes for key, value in base.declared_ports.iteritems(): if key in klass.declared_ports: raise AttributeError("Base class {0} defines port '{1}', which class {2} attempts to redefine".format(base.__name__, key, name)) elif (key in base_ports): raise AttributeError("Port {} in class {} already inherited. Attempt to inherit it again from another base class {}.".format(key, name, base.__name__)) base_ports[key] = value # Collect declared field and view groups from base classes for key, value in base.declared_basicGroups.iteritems(): if (key in klass.declared_basicGroups): # The group is redefined in the new class, so don't inherit it pass else: if (key not in base_basicGroups): base_basicGroups[key] = value.copyByName() else: raise AttributeError("Group {} in class {} already inherited. Attempt to inherit it again from base class {}.".format(key, name, base.__name__)) # Collect declared super-groups from base classes for key, value in base.declared_superGroups.iteritems(): if (key in klass.declared_superGroups): # The group is redefined in the new class, so don't inherit it pass else: if (key not in base_superGroups): base_superGroups[key] = value.copyByName() else: raise AttributeError("Group {} in class {} already inherited. Attempt to inherit it again from base class {}.".format(key, name, base.__name__)) # Collect declared model-views from base classes for key, value in base.declared_modelViews.iteritems(): if (key in klass.declared_modelViews): # The group is redefined in the new class, so don't inherit it pass else: if (key not in base_modelViews): base_modelViews[key] = value.copyByName() else: raise AttributeError("ModelView {} in class {} already inherited. Attempt to inherit it again from base class {}.".format(key, name, base.__name__)) klass.declared_fields.update(base_fields) klass.declared_submodels.update(base_submodels) klass.declared_attrs.update(base_fields) klass.declared_attrs.update(base_submodels) klass.declared_basicGroups.update(base_basicGroups) klass.declared_superGroups.update(base_superGroups) klass.declared_modelViews.update(base_modelViews) klass.declared_ports.update(base_ports) # Resolving fields in field- and view-groups by name for value in klass.declared_basicGroups.itervalues(): if (isinstance(value, BasicGroup)): # Could be submodel group value.resolve(klass.declared_fields) #Resolving field groups, view groups or submodel groups by name for value in klass.declared_superGroups.itervalues(): if (isinstance(value, SuperGroup)): value.resolve(klass.declared_basicGroups) # Resolving supergroups by name for value in klass.declared_modelViews.itervalues(): value.resolve(klass.declared_superGroups) # Checking for obligatory attribute 'modelBlocks' if (name != 'NumericalModel' and not klass.abstract): if ('modelBlocks' not in klass.__dict__): modelBlocks = None for base in klass.__bases__: if hasattr(base, 'modelBlocks'): modelBlocks = [block.name if isinstance(block, ModelView) else block for block in base.modelBlocks] if (modelBlocks is None): raise AttributeError("Page structure undefined. Class {0} must have attribute 'modelBlocks' or must inherit it from a base class.".format(name)) else: klass.modelBlocks = modelBlocks for i in range(len(klass.modelBlocks)): # Then resolve them to the ModelViews from the current model if (isinstance(klass.modelBlocks[i], basestring)): klass.modelBlocks[i] = klass.declared_modelViews[klass.modelBlocks[i]] #print("NumericalModel class: {}, {}".format(name, [modelBlock.name for modelBlock in klass.modelBlocks])) if (klass.__name__ != 'NumericalModel'): modelRegistry[klass.__name__] = klass return klass
[docs]class NumericalModel(object): """ Abstract base class for numerical models. Class attributes: * :attr:`label`: label for the numerical model class (default is the numerical model class name), shows as title and thumbnail text for the model * :attr:`showOnHome`: used to specify if a thumbnail of the model is to show on the home page (default is True) * :attr:`figure`: ModelFigure object representing a figure, displayed on the page module of the model and on its thumbnail * :attr:`description`: ModelDescription object representing a description for the model, also used as tooltip of the model's thumbnail * :attr:`async`: Boolean value indicating if the computation is to be done asynchronously * :attr:`progressOptions`: dictionary of progress display options in an asynchronous computation. Includes keys 'suffix', a string, and 'fractionOutput', boolean indicating if the progress value is to show in fraction format * :attr:`declared_fields`: OrderedDict containing the fields declared in the model * :attr:`declared_submodels`: OrderedDict containing the submodels declared in the model * :attr:`declared_attrs`: dictionary containing the declared fields and submodels * :attr:`declared_basicGroups`: dictionary containing the field-groups and view-groups declared in the model * :attr:`declared_superGroups`: dictionary containing the supergroups declared in the model * :attr:`declared_modelViews`: dictionary containing the declared model views * :attr:`modelBlocks`: (mandatory) list of blocks making up the model's page module. Block types may be: ModelView, HtmlBlock, JsBlock """ __metaclass__ = NumericalModelMeta
[docs] def __new__(cls, *args, **kwargs): """Constructor for all numerical models. Sets default values for all model fields""" self = object.__new__(cls) # Set default values to fields for name, field in self.declared_fields.iteritems(): self.__setattr__(name, field.default) # Create submodel instances for name, submodel in self.declared_submodels.iteritems(): if name in kwargs: params = kwargs.pop(name) instance = submodel.klass(**params) else: instance = submodel.klass() self.__dict__[name] = instance # Modify fields with values from the constructor for name, value in kwargs.iteritems(): self.__setattr__(name, value) # Create port instances for name, port in self.declared_ports.iteritems(): self.__dict__[name] = port.klass() return self
[docs] def __setattr__(self, name, value): """Sets field value using the :func:`Field.parseValue` method""" if name in self.declared_fields.keys(): object.__setattr__(self, name, self.declared_fields[name].parseValue(value)) else: object.__setattr__(self, name, value) #raise AttributeError("Class '{0}' has no field '{1}'".format(self.__class__.__name__, name))
def __getattr__(self, name): return object.__getattribute__(self, name) def redefineField(self, fieldName, basicGroupName, newField): newField.name = fieldName i = self.declared_basicGroups[basicGroupName].fields.index(self.declared_fields[fieldName]) self.declared_basicGroups[basicGroupName].fields[i] = newField self.declared_fields[fieldName] = newField
[docs] def modelView2Json(self, modelView): """Creates JSON representation of the modelView including field definitions, field values and actions""" if (isinstance(modelView, basestring)): modelView = self.declared_modelViews[modelView] definitions = [] fieldValues = {} actions = [] for group in modelView.superGroups: if (isinstance(group, fields.SubModelGroup)): groupContent = self.subModelGroup2Json(group, fieldValues) elif (isinstance(group, fields.SuperGroup)): groupContent = self.superGroup2Json(group, fieldValues) else: raise TypeError("The argument to 'groupList2Json' must be a list of SuperGroups" ) definitions.append(groupContent) if (modelView.actionBar is not None): for action in modelView.actionBar.actionList: actions.append(action.toJson()) print fieldValues return {'definitions': definitions, 'values': fieldValues, 'actions': actions, 'keepDefaultDefs': modelView.keepDefaultDefs, 'computeAsync' : self.async}
[docs] def superGroup2Json(self, group, fieldValues): """ Provides JSON serializaton of super-group """ jsonObject = {'type': 'SuperGroup', 'name': group.name, 'label': group.label} if (group.show is not None): jsonObject['show'] = group.show subgroupList = [] for subgroup in group.groups: if (isinstance(subgroup, fields.SubModelGroup)): groupContent = self.subModelGroup2Json(subgroup, fieldValues) elif (isinstance(subgroup, fields.BasicGroup)): groupContent = self.basicGroup2Json(subgroup, fieldValues) else: raise TypeError("SuperGroup can only contain Field groups and View groups, not {}".format(type(subgroup))) subgroupList.append(groupContent) jsonObject['groups'] = subgroupList return jsonObject
[docs] def basicGroup2Json(self, group, fieldValues): """ Provides JSON serializaton of field-group and view-group """ jsonObject = {'name': group.name, 'label': group.label} if (group.show is not None): jsonObject['show'] = group.show jsonObject['hideContainer'] = group.hideContainer if (isinstance(group, FieldGroup)): jsonObject['type'] = 'FieldGroup' else: jsonObject['type'] = 'ViewGroup' fieldList = [] for field in group.fields: fieldList.append(field.toFormDict()) fieldValues[field.name] = field.getValueRepr(self.__getattr__(field.name)) jsonObject['fields'] = fieldList return jsonObject
[docs] def subModelGroup2Json(self, group, fieldValues): """ Provides JSON serializaton of sub-model group """ instance = self.__getattr__(group.name) subFieldValues = {} if (isinstance(group.group, BasicGroup)): jsonObject = instance.basicGroup2Json(group.group, subFieldValues) elif (isinstance(group.group, SuperGroup)): jsonObject = instance.superGroup2Json(group.group, subFieldValues) else: pass jsonObject['name'] = group.name if (group.label is not None): jsonObject['label'] = group.label if (group.show is not None): jsonObject['show'] = group.show jsonObject['dataSourceRoot'] = group.name fieldValues[group.name] = subFieldValues return jsonObject
[docs] def fieldValuesFromJson(self, jsonDict): """ Sets field values from dictionary representing JSON object """ for key, value in jsonDict.iteritems(): if (key in self.declared_fields): field = self.declared_fields[key] self.__dict__[key] = field.parseValue(value) elif (key in self.declared_submodels): self.__getattr__(key).fieldValuesFromJson(value) else: raise E.FieldError('No field with name {} in model {}'.format(key, self.name))
[docs] def updateProgress(self, current, total): """ Updates the progress state of asynchronous computation :param current: the current progress value :param total: the total progress value """ self.task.update_state(state='PROGRESS', meta={'current': current, 'total': total})