Skip to content

Commit

Permalink
Remove superfluous ERROR_NO_RETRIEVED_FOLDER from CalcJob subclasses
Browse files Browse the repository at this point in the history
The `ERROR_NO_RETRIEVED_FOLDER` is now defined on the `CalcJob`
base class and the `CalcJob.parse` method already checks for the
presence of the retrieved folder and return the exit code if it is
missing. This allows us to remove the similar exit codes that are
currently defined on the calculation plugins shipped with `aiida-core`
`ArithmeticAddCalculation` and `TemplateReplacerCalculation` as well as
the check for the presence of the `retrieved` output from the
corresponding parsers. The fact that is now checked in the `CalcJob`
base class means that `Parser` implementations can assume safely that
the retrieved output node exists.
  • Loading branch information
sphuber committed Aug 27, 2020
1 parent e72a4a3 commit 8562f5e
Show file tree
Hide file tree
Showing 6 changed files with 15 additions and 49 deletions.
1 change: 0 additions & 1 deletion aiida/calculations/arithmetic/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def define(cls, spec: CalcJobProcessSpec):
spec.inputs['metadata']['options']['output_filename'].default = 'aiida.out'
spec.inputs['metadata']['options']['resources'].default = {'num_machines': 1, 'num_mpiprocs_per_machine': 1}
# start exit codes - marker for docs
spec.exit_code(300, 'ERROR_NO_RETRIEVED_FOLDER', message='The retrieved output node does not exist.')
spec.exit_code(310, 'ERROR_READING_OUTPUT_FILE', message='The output file could not be read.')
spec.exit_code(320, 'ERROR_INVALID_OUTPUT', message='The output file contains invalid output.')
spec.exit_code(410, 'ERROR_NEGATIVE_NUMBER', message='The sum of the operands is a negative number.')
Expand Down
2 changes: 0 additions & 2 deletions aiida/calculations/templatereplacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ def define(cls, spec):
spec.output('output_parameters', valid_type=orm.Dict, required=True)
spec.default_output_node = 'output_parameters'

