Source code for pyiron_contrib.protocol.utils.pointer

# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.

from enum import Enum
from pyiron_contrib.protocol.utils.misc import  LoggerMixin, requires_arguments
from types import MethodType, FunctionType

"""
Python implementation of pointers. Pointer can be resolved using ~ operator
"""

__author__ = "Dominik Gehringer, Liam Huber"
__copyright__ = "Copyright 2019, Max-Planck-Institut für Eisenforschung GmbH " \
                "- Computational Materials Design (CM) Department"
__version__ = "0.0"
__maintainer__ = "Liam Huber"
__email__ = "huber@mpie.de"
__status__ = "development"
__date__ = "December 10, 2019"

[docs]class CrumbType(Enum): """ Enum class. Provides the types which Crumbs in an IODictionary are allowed to have """ Root = 0 Attribute = 1 Item = 2
[docs]class Crumb(LoggerMixin): """ Represents a piece in the path of the IODictionary. The Crumbs are used to resolve a recipe path correctly """ def __init__(self, crumb_type, name): """ Initializer from crumb Args: crumb_type (CrumbType): the crumb type of the object name (str, object): An object if crumb type is CrumbType.Root otherwise a string """ self._crumb_type = crumb_type self._name = name @property def name(self): return self._name @property def crumb_type(self): return self._crumb_type @property def object(self): if self.crumb_type != CrumbType.Root: raise ValueError('Only root crumbs store an object') return self._name
[docs] @staticmethod def attribute(name): """ Convenience method to produce an attribute crumb Args: name (str): the name of the attribute Returns: Crumb: the attribute crumbs """ return Crumb(CrumbType.Attribute, name)
[docs] @staticmethod def item(name): """ Convenience method to produce an item crumb Args: name (str): the name of the item Returns: Crumb: the item crumb """ return Crumb(CrumbType.Item, name)
[docs] @staticmethod def root(obj): """ Convenience method to produce a root crumb Args: obj: The root object of the path in the IODictionary Returns: Crumb: A root crumb, meant to be the first item in a IODictionary path """ return Crumb(CrumbType.Root, obj)
def __repr__(self): return '<{}({}, {})'.format(self.__class__.__name__, self.crumb_type.name, self.name if isinstance(self.name, str) else self.name.__class__.__name__) def __hash__(self): crumb_hash = hash(self._crumb_type) if self._crumb_type == CrumbType.Root: crumb_hash += hash(hex(id(self.object))) else: try: crumb_hash += hash(self._name) except Exception as e: self.logger.exception('Failed to hash "{}" object'.format(type(self._name).__name__), exc_info=e) return crumb_hash def __eq__(self, other): """ Compares the crumb with an an object "other". Will return False if "other" is not an instance of Crumb Args: other: (object) the object to compare Returns: (bool) wether "other" is equal to self """ if not isinstance(other, Crumb): return False if self.crumb_type == other.crumb_type: if self.crumb_type == CrumbType.Root: return hex(id(self.object)) == hex(id(other.object)) else: return self.name == other.name else: return False
[docs]class Path(list, LoggerMixin): """ A object representing a path to an objects attribute. It is a list of "Crumbs" The first object of always a Crumb of type CrumbType.Root followed by an arbitrary sequence of CrumbType.Item or CrumbType.Attribute """
[docs] def append(self, item): if not isinstance(item, Crumb): raise TypeError('A path can only consist of crumbs') else: super(Path, self).append(item)
[docs] def extend(self, collection): if not all([isinstance(item, Crumb) for item in collection]): raise TypeError('A path can only consist of crumbs') else: super(Path, self).extend(collection)
[docs] def index(self, item, **kwargs): if not isinstance(item, Crumb): raise TypeError('A path can only consist of crumbs') else: return super(Path, self).index(item, **kwargs)
[docs] def count(self, item): if not isinstance(item, Crumb): raise TypeError('A path can only consist of crumbs') else: return super(Path, self).count(item)
[docs] @classmethod def join(cls, *p): return Path(p)
[docs]class Pointer(LoggerMixin): """ A class pointing to an object. Can be resolved with ~p. It can be dangling at definition """ def __init__(self, root): if root is not None: if not isinstance(root, Path): if not isinstance(root, Crumb): path = [Crumb.root(root)] else: path = [root] else: path = root.copy() else: raise ValueError('Root object can never be "None"') self.__path = path def __getattr__(self, item): return Pointer(Path.join(*self.__path, Crumb.attribute(item))) def __getitem__(self, item): return Pointer(Path.join(*self.__path, Crumb.item(item))) @property def path(self): return self.__path def _resolve_path(self): """ This method resolves the object hiding behind a path (A list of Crumbs) Args: path (list<Crumb>): A list of path crumbs used to resolve the data object remaining (int): How many crumbs should be resolved Returns: The underlying data object, hiding behind path parameter """ # Make a copy to ensure, because by passing by reference path = self.path.copy() # Have a look at the path and check that it starts with a root crumb root = path.pop(0) if root.crumb_type != CrumbType.Root: raise ValueError('Got invalid path. A valid path starts with a root object') # First element is always an object result = root.object while len(path) > 0: # Take one step in the path, pop the next crumb from the list crumb = path.pop(0) crumb_type = crumb.crumb_type crumb_name = crumb.name # If the result is a pointer itself we have to resolve it first if isinstance(result, Pointer): self.logger.info('Resolved pointer in a pointer path') result = ~result if isinstance(crumb_name, Pointer): self.logger.info('Resolved pointer in a pointer path') crumb_name = ~crumb_name # Resolve it with the correct method - dig deeper if crumb_type == CrumbType.Attribute: try: result = getattr(result, crumb_name) except AttributeError as e: self.logger.exception('Cannot fetch value "{}"'.format(crumb_name), exc_info=e) raise e elif crumb_type == CrumbType.Item: try: result = result.__getitem__(crumb_name) except (TypeError, KeyError) as e: raise e # Get out of resolve mode return result def _resolve_function(self, function): """ Convenience function to make IODictionary.resolve more readable. If the value is a function it calls the resolved functions if the do not require arguments Args: key (str): the key the value belongs to, just for logging purposes value (object/function): the object to resolve Returns: (object): The return value of the functions, if no functions were passed "value" is returned """ result = function if isinstance(function, (MethodType, FunctionType)): # Make sure that the function has not parameters or all parameters start with if not requires_arguments(function): try: # Get the return value result = function() except Exception as e: self.logger.exception('Failed to execute callable to resolve values for ' 'path {}'.format(self), exc_info=e) else: self.logger.debug('Successfully resolved callable for path {}'.format(self)) else: self.logger.warning('Found function, but it takes arguments! I \'ll not resolve it.') return result def __invert__(self): return self.resolve()
[docs] def resolve(self): return self._resolve_function(self._resolve_path())