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

node.open: more useful warnings #4434

Merged
merged 2 commits into from
Oct 27, 2020

Conversation

ltalirz
Copy link
Member

@ltalirz ltalirz commented Oct 10, 2020

aiida-core started warning when methods like Node.read and Node.del
are used outside a context manager.
The warning, however, did not include any indication of where the
problematic function call was made (nor did it include instructions on
how to fix it).

Furthermore, this PR fixes an overlooked use of Node.read in
aiida-core that did not rely on the context manager.

@ltalirz ltalirz force-pushed the issue_4335_deprecation_warning branch from a6c0f11 to ac1479a Compare October 10, 2020 21:16
@ltalirz
Copy link
Member Author

ltalirz commented Oct 10, 2020

Example warning:

In [1]: n = load_node(123)

In [2]: n.open('aiida.out').read()[1]
/Users/leopold/Personal/Postdoc-MARVEL/repos/aiida/aiida_rmq/aiida/orm/nodes/node.py:64: AiidaDeprecationWarning:
`Node.read` used outside context manager for <FolderData: uuid: 2146a20c-193a-4e76-b492-f868a8cf31a0 (pk: 123)>.
Please use `with <node>.open(): ...` instead. This will raise starting from `aiida-core==2.0.0`
File <ipython-input-2-6452f92039da>, line 1 in module <module>  File "<ipython-input-2-6452f92039da>", line 1, in <module>
    n.open('aiida.out').read()[1]

  warnings.warn(msg, AiidaDeprecationWarning)  # pylint: disable=no-member
/Users/leopold/Personal/Postdoc-MARVEL/repos/aiida/aiida_rmq/aiida/orm/nodes/node.py:64: AiidaDeprecationWarning:
`Node.del` used outside context manager for <FolderData: uuid: 2146a20c-193a-4e76-b492-f868a8cf31a0 (pk: 123)>.
Please use `with <node>.open(): ...` instead. This will raise starting from `aiida-core==2.0.0`
File <ipython-input-2-6452f92039da>, line 1 in module <module>  File "<ipython-input-2-6452f92039da>", line 1, in <module>
    n.open('aiida.out').read()[1]

  warnings.warn(msg, AiidaDeprecationWarning)  # pylint: disable=no-member

It's a bit ugly that traceback.StackSummary.from_list(caller_traceback).format() adds the blank line.
I could also use inspect.getframeinfo and then essentially recreate

File <ipython-input-2-6452f92039da>, line 1 in module <module>  File "<ipython-input-2-6452f92039da>", line 1, in <module>
    n.open('aiida.out').read()[1]

