Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

function node can return the source of function rather than only the source of file #4554

Merged
merged 1 commit into from
Nov 2, 2022

Conversation

unkcpz
Copy link
Member

@unkcpz unkcpz commented Nov 11, 2020

Fixes #4543

When checking the source of the process function over MaterialCloud discover(or when explore the aiida archive of others), the whole source file returns and show makes it hard to quickly find the code if there are many other things in the source file. @ltalirz You may want to comment on this ;)
In this PR, I simply rename the node.get_function_source_code to get_function_source_file and retrieve the source of the function by inspect.getsource and store it as an attribute of the node.

Not sure about whether to store it as a node attribute is a good idea, since if the code of the function is very long it seems not wise to store a long string in attributes. The other choice is along with storing the 'function_starting_line_number' also storing the number of lines of function, and clip the source code of function from these two value.

@codecov
Copy link

codecov bot commented Nov 11, 2020

Codecov Report

Merging #4554 (b234623) into develop (9ff07c1) will increase coverage by 0.09%.
The diff coverage is 100.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #4554      +/-   ##
===========================================
+ Coverage    79.40%   79.48%   +0.09%     
===========================================
  Files          480      482       +2     
  Lines        35087    35364     +277     
===========================================
+ Hits         27856    28107     +251     
- Misses        7231     7257      +26     
Flag Coverage Δ
django 73.65% <100.00%> (+0.13%) ⬆️
sqlalchemy 72.83% <100.00%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
aiida/orm/utils/mixins.py 91.47% <100.00%> (+1.47%) ⬆️
aiida/tools/importexport/archive/common.py 76.05% <0.00%> (-20.25%) ⬇️
aiida/common/log.py 89.71% <0.00%> (-5.88%) ⬇️
aiida/tools/importexport/archive/writers.py 92.04% <0.00%> (-5.26%) ⬇️
aiida/tools/graph/graph_traversers.py 88.60% <0.00%> (-4.67%) ⬇️
aiida/tools/importexport/common/archive.py 74.70% <0.00%> (-2.51%) ⬇️
aiida/tools/importexport/dbexport/utils.py 79.57% <0.00%> (-2.18%) ⬇️
aiida/manage/database/delete/nodes.py 78.73% <0.00%> (-1.66%) ⬇️
aiida/tools/importexport/dbexport/__init__.py 97.40% <0.00%> (-0.69%) ⬇️
aiida/cmdline/commands/cmd_group.py 87.23% <0.00%> (-0.54%) ⬇️
... and 51 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9ff07c1...ce440f5. Read the comment docs.

@sphuber
Copy link
Contributor

sphuber commented Nov 11, 2020

Thanks @unkcpz . As you said yourself in your comment, we shouldn't store the function's source code in the database. It should be stored in the repository, but there it already is stored, so storing it again would be inefficient and unnecessary. Your other suggestion may actually be better, to, in addition to the starting line number which is already stored, add the total number of lines. The only problem is backwards compatibility. Whatever function that may use this new total number of lines attribute, will have to deal with it not existing, since all existing process function nodes won't have that attribute and I don't think we can or should write a database migration for this. On that note of backwards compatibility, your current PR also breaks it as you change the behavior of the get_funciton_source_code, which we cannot do just like that

@unkcpz
Copy link
Member Author

unkcpz commented Nov 12, 2020

Thanks, @sphuber! I notice as you mentioned there is a backwards compatibility problem. But I have no experience in database or orm stuff, what am I suppose to do? Can you point out which part of code is involved and I should look at?

@sphuber
Copy link
Contributor

sphuber commented Nov 17, 2020

Thanks, @sphuber! I notice as you mentioned there is a backwards compatibility problem. But I have no experience in database or orm stuff, what am I suppose to do? Can you point out which part of code is involved and I should look at?

You should revert the change of get_function_source_code and keep it as it was, i.e. return the entire file. You can then add a method that returns just the source code of the function, but you will have to find a different method name for this.

Secondly, as we discussed, it is not a good idea to store the source code of the function in the database. The best we can do is store the number of lines as an attribute. This you can then use together with the function_starting_line_number you can return the correct line range. However, you need to built a case in the logic that accounts for nodes not having this new line_count, because all existing nodes won't have it. In that case you need to raise an exception saying that it cannot be done.

