From 382efdab1afe35c635fd1c1e68941d4e4b093aba Mon Sep 17 00:00:00 2001 From: Snehal Kumbhar Date: Thu, 22 Feb 2018 19:16:33 +0100 Subject: [PATCH 1/3] Added load_plugin_safe method to return calc node if plugin is not available --- aiida/backends/djsite/db/models.py | 5 +-- aiida/backends/sqlalchemy/models/node.py | 4 +-- aiida/backends/tests/nodes.py | 43 ++++++++++++++++++++++++ aiida/common/pluginloader.py | 42 +++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/aiida/backends/djsite/db/models.py b/aiida/backends/djsite/db/models.py index 253084987a..c2e8d316e6 100644 --- a/aiida/backends/djsite/db/models.py +++ b/aiida/backends/djsite/db/models.py @@ -189,7 +189,7 @@ def get_aiida_class(self): """ from aiida.orm.node import Node from aiida.common.old_pluginloader import from_type_to_pluginclassname - from aiida.common.pluginloader import load_plugin + from aiida.common.pluginloader import load_plugin_safe from aiida.common import aiidalogger try: @@ -199,8 +199,9 @@ def get_aiida_class(self): "not valid: '{}'".format(self.pk, self.type)) try: - PluginClass = load_plugin(Node, 'aiida.orm', pluginclassname) + PluginClass = load_plugin_safe(Node, 'aiida.orm', pluginclassname) except MissingPluginError: + aiidalogger.error("Unable to find plugin for type '{}' (node= {}), " "will use base Node class".format(self.type, self.pk)) PluginClass = Node diff --git a/aiida/backends/sqlalchemy/models/node.py b/aiida/backends/sqlalchemy/models/node.py index 2101801563..abfd31593f 100644 --- a/aiida/backends/sqlalchemy/models/node.py +++ b/aiida/backends/sqlalchemy/models/node.py @@ -28,7 +28,6 @@ from aiida.backends.sqlalchemy.models.utils import uuid_func from aiida.common import aiidalogger -from aiida.common.pluginloader import load_plugin from aiida.common.exceptions import DbContentError, MissingPluginError from aiida.common.datastructures import calc_states, _sorted_datastates, sort_states @@ -154,6 +153,7 @@ def get_aiida_class(self): """ from aiida.common.old_pluginloader import from_type_to_pluginclassname from aiida.orm.node import Node + from aiida.common.pluginloader import load_plugin_safe try: pluginclassname = from_type_to_pluginclassname(self.type) @@ -162,7 +162,7 @@ def get_aiida_class(self): "not valid: '{}'".format(self.pk, self.type)) try: - PluginClass = load_plugin(Node, 'aiida.orm', pluginclassname) + PluginClass = load_plugin_safe(Node, 'aiida.orm', pluginclassname) except MissingPluginError: aiidalogger.error("Unable to find plugin for type '{}' (node= {}), " "will use base Node class".format(self.type, self.pk)) diff --git a/aiida/backends/tests/nodes.py b/aiida/backends/tests/nodes.py index 012cca72e9..f85b6aaf80 100644 --- a/aiida/backends/tests/nodes.py +++ b/aiida/backends/tests/nodes.py @@ -1449,6 +1449,49 @@ def test_load_node(self): with self.assertRaises(NotExistent): load_node(spec, parent_class=ArrayData) + def test_load_plugin_safe(self): + from aiida.orm import (JobCalculation, CalculationFactory, DataFactory) + + ###### for calculation + calc_params = { + 'computer': self.computer, + 'resources': {'num_machines': 1, 'num_mpiprocs_per_machine': 1} + } + + TemplateReplacerCalc = CalculationFactory('simpleplugins.templatereplacer') + testcalc = TemplateReplacerCalc(**calc_params).store() + jobcalc = JobCalculation(**calc_params).store() + + # compare if plugin exist + obj = testcalc.dbnode.get_aiida_class() + self.assertEqual(type(testcalc), type(obj)) + + # change node type and save in database again + testcalc.dbnode.type = "calculation.job.simpleplugins_tmp.templatereplacer.TemplatereplacerCalculation." + testcalc.dbnode.save() + + # changed node should return job calc as its plugin is not exist + obj = testcalc.dbnode.get_aiida_class() + self.assertEqual(type(jobcalc), type(obj)) + + ####### for data + KpointsData = DataFactory('array.kpoints') + kpoint = KpointsData().store() + Data = DataFactory("Data") + data = Data().store() + + # compare if plugin exist + obj = kpoint.dbnode.get_aiida_class() + self.assertEqual(type(kpoint), type(obj)) + + # change node type and save in database again + kpoint.dbnode.type = "data.array.kpoints_tmp.KpointsData." + kpoint.dbnode.save() + + # changed node should return data node as its plugin is not exist + obj = kpoint.dbnode.get_aiida_class() + self.assertEqual(type(data), type(obj)) + class TestSubNodesAndLinks(AiidaTestCase): diff --git a/aiida/common/pluginloader.py b/aiida/common/pluginloader.py index a85c15ddf4..24607cc4ba 100644 --- a/aiida/common/pluginloader.py +++ b/aiida/common/pluginloader.py @@ -125,6 +125,48 @@ def get_plugin(category, name): return plugin +def load_plugin_safe(base_class, plugins_module, plugin_type): + """ + It is a copy of load_plugin function to return closely related node class + if plugin is not available. We are duplicating load_plugin function to not break + its default behaviour. + + params: Look at the docstring of aiida.common.old_pluginloader.load_plugin for more Info + :return: The plugin class + """ + + try: + PluginClass = load_plugin(base_class, plugins_module, plugin_type) + except MissingPluginError: + nodeParts = plugin_type.partition(".") + baseNodeType = nodeParts[0] + + ## data node: temporarily returning base data node. + # In future its better to check the closest available plugin and return it. + # For example if type is "aiida.orm.data.array.kpoints_tmp.KpointsData" + # it should return array data node and not base data node + if baseNodeType == "data": + PluginClass = load_plugin(base_class, plugins_module, 'data.Data') + + ## code node + elif baseNodeType == "code": + PluginClass = load_plugin(base_class, plugins_module, 'code.Code') + + ## calculation node: for calculation currently we are hardcoding cases + elif baseNodeType == "calculation": + subNodeParts = nodeParts[2].partition(".") + subNodeType = subNodeParts[0] + if subNodeType == "job": + PluginClass = load_plugin(base_class, plugins_module, 'calculation.job.JobCalculation') + elif subNodeType == "inline": + PluginClass = load_plugin(base_class, plugins_module, 'calculation.inline.InlineCalculation') + elif subNodeType == "work": + PluginClass = load_plugin(base_class, plugins_module, 'calculation.work.WorkCalculation') + else: + PluginClass = load_plugin(base_class, plugins_module, 'calculation.Calculation') + + return PluginClass + def load_plugin(base_class, plugins_module, plugin_type): """ From 09cf0ff89cc309c8f248d39d3eae8ce95cfe96d6 Mon Sep 17 00:00:00 2001 From: Snehal Kumbhar Date: Fri, 23 Feb 2018 13:01:19 +0100 Subject: [PATCH 2/3] Added node and default case to return base Node type in load_plugin_safe --- aiida/backends/djsite/db/models.py | 9 +-------- aiida/backends/sqlalchemy/models/node.py | 7 +------ aiida/backends/tests/nodes.py | 6 ++++++ aiida/common/pluginloader.py | 24 +++++++++++++++++++----- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/aiida/backends/djsite/db/models.py b/aiida/backends/djsite/db/models.py index c2e8d316e6..f7b469b609 100644 --- a/aiida/backends/djsite/db/models.py +++ b/aiida/backends/djsite/db/models.py @@ -190,7 +190,6 @@ def get_aiida_class(self): from aiida.orm.node import Node from aiida.common.old_pluginloader import from_type_to_pluginclassname from aiida.common.pluginloader import load_plugin_safe - from aiida.common import aiidalogger try: pluginclassname = from_type_to_pluginclassname(self.type) @@ -198,13 +197,7 @@ def get_aiida_class(self): raise DbContentError("The type name of node with pk= {} is " "not valid: '{}'".format(self.pk, self.type)) - try: - PluginClass = load_plugin_safe(Node, 'aiida.orm', pluginclassname) - except MissingPluginError: - - aiidalogger.error("Unable to find plugin for type '{}' (node= {}), " - "will use base Node class".format(self.type, self.pk)) - PluginClass = Node + PluginClass = load_plugin_safe(Node, 'aiida.orm', pluginclassname, self.type, self.pk) return PluginClass(dbnode=self) diff --git a/aiida/backends/sqlalchemy/models/node.py b/aiida/backends/sqlalchemy/models/node.py index abfd31593f..82de0f729a 100644 --- a/aiida/backends/sqlalchemy/models/node.py +++ b/aiida/backends/sqlalchemy/models/node.py @@ -161,12 +161,7 @@ def get_aiida_class(self): raise DbContentError("The type name of node with pk= {} is " "not valid: '{}'".format(self.pk, self.type)) - try: - PluginClass = load_plugin_safe(Node, 'aiida.orm', pluginclassname) - except MissingPluginError: - aiidalogger.error("Unable to find plugin for type '{}' (node= {}), " - "will use base Node class".format(self.type, self.pk)) - PluginClass = Node + PluginClass = load_plugin_safe(Node, 'aiida.orm', pluginclassname, self.type, self.pk) return PluginClass(dbnode=self) diff --git a/aiida/backends/tests/nodes.py b/aiida/backends/tests/nodes.py index f85b6aaf80..64050d5764 100644 --- a/aiida/backends/tests/nodes.py +++ b/aiida/backends/tests/nodes.py @@ -1492,6 +1492,12 @@ def test_load_plugin_safe(self): obj = kpoint.dbnode.get_aiida_class() self.assertEqual(type(data), type(obj)) + ###### for node + n1 = Node().store() + obj = n1.dbnode.get_aiida_class() + self.assertEqual(type(n1), type(obj)) + + class TestSubNodesAndLinks(AiidaTestCase): diff --git a/aiida/common/pluginloader.py b/aiida/common/pluginloader.py index 24607cc4ba..3467da5a97 100644 --- a/aiida/common/pluginloader.py +++ b/aiida/common/pluginloader.py @@ -125,15 +125,19 @@ def get_plugin(category, name): return plugin -def load_plugin_safe(base_class, plugins_module, plugin_type): +def load_plugin_safe(base_class, plugins_module, plugin_type, nodeType, nodePk): """ - It is a copy of load_plugin function to return closely related node class - if plugin is not available. We are duplicating load_plugin function to not break - its default behaviour. + It is a wrapper of load_plugin function to return closely related node class + if plugin is not available. By default it returns base Node class and does not + raise exception. + + params: Look at the docstring of aiida.common.old_pluginloader.load_plugin for more Info + + :param: nodeType: type of the node + :param nodePk: node pk - params: Look at the docstring of aiida.common.old_pluginloader.load_plugin for more Info :return: The plugin class """ + from aiida.common import aiidalogger try: PluginClass = load_plugin(base_class, plugins_module, plugin_type) @@ -165,6 +169,16 @@ def load_plugin_safe(base_class, plugins_module, plugin_type): else: PluginClass = load_plugin(base_class, plugins_module, 'calculation.Calculation') + ## for base node + elif baseNodeType == "node": + PluginClass = base_class + + ## default case + else: + aiidalogger.error("Unable to find plugin for type '{}' (node= {}), " + "will use base Node class".format(nodeType, nodePk)) + PluginClass = base_class + return PluginClass From 2d096b9e3af2937b522acc2cc4e1a91ffb8bc2c8 Mon Sep 17 00:00:00 2001 From: Snehal Kumbhar Date: Fri, 23 Feb 2018 14:02:51 +0100 Subject: [PATCH 3/3] changes in variable names --- aiida/common/pluginloader.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/aiida/common/pluginloader.py b/aiida/common/pluginloader.py index 3467da5a97..fbbf5a5ef2 100644 --- a/aiida/common/pluginloader.py +++ b/aiida/common/pluginloader.py @@ -125,15 +125,15 @@ def get_plugin(category, name): return plugin -def load_plugin_safe(base_class, plugins_module, plugin_type, nodeType, nodePk): +def load_plugin_safe(base_class, plugins_module, plugin_type, node_type, node_pk): """ It is a wrapper of load_plugin function to return closely related node class if plugin is not available. By default it returns base Node class and does not raise exception. params: Look at the docstring of aiida.common.old_pluginloader.load_plugin for more Info + - :param: nodeType: type of the node - :param nodePk: node pk + :param: node_type: type of the node + :param node_pk: node pk :return: The plugin class """ @@ -142,41 +142,41 @@ def load_plugin_safe(base_class, plugins_module, plugin_type, nodeType, nodePk): try: PluginClass = load_plugin(base_class, plugins_module, plugin_type) except MissingPluginError: - nodeParts = plugin_type.partition(".") - baseNodeType = nodeParts[0] + node_parts = plugin_type.partition(".") + base_node_type = node_parts[0] ## data node: temporarily returning base data node. # In future its better to check the closest available plugin and return it. # For example if type is "aiida.orm.data.array.kpoints_tmp.KpointsData" # it should return array data node and not base data node - if baseNodeType == "data": + if base_node_type == "data": PluginClass = load_plugin(base_class, plugins_module, 'data.Data') ## code node - elif baseNodeType == "code": + elif base_node_type == "code": PluginClass = load_plugin(base_class, plugins_module, 'code.Code') ## calculation node: for calculation currently we are hardcoding cases - elif baseNodeType == "calculation": - subNodeParts = nodeParts[2].partition(".") - subNodeType = subNodeParts[0] - if subNodeType == "job": + elif base_node_type == "calculation": + sub_node_parts = node_parts[2].partition(".") + sub_node_type = sub_node_parts[0] + if sub_node_type == "job": PluginClass = load_plugin(base_class, plugins_module, 'calculation.job.JobCalculation') - elif subNodeType == "inline": + elif sub_node_type == "inline": PluginClass = load_plugin(base_class, plugins_module, 'calculation.inline.InlineCalculation') - elif subNodeType == "work": + elif sub_node_type == "work": PluginClass = load_plugin(base_class, plugins_module, 'calculation.work.WorkCalculation') else: PluginClass = load_plugin(base_class, plugins_module, 'calculation.Calculation') ## for base node - elif baseNodeType == "node": + elif base_node_type == "node": PluginClass = base_class ## default case else: aiidalogger.error("Unable to find plugin for type '{}' (node= {}), " - "will use base Node class".format(nodeType, nodePk)) + "will use base Node class".format(node_type, node_pk)) PluginClass = base_class return PluginClass