spec.exit_code(100, 'ERROR_NO_RETRIEVED_FOLDER',
message='The retrieved folder data node could not be accessed.')
spec.exit_code(101, 'ERROR_NO_TEMPORARY_RETRIEVED_FOLDER',
message='The temporary retrieved folder data node could not be accessed.')
spec.exit_code(105, 'ERROR_NO_OUTPUT_FILE_NAME_DEFINED',
Expand Down
7 changes: 1 addition & 6 deletions aiida/parsers/plugins/arithmetic/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@ def parse(self, **kwargs):
from aiida.orm import Int

try:
output_folder = self.retrieved
except AttributeError:
return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER

try:
with output_folder.open(self.node.get_option('output_filename'), 'r') as handle:
with self.retrieved.open(self.node.get_option('output_filename'), 'r') as handle:
result = int(handle.read())
except OSError:
return self.exit_codes.ERROR_READING_OUTPUT_FILE
Expand Down
13 changes: 2 additions & 11 deletions aiida/parsers/plugins/templatereplacer/doubler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,18 @@
"""Parser for the `TemplatereplacerCalculation` calculation job doubling a number."""
import os

from aiida.common import exceptions
from aiida.orm import Dict
from aiida.parsers.parser import Parser
from aiida.plugins import CalculationFactory

TemplatereplacerCalculation = CalculationFactory('templatereplacer')


class TemplatereplacerDoublerParser(Parser):
"""Parser for the `TemplatereplacerCalculation` calculation job doubling a number."""

def parse(self, **kwargs):
"""Parse the contents of the output files retrieved in the `FolderData`."""
output_folder = self.retrieved
template = self.node.inputs.template.get_dict()

try:
output_folder = self.retrieved
except exceptions.NotExistent:
return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER

try:
output_file = template['output_file_name']
except KeyError:
Expand Down Expand Up @@ -77,8 +69,7 @@ def parse(self, **kwargs):

@staticmethod
def parse_stdout(filelike):
"""
Parse the sum from the output of the ArithmeticAddcalculation written to standard out
"""Parse the sum from the output of the ArithmeticAddcalculation written to standard out.
:param filelike: filelike object containing the output
:returns: the sum
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from aiida.common import exceptions
from aiida.orm import Int
from aiida.parsers.parser import Parser

Expand All @@ -8,10 +7,7 @@ class ArithmeticAddParser(Parser):

def parse(self, **kwargs):
"""Parse the contents of the output files retrieved in the `FolderData`."""
try:
output_folder = self.retrieved
except exceptions.NotExistent:
return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER
output_folder = self.retrieved

try:
with output_folder.open(self.node.get_option('output_filename'), 'r') as handle:
Expand Down
35 changes: 11 additions & 24 deletions docs/source/topics/calculations/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -466,45 +466,32 @@ The advantage of adding the raw output data in different form as output nodes, i
This allows one to query for calculations that produced specific outputs with a certain value, which becomes a very powerful approach for post-processing and analyses of big databases.
The ``retrieved`` attribute of the parser will return the ``FolderData`` node that should have been attached by the engine containing all the retrieved files, as specified using the :ref:`retrieve list<topics:calculations:usage:calcjobs:file_lists_retrieve>` in the :ref:`preparation step of the calculation job<topics:calculations:usage:calcjobs:prepare>`.
If this node has not been attached for whatever reason, this call will throw an :py:class:`~aiida.common.exceptions.NotExistent` exception.
This is why we wrap the ``self.retrieved`` call in a try-catch block:
This retrieved folder can be used to open and read the contents of the files it contains.
In this example, there should be a single output file that was written by redirecting the standard output of the bash script that added the two integers.
The parser opens this file, reads its content and tries to parse the sum from it:
.. literalinclude:: include/snippets/calcjobs/arithmetic_add_parser.py
:language: python
:lines: 11-14
:lines: 12-16
:linenos:
:lineno-start: 11
:lineno-start: 12
If the exception is thrown, it means the retrieved files are not available and something must have has gone terribly awry with the calculation.
In this case, there is nothing to do for the parser and so we return an exit code.
Specific exit codes can be referenced by their label, such as ``ERROR_NO_RETRIEVED_FOLDER`` in this example, through the ``self.exit_codes`` property.
Note that this parsing action is wrapped in a try-except block to catch the exceptions that would be thrown if the output file could not be read.
If the exception would not be caught, the engine will catch the exception instead and set the process state of the corresponding calculation to ``Excepted``.
Note that this will happen for any uncaught exception that is thrown during parsing.
Instead, we catch these exceptions and return an exit code that is retrieved by referencing it by its label, such as ``ERROR_READING_OUTPUT_FILE`` in this example, through the ``self.exit_codes`` property.
This call will retrieve the corresponding exit code defined on the ``CalcJob`` that we are currently parsing.
Returning this exit code from the parser will stop the parsing immediately and will instruct the engine to set its exit status and exit message on the node of this calculation job.
This should scenario should however never occur, but it is just here as a safety.
If the exception would not be caught, the engine will catch the exception instead and set the process state of the corresponding calculation to ``Excepted``.
Note that this will happen for any exception that occurs during parsing.
Assuming that everything went according to plan during the retrieval, we now have access to those retrieved files and can start to parse them.
In this example, there should be a single output file that was written by redirecting the standard output of the bash script that added the two integers.
The parser opens this file, reads its content and tries to parse the sum from it:
.. literalinclude:: include/snippets/calcjobs/arithmetic_add_parser.py
:language: python
:lines: 16-20
:linenos:
:lineno-start: 16
Note that again we wrap this parsing action in a try-except block.
If the file cannot be found or cannot be read, we return the appropriate exit code.
The ``parse_stdout`` method is just a small utility function to separate the actual parsing of the data from the main parser code.
In this case, the parsing is so simple that we might have as well kept it in the main method, but this is just to illustrate that you are completely free to organize the code within the ``parse`` method for clarity.
If we manage to parse the sum, produced by the calculation, we wrap it in the appropriate :py:class:`~aiida.orm.nodes.data.int.Int` data node class, and register it as an output through the ``out`` method:
.. literalinclude:: include/snippets/calcjobs/arithmetic_add_parser.py
:language: python
:lines: 25-25
:lines: 21-21
:linenos:
:lineno-start: 25
:lineno-start: 21
Note that if we encountered no problems, we do not have to return anything.
The engine will interpret this as the calculation having finished successfully.
Expand Down

0 comments on commit 8562f5e

Please sign in to comment.