Always getattr()
in lazy import machinery
#1963
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
@lilyminium 's fix in #1961 was exactly right:
My only tweak is that I don't think looking in
__dict__
is now ever useful. I originally did this out of an abundance of caution to avoid infinite recursion where__getattr__
is calling itself viagetattr
. Having reviewed it now I'm convinced that this recursive case can't actually happen. In any case, the current approach of trying__dict__
and falling back togetattr
does not protect against this recursion error, so this PR simplifies the code and adds an explanatory comment.The remainder of this description just gives context on why this recursion shouldn't happen. It's summarized in a safety comment in the code itself. I may or may not have been burned by indirect recursion before so I just wanted to document this.
This infinite recursion can happen if an object that doesn't exist in
openff.toolkit
is included in the_lazy_imports_obj
dictionary as though it was in that module:It doesn't happen for objects that are simply missing from
_lazy_imports_obj
because the name is not found in the_lazy_imports_obj
dictionary, and it doesn't happen for objects in the current module that are present in_lazy_imports_obj
, because these objects are found in the usual Python way and never make it to the custom__getattr__
. It doesn't happen for objects from other modules because it's not recursive in this case - instead ofopenff.toolkit.__getattr__
callinggetattr
callingopenff.toolkit.__getattr__
,getattr
callssome_other_module.__getattr__
.This error shouldn't happen for two reasons: there's no reason to include an object from the current module in
_lazy_imports_obj
, and there's no reason to include an object that doesn't exist. For it to happen, we'd have to make a release that tried to lazy load an object that doesn't exist from the one module that we know is already loaded, and users would have to try to load that exact name (which, again, doesn't exist). If this unusual circumstance happened with the original code, it would fail with the usual Python message about not being able to import something. With the new code in this PR or the existing code, it's a recursion error instead, but it still fails. Since the worst case scenario is just a slightly different error message and it requires a coordinated mistake between users and us, I think this is an acceptable risk.