Skip to content

Commit

Permalink
Add CalculationTools base and entry point aiida.tools.calculations (
Browse files Browse the repository at this point in the history
#2331)

With the migration to the new provenance design, the type string of
calculation nodes, no longer refer to the actual sub class of the
`JobCalculation` that was run, but just the base `CalcJobNode`. The
actual class of the calculation process was stored in the `process_type`
if it could be mapped onto a known entry point.

However, this change means that when a user now loads a node of a
completed calculation node, let's say `PwCalculation`, the loaded node
will be an instance of `CalcJobNode` and not `PwCalculation`, which means
that any utility methods defined on the `PwCalculation` class are
inaccessible.

We can return this functionality through the concept of calculation
tools, which will get exposed through the `CalcJobNode` class and will
load a specifically registered entry point.
  • Loading branch information
sphuber authored Dec 10, 2018
1 parent a64d5c1 commit c9a1d7f
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 0 deletions.
34 changes: 34 additions & 0 deletions aiida/orm/node/process/calculation/calcjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,40 @@ class CalcJobNode(CalculationNode):
JOB_STATE_KEY = 'state'
JOB_STATE_ATTRIBUTE_KEY = 'attributes.{}'.format(JOB_STATE_KEY)

# An optional entry point for a CalculationTools instance
_tools = None

@property
def tools(self):
"""Return the calculation tools that are registered for the process type associated with this calculation.
If the entry point name stored in the `process_type` of the CalcJobNode has an accompanying entry point in the
`aiida.tools.calculations` entry point category, it will attempt to load the entry point and instantiate it
passing the node to the constructor. If the entry point does not exist, cannot be resolved or loaded, a warning
will be logged and the base CalculationTools class will be instantiated and returned.
:return: CalculationTools instance
"""
from aiida.common.exceptions import MultipleEntryPointError, MissingEntryPointError, LoadingEntryPointError
from aiida.plugins.entry_point import is_valid_entry_point_string, get_entry_point_from_string, load_entry_point
from aiida.tools.calculations import CalculationTools

if self._tools is None:
entry_point_string = self.process_type

if is_valid_entry_point_string(entry_point_string):
entry_point = get_entry_point_from_string(entry_point_string)

try:
tools_class = load_entry_point('aiida.tools.calculations', entry_point.name)
self._tools = tools_class(self)
except (MultipleEntryPointError, MissingEntryPointError, LoadingEntryPointError) as exception:
self._tools = CalculationTools(self)
self.logger.warning('could not load the calculation tools entry point {}: {}'.format(
entry_point.name, exception))

return self._tools

def __dir__(self):
"""
Allow to list all valid attributes, adding also the use_* methods
Expand Down
7 changes: 7 additions & 0 deletions aiida/orm/node/process/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ class ProcessNode(Sealable, Node):
# Specific sub classes should be marked as cacheable when appropriate
_cacheable = False

def __str__(self):
base = super(ProcessNode, self).__str__()
if self.process_type:
return '{} ({})'.format(base, self.process_type)

return '{}'.format(base)

@classproperty
def _updatable_attributes(cls):
return super(ProcessNode, cls)._updatable_attributes + (
Expand Down
7 changes: 7 additions & 0 deletions aiida/tools/calculations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# pylint: disable=wildcard-import,undefined-variable
"""Calculation tool plugins for Calculation classes."""

from .base import *

__all__ = (base.__all__)
17 changes: 17 additions & 0 deletions aiida/tools/calculations/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
"""Base class for CalculationTools
Sub-classes can be registered in the `aiida.tools.calculations` category to enable the `CalcJobNode` class from being
able to find the tools plugin, load it and expose it through the `tools` property of the `CalcJobNode`.
"""

__all__ = ('CalculationTools',)


class CalculationTools(object):
"""Base class for CalculationTools."""

# pylint: disable=too-few-public-methods

def __init__(self, node):
self._node = node
2 changes: 2 additions & 0 deletions setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@
"tcod = aiida.tools.dbexporters.tcod"
],
"aiida.tests": [],
"aiida.tools.calculations": [
],
"aiida.tools.dbimporters": [
"cod = aiida.tools.dbimporters.plugins.cod:CodDbImporter",
"icsd = aiida.tools.dbimporters.plugins.icsd:IcsdDbImporter",
Expand Down

0 comments on commit c9a1d7f

Please sign in to comment.