@unkcpz unkcpz force-pushed the feature/4543/source-code branch 3 times, most recently from fb74cc8 to b9e9d35 Compare November 26, 2020 07:09
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
Copy link
Member Author

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @sphuber I change the code as your recommend.

Just out of curiosity, what to do if I want to change the method name?

@unkcpz unkcpz requested a review from sphuber November 26, 2020 07:17
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
aiida/orm/utils/mixins.py Show resolved Hide resolved
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
@sphuber
Copy link
Contributor

sphuber commented Nov 26, 2020

Just out of curiosity, what to do if I want to change the method name?

You will have to deprecate the get_function_source_code saying that in the future this will return only the source of the function and that in the future you should use get_function_source_file to get the entire content of the file, and then you forward the get_function_source_code by calling get_function_source_file. You can look in source code for examples of deprecation warnings. Look for warnings.warn('some message', AiidaDeprecationWarning).

Now, the problem is, that ideally, in the future when we remove the deprecation and make get_function_source_code return just the function source, you can do so, but you will remove get_function_source (the function you added now) but that would also be breaking. At that point, what we should do is do the change and deprecate get_function_source and only remove that a release cycle later. I hope that is clear, if not let me know

@unkcpz
Copy link
Member Author

unkcpz commented Jan 20, 2021

hi @sphuber, anymore to add of this? I also add the deprecated warning.

Copy link
Member

@ramirezfranciscof ramirezfranciscof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @unkcpz , nice work! I have a couple of comments to discuss with you and @sphuber. Also, to justify why I'm suddenly interested in this, it might become necessary for an upcoming PR to solve #4401 (see this comment, the row for source_code).

Comment on lines 116 to 118
:returns: the number of lines or None
"""
return self.get_attribute(self.FUNCTION_NUMBER_OF_LINES_KEY, -1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says it will return the number of lines or None but the default value of the get is -1. Shouldn't it be None?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that is ambiguous. Originally try to use -1 here to return the whole file code if the key is not found. I think None would much more make sense. Changed.

Comment on lines 132 to 138
warnings.warn(
'in the future this will return only the source of the function and'
' please use `get_function_source_file` to get'
' the entire content of the file instead', AiidaDeprecationWarning
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, there are a couple of comments regarding this depracation you are doing:

  1. You need to already include the get_function_source_file method so that users can immediately start fixing their scripts with the new way of doing it.
  2. The depracation path for this is a bit complicated. I know you discussed with @sphuber in the general thread what would be the hypothetical way to do this, but I'm not sure he was actually agreeing it would be a good idea to go this way (maybe he can confirm in case I misinterpreted?). I personally agree that it is better "namewise", but changing to that might generate a bit of annoyance on the users.
  3. The warning itself it is a bit unclear to me, I would maybe add a legthier description if we are going to go this way. Something like:
Suggested change
warnings.warn(
'in the future this will return only the source of the function and'
' please use `get_function_source_file` to get'
' the entire content of the file instead', AiidaDeprecationWarning
)
warnings.warn(
'you are currently using `get_function_source_code` to get the entire'
' file where the process is defined, but in the future this method will'
' be used to return only the part of that file that corresponds to the'
' process itself. Please use the method `get_function_source_file`'
' instead.', AiidaDeprecationWarning
)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to already include the get_function_source_file method so that users can immediately start fixing their scripts with the new way of doing it.

My bad. add it as the implemented function and forward it to get_function_source_code.

The depracation path for this is a bit complicated. I know you discussed with @sphuber in the general thread what would be the hypothetical way to do this, but I'm not sure he was actually agreeing it would be a good idea to go this way (maybe he can confirm in case I misinterpreted?). I personally agree that it is better "namewise", but changing to that might generate a bit of annoyance on the users.

However, I can not figure out a better way to do this. I think it can reduce the burden to users only by a clear docstring and comprehensive warning information.

The warning itself it is a bit unclear to me, I would maybe add a legthier description if we are going to go this way.

Thanks! changed as you recommend.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, I can not figure out a better way to do this

So, I think the alternative is to accept that get_function_source_code now returns the full file and create a new name for the one that just returns the function (get_function_source_def?). This is less consistent with the original information (where source_file had the file and source_code had the definition) but more consistent with the current status (where source_code is the name of the file) and also causes less friction with the users because there is no deprecations needed. This was what I understood from the first comment that @sphuber made:

You should revert the change of get_function_source_code and keep it as it was, i.e. return the entire file. You can then add a method that returns just the source code of the function, but you will have to find a different method name for this.

There was no mention of any further change there, just to rename the new function and leave the old one as it was. Then the longer path in the latter comment read more like a hipothetical situation than a prescription for this case.

Again, I am personally in favour of doing the longer deprecation path, I just want to make sure that we are all on the same page and there is not misunderstandings or something we are not considering.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ramirezfranciscof Thanks for the clarification. So in this simple pr, only need to add a new method for section of source code, then left deprecation and renaming to the future, correct? I rebase the PR and please have a look at your available.

return self.get_object_content(self.FUNCTION_SOURCE_FILE_PATH)

def get_function_source(self):
"""Return the source code of the function and only of the function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are doing the deprecation path, I would add to this docstring that this is a temporary method.

