Source code for propertyestimator.workflow.decorators

"""
A collection of decorators used to mark-up elements in a workflow, such
as inputs or outputs of protocol building blocks.
"""

import numpy as np

from enum import Enum

from propertyestimator.workflow.utils import PlaceholderInput


[docs]class MergeBehaviour(Enum): """A enum which describes how attributes should be handled when attempting to merge similar protocols. Notes ----- Any attributes marked with a merge behavior of `ExactlyEqual` must be exactly for two protocols to merge. """ ExactlyEqual = 0, SmallestValue = 1, GreatestValue = 2,
[docs]class BaseProtocolInputObject: """A custom decorator used to mark class attributes as either a required input, or output, of a protocol. Notes ----- This decorator expects the protocol to have a matching private field in addition to the public attribute. For example if a protocol has an attribute `substance`, by default the protocol must also have a `_substance` field. """
[docs] def __init__(self, class_attribute): documentation = class_attribute.__doc__ or None self.__doc__ = documentation self.attribute = '_' + class_attribute.__name__ self.value_type = None
def __get__(self, instance, owner=None): if instance is None: # Added in to fix an issue where RTD tries to call this # on a class, rather than an instance. return self if not hasattr(instance, self.attribute): raise ValueError('Missing {} attribute.'.format(self.attribute)) return getattr(instance, self.attribute) def __set__(self, instance, value): if instance is None: raise ValueError('Unexpected ProtocolArgumentDecorator set use case.') if not hasattr(instance, self.attribute): raise ValueError('Missing {} attribute.'.format(self.attribute)) if not isinstance(value, self.value_type) and not isinstance(value, PlaceholderInput) and value is not None: # Handle the special case where the decimal has been lost on float types... if (not (self.value_type is float and isinstance(value, int)) and not np.issubdtype(type(value), self.value_type)): raise ValueError('The {} attribute can only accept values ' 'of type {}'.format(self.attribute, self.value_type)) setattr(instance, self.attribute, value)
[docs]def protocol_input(value_type, merge_behavior=MergeBehaviour.ExactlyEqual): """A custom decorator used to mark a protocol attribute as a possible input. Examples ---------- To mark an attribute as an input: >>> from propertyestimator.substances import Substance >>> >>> @protocol_input(value_type=Substance) >>> def substance(self, value): >>> pass To control how this input should behave when protocols are being / considered being merged, use the merge_behavior attribute: >>> @protocol_input(value_type=int, merge_behavior=MergeBehaviour.GreatestValue) >>> def simulation_steps(self, value): >>> pass """ class ProtocolInputObject(BaseProtocolInputObject): def __init__(self, class_attribute): super().__init__(class_attribute) self.value_type = value_type self.merge_behavior = merge_behavior return ProtocolInputObject
[docs]def protocol_output(value_type): """A custom decorator used to mark a protocol attribute as an output of the protocol. Examples ---------- To mark a property as an output: >>> @protocol_output(value_type=str) >>> def coordinate_file_path(self): >>> pass """ class ProtocolOutputObject(BaseProtocolInputObject): def __init__(self, class_attribute): super().__init__(class_attribute) self.value_type = value_type return ProtocolOutputObject