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

mypy contrib plugin #5172

Merged
merged 1 commit into from
Dec 25, 2017
Merged

mypy contrib plugin #5172

merged 1 commit into from
Dec 25, 2017

Conversation

tdyas
Copy link
Contributor

@tdyas tdyas commented Dec 6, 2017

This plugin runs the mypy static type analyzer.

@tdyas
Copy link
Contributor Author

tdyas commented Dec 6, 2017

This PR will likely fail because mypy requires Python 3.x, even though it can analyze Python 2.7 sources. Namely, the attempt to install Python 3.x if it is in the PATH will fail. The task runs nicely if Python 3.x is available.

Where should we discuss optionally making Python 3.x available from within the Pants venv? I can open a separate issue for that.

@stuhood stuhood requested review from benjyw and kwlzn December 6, 2017 01:25
Copy link
Member

@kwlzn kwlzn left a comment

Choose a reason for hiding this comment

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

awesome! this lgtm w/ a few comments.

a basic integration test would also be good.

'src/python/pants/goal:task_registrar',
],
distribution_name='pantsbuild.pants.contrib.mypy',
description='RPM package builder for pants',
Copy link
Member

Choose a reason for hiding this comment

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

wrong description

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -21,6 +21,9 @@ function activate_venv() {

function create_venv() {
rm -rf "$(venv_dir)"
if which python3; then
python3 -m venv "$(venv_dir)"
fi
Copy link
Member

Choose a reason for hiding this comment

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

intended? probably best to rely on interpreter/compatibility constraints in pants vs constructing the venv with python3 here.

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 thought I had to get python3 into the venv. Plugin works with the python3 I have on my PATH.



class MypyTask(PythonTask):
"""Invoke the mypy static type analzyer for Python."""
Copy link
Member

Choose a reason for hiding this comment

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

analyzer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

return True

def __init__(self, *args, **kwargs):
super(MypyTask, self).__init__(*args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

probably better to just remove this __init__ method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

pex_info = PexInfo.default()
pex_info.entry_point = 'mypy'
chroot = self.cached_chroot(interpreter=py3_interpreter, pex_info=pex_info, targets=[],
extra_requirements=[PythonRequirement('mypy==0.550')])
Copy link
Member

Choose a reason for hiding this comment

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

this probably deserves either an option to override the version (e.g. --mypy-version w default='0.550') or a tool address (e.g. --mypy-tool-address w default='3rdparty/python:mypy').

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@kwlzn
Copy link
Member

kwlzn commented Dec 6, 2017

@tdyas would file an issue for the python3 venv discussion and we can all chime in there.

@tdyas tdyas force-pushed the mypy_contrib_plugin branch from a4f2cd4 to 233cb63 Compare December 6, 2017 19:42
@tdyas
Copy link
Contributor Author

tdyas commented Dec 6, 2017

I'll need to make some adjustments to the plugin. I have to expose the Python environment expected by the source code to mypy via the MYPYPATH environment variable.

if directory:
roots.add(directory)

import pdb ; pdb.set_trace()
Copy link
Contributor

Choose a reason for hiding this comment

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

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Debugging leftover. Pushed too early. Made better progress locally. Will push when the integration test is working.

)
return list(sources)

def _calculate_python_package_roots(self, targets):
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't {target.target_base for target in targets} give you what you want?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I switched to that after getting that from John on Slack general.

@tdyas tdyas force-pushed the mypy_contrib_plugin branch from 233cb63 to 290c181 Compare December 7, 2017 00:32
@tdyas
Copy link
Contributor Author

tdyas commented Dec 7, 2017

Ok I pushed my current version. I added an integration test but it can't really do anything because the CI environment may not provide Python 3.x? How should I go about getting Py3 into the CI environment to ensure a proper integration test?

@jsirois
Copy link
Contributor

jsirois commented Dec 7, 2017

... because the CI environment may not provide Python 3.x?

CI does provide 3.x (https://docs.travis-ci.com/user/reference/trusty/#Python-images), but a guard is good sanity since Pants developers may not have this.

Inspiration for the guard here: https://github.com/jsirois/pants/blob/master/tests/python/pants_test/python/test_interpreter_selection_integration.py#L24

Copy link
Contributor

@benjyw benjyw left a comment

Choose a reason for hiding this comment

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

LGTM! Just one typo.

self.context.log.debug('No Python sources to check.')
return

# Determine interprter used by the sources so we can tell mypy.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/interprter/interpreter/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@tdyas
Copy link
Contributor Author

tdyas commented Dec 7, 2017

I modified the integration test to call self.has_python_version('3'). That succeeds but the integration test still fails with the mypy task failing to find a py3-compatible interpreter. (I have not pushed this change to the PR yet.)

--- a/contrib/mypy/tests/python/pants_test/contrib/mypy/tasks/test_mypy_integration.py
+++ b/contrib/mypy/tests/python/pants_test/contrib/mypy/tasks/test_mypy_integration.py
@@ -11,6 +11,12 @@ from pants_test.pants_run_integration_test import PantsRunIntegrationTest

 class MypyIntegrationTest(PantsRunIntegrationTest):
   def test_mypy(self):
-    with self.pants_results(['mypy', 'contrib/mypy/tests/python/pants_test/contrib/mypy::']) as pants_run:
-      self.assert_failure(pants_run)
-      self.assertTrue('Unable to find a Python 3.x interpreter (required for mypy)' in pants_run.stdout_data)
+    if self.has_python_version('3'):
+      # Python 3.x is available. Test that we see an error in this integration test.
+      with self.pants_results(['mypy', 'contrib/mypy/tests/python/pants_test/contrib/mypy::']) as pants_run:
+        self.assert_success(pants_run)
+    else:
+      # Python 3.x was not found. Test whether mypy task fails for that reason.
+      with self.pants_results(['mypy', 'contrib/mypy/tests/python/pants_test/contrib/mypy::']) as pants_run:
+        self.assert_failure(pants_run)
+        self.assertTrue('Unable to find a Python 3.x interpreter (required for mypy)' in pants_run.stdout_data)

@tdyas tdyas force-pushed the mypy_contrib_plugin branch from 290c181 to f58e75a Compare December 7, 2017 01:08
return True

def find_py3_interpreter(self):
interpreters = self._interpreter_cache.setup(filters=['Python>=3'])
Copy link
Contributor

Choose a reason for hiding this comment

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

s/Python/CPython/ or else s/Python//

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@kwlzn
Copy link
Member

kwlzn commented Dec 7, 2017

@tdyas fwiw, @CMLivingston recently added this utility to pex for materializing version-specific interpreters on demand in tests: pex-tool/pex@df326ff#diff-b619e563eafa44775c3411bd4c79e252R283

and its likely that we'll want to land something similar in pants soon as part of other python3 work - but it might be worth pow-wowing with him on Slack to see if it would make sense to break the landing of that utility into a separate earlier PR.

Copy link
Member

@kwlzn kwlzn left a comment

Choose a reason for hiding this comment

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

lgtm!

# Construct the MYPYPATH from both the source roots and targets' sys.path.
mypy_path = ':'.join([os.path.join(get_buildroot(), root) for root in source_roots])
mypy_path += ':'
mypy_path += pythonpath_for_targets
Copy link
Member

Choose a reason for hiding this comment

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

maybe:

mypy_path = ':'.join((mypy_path, pythonpath_for_targets)

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 also could combine the two lines into mypy_path += ':' + pythonpath_for_targets. Using .join here seems too fancy for plain old string concatenation, unless the style here is to avoid mutability?

Copy link
Contributor

Choose a reason for hiding this comment

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

No strong opinion on join, but it's easy enough to use os.pathsep instead of a raw :.

Copy link
Contributor Author

@tdyas tdyas Dec 11, 2017

Choose a reason for hiding this comment

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

Good idea. Will do that. (os.pathsep)

@tdyas tdyas force-pushed the mypy_contrib_plugin branch 3 times, most recently from f5cee4b to 6f36f34 Compare December 12, 2017 21:13
This plugin runs the `mypy` static type analyzer.
@tdyas tdyas force-pushed the mypy_contrib_plugin branch from 6f36f34 to 620b35e Compare December 19, 2017 21:03
@tdyas
Copy link
Contributor Author

tdyas commented Dec 21, 2017

Anything else remaining for me to do with this PR?

@stuhood stuhood merged commit dec8a9a into pantsbuild:master Dec 25, 2017
return

# Determine interpreter used by the sources so we can tell mypy.
interpreter_for_targets = self._interpreter_cache.select_interpreter_for_targets(self.context.target_roots)
Copy link
Contributor

Choose a reason for hiding this comment

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

@tdyas : I'm deprecating PythonTask, as it's part of the old pipeline (should have noticed you were relying on it in this change, my bad). So quick question - why are you selecting the interpreter only for self.context.target_roots rather than all targets (later you collect source roots for all targets, which I assume means that mypy operates on all of them)? Does this matter, or can I have this select an interpreter for all targets (and then use the already-computed product for that)?

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 chose to use target_roots since I only wanted mypy to operate on the targets passed on the pants command-line. I'm not wedded to that idea though. What is expected of a Pants code linting task?

As for switching to another Python pipeline, just point me at what I should be using.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm still figuring out how this would fit into the new pipeline, since it has some unusual usage patterns (e.g., having to select two interpreters, one for running mypy and one for discovering the python version of the input files).

@benjyw
Copy link
Contributor

benjyw commented Feb 5, 2018

And another question: don't the external requirements need to be on the MYPYPATH for mypy to work properly? E.g., if the code being checked relies on some code from 3rdparty, doesn't mypy need to know that?

And while I have your attention, why do the input source roots need to be on MYPYPATH? The documentation states that "The directories containing the sources given on the command line" are searched for imports anyway, so it seems superfluous to also add them to MYPYPATH?

@benjyw
Copy link
Contributor

benjyw commented Feb 5, 2018

Hmm, https://mypy.readthedocs.io/en/latest/basics.html says

You may be tempted to point MYPYPATH to the standard library or to the site-packages directory where your 3rd party packages are installed. This is almost always a bad idea – you will likely get tons of error messages about code you didn’t write and that mypy can’t analyze all that well yet, and in the worst case scenario mypy may crash due to some construct in a 3rd party package that it didn’t expect.

So what are you supposed to do about 3rdparty code? Either create typeshed stubs, or give up on that information?

@tdyas
Copy link
Contributor Author

tdyas commented Feb 5, 2018

I'm not actually sure what the "right" answer should be for third party code. In an earlier version of the task, I had the task compute the locations within the pex for third party modules and then added those paths to the MYPYPATH. This hit exactly the issue you're quoting where mypy tries to analyze third party code that it really shouldn't be analyzing. One answer is to write typeshed stubs for those projects or find some other way for mypy to find those stubs. Another is to set --follow-imports=skip or --follow-imports=silent to basically have mypy ignore imports for which it can't find source or type stubs. (It basically then treats those modules as type Any which is compatible with everything.)

I'm still trying to understand the recommended way to use mypy. Don't assume I've figured that out yet!

@tdyas
Copy link
Contributor Author

tdyas commented Feb 5, 2018

And another question: don't the external requirements need to be on the MYPYPATH for mypy to work
properly? E.g., if the code being checked relies on some code from 3rdparty, doesn't mypy need to
know that?

This was true in a prior version of the task. See comment above for more info. I basically found it too verbose for issues that the user couldn't fix, so removed the code that put the third party code on the MYPYPATH.

@tdyas
Copy link
Contributor Author

tdyas commented Feb 5, 2018

And while I have your attention, why do the input source roots need to be on MYPYPATH? The documentation states that "The directories containing the sources given on the command line" are searched for imports anyway, so it seems superfluous to also add them to MYPYPATH?

So that mypy can find imports correctly of modules that were not specified on the command-line, but that are imported from code that was part of target_roots. Obviously the need for this changes if all Python code (and not just target_roots) is checked by mypy.

@tdyas
Copy link
Contributor Author

tdyas commented Feb 5, 2018

FYI I just found PEP 561 which proposes how to distribute type information with modules.

@tdyas tdyas deleted the mypy_contrib_plugin branch February 20, 2023 20:35
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.

5 participants