I'm also wondering if we should add a test for this or not, considering that it is going to be temporary. I guess it would be the same that adding a unit test for the warning. @sphuber would it be a good idea to have a unit test file with all the deprecation warnings, considering it would have to be explicitly updated when removing the deprecations? Somehow it sounds both a bit insane but not a bad idea at the same time, as a way to keep track of all deprecations in a single place...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three relevant functions are closely packed in the code. So I think the source code itself with its docstring makes enough explanation for how to do the future migration. Let's see if @sphuber has any better ideas.

Copy link
Member Author

@unkcpz unkcpz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ramirezfranciscof My apologise for the delay and thanks for such details reviewing.

Comment on lines 116 to 118
:returns: the number of lines or None
"""
return self.get_attribute(self.FUNCTION_NUMBER_OF_LINES_KEY, -1)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that is ambiguous. Originally try to use -1 here to return the whole file code if the key is not found. I think None would much more make sense. Changed.

Comment on lines 132 to 138
warnings.warn(
'in the future this will return only the source of the function and'
' please use `get_function_source_file` to get'
' the entire content of the file instead', AiidaDeprecationWarning
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to already include the get_function_source_file method so that users can immediately start fixing their scripts with the new way of doing it.

My bad. add it as the implemented function and forward it to get_function_source_code.

The depracation path for this is a bit complicated. I know you discussed with @sphuber in the general thread what would be the hypothetical way to do this, but I'm not sure he was actually agreeing it would be a good idea to go this way (maybe he can confirm in case I misinterpreted?). I personally agree that it is better "namewise", but changing to that might generate a bit of annoyance on the users.

However, I can not figure out a better way to do this. I think it can reduce the burden to users only by a clear docstring and comprehensive warning information.

The warning itself it is a bit unclear to me, I would maybe add a legthier description if we are going to go this way.

Thanks! changed as you recommend.

return self.get_object_content(self.FUNCTION_SOURCE_FILE_PATH)

def get_function_source(self):
"""Return the source code of the function and only of the function.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three relevant functions are closely packed in the code. So I think the source code itself with its docstring makes enough explanation for how to do the future migration. Let's see if @sphuber has any better ideas.

This method returns the source code of just the process function itself,
i.e., the wrapped function plus the decorator itself.

The existing method `get_function_source_code` would return the source
code of the entire file in which the process function was defined, but
this can at times include a lot of other code that is not always useful
to have. The `get_function_source_code` method is deprecated and replaced
by the `get_source_code_file` method to have consistent naming.

The `get_source_code_function` implementation retrieves the source code
of the function by calling `get_source_code_file` and extracting just
the lines of the function. This is accomplished by using the starting
line of the function, which already used to be stored, combined with the
total number of lines that make up the function, which is an attribute
that is stored from now on for process functions.
Copy link
Contributor

@sphuber sphuber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @unkcpz . I think this is good to go in. I have just suggested some changes to deal with the inconvenient name. I would deprecate get_function_source_code and rename it get_source_code_file and then add get_source_code_function with your new implementation.

