-
Notifications
You must be signed in to change notification settings - Fork 13
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
Add some Google Styleguide formatting rules #4
Conversation
@@ -6,7 +6,6 @@ python: | |||
- "3.3" | |||
- "3.4" | |||
- "3.5" | |||
- "3.6" |
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.
Sadly pylint doesn't support Python 3.6 yet and our Python 3.6 tests fail.
@@ -27,7 +27,7 @@ autolint: autopep8 lint | |||
run_tests: clean | |||
py.test --durations=10 . | |||
|
|||
test: autopep8 lint run_tests | |||
test: autopep8 run_tests lint |
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.
Now that we lint the linter with its own rules, we should run tests first.
@@ -19,7 +19,7 @@ persistent=yes | |||
|
|||
# List of plugins (as comma separated values of python modules names) to load, | |||
# usually to register additional checkers. | |||
load-plugins= | |||
load-plugins=shopify_python |
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.
Let's lint ourselves.
shopify_python/google_styleguide.py
Outdated
self._import_modules_only(node) | ||
self._import_full_path_only(node) | ||
|
||
def _import_modules_only(self, node): # type: (astroid.ImportFrom) -> None |
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.
Double-underscore class-private methods as per the code style guidelines.
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.
Should these be private (__
) or just protected (_
)?
shopify_python/google_styleguide.py
Outdated
|
||
|
||
class GoogleStyleGuideChecker(checkers.BaseChecker): | ||
__implements__ = (interfaces.IAstroidChecker,) |
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.
I think we should get in the habit of docstrings for at least these classes.
shopify_python/google_styleguide.py
Outdated
name, _ = name | ||
module = '.'.join((node.modname, name)) | ||
try: | ||
importlib.import_module(module) |
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.
This has the potential for side-effects. It would be nice if we could check at least if the rule was disabled globally, and not execute this if required.
shopify_python/google_styleguide.py
Outdated
""" | ||
def can_import(module): | ||
try: | ||
importlib.import_module(module) |
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.
This has the potential for side-effects. It would be nice if we could check at least if the rule was disabled globally, and not execute this if required.
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.
Can you use inspect for this? Squinting at the docs, it doesn't look like it requires an actual 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.
If I could get the path to the source file then I could use inspect
. I can get the path by importing the module and then calling __file__
on it but that defeats the purpose of this. I'm not sure if there's a way to resolve package.module
into path/to/package/module
without actually importing the module.
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.
Just a request to avoid import
ing if possible. And some test cases.
shopify_python/google_styleguide.py
Outdated
name = 'google-styleguide-checker' | ||
|
||
msgs = { | ||
'C9901': ('Imported an object that is not a package or module', |
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 there any official advice on choosing the msg_id
for new rules? Is e.g. the C99XX
range reserved at all, or are we just trying to avoid collisions?
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.
The pylint docs on custom checkers state:
The message id should be a 5-digits number, prefixed with a message category. There are multiple message categories, these being C, W, E, F, R, standing for Convention, Warning, Error, Fatal and Refactoring. The rest of the 5 digits should not conflict with existing checkers and they should be consistent across the checker. For instance, the first two digits should not be different across the checker.
shopify_python/google_styleguide.py
Outdated
""" | ||
def can_import(module): | ||
try: | ||
importlib.import_module(module) |
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.
Can you use inspect for this? Squinting at the docs, it doesn't look like it requires an actual import
.
|
||
CHECKER_CLASS = google_styleguide.GoogleStyleGuideChecker | ||
|
||
def test_importing_function_fails(self): |
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 add a test for importing class
es as well.
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.
Oh, and attributes.
shopify_python/__init__.py
Outdated
from pylint import lint | ||
from shopify_python import google_styleguide | ||
|
||
|
||
__version__ = '0.0.0' |
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.
Not really for this PR, but we should have a process for deciding how and when to bump versions.
shopify_python/google_styleguide.py
Outdated
def __import_modules_only(self, node): # type: (astroid.ImportFrom) -> None | ||
"""Use imports for packages and modules only.""" | ||
if hasattr(self.linter, 'config') and 'import-modules-only' in self.linter.config.disable: | ||
return # Skip if disable to avoid side-effects from importing modules |
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.
Bails before we call importlib.import_module
if this message is disabled.
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.
👍
shopify_python/google_styleguide.py
Outdated
if hasattr(self.linter, 'config') and 'import-modules-only' in self.linter.config.disable: | ||
return # Skip if disable to avoid side-effects from importing modules | ||
|
||
def can_import(module): |
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.
We talked IRL about maybe using importlib's find_spec
to perform this check instead.
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 are older, deprecated functions for doing this for versions < 3.4)
shopify_python/google_styleguide.py
Outdated
Pylint already handles bare-except and broad-except (for Exception). | ||
""" | ||
if node.type.name == 'StandardError': | ||
self.add_message('catch-standard-error', node=node) |
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.
I think it's fine to catch Exception
as long as you re-raise
later on in the handler. You might also want to support the exception chaining syntax.
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.
(this is a weak opinion weakly held, btw)
shopify_python/google_styleguide.py
Outdated
|
||
Pylint already handles bare-except and broad-except (for Exception). | ||
""" | ||
if node.type.name == 'StandardError': |
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.
Don't you mean to check for Exception
here as well? (also see below)
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.
Nay, pylint already checks for Exception
as broad-except
.
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.
sorry, I didn't read carefully enough
shopify_python/google_styleguide.py
Outdated
"Use either raise Exception('message') or raise Exception"), | ||
'C2606': ('Caught StandardError', | ||
'catch-standard-error', | ||
"Don't catch broad exceptions"), |
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.
This line is inconsistent with the specificity of the other two lines above.
shopify_python/google_styleguide.py
Outdated
"The larger the except body, the more likely that an exception will be raised"), | ||
} | ||
|
||
max_try_exc_finally_body_size = 5 |
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.
This seems rather low, especially if it's counting raw lines of code instead of logical statements/expressions.
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 fact, does it make sense to lint on this at all? I can easily see people disabling this check (or calling out to error handling functions) to avoid it if their error handling is sound.
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.
It's technically counting AST objects, not raw lines of code. E.g. an embedded try/except is one object (even if it has a try body with 5 lines and an except body with another 5).
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.
I'm in favour of this linter rule. Its purpose is to educate and inform people of the expectation, and to catch the accidental violations we all make. If people intentionally rewrite their code to get around linter rules instead of adhering to the principle, that's what code review is for. People can also just disable the check with a comment.
I worry less about what the limit is than that there is a limit. 5 seems reasonable to me. I could be OK with <=10. Another option is to specify an overridable default, similar to the "max lines per module" rule.
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.
For now I'll remove these linter rules and address them in a future PR where I'll probably consider tree size as a measure of complexity over AST children (as it was implemented here).
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 people intentionally rewrite their code to get around linter rules instead of adhering to the principle, that's what code review is for.
Code review is also for pointing out complexity problems like the ones this linter rule is meant to address. It's easier to spot too many lines of code in a single try
block than too many lines of code split up into functions.
I'm in favour of the spirit of the rule, but not this particular implementation.
Another option is to specify an overridable default, similar to the "max lines per module" rule.
I'd be in favour of something like this. Number of lines might even make the best metric since they're easy to understand (and therefore adjust) by other teams on other projects.
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.
For now I'll remove these linter rules and address them in a future PR
👍
shopify_python/google_styleguide.py
Outdated
try: | ||
return bool(importlib.util.find_spec(module_name)) | ||
except AttributeError: | ||
return False |
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.
👍
self.add_message('import-full-path', node=node) | ||
|
||
def __avoid_global_variables(self, node): # type: (astroid.Assign) -> None | ||
"""Avoid global variables.""" |
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.
Can you please expand the docstring to explain how this works?
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.
I added a comment below; I assume that implementation details like this should be a comment whereas the purpose of the method should be a docstring.
shopify_python/google_styleguide.py
Outdated
|
||
Pylint already handles bare-except and broad-except (for Exception). | ||
""" | ||
if node.type.name == 'StandardError': |
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.
This fails if no exception type is given, e.g.:
try:
foo()
except:
bar()
node.type is None
in this case
def can_import(module_name): | ||
if sys.version_info >= (3, 4): | ||
try: | ||
return bool(importlib.util.find_spec(module_name)) |
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.
This throws an ImportError
when trying to import pyspark
, which of course needs some path mangling to import dynamically. I think we should just ignore on ImportError
.
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.
boo-urns
d6da79b
to
f84246f
Compare
f84246f
to
541710e
Compare
LGTM after addressing @JasonMWhite's |
Let's try to adhere to the Google Python Style Guide using pylint. This PR adds rules for: