Skip to content

Commit

Permalink
Always attach all registered outputs in Parser.parse_from_node (#2960)
Browse files Browse the repository at this point in the history
This was not done when an exit code was returned by `Parser.parse` but
the actually parser could have attached outputs before returning this.
By failing to attach these outputs on the `CalcFunctionNode` that
represents the re-parsing, the caller would receive an empty dictionary
as a result.
  • Loading branch information
sphuber authored Jun 4, 2019
1 parent 4cf8fa9 commit 34188d8
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 1 deletion.
30 changes: 30 additions & 0 deletions aiida/backends/tests/parsers/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from __future__ import print_function
from __future__ import absolute_import

import io

from aiida import orm
from aiida.backends.testbase import AiidaTestCase
from aiida.common import LinkType
Expand Down Expand Up @@ -85,3 +87,31 @@ def test_parser_get_outputs_for_parsing(self):
self.assertEqual(outputs_for_parsing['retrieved'].uuid, retrieved.uuid)
self.assertIn('output', outputs_for_parsing)
self.assertEqual(outputs_for_parsing['output'].uuid, output.uuid)

def test_parse_from_node(self):
"""Test that the `parse_from_node` returns a tuple of the parsed output nodes and a calculation node.
The calculation node represents the parsing process
"""
summed = 3
output_filename = 'aiida.out'

# Mock the `CalcJobNode` which should have the `retrieved` folder containing the sum in the outputfile file
# This is the value that should be parsed into the `sum` output node
node = orm.CalcJobNode(computer=self.computer, process_type=ArithmeticAddCalculation.build_process_type())
node.set_option('resources', {'num_machines': 1, 'num_mpiprocs_per_machine': 1})
node.set_option('max_wallclock_seconds', 1800)
node.set_option('output_filename', output_filename)
node.store()

retrieved = orm.FolderData()
retrieved.put_object_from_filelike(io.StringIO(u'{}'.format(summed)), output_filename)
retrieved.store()
retrieved.add_incoming(node, link_type=LinkType.CREATE, link_label='retrieved')

result, node = ArithmeticAddParser.parse_from_node(node)

self.assertIsInstance(result['sum'], orm.Int)
self.assertEqual(result['sum'].value, summed)
self.assertIsInstance(node, orm.CalcFunctionNode)
self.assertEqual(node.exit_status, 0)
21 changes: 20 additions & 1 deletion aiida/parsers/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,22 +119,41 @@ def parse_from_node(cls, node, store_provenance=True):
It's storing method will also be disabled, making it impossible to store, because storing it afterwards would
not have the expected effect, as the outputs it produced will not be stored with it.
This method is useful to test parsing in unit tests where a `CalcJobNode` can be mocked without actually having
to run a `CalcJob`. It can also be useful to actually re-perform the parsing of a completed `CalcJob` with a
different parser.
:param node: a `CalcJobNode` instance
:param store_provenance: bool, if True will store the parsing as a `CalcFunctionNode` in the provenance
:return: a tuple of the parsed results and the `CalcFunctionNode`
:return: a tuple of the parsed results and the `CalcFunctionNode` representing the process of parsing
"""
parser = cls(node=node)

@calcfunction
def parse_calcfunction(**kwargs):
"""A wrapper function that will turn calling the `Parser.parse` method into a `CalcFunctionNode`.
.. warning:: This implementation of a `calcfunction` uses the `Process.current` to circumvent the limitation
of not being able to return both output nodes as well as an exit code. However, since this calculation
function is supposed to emulate the parsing of a `CalcJob`, which *does* have that capability, I have
to use this method. This method should however not be used in process functions, in other words:
Do not try this at home!
:param kwargs: keyword arguments that are passed to `Parser.parse` after it has been constructed
"""
from aiida.engine import Process

exit_code = parser.parse(**kwargs)
outputs = parser.outputs

if exit_code and exit_code.status:
# In the case that an exit code was returned, still attach all the registered outputs on the current
# process as well, which should represent this `CalcFunctionNode`. Otherwise the caller of the
# `parse_from_node` method will get an empty dictionary as a result, despite the `Parser.parse` method
# having registered outputs.
process = Process.current()
process.out_many(outputs)
return exit_code

return dict(outputs)
Expand Down

0 comments on commit 34188d8

Please sign in to comment.