Source code for idaes.dmf.propindex

##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2019, by the
# software owners: The Regents of the University of California, through
# Lawrence Berkeley National Laboratory,  National Technology & Engineering
# Solutions of Sandia, LLC, Carnegie Mellon University, West Virginia
# University Research Corporation, et al. All rights reserved.
#
# Please see the files COPYRIGHT.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes-pse".
##############################################################################
"""
Index Property metadata
"""
# stdlib
import logging
# local
import idaes
from idaes.dmf import codesearch
from idaes.dmf import resource
from idaes.core import property_base  # noqa: F401

__author__ = 'Dan Gunter <dkgunter@lbl.gov>'

_log = logging.getLogger(__name__)


[docs]def index_property_metadata(dmf, pkg=idaes, expr='_PropertyMetadata.*', default_version='0.0.1', **kwargs): """Index all the PropertyMetadata classes in this package. Usually the defaults will be correct, but you can modify the package explored and set of classes indexed. When you re-index the same class (in the same module), whether or not that is a "duplicate" will depend on the version found in the containing module. If there is no version in the containing module, the default version is used (so it is always the same). If it is a duplicate, nothing is done, this is not considered an error. If a new version is added, it will be explicitly connected to the highest version of the same module/code. So, for example, 1. Starting with (a.module.ClassName version=0.1.2) 2. If you then find a new version (a.module.ClassName version=1.2.3) There will be 2 resources, and you will have the relation:: a.module.ClassName/1.2.3 --version---> a.module.ClassName/0.1.2 3. If you add another version (a.module.ClassName version=1.2.4), you will have two relations:: a.module.ClassName/1.2.3 --version---> a.module.ClassName/0.1.2 a.module.ClassName/1.2.4 --version---> a.module.ClassName/1.2.3 Args: dmf (idaes.dmf.DMF): Data Management Framework instance in which to record the found metadata. pkg (module): Root module (i.e. package root) from which to find the classes containing metadata. expr (str): Regular expression pattern for the names of the classes in which to look for metadata. default_version (str): Default version to use for modules with no explicit version. kwargs: Other keyword arguments passed to :class:`codesearch.ModuleClassWalker`. Returns: codesearch.ModuleClassWalker: Class that walked through the modules. You can call `.get_indexed_classes()` to see the list of classes walked, or `.walk()` to walk the modules again. Raises: This instantiated a `DMFVisitor` and calls its `walk()` method to walk/visit each found class, so any exception raised by the constructor or `DMFVisitor.visit_metadata()`. """ wlk = codesearch.ModuleClassWalker( from_pkg=pkg, class_expr=expr, parent_class=idaes.core.property_meta.HasPropertyClassMetadata, suppress_warnings=True, **kwargs) vst = DMFVisitor(dmf, default_version=default_version) wlk.walk(vst) return wlk
[docs]class DMFVisitor(codesearch.PropertyMetadataVisitor): #: Added to resource 'tags', so easier to find later INDEXED_PROPERTY_TAG = 'indexed-property' def __init__(self, dmf, default_version=None): """Constructor. Args: dmf (idaes.dmf.DMF): Data management framework. default_version (Union[None, list, tuple]): Default version to give the class, if the containing module does not have a `__version__` variable. If None, the absence of that variable will cause an error. Raises: TypeError: if `default_version` isn't something that :func:`resource.version_list` can convert. """ self._dmf = dmf self._defver = None if default_version is not None: try: self._defver = resource.version_list(default_version) except ValueError as err: raise TypeError('Bad "default_version": {}'.format(err))
[docs] def visit_metadata(self, obj, meta): """Called for each property class encountered during the "walk" initiated by `index_property_metadata()`. Args: obj (property_base.PropertyParameterBase): Property class instance meta (property_base.PropertyClassMetadata): Associated metadata Returns: None Raises: AttributeError: if """ _log.debug('Adding resource to DMF that indexes the property package ' '"{}"'.format('.'.join([obj.__module__, obj.__name__]))) r = resource.Resource() r.v[r.TYPE_FIELD] = resource.TY_CODE r.data = {'units': meta.default_units, 'properties': meta.properties} containing_module = obj.__module__ if hasattr(containing_module, '__version__'): obj_ver = resource.version_list(containing_module.__version__) elif self._defver is None: raise AttributeError('No __version__ for module {}, and no ' 'default'.format(containing_module)) else: obj_ver = self._defver r.v['codes'].append({ 'type': 'class', 'language': 'python', 'name': '.'.join([obj.__module__, obj.__name__]), 'version': obj_ver }) r.v['tags'].append(self.INDEXED_PROPERTY_TAG) # Search for existing indexed codes. # A match exists if all 3 of these are the same: # codes.type == class # codes.language == python # codes.name == <module>.<class> info = {k: r.v['codes'][0][k] for k in ('type', 'language', 'name')} rsrc_list, dup_rsrc = [], None # Loop through all the right kind of resources for rsrc in self._dmf.find({r.TYPE_FIELD: resource.TY_CODE, 'tags': ['indexed-property']}): # skip any resources without one code if len(rsrc.v['codes']) != 1: continue code = rsrc.v['codes'][0] # skip any resource of wrong code type, name, lang. skip = False for k in info: if code[k] != info[k]: skip = True break if skip: continue # skip any resources missing the recorded metadata skip = False for data_key in r.data.keys(): if data_key not in rsrc.data: skip = True break if skip: continue # If the version of the found code is the same as the # version of the one to be added, then it is a duplicate if code['version'] == obj_ver: dup_rsrc = rsrc break rsrc_list.append(rsrc) if dup_rsrc: # This is considered a normal, non-exceptional situation _log.debug('DMFVisitor: Not adding duplicate index for ' '{}v{}'.format(info['name'], obj_ver)) else: # add the resource r.validate() _log.debug('DMFVisitor: Adding resource for code "{}"v{} type={}' .format(r.v['codes'][0]['name'], r.v['codes'][0]['version'], r.v['codes'][0]['type'])) self._dmf.add(r) if rsrc_list: # Connect to most recent (highest) version rsrc_list.sort(key=lambda rs: rs.v['codes'][0]['version']) # for rsrc in rsrc_list: rsrc = rsrc_list[-1] rel = resource.Triple(r, resource.PR_VERSION, rsrc) resource.create_relation(rel) self._dmf.update(rsrc) self._dmf.update(r)