diff --git a/aiida/engine/processes/builder.py b/aiida/engine/processes/builder.py index b5a97a70d8..29b1c80cf3 100644 --- a/aiida/engine/processes/builder.py +++ b/aiida/engine/processes/builder.py @@ -19,6 +19,8 @@ from aiida.orm import Dict, Node from aiida.orm.nodes.data.base import BaseType +from .utils import prune_mapping + if TYPE_CHECKING: from aiida.engine.processes.process import Process @@ -162,7 +164,7 @@ def __delattr__(self, item): def _recursive_merge(self, dictionary, key, value): """Recursively merge the contents of ``dictionary`` setting its ``key`` to ``value``.""" - if isinstance(value, Mapping): + if isinstance(value, Mapping) and key in dictionary: for inner_key, inner_value in value.items(): self._recursive_merge(dictionary[key], inner_key, inner_value) else: @@ -219,30 +221,10 @@ def _inputs(self, prune: bool = False) -> dict: :return: mapping of inputs ports and their input values. """ if prune: - return self._prune(dict(self)) + return prune_mapping(dict(self)) return dict(self) - def _prune(self, value): - """Prune a nested mapping from all mappings that are completely empty. - - .. note:: a nested mapping that is completely empty means it contains at most other empty mappings. Other null - values, such as `None` or empty lists, should not be pruned. - - :param value: a nested mapping of port values - :return: the same mapping but without any nested namespace that is completely empty. - """ - if isinstance(value, Mapping) and not isinstance(value, Node): - result = {} - for key, sub_value in value.items(): - pruned = self._prune(sub_value) - # If `pruned` is an "empty'ish" mapping and not an instance of `Node`, skip it, otherwise keep it. - if not (isinstance(pruned, Mapping) and not pruned and not isinstance(pruned, Node)): - result[key] = pruned - return result - - return value - class ProcessBuilder(ProcessBuilderNamespace): # pylint: disable=too-many-ancestors """A process builder that helps setting up the inputs for creating a new process.""" diff --git a/aiida/engine/processes/functions.py b/aiida/engine/processes/functions.py index a227abf948..41b33a3c87 100644 --- a/aiida/engine/processes/functions.py +++ b/aiida/engine/processes/functions.py @@ -421,8 +421,8 @@ def run(self) -> Optional['ExitCode']: for name, value in (self.inputs or {}).items(): try: - if self.spec().inputs[name].non_db: # type: ignore[union-attr] - # Don't consider non-database inputs + if self.spec().inputs[name].is_metadata: # type: ignore[union-attr] + # Don't consider ports that defined ``is_metadata=True`` continue except KeyError: pass # No port found diff --git a/aiida/engine/processes/ports.py b/aiida/engine/processes/ports.py index 16f9298d1f..4ffd0e5998 100644 --- a/aiida/engine/processes/ports.py +++ b/aiida/engine/processes/ports.py @@ -30,13 +30,7 @@ class WithNonDb: - """ - A mixin that adds support to a port to flag that it should not be stored - in the database using the non_db=True flag. - - The mixins have to go before the main port class in the superclass order - to make sure the mixin has the chance to strip out the non_db keyword. - """ + """A mixin that adds support to a port to flag it should not be stored in the database using the ``non_db`` flag.""" def __init__(self, *args, **kwargs) -> None: self._non_db_explicitly_set: bool = bool('non_db' in kwargs) @@ -46,28 +40,66 @@ def __init__(self, *args, **kwargs) -> None: @property def non_db_explicitly_set(self) -> bool: - """Return whether the a value for `non_db` was explicitly passed in the construction of the `Port`. + """Return whether the ``non_db`` keyword was explicitly passed in the construction of the ``InputPort``. - :return: boolean, True if `non_db` was explicitly defined during construction, False otherwise + :return: ``True`` if ``non_db`` was explicitly defined during construction, ``False`` otherwise """ return self._non_db_explicitly_set @property def non_db(self) -> bool: - """Return whether the value of this `Port` should be stored as a `Node` in the database. + """Return whether the value of this ``InputPort`` should be stored in the database. - :return: boolean, True if it should be storable as a `Node`, False otherwise + :return: ``True`` if it should be stored, ``False`` otherwise """ return self._non_db @non_db.setter def non_db(self, non_db: bool) -> None: - """Set whether the value of this `Port` should be stored as a `Node` in the database. - """ + """Set whether the value of this ``InputPort`` should be stored as a ``Data`` in the database.""" self._non_db_explicitly_set = True self._non_db = non_db +class WithMetadata: + """A mixin that allows an input port to be marked as metadata through the keyword ``is_metadata``. + + A metadata input differs from a normal input as in that it is not linked to the ``ProcessNode`` as a ``Data`` node + but rather is stored on the ``ProcessNode`` itself (as an attribute, for example). + """ + + def __init__(self, *args, **kwargs) -> None: + self._explicitly_set: bool = bool('is_metadata' in kwargs) + is_metadata = kwargs.pop('is_metadata', False) + super().__init__(*args, **kwargs) + self._is_metadata: bool = is_metadata + + @property + def is_metadata_explicitly_set(self) -> bool: + """Return whether the ``is_metadata`` keyword was explicitly passed in the construction of the ``InputPort``. + + :return: ``True`` if ``is_metadata`` was explicitly defined during construction, ``False`` otherwise + """ + return self._explicitly_set + + @property + def is_metadata(self) -> bool: + """Return whether the value of this ``InputPort`` should be stored as a ``Node`` in the database. + + :return: ``True`` if it should be storable as a ``Node``, ``False`` otherwise + """ + return self._is_metadata + + @is_metadata.setter + def is_metadata(self, is_metadata: bool) -> None: + """Set whether the value of this ``InputPort`` should be stored as a ``Node`` in the database. + + :param is_metadata: ``False`` if the port value should be linked as a ``Node``, ``True`` otherwise. + """ + self._explicitly_set = True + self._is_metadata = is_metadata + + class WithSerialize: """ A mixin that adds support for a serialization function which is automatically applied on inputs @@ -91,10 +123,13 @@ def serialize(self, value: Any) -> 'Data': return self._serializer(value) -class InputPort(WithSerialize, WithNonDb, ports.InputPort): +class InputPort(WithMetadata, WithSerialize, WithNonDb, ports.InputPort): """ Sub class of plumpy.InputPort which mixes in the WithSerialize and WithNonDb mixins to support automatic value serialization to database storable types and support non database storable input types as well. + + The mixins have to go before the main port class in the superclass order to make sure they have the chance to + process their specific keywords. """ def __init__(self, *args, **kwargs) -> None: @@ -121,12 +156,12 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) def get_description(self) -> Dict[str, str]: - """ - Return a description of the InputPort, which will be a dictionary of its attributes + """Return a description of the InputPort, which will be a dictionary of its attributes :returns: a dictionary of the stringified InputPort attributes """ description = super().get_description() + description['is_metadata'] = f'{self.is_metadata}' description['non_db'] = f'{self.non_db}' return description @@ -145,7 +180,7 @@ def pass_to_parser(self) -> bool: return self._pass_to_parser -class PortNamespace(WithNonDb, ports.PortNamespace): +class PortNamespace(WithMetadata, WithNonDb, ports.PortNamespace): """ Sub class of plumpy.PortNamespace which implements the serialize method to support automatic recursive serialization of a given mapping onto the ports of the PortNamespace. @@ -165,6 +200,11 @@ def __setitem__(self, key: str, port: ports.Port) -> None: self.validate_port_name(key) + if hasattr( + port, 'is_metadata_explicitly_set' + ) and not port.is_metadata_explicitly_set: # type: ignore[attr-defined] + port.is_metadata = self.is_metadata # type: ignore[attr-defined] + if hasattr(port, 'non_db_explicitly_set') and not port.non_db_explicitly_set: # type: ignore[attr-defined] port.non_db = self.non_db # type: ignore[attr-defined] diff --git a/aiida/engine/processes/process.py b/aiida/engine/processes/process.py index 2327889454..08ed2e54f7 100644 --- a/aiida/engine/processes/process.py +++ b/aiida/engine/processes/process.py @@ -48,12 +48,14 @@ from aiida.common.lang import classproperty, override from aiida.common.links import LinkType from aiida.common.log import LOG_LEVEL_REPORT +from aiida.orm.implementation.utils import clean_value from aiida.orm.utils import serialize from .builder import ProcessBuilder from .exit_code import ExitCode, ExitCodesNamespace from .ports import PORT_NAMESPACE_SEPARATOR, InputPort, OutputPort, PortNamespace from .process_spec import ProcessSpec +from .utils import prune_mapping if TYPE_CHECKING: from aiida.engine.runners import Runner @@ -92,7 +94,7 @@ def define(cls, spec: ProcessSpec) -> None: # type: ignore[override] """ super().define(spec) - spec.input_namespace(spec.metadata_key, required=False, non_db=True) + spec.input_namespace(spec.metadata_key, required=False, is_metadata=True) spec.input( f'{spec.metadata_key}.store_provenance', valid_type=bool, @@ -745,6 +747,16 @@ def _setup_metadata(self, metadata: dict) -> None: else: raise RuntimeError(f'unsupported metadata key: {name}') + # Store JSON-serializable values of ``metadata`` ports in the node's attributes. Note that instead of passing in + # the ``metadata`` inputs directly, the entire namespace of raw inputs is passed. The reason is that although + # currently in ``aiida-core`` all input ports with ``is_metadata=True`` in the port specification are located + # within the ``metadata`` port namespace, this may not always be the case. The ``_filter_serializable_metadata`` + # method will filter out all ports that set ``is_metadata=True`` no matter where in the namespace they are + # defined so this approach is more robust for the future. + serializable_inputs = self._filter_serializable_metadata(self.spec().inputs, self.raw_inputs) + pruned = prune_mapping(serializable_inputs) + self.node.set_metadata_inputs(pruned) + def _setup_inputs(self) -> None: """Create the links between the input nodes and the ProcessNode that represents this process.""" for name, node in self._flat_inputs().items(): @@ -760,6 +772,51 @@ def _setup_inputs(self) -> None: elif isinstance(self.node, orm.WorkflowNode): self.node.base.links.add_incoming(node, LinkType.INPUT_WORK, name) + def _filter_serializable_metadata( + self, + port: Union[None, InputPort, PortNamespace], + port_value: Any, + ) -> Union[Any, None]: + """Return the inputs that correspond to ports with ``is_metadata=True`` and that are JSON serializable. + + The function is called recursively for any port namespaces. + + :param port: An ``InputPort`` or ``PortNamespace``. If an ``InputPort`` that specifies ``is_metadata=True`` the + ``port_value`` is returned. For a ``PortNamespace`` this method is called recursively for the keys within + the namespace and the resulting dictionary is returned, omitting ``None`` values. If either ``port`` or + ``port_value`` is ``None``, ``None`` is returned. + :return: The ``port_value`` where all inputs that do no correspond to a metadata port or are not JSON + serializable, have been filtered out. + """ + if port is None or port_value is None: + return None + + if isinstance(port, InputPort): + if not port.is_metadata: + return None + + try: + clean_value(port_value) + except exceptions.ValidationError: + return None + else: + return port_value + + result = {} + + for key, value in port_value.items(): + if key not in port: + continue + + metadata_value = self._filter_serializable_metadata(port[key], value) # type: ignore[arg-type] + + if metadata_value is None: + continue + + result[key] = metadata_value + + return result or None + def _flat_inputs(self) -> Dict[str, Any]: """ Return a flattened version of the parsed inputs dictionary. @@ -802,7 +859,9 @@ def _flatten_inputs( :return: flat list of inputs """ - if (port is None and isinstance(port_value, orm.Node)) or (isinstance(port, InputPort) and not port.non_db): + if (port is None and + isinstance(port_value, + orm.Node)) or (isinstance(port, InputPort) and not (port.is_metadata or port.non_db)): return [(parent_name, port_value)] if port is None and isinstance(port_value, Mapping) or isinstance(port, PortNamespace): @@ -822,7 +881,7 @@ def _flatten_inputs( items.extend(sub_items) return items - assert (port is None) or (isinstance(port, InputPort) and port.non_db) + assert (port is None) or (isinstance(port, InputPort) and (port.is_metadata or port.non_db)) return [] def _flatten_outputs( diff --git a/aiida/engine/processes/utils.py b/aiida/engine/processes/utils.py new file mode 100644 index 0000000000..340131a78b --- /dev/null +++ b/aiida/engine/processes/utils.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +"""Module with utilities.""" +from collections.abc import Mapping + +from aiida.orm import Node + + +def prune_mapping(value): + """Prune a nested mapping from all mappings that are completely empty. + + .. note:: A nested mapping that is completely empty means it contains at most other empty mappings. Other null + values, such as `None` or empty lists, should not be pruned. + + :param value: A nested mapping of port values. + :return: The same mapping but without any nested namespace that is completely empty. + """ + if isinstance(value, Mapping) and not isinstance(value, Node): + result = {} + for key, sub_value in value.items(): + pruned = prune_mapping(sub_value) + # If `pruned` is an "empty'ish" mapping and not an instance of `Node`, skip it, otherwise keep it. + if not (isinstance(pruned, Mapping) and not pruned and not isinstance(pruned, Node)): + result[key] = pruned + return result + + return value diff --git a/aiida/orm/nodes/process/calculation/calcjob.py b/aiida/orm/nodes/process/calculation/calcjob.py index c4ac0de30c..e651f70bf6 100644 --- a/aiida/orm/nodes/process/calculation/calcjob.py +++ b/aiida/orm/nodes/process/calculation/calcjob.py @@ -142,21 +142,6 @@ def _hash_ignored_attributes(cls) -> Tuple[str, ...]: # pylint: disable=no-self 'max_memory_kb', ) - def get_builder_restart(self) -> 'ProcessBuilder': - """Return a `ProcessBuilder` that is ready to relaunch the same `CalcJob` that created this node. - - The process class will be set based on the `process_type` of this node and the inputs of the builder will be - prepopulated with the inputs registered for this node. This functionality is very useful if a process has - completed and you want to relaunch it with slightly different inputs. - - In addition to prepopulating the input nodes, which is implemented by the base `ProcessNode` class, here we - also add the `options` that were passed in the `metadata` input of the `CalcJob` process. - - """ - builder = super().get_builder_restart() - builder.metadata.options = self.get_options() # type: ignore[attr-defined] - return builder - @property def is_imported(self) -> bool: """Return whether the calculation job was imported instead of being an actual run.""" diff --git a/aiida/orm/nodes/process/process.py b/aiida/orm/nodes/process/process.py index cc25240cc9..6748636b7b 100644 --- a/aiida/orm/nodes/process/process.py +++ b/aiida/orm/nodes/process/process.py @@ -9,7 +9,7 @@ ########################################################################### """Module with `Node` sub class for processes.""" import enum -from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union from plumpy.process_states import ProcessState @@ -143,6 +143,7 @@ class ProcessNode(Sealable, Node): PROCESS_LABEL_KEY = 'process_label' PROCESS_STATE_KEY = 'process_state' PROCESS_STATUS_KEY = 'process_status' + METADATA_INPUTS_KEY: str = 'metadata_inputs' _unstorable_message = 'only Data, WorkflowNode, CalculationNode or their subclasses can be stored' @@ -167,6 +168,14 @@ def _updatable_attributes(cls) -> Tuple[str, ...]: cls.PROCESS_STATUS_KEY, ) + def set_metadata_inputs(self, value: Dict[str, Any]) -> None: + """Set the mapping of inputs corresponding to ``metadata`` ports that were passed to the process.""" + return self.base.attributes.set(self.METADATA_INPUTS_KEY, value) + + def get_metadata_inputs(self) -> Optional[Dict[str, Any]]: + """Return the mapping of inputs corresponding to ``metadata`` ports that were passed to the process.""" + return self.base.attributes.get(self.METADATA_INPUTS_KEY, None) + @property def logger(self): """ @@ -188,6 +197,7 @@ def get_builder_restart(self) -> 'ProcessBuilder': """ builder = self.process_class.get_builder() builder._update(self.base.links.get_incoming(link_type=(LinkType.INPUT_CALC, LinkType.INPUT_WORK)).nested()) # pylint: disable=protected-access + builder._merge(self.get_metadata_inputs() or {}) # pylint: disable=protected-access return builder diff --git a/aiida/sphinxext/process.py b/aiida/sphinxext/process.py index dcdb7f1dad..759617f5e2 100644 --- a/aiida/sphinxext/process.py +++ b/aiida/sphinxext/process.py @@ -180,6 +180,9 @@ def build_port_content(self, name, port): if _is_non_db(port): res.append(nodes.Text(', ')) res.append(nodes.emphasis(text='non_db')) + if _is_metadata(port): + res.append(nodes.Text(', ')) + res.append(nodes.emphasis(text='is_metadata')) if port.help: res.append(nodes.Text(' -- ')) # publish_doctree returns >. @@ -226,3 +229,7 @@ def build_outline_lines(self, outline, indent): def _is_non_db(port): return getattr(port, 'non_db', False) + + +def _is_metadata(port): + return getattr(port, 'is_metadata', False) diff --git a/tests/engine/processes/test_builder.py b/tests/engine/processes/test_builder.py index 58ac49cbae..c8aa47082e 100644 --- a/tests/engine/processes/test_builder.py +++ b/tests/engine/processes/test_builder.py @@ -17,7 +17,7 @@ from aiida import orm from aiida.common import LinkType -from aiida.engine import Process, WorkChain +from aiida.engine import Process, WorkChain, run_get_node from aiida.engine.processes.builder import ProcessBuilderNamespace from aiida.plugins import CalculationFactory @@ -72,6 +72,7 @@ def define(cls, spec): spec.input('nested.namespace.int', valid_type=int, required=True, non_db=True) spec.input('nested.namespace.float', valid_type=float, required=True, non_db=True) spec.input('nested.namespace.str', valid_type=str, required=False, non_db=True) + spec.input_namespace('opt', required=False, dynamic=True) class MappingData(Mapping, orm.Data): # type: ignore[misc] @@ -288,27 +289,32 @@ def test_port_names_overlapping_mutable_mapping_methods(): # pylint: disable=in assert builder.boolean == orm.Bool(False) -def test_calc_job_node_get_builder_restart(aiida_localhost): +def test_calc_job_node_get_builder_restart(aiida_local_code_factory): """Test the `CalcJobNode.get_builder_restart` method.""" - original = orm.CalcJobNode( - computer=aiida_localhost, process_type='aiida.calculations:core.arithmetic.add', label='original' - ) - original.set_option('resources', {'num_machines': 1, 'num_mpiprocs_per_machine': 1}) - original.set_option('max_wallclock_seconds', 1800) - - original.base.links.add_incoming(orm.Int(1).store(), link_type=LinkType.INPUT_CALC, link_label='x') - original.base.links.add_incoming(orm.Int(2).store(), link_type=LinkType.INPUT_CALC, link_label='y') - original.store() + code = aiida_local_code_factory('core.arithmetic.add', '/bin/bash') + inputs = { + 'metadata': { + 'label': 'some-label', + 'description': 'some-description', + 'options': { + 'resources': { + 'num_machines': 1, + 'num_mpiprocs_per_machine': 1 + }, + 'max_wallclock_seconds': 1800 + } + }, + 'x': orm.Int(1), + 'y': orm.Int(2), + 'code': code, + } - builder = original.get_builder_restart() + _, node = run_get_node(CalculationFactory('core.arithmetic.add'), **inputs) + builder = node.get_builder_restart() - assert 'x' in builder - assert 'y' in builder - assert 'metadata' in builder - assert 'options' in builder.metadata assert builder.x == orm.Int(1) assert builder.y == orm.Int(2) - assert builder._inputs(prune=True)['metadata']['options'] == original.get_options() + assert builder._inputs(prune=True)['metadata'] == inputs['metadata'] def test_code_get_builder(aiida_localhost): @@ -378,12 +384,12 @@ def test_merge(): # Define only one of the required ports of `nested.namespace`. This should leave the `float` input untouched and # even though not specified explicitly again, since the merged dictionary still contains it, the # `nested.namespace` port should still be valid. - builder._merge({'nested': {'namespace': {'int': 5}}}) - assert builder._inputs(prune=True) == {'nested': {'namespace': {'int': 5, 'float': 2.0}}} + builder._merge({'nested': {'namespace': {'int': 5}}, 'opt': {'m': {'a': 1}}}) + assert builder._inputs(prune=True) == {'nested': {'namespace': {'int': 5, 'float': 2.0}}, 'opt': {'m': {'a': 1}}} # Perform same test but passing the dictionary in as keyword arguments builder._merge(**{'nested': {'namespace': {'int': 5}}}) - assert builder._inputs(prune=True) == {'nested': {'namespace': {'int': 5, 'float': 2.0}}} + assert builder._inputs(prune=True) == {'nested': {'namespace': {'int': 5, 'float': 2.0}}, 'opt': {'m': {'a': 1}}} def test_instance_interference(): diff --git a/tests/engine/test_process.py b/tests/engine/test_process.py index c32cb5204e..e3f2576b2c 100644 --- a/tests/engine/test_process.py +++ b/tests/engine/test_process.py @@ -497,3 +497,46 @@ def test_not_required_accepts_none(): process = instantiate_process(runner, TestNotRequiredNoneProcess, valid_type=orm.Int(1), any_type=orm.Bool(True)) assert process.inputs.valid_type == orm.Int(1) assert process.inputs.any_type == orm.Bool(True) + + +class TestMetadataInputsProcess(Process): + """Process with various ports that are ``is_metadata=True``.""" + + _node_class = orm.WorkflowNode + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input('metadata_port', is_metadata=True) + spec.input('metadata_port_non_serializable', is_metadata=True) + spec.input_namespace('metadata_portnamespace', is_metadata=True) + spec.input('metadata_portnamespace.with_default', default='default-value', is_metadata=True) + spec.input('metadata_portnamespace.without_default', is_metadata=True) + + +def test_metadata_inputs(): + """Test that explicitly passed ``is_metadata`` inputs are stored in the attributes. + + This is essential to make it possible to recreate a builder for the process with the original inputs. + """ + from aiida.manage import get_manager + + runner = get_manager().get_runner() + + inputs = { + 'metadata_port': 'value', + 'metadata_port_non_serializable': orm.Data().store(), + 'metadata_portnamespace': { + 'without_default': 100 + } + } + process = TestMetadataInputsProcess(runner=runner, inputs=inputs) + + # The ``is_metadata`` inputs should only have stored the "raw" inputs (so not including any defaults) and any inputs + # that are not JSON-serializable should also have been filtered out. + assert process.node.get_metadata_inputs() == { + 'metadata_port': 'value', + 'metadata_portnamespace': { + 'without_default': 100 + } + } diff --git a/tests/sphinxext/test_workchain/test_workchain_build.xml b/tests/sphinxext/test_workchain/test_workchain_build.xml index 806500e7d0..3cf00c81f7 100644 --- a/tests/sphinxext/test_workchain/test_workchain_build.xml +++ b/tests/sphinxext/test_workchain/test_workchain_build.xml @@ -13,7 +13,7 @@ A demo workchain to show how the workchain auto-documentation works. - Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, non_db – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, non_db – Description to set on the process node.label, (str, NoneType), optional, non_db – Label to set on the process node.store_provenance, bool, optional, non_db – If set to False provenance will not be stored in the database.
nsp, Namespace – A separate namespace, nsp.nsp2, Namespacex, Float, required – First input argument.y, Namespace
Namespace Portsnested, Namespace – A nested namespace.
Namespace Portsa, Int, required – An input in the nested namespace.
z, Int, required – Input in a separate namespace.
+ Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, is_metadata – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, is_metadata – Description to set on the process node.label, (str, NoneType), optional, is_metadata – Label to set on the process node.store_provenance, bool, optional, is_metadata – If set to False provenance will not be stored in the database.
nsp, Namespace – A separate namespace, nsp.nsp2, Namespacex, Float, required – First input argument.y, Namespace
Namespace Portsnested, Namespace – A nested namespace.
Namespace Portsa, Int, required – An input in the nested namespace.
z, Int, required – Input in a separate namespace.
Outputs:z, Bool, required – Output of the demoworkchain. Outline:start while(some_check) @@ -30,7 +30,7 @@ finalize A demo workchain to show how the workchain auto-documentation works. - Inputs:nsp, Namespace – A separate namespace, nsp.nsp2, Namespacex, Float, required – First input argument.y, Namespace
Namespace Portsnested, Namespace – A nested namespace.
Namespace Portsa, Int, required – An input in the nested namespace.
z, Int, required – Input in a separate namespace.
+ Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, is_metadata – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, is_metadata – Description to set on the process node.label, (str, NoneType), optional, is_metadata – Label to set on the process node.store_provenance, bool, optional, is_metadata – If set to False provenance will not be stored in the database.
nsp, Namespace – A separate namespace, nsp.nsp2, Namespacex, Float, required – First input argument.y, Namespace
Namespace Portsnested, Namespace – A nested namespace.
Namespace Portsa, Int, required – An input in the nested namespace.
z, Int, required – Input in a separate namespace.
Outputs:z, Bool, required – Output of the demoworkchain. Outline:start while(some_check) @@ -47,7 +47,7 @@ finalize A demo workchain to show how the workchain auto-documentation works. - Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, non_db – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, non_db – Description to set on the process node.label, (str, NoneType), optional, non_db – Label to set on the process node.store_provenance, bool, optional, non_db – If set to False provenance will not be stored in the database.
nsp, Namespace – A separate namespace, nsp.nsp2, Namespacex, Float, required – First input argument.y, Namespace
Namespace Portsnested, Namespace – A nested namespace.
Namespace Portsa, Int, required – An input in the nested namespace.
z, Int, required – Input in a separate namespace.
+ Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, is_metadata – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, is_metadata – Description to set on the process node.label, (str, NoneType), optional, is_metadata – Label to set on the process node.store_provenance, bool, optional, is_metadata – If set to False provenance will not be stored in the database.
nsp, Namespace – A separate namespace, nsp.nsp2, Namespacex, Float, required – First input argument.y, Namespace
Namespace Portsnested, Namespace – A nested namespace.
Namespace Portsa, Int, required – An input in the nested namespace.
z, Int, required – Input in a separate namespace.
Outputs:z, Bool, required – Output of the demoworkchain. Outline:start while(some_check) @@ -64,7 +64,7 @@ finalize Here we check that the directive works even if the outline is empty. - Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, non_db – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, non_db – Description to set on the process node.label, (str, NoneType), optional, non_db – Label to set on the process node.store_provenance, bool, optional, non_db – If set to False provenance will not be stored in the database.
x, Float, required – First input argument.
+ Inputs:metadata, Namespace
Namespace Portscall_link_label, str, optional, is_metadata – The label to use for the CALL link if the process is called by another process.description, (str, NoneType), optional, is_metadata – Description to set on the process node.label, (str, NoneType), optional, is_metadata – Label to set on the process node.store_provenance, bool, optional, is_metadata – If set to False provenance will not be stored in the database.
x, Float, required – First input argument.
Outputs:None defined.