From 722a0bae54609513b853095c0fdfdf6249812a2a Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Fri, 28 Oct 2022 15:18:31 +0200 Subject: [PATCH] ORM: Return `None` in `get_function_source_code` instead of excepting The `FunctionCalculationMixin` which is used for the nodes of process functions has the function `get_function_source_code` which will raise `FileNotFoundError` if the source code file does not exist in the repository. This will happen typically for functions defined in an interactive shell where the `inspect` package cannot determine the source code. The docstring erroneously said that `None` would be returned, but even worse it said that the absolute filepath would be returned, whereas it was supposed to give the source code as a string. The docstring is fixed and the method now returns `None` if the source code file doesn't exist instead of raising. --- aiida/orm/utils/mixins.py | 17 +++++++++++++---- tests/engine/test_process_function.py | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/aiida/orm/utils/mixins.py b/aiida/orm/utils/mixins.py index f4575b66cf..40ebfad718 100644 --- a/aiida/orm/utils/mixins.py +++ b/aiida/orm/utils/mixins.py @@ -8,6 +8,8 @@ # For further information please visit http://www.aiida.net # ########################################################################### """Mixin classes for ORM classes.""" +from __future__ import annotations + import inspect from typing import List, Optional @@ -104,12 +106,19 @@ def _set_function_starting_line_number(self, function_starting_line_number): """ self.base.attributes.set(self.FUNCTION_STARTING_LINE_KEY, function_starting_line_number) - def get_function_source_code(self): - """Return the absolute path to the source file in the repository. + def get_function_source_code(self) -> str | None: + """Return the source code of the function stored in the repository. + + If the source code file does not exist, this will return ``None`` instead. This can happen for example when the + function was defined in an interactive shell in which case ``store_source_info`` will have failed to retrieve + the source code using ``inspect.getsourcefile``. - :returns: the absolute path of the source file in the repository, or None if it does not exist + :returns: The source code of the function or ``None`` if it could not be determined when storing the node. """ - return self.base.repository.get_object_content(self.FUNCTION_SOURCE_FILE_PATH) + try: + return self.base.repository.get_object_content(self.FUNCTION_SOURCE_FILE_PATH) + except FileNotFoundError: + return None class Sealable: diff --git a/tests/engine/test_process_function.py b/tests/engine/test_process_function.py index 7c5cfbbc4a..2e1ea711d9 100644 --- a/tests/engine/test_process_function.py +++ b/tests/engine/test_process_function.py @@ -186,6 +186,23 @@ def test_process_function(data): assert node.function_name in function_name_from_source +@pytest.mark.usefixtures('aiida_profile') +def test_get_function_source_code(): + """Test that ``get_function_source_code`` returns ``None`` if no source code was stored. + + This is the case for example for functions defined in an interactive shell, where the retrieval of the source code + upon storing the node fails and nothing is stored. The function should not except in this case. + """ + from aiida.orm.utils.mixins import FunctionCalculationMixin + + _, node = function_return_true.run_get_node() + + # Delete the source file by going down to the ``RepositoryBackend`` to circumvent the immutability check. + node.base.repository._repository.delete_object(FunctionCalculationMixin.FUNCTION_SOURCE_FILE_PATH) # pylint: disable=protected-access + + assert node.get_function_source_code() is None + + @pytest.mark.usefixtures('aiida_profile_clean') def test_function_varargs(): """Variadic arguments are not supported and should raise."""