-
Notifications
You must be signed in to change notification settings - Fork 192
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
♻️ REFACTOR: Make __all__
imports explicit (via pre-commit)
#5061
Conversation
Hmm, workflows are failing on Ubuntu install; maybe GH is having some down time |
|
||
if __name__ == '__main__': | ||
_folder = Path(__file__).parent.parent.joinpath('aiida') | ||
_skip = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note also, here we specify children imports that should always be skipped
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please document in a comment why these imports are being skipped (individually, if there are different reasons, or once if they are all skipped for the same reason - e.g. import clashes that should be fixed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!
for the 'orm': 'implementation'
case - just to make sure I understand:
this is just a precaution, correct?
I.e. it would only take effect if we were to from implementation import *
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep thats correct: I tried to keep it as close to the existing imports as possible for now, but obviously we can iterate on exactly what is exposed where
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the tests are passing, so hopefully that gives some sense of assurance that everythin is still exposed as before. and I had a quick check against: https://aiida.readthedocs.io/projects/aiida-core/en/latest/reference/api/public.html (although obviously that is missing a lot)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I didn't read the PR description carefully enough - I thought there might be some "priming" by the current imports, but what this PR does is to impose from ... import *
for all submodules, correct?
I.e. there is no way to export a functionality only on aiida.x.y.z - it will always bubble up to aiida.x (unless it's in the skip list).
Is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a way, it means that we are using __all__
(anywhere in the source tree) to define AiiDA's public API.
That is is ok, but I think we should document this and get input from some others to check they agree
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that correct?
yep thats correct, and is basically what was already happening, just more manually 😬 (there was definitely places I think where people had just forgot to add certain imports)
there is no way to export a functionality only on aiida.x.y.z - it will always bubble up to aiida.x (unless it's in the skip list)
currently yeh, although obviously now the basic code is in place, I could always make it fancier lol, for more fine-grained control if necessary
Codecov Report
@@ Coverage Diff @@
## develop #5061 +/- ##
===========================================
+ Coverage 80.25% 80.38% +0.13%
===========================================
Files 515 529 +14
Lines 36753 36862 +109
===========================================
+ Hits 29492 29627 +135
+ Misses 7261 7235 -26
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
__all__
imports explicit (add pre-commit)__all__
imports explicit (via pre-commit)
all tests passing 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @chrisjsewell
As mentioned in the AiiDa meeting I think this will help us clean up the public API and be more mindful of what we expose going forward.
|
||
if __name__ == '__main__': | ||
_folder = Path(__file__).parent.parent.joinpath('aiida') | ||
_skip = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please document in a comment why these imports are being skipped (individually, if there are different reasons, or once if they are all skipped for the same reason - e.g. import clashes that should be fixed).
Co-authored-by: Leopold Talirz <leopold.talirz@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @chrisjsewell , for me this can proceed.
please wait for at least one more review e.g. from @sphuber or @greschd before merging.
I understand that the idea is to have a follow-up PR to update reference/api/public
, both in terms of creating the list programmatically, and in documenting that the public API is simply everything people expose via __all__
anywhere inside the AiiDA codebase (with exceptions defined at ...)
Yeh so @sphuber @giovannipizzi @greschd, whoever lol, it would be good to get the review so I can move on to the next PRs (#5063, ##4996, ...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @chrisjsewell . Just some minor changes and questions and this would be good to go for now. There is one bigger question, but that would be for another PR. I think we are exposing too much right now, at least there are quite a few things that were never intended to be public facing API, and since we are now declaring that anything that is present in the init's of the second level modules, we will have to revise this. But as I said, I think we can address this in a separate PR, but this should be done before the 2.0 release. In that PR, we should also not forget to update the docs, which contains a hand-written page with public API resources. this should be removed.
aiida/cmdline/utils/echo.py
Outdated
@@ -18,7 +18,7 @@ | |||
|
|||
__all__ = ( | |||
'echo', 'echo_info', 'echo_success', 'echo_warning', 'echo_error', 'echo_critical', 'echo_highlight', | |||
'echo_dictionary' | |||
'echo_dictionary', 'VALID_DICT_FORMATS_MAPPING' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be exported? I think this should remain internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the fact that it is used internally by the module does not mean it should be in the __all__
. Even if it is not there, it can still be imported without any issue. So I would really just revert this change as I don't think this should necessarily be exposed at the top level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done 👍
) | ||
|
||
FORCE = OverridableOption('-f', '--force', is_flag=True, default=False, help='Do not ask for confirmation.') | ||
# yapf: disable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are removing all other code from the init's and just have the __all__
, why do we need to disable and reenable yapf
? I know that yapf
would change the one resource per line to just try and fit as much on one line, but why is this not desirable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well firstly, I feel it makes it less readable, but mainly I think it would make the pre-commit hooks clash, since each would keep changing it back and forth, and failing all the pre-commits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to really keep it on one line, all you need to do to have yapf accept that is to also have a comma after the last entry . This is interpreted by yapf that we want to keep one entry per line..this would allow us to get rid of all the disable en able statements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the spirit of this PR, I'm happy with the explicit nature of using # yapf: disable
, rather than relying on some obscure interpretation of commas by yapf. So we'll agree to disagree on this one 😉
aiida/orm/utils/load_funcs.py
Outdated
@@ -0,0 +1,205 @@ | |||
# -*- coding: utf-8 -*- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would rename this file load_funcs.py
to loaders.py
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha, so there is already a loaders.py in that module. Open to other suggestions though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah of course, my bad. But perfect, that is where they should go. I would just put them at the top of that file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done 👍
Yep agree; in this PR I was just trying to keep ~parity with the existing installs, but we can certainly do that |
cheers @sphuber, addressed your comments |
I've removed #4558 from the linked PRs, to keep it open for the follow PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @chrisjsewell , amazing refactor! This makes the code feel much more organized, besides enabling the linting features you already mentioned. For me this would be good to merge once @sphuber remaining comments are addressed.
Perhaps there is only one thing I would point out: all this PR is almost purely the inclusion of the script, the moving around of content of the __init__
files to main
files or the like (I understand you didn't modify any of this content), and the automated __all__
writting. There are however two modifications of content that I'll point out below. Are these strictly necessary? If they are just convenient, I would try to put them in a separate PR that we can review very quickly after this one. I hope it doesn't come out as too pedantic, but I just feel that mixing content modification on a PR that is heavily moving content may "hide" it if we need to revisit the changes at some point.
'handlers': ['console'], | ||
'level': lambda: get_config_option('logging.aiida_loglevel'), | ||
'propagate': False, | ||
def get_logging_config(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the changes in this file necessary for implementing the automation of __all__
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeh otherwise there were messy circular dependency errors.
Two key points here are (a) top-level imports should always flow from aiida.common
to other modules (since its meant to be a common module) and (b) you should always try to avoid mutable global variables
@@ -95,7 +95,7 @@ | |||
], | |||
"pre-commit": [ | |||
"astroid<2.5", | |||
"mypy==0.790", | |||
"mypy==0.910", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this dependency bump necessary for implementing the automation of __all__
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, one of the primary purposes of this PR was to get the new version of mypy working, so I wanted to make sure that was actually the case
What would be the best way to do this later? We would need to use this kind of "exceptions" list in your automated code (this) or maybe if we are going to increase significantly the number of "unexposed" methods we need to find a cleaner way to do it (which is perhaps "positive" rather than "negative", i.e. we specify what we want to include instead of what we want to skip)? Another question: does this now automatically clean up the autogenerated docs? Or if not, is there a way in which we could adapt it so that it does (so there is a specific section of the docs with only the exposed public API that is automatically generated)? |
We simply remove from the |
Yeh so I would say we are indeed being "positive" rather than "negative", since you specify positively what you want exposed by adding it to its files |
it doesn't yet but it can be easily added, see #4558 (comment) |
This PR adds a pre-commit hook (in
utils/make_all.py
) which:__all__
values__init__.py
files and auto-generates the*
imports of submodules and__all__
list for all (recursive) children__all__
and/or conflicting importsThis intends to address two related issues:
yapf
andmypy
config topyproject.toml
#4996 (comment), the current*
imports are incompatible with mypy, pylance and a number of other tools which utilise static analysis of the code.*
imports make it extremely difficult to understand what is being exposed at each level of the API (Docs: clarify public API #4558), and can lead to unreported name clashesCurrently, this method works best when there is no actual code present in the init files, although I added
# END AUTO-GENERATED
to be able to leaveaiida/manage/configuration/__init__.py
, as the tests were failing when moving them to another module.For a few other modules I moved the
__init__.py
content to amain.py
see if the tests pass 🤞