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)