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

New check: import-outside-toplevel (close #3067) #3079

Merged
merged 2 commits into from
Sep 17, 2019

Conversation

nickdrozd
Copy link
Contributor

Steps

  • [x ] Add yourself to CONTRIBUTORS if you are a new contributor.
  • [ x] Add a ChangeLog entry describing what your PR does.
  • [ x] If it's a new feature or an important bug fix, add a What's New entry in doc/whatsnew/<current release.rst>.
  • [ x] Write a good description on what the PR does.

Description

This check warns when modules are imported from places other than a
module toplevel, e.g. inside a function or a class.

Type of Changes

Type
✨ New feature

@coveralls
Copy link

Coverage Status

Coverage increased (+0.003%) to 90.031% when pulling c9b2d21 on nickdrozd:import-outside-toplevel into 6b3afd4 on PyCQA:master.

@coveralls
Copy link

coveralls commented Aug 27, 2019

Coverage Status

Coverage increased (+0.02%) to 90.219% when pulling 1ed5801 on nickdrozd:import-outside-toplevel into a7f2365 on PyCQA:master.

Copy link
Contributor

@AWhetter AWhetter left a comment

Choose a reason for hiding this comment

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

Looks good to me!

pylint/checkers/imports.py Outdated Show resolved Hide resolved
@nickdrozd nickdrozd force-pushed the import-outside-toplevel branch from c9b2d21 to 514a5ed Compare September 8, 2019 18:45
@nickdrozd
Copy link
Contributor Author

There's a false negative for this check:

def k():
    for _ in range(5):
        import sys  # should trigger, but doesn't

Is there a good way to detect this situation?

@nickdrozd nickdrozd force-pushed the import-outside-toplevel branch from 514a5ed to 147f4f7 Compare September 8, 2019 18:51
Copy link
Contributor

@AWhetter AWhetter left a comment

Choose a reason for hiding this comment

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

Having looked at this again, please could we fix the false negative before merging?

@@ -969,6 +976,14 @@ def _wildcard_import_is_allowed(self, imported_module):
and "__all__" in imported_module.locals
)

def _check_toplevel(self, node):
if isinstance(node.parent, (astroid.FunctionDef, astroid.ClassDef)):
Copy link
Contributor

Choose a reason for hiding this comment

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

If you use node.scope() here, it will traverse up the syntax tree until it finds the node that defines the scope (https://astroid.readthedocs.io/en/latest/api/base_nodes.html#astroid.node_classes.NodeNG.scope). Anything other than a Module would mean that the message should be raised. This should even account for cases like the following where we wouldn't want to raise a message:

if some_condition:
    import this
else:
    import that

and cases like the following where we would want to raise the message:

def func():
    if some_condition:
        import this
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I knew there was something like that, but I couldn't remember the name. This should be fixed now.

Copy link
Contributor

@PCManticore PCManticore left a comment

Choose a reason for hiding this comment

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

Overall this looks solid, thanks @nickdrozd !

One minor suggestion before getting this in. Sometimes some imports need to be scoped to a function scope in case they are related to a circular import between two components. In that case, we'd need a csv option for allowing certain imports to be exempted from this check when encountered. This would avoid a proliferation of # disable: import-outside-toplevel for such cases.

@nickdrozd
Copy link
Contributor Author

************* Module pylint.reporters.text
pylint/lib/python3.6/site-packages/pylint/reporters/text.py:206:16: C0415: Import outside toplevel (colorama) (import-outside-toplevel)

It seems to work!

@nickdrozd nickdrozd force-pushed the import-outside-toplevel branch from 456cb9e to fdb908f Compare September 11, 2019 14:50
@nickdrozd
Copy link
Contributor Author

@PCManticore You mean like a whitelist of modules that can be imported outside the toplevel? I have no idea how to implement that. Are there any similar examples?

@PCManticore
Copy link
Contributor

@nickdrozd Yes, something along those lines.

This would involve:

@nickdrozd
Copy link
Contributor Author

@AWhetter @PCManticore Okay, I think that should do it. I added some unit tests as well.

@nickdrozd nickdrozd force-pushed the import-outside-toplevel branch from a6a6141 to c041cc4 Compare September 15, 2019 01:16
@nickdrozd nickdrozd force-pushed the import-outside-toplevel branch from c041cc4 to 263b8b1 Compare September 15, 2019 01:20
@PCManticore PCManticore merged commit 8bf8fe1 into pylint-dev:master Sep 17, 2019
@PCManticore
Copy link
Contributor

Thank you @nickdrozd !

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.

4 participants