aiida/orm/utils/mixins.py Show resolved Hide resolved
Comment on lines 132 to 147
def get_function_source_def(self):
"""
The temporary method that return the section of code for the
function only correspond to the function process. It will be
replaced with `get_function_source_code` in future.

:returns: the function source code.
"""
content_list = self.get_function_source_code().splitlines()
start_line = self.function_starting_line_number
end_line = start_line + self.function_number_of_lines

# start_line - 1 to include the decorator
function_source = '\n'.join(content_list[start_line - 1:end_line])

return function_source
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_function_source_def(self):
"""
The temporary method that return the section of code for the
function only correspond to the function process. It will be
replaced with `get_function_source_code` in future.
:returns: the function source code.
"""
content_list = self.get_function_source_code().splitlines()
start_line = self.function_starting_line_number
end_line = start_line + self.function_number_of_lines
# start_line - 1 to include the decorator
function_source = '\n'.join(content_list[start_line - 1:end_line])
return function_source
def get_function_source_code(self):
"""Return the absolute path to the source file in the repository.
:returns: the absolute path of the source file in the repository, or None if it does not exist
"""
warn_deprecation('This method will be removed, use `get_source_code_file` instead.', version=3)
return self.get_object_content(self.FUNCTION_SOURCE_FILE_PATH)
def get_source_code_file(self) -> str | None:
"""Return the source code of the file in which the function was defined.
:returns: The source code of the file or ``None`` if not available.
"""
return self.get_object_content(self.FUNCTION_SOURCE_FILE_PATH, None)
def get_source_code_function(self) -> str | None:
"""Return the source code of the function including the decorator.
:returns: The source code of the function or ``None`` if not available.
"""
source_code = self.get_source_code_file()
if source_code is None:
return None
content_list = source_code.splitlines()
start_line = self.function_starting_line_number
end_line = start_line + self.function_number_of_lines
# Start at ``start_line - 1`` to include the decorator
return '\n'.join(content_list[start_line - 1:end_line])

Copy link
Contributor

@sphuber sphuber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good, thanks @unkcpz , just a question on the method naming

@@ -179,6 +181,9 @@ def test_process_function(data):
assert node.function_name == function_name
assert isinstance(node.function_starting_line_number, int)

# Check the source code of the function is stored
assert node.get_function_source_def() == inspect.getsource(test_process_function)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert node.get_function_source_def() == inspect.getsource(test_process_function)
assert node.get_function_source_file() == inspect.getsource(test_process_function)