from this but that also seemed like overkill.
Let me know in case you know a better way (maybe I'm overlooking some obvious built-in function here)

@codecov
Copy link

codecov bot commented Oct 10, 2020

Codecov Report

Merging #4434 into develop will decrease coverage by 0.01%.
The diff coverage is 80.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #4434      +/-   ##
===========================================
- Coverage    79.36%   79.35%   -0.00%     
===========================================
  Files          476      476              
  Lines        34951    34959       +8     
===========================================
+ Hits         27736    27739       +3     
- Misses        7215     7220       +5     
Flag Coverage Δ
#django 73.22% <80.00%> (-<0.01%) ⬇️
#sqlalchemy 72.45% <80.00%> (-<0.01%) ⬇️

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

Impacted Files Coverage Δ
aiida/orm/nodes/node.py 81.15% <71.43%> (-0.66%) ⬇️
aiida/orm/nodes/data/cif.py 86.65% <100.00%> (+0.08%) ⬆️
aiida/engine/daemon/client.py 72.42% <0.00%> (-1.14%) ⬇️
aiida/transports/plugins/local.py 82.83% <0.00%> (+0.26%) ⬆️

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 7c49471...cea718b. Read the comment docs.

msg = f'\n`Node.{method}` used outside context manager for {self._name}.\n' + \
'Please use `with <node>.open(): ...` instead. ' + \
'This will raise starting from `aiida-core==2.0.0`\n'
caller_frame = inspect.currentframe().f_back.f_back
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if there is a possibility that this code fails - maybe we can just wrap it in a try / except such that it fails gracefully?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done (for the case when f_back returns None

@sphuber sphuber self-requested a review October 11, 2020 13:38
msg = '`{}` used without context manager for {}. This will raise starting from `aiida-core==2.0.0`'.format(
method, self._name
)
msg = f'\n`Node.{method}` used outside context manager for {self._name}.\n' + \
Copy link
Contributor

Choose a reason for hiding this comment

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

The Node.{method} is not actually correct and may be misleading. The method here is what is actually called on the return value of Node.open. Normally this is a filelike object, but now it is this instance of WarnWhenNotEntered that we added. You can see this when this object is garbage collected, because it will then say Node.del used outside .. but Node doesn't have a del method. Same goes for read though.

Consider following example:

handle = node.open()
content = handle.read()

It is when read is called on handle that we get the warning, not on the node. Likewise, it is del handle that is called when it is garbage collected that incurs the second warning.

This shows another potential problem, with the example above, the line of code that you will include in your warning will be content = handle.read() and people may be confused by you to tell them that this should be with node.open(). Note that in this case, the fix may be obvious, because they will go to the line mentioned, and see the node.open() call straight above it. However, they are not necessarily very close. They could have passed the handle to some function, so then it may not be clear at all where the actual problem is.

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 Node.{method} is not actually correct and may be misleading. The method here is what is actually called on the return value of Node.open.

Right, I've now tried to rephrase the message to make this clearer. Further suggestions welcome.

the line of code that you will include in your warning will be content = handle.read() and people may be confused by you to tell them that this should be with node.open()

You are very welcome to suggest a better way.
In my view, this warning already improves the situation enormously, since it shows which call caused the warning rather than where the warning is defined.
For me this is good enough.

'This will raise starting from `aiida-core==2.0.0`\n'
caller_frame = inspect.currentframe().f_back.f_back
caller_tb = inspect.getframeinfo(caller_frame)
msg += f'File {caller_tb.filename}, line {caller_tb.lineno} in module {caller_tb.function}'
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure why, but I see the line with File {caller_tb.filename} .. twice

  /home/sph/code/aiida/env/dev/aiida-core/aiida/orm/nodes/node.py:62: AiidaDeprecationWarning: 
  `Node.read` used outside context manager for <ForceConstantsData: uuid: 6444b4db-4c69-484d-87d1-5ab6d38d8e46 (unstored)>.
  Please use `with <node>.open(): ...` instead. This will raise starting from `aiida-core==2.0.0`
  File /home/sph/code/aiida/env/dev/aiida-quantumespresso/aiida_quantumespresso/data/force_constants.py, line 22 in module set_file  File "/home/sph/code/aiida/env/dev/aiida-quantumespresso/aiida_quantumespresso/data/force_constants.py", line 22, in set_file
      content = handle.read().splitlines()
  
    warnings.warn(msg, AiidaDeprecationWarning)  # pylint: disable=no-member

Any idea why?

Copy link
Member Author

@ltalirz ltalirz Oct 26, 2020

Choose a reason for hiding this comment

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

Sorry, this didn't show up for me since I was working inside the ipython shell, and so I thought I had to recreate this line manually.
I've removed it now; should be fixed.

@sphuber
Copy link
Contributor

sphuber commented Oct 26, 2020

@ltalirz I think it would be good to try and get this into the v1.5.0 release as well. Do you have the time to still take a look at this?

@ltalirz
Copy link
Member Author

ltalirz commented Oct 26, 2020

Yes

@ltalirz
Copy link
Member Author

ltalirz commented Oct 26, 2020

@sphuber Could you please check #4393 and #4511 ?

@ltalirz ltalirz force-pushed the issue_4335_deprecation_warning branch from ac1479a to cf99009 Compare October 26, 2020 18:14
@ltalirz ltalirz requested a review from sphuber October 26, 2020 18:17
@ltalirz ltalirz force-pushed the issue_4335_deprecation_warning branch from cf99009 to fdbe999 Compare October 26, 2020 18:18
Comment on lines 52 to 63
msg = f'\n`{method}` called outside Node.open() context manager for {self._name}.\n' + \
'Please wrap this call inside `with <node instance>.open(): ...` to silence this warning. ' + \
'This will raise starting from `aiida-core==2.0.0`\n'
try:
caller_frame = inspect.currentframe().f_back.f_back
except AttributeError:
warnings.warn(msg, AiidaDeprecationWarning)
return

caller_traceback = traceback.extract_stack(caller_frame, limit=1)
for item in traceback.StackSummary.from_list(caller_traceback).format():
msg += item
warnings.warn(msg, AiidaDeprecationWarning) # pylint: disable=no-member
Copy link
Contributor

Choose a reason for hiding this comment

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

I think putting the one call in try/except helps but is potentially not enough. We really want to avoid exceptions occurring in this handler. I also think the code to extract the relevant line of code can be much simpler. I propose the following for the entire method, which has updated warning message and stack extraction:

        if not self._was_entered:
            msg = f'\nThe method `{method}` was called on the return value of `{self._name}.open()` outside of a context manager.\n' + \
                  'Please wrap this call inside `with <node instance>.open(): ...` to silence this warning. ' + \
                  'This will raise an exception, starting from `aiida-core==2.0.0`.\n'

            try:
                caller = traceback.format_stack()[-3]
            except Exception:  # pylint: disable=broad-except
                msg += 'Could not determine the line of code responsible for triggering this warning.'
            else:
                msg += f'The offending call comes from:\n{caller}'

            warnings.warn(msg, AiidaDeprecationWarning)  # pylint: disable=no-member

Copy link
Member Author

Choose a reason for hiding this comment

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

sounds good to me!

@ltalirz ltalirz force-pushed the issue_4335_deprecation_warning branch from fdbe999 to ef4ddbf Compare October 26, 2020 19:21
aiida-core started warning when methods like `Node.read` and `Node.del`
are used outside a context manager.
The warning, however, did not include any indication of where the
problematic function call was made (nor did it include instructions on
how to fix it).

Furthermore, this PR fixes an overlooked use of `Node.read` in
aiida-core that did not rely on the context manager.
@ltalirz ltalirz force-pushed the issue_4335_deprecation_warning branch from ef4ddbf to a7f4867 Compare October 26, 2020 19:45
@ltalirz ltalirz requested a review from sphuber October 26, 2020 23:15
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 @ltalirz

@sphuber sphuber merged commit 57c8afa into aiidateam:develop Oct 27, 2020
@sphuber sphuber deleted the issue_4335_deprecation_warning branch October 27, 2020 08:23
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.

3 participants