:returns: The source code of the function or ``None`` if it could not be determined when storing the node.
"""
try:
return self.base.repository.get_object_content(self.FUNCTION_SOURCE_FILE_PATH)
except FileNotFoundError:
return None

def get_function_source_function(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally find get_function_source_function a bit weird, with the double function in there. I would propose get_source_code_function and get_source_code_file for the entire file source code. Think that makes more sense no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I mess up when resolve confliction on github interface. Will change it now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, I hope it is all fine.

@unkcpz unkcpz requested a review from sphuber November 2, 2022 09:35
Copy link
Contributor

@sphuber sphuber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unkcpz , two more tiny things I missed. They are suggestions so you can just commit them directly, and I will merge. Thanks!

aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
aiida/orm/utils/mixins.py Outdated Show resolved Hide resolved
@unkcpz
Copy link
Member Author

unkcpz commented Nov 2, 2022

Sorry @unkcpz , two more tiny things I missed. They are suggestions so you can just commit them directly, and I will merge. Thanks!

No worries, I am also supposed to find it 😆. Done!

@sphuber
Copy link
Contributor

sphuber commented Nov 2, 2022

@unkcpz while typing up the commit message, I realized there still was a bug. The new method get_source_code_function relies on the FUNCTION_NUMBER_OF_LINES_KEY attribute being set, but this will only exist for nodes stored with this patch. Existing nodes will return None and so get_source_code_function needs to check for this. The same goes for the starting_line_number. Anyway, I added type checking and enabled the file for mypy which confirmed the bugs that were hiding.

@unkcpz
Copy link
Member Author

unkcpz commented Nov 2, 2022

You are right. The mypy type check only manifests the issue but you did not fix it, correct? Do you have any idea how to process if the attributes do not exist for old nodes?

@sphuber
Copy link
Contributor

sphuber commented Nov 2, 2022

You are right. The mypy type check only manifests the issue but you did not fix it, correct? Do you have any idea how to process if the attributes do not exist for old nodes?

No it didn't automatically fix it. I fixed it by checking whether those attributes return None and if so, just return None, just as if the source code file doesn't exist. It now reads:

    def get_source_code_function(self) -> str | None:
        """Return the source code of the function including the decorator.
        :returns: The source code of the function or ``None`` if not available.
        """
        source_code = self.get_source_code_file()

        if source_code is None or self.function_number_of_lines is None or self.function_starting_line_number is None:
            return None

        content_list = source_code.splitlines()
        start_line = self.function_starting_line_number
        end_line = start_line + self.function_number_of_lines

        # Start at ``start_line - 1`` to include the decorator
        return '\n'.join(content_list[start_line - 1:end_line])

See the conditional if source code ....

@unkcpz
Copy link
Member Author

unkcpz commented Nov 2, 2022

Thanks a lot! I retrigger the failed test (3.10) which I guess it is failed randomly. Only the docs tests failed, but I have no idea why it shows here, I don't think there is any code related to that.

@sphuber
Copy link
Contributor

sphuber commented Nov 2, 2022

I don't understand either why it is failing all of a sudden. I can reproduce it locally, but the problem seems to be with the tutorial notebook. It creates and loads a temporary profile but doesn't set it as a default. So later when it calls verdi it operates on a different default profile (the one of the system). This should have nothing to do with these changes, so I wonder if all builds will now fail.

@sphuber sphuber force-pushed the feature/4543/source-code branch 2 times, most recently from c1d718a to ce1ad67 Compare November 2, 2022 18:15
@sphuber
Copy link
Contributor

sphuber commented Nov 2, 2022

Ok I found the problem, but I understand literally nothing. In the constructor if we do

        try:
            source_file_path = inspect.getsourcefile(func)
            if source_file_path:
                with open(source_file_path, 'rb') as handle:
                    self.base.repository.put_object_from_filelike(  # type: ignore[attr-defined]
                        handle, self.FUNCTION_SOURCE_FILE_PATH
                    )
        except (IOError, OSError):
            pass

it works without any provblems. However if we do

        try:
            source_file_path = inspect.getsourcefile(func)
        except (IOError, OSError):
            pass
        else:
            if source_file_path is not None:
                with open(source_file_path, 'rb') as handle:
                    self.base.repository.put_object_from_filelike(  # type: ignore[attr-defined]
                        handle, self.FUNCTION_SOURCE_FILE_PATH
                    )

somehow the docs build fails 😕 what the f?

@unkcpz
Copy link
Member Author

unkcpz commented Nov 2, 2022

It seems the docs/source/intro/tutorial.md having process function generated on the fly inside the jupyter notebook but deleted and can not be found afterward. I got the following log from running make -C docs html.

~/Projects/WP-aiida/aiida-core/aiida/orm/utils/mixins.py�[0m in �[0;36mstore_source_info�[0;34m(self, func)�[0m
�[1;32m     64�[0m         �[0;32melse�[0m�[0;34m:�[0m�[0;34m�[0m�[0;34m�[0m�[0m
�[1;32m     65�[0m             �[0;32mif�[0m �[0msource_file_path�[0m �[0;32mis�[0m �[0;32mnot�[0m �[0;32mNone�[0m�[0;34m:�[0m�[0;34m�[0m�[0;34m�[0m�[0m
�[0;32m---> 66�[0;31m                 �[0;32mwith�[0m �[0mopen�[0m�[0;34m(�[0m�[0msource_file_path�[0m�[0;34m,�[0m �[0;34m'rb'�[0m�[0;34m)�[0m �[0;32mas�[0m �[0mhandle�[0m�[0;34m:�[0m�[0;34m�[0m�[0;34m�[0m�[0m
�[0m�[1;32m     67�[0m                     self.base.repository.put_object_from_filelike(  # type: ignore[attr-defined]
�[1;32m     68�[0m                         �[0mhandle�[0m�[0;34m,�[0m �[0mself�[0m�[0;34m.�[0m�[0mFUNCTION_SOURCE_FILE_PATH�[0m�[0;34m�[0m�[0;34m�[0m�[0m

�[0;31mFileNotFoundError�[0m: [Errno 2] No such file or directory: '/home/jyu/Projects/WP-aiida/aiida-core/docs/source/intro/ipykernel_2627531/3198245695.py'
FileNotFoundError: [Errno 2] No such file or directory: '/home/jyu/Projects/WP-aiida/aiida-core/docs/source/intro/ipykernel_2627531/3198245695.py'

It has some timestamps in the log, but you can find it point to the code we changed in this PR. I suspect there will be issue with create FunctionProcess inside the notebook. I'll test it tomorrow.

somehow the docs build fails 😕 what the f?

BTW, is the "f" for "fix" as I did for the commit message? 😆

@sphuber
Copy link
Contributor

sphuber commented Nov 2, 2022

BTW, is the "f" for "fix" as I did for the commit message? laughing

No, in this case it was something else 😅

@sphuber sphuber merged commit 9a9440b into aiidateam:main Nov 2, 2022
@unkcpz
Copy link
Member Author

unkcpz commented Nov 3, 2022

I understand more why it was going on for the docs fail. By testing the behavior in the IPython shell, it proves my guess that It seems in ipython or Jupyter notebook, the classes/functions defined or the main module does not have a file attribute associated with them so inspect wasn't able to retrieve a source file. The issue can be reproduced by:

In [1]: from aiida import orm                                                 
                                                                              
In [2]: from aiida.engine import calcfunction                                 
                                                                                                                                                             
In [3]: @calcfunction                                                         
   ...: def add(a, b):                                                                                                                                       
   ...:     return a + b                                                                                                                                     
   ...:                                                                                                                                                      
                                                                                                                                                             
In [4]: d = add(orm.Int(4), orm.Int(5))                                       
<ipython-input-3-f301de63af89>                                                                                                                               
['@calcfunction\n', 'def add(a, b):\n', '    return a + b\n']   

---------------------------------------------------------------------------                                                                                  
FileNotFoundError                         Traceback (most recent call last)                                                                                  
<ipython-input-4-677ea2e3e348> in <module>                                                                                                                   
----> 1 d = add(orm.Int(4), orm.Int(5))                   
.....

~/Projects/WP-aiida/aiida-core/aiida/engine/processes/functions.py in _setup_db_record(self)                                                                 
    361         """Set up the database record for the process."""                                                                                            
    362         super()._setup_db_record()                                    
--> 363         self.node.store_source_info(self._func)                       
    364                                                                       
    365     @override                                                         

~/Projects/WP-aiida/aiida-core/aiida/orm/utils/mixins.py in store_source_info(self, func)                                                                    
     66                 print(source_file_path)                               
     67                 print(source_list)                                    
---> 68                 with open(source_file_path, 'rb') as handle:                                                                                         
     69                     self.base.repository.put_object_from_filelike(  # type: ignore[attr-defined]                                                     
     70                         handle, self.FUNCTION_SOURCE_FILE_PATH                                                                                       

FileNotFoundError: [Errno 2] No such file or directory: '<ipython-input-3-f301de63af89>'   

So in principle, the try-catch block with IOError catch this exception and will not store the source file for the jupyter-notebook or IPython shell. It can be regarded as a bug since now we add the get_function_source_code which is supposed to return the code of the function but for the IPython shell it returns nothing.
I think there is an alternative to inspect called dill that seems able to pickle the jupyter notebook cells.
I think the correct behaviour for ipython/jupyter-nb is to store cell content as the source code file in file repository.

@unkcpz unkcpz deleted the feature/4543/source-code branch November 3, 2022 08:35
@sphuber
Copy link
Contributor

sphuber commented Nov 3, 2022

ah yeah of course, source code storing for interactive shells and notebooks has never been supported. I would rather call this a feature request then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Why get_function_source_code return source file of the function not the source function itself?
3 participants