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 support for .pyi
type stubs (including for first-party)
#10759
Add support for .pyi
type stubs (including for first-party)
#10759
Conversation
.pyi
type stubs.pyi
type stubs (including for first-party)
Hey @cristianmatache, if you have the time, it'd be helpful to add tests that Black, Docformatter, and Isort all correctly format First, check out https://www.pantsbuild.org/v2.0/docs/contributor-overview and then https://www.pantsbuild.org/v2.0/docs/contributor-setup for how to get set up. Warning that compiling Rust is slow, but cached. Then, add a new test to each of pants/src/python/pants/backend/python/lint/isort/rules_integration_test.py Lines 102 to 121 in 7ac6480
I think everything will be the same, except you'll set up a different file that ends in Doesn't need to be super exhaustive - testing one file should be adequate, and avoids slowing down CI too much. Although, maybe we should do a more robust test that includes a Feel free to push directly to this branch! (Lmk if I need to give you permissions.) |
DONE: * Loosen target types * Teach Pytest to skip type stubs * Update dep inference TODO: * Add tests to MyPy [ci skip-rust] [ci skip-build-wheels]
e2b9dcc
to
c36028e
Compare
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
c36028e
to
1e6467b
Compare
.pyi
type stubs (including for first-party).pyi
type stubs (including for first-party)
[ci skip-rust] [ci skip-build-wheels]
1e6467b
to
a5fb27a
Compare
@@ -129,20 +129,20 @@ def test_generate_chroot(chroot_rule_runner: RuleRunner) -> None: | |||
"src/python/foo/bar/baz", | |||
textwrap.dedent( | |||
""" | |||
python_distribution( |
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 only fixes indentation.
"test_*.py", | ||
"*_test.py", | ||
"tests.py", | ||
"conftest.py", | ||
"test_*.pyi", | ||
"*_test.pyi", | ||
"tests.pyi", |
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 don't love this. But I think it's important to include .pyi
in python_tests
too. If a user for some reason writes type stubs for their tests, then we don't want it to show up in, say, setup-py
. That is, we don't want the test type stubs defaulting to a python_library
.
(Even though it's really weird to write a type stub file for your test)
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 don't love this.
Is it just the number of lines to express it? Or you don't like how much it globs?
I might recommend putting the .pyi
globs right after the .py
globs, I think it might make it easier to read. But it's fine either way.
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 don't like how many globs we now have. But I do think that this is the correct behavior. Just clunky.
This still needs tests for the formatters, Flake8, and Pylint, which Cristian and I working on. But otherwise, this is good to go. |
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
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.
Thank you Cristian!
src/python/pants/backend/python/lint/black/rules_integration_test.py
Outdated
Show resolved
Hide resolved
src/python/pants/backend/python/lint/black/rules_integration_test.py
Outdated
Show resolved
Hide resolved
src/python/pants/backend/python/lint/black/rules_integration_test.py
Outdated
Show resolved
Hide resolved
@@ -1,6 +1,6 @@ | |||
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md). |
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.
(Other reviewers: credit to @cristianmatache for writing this file. Thank you!)
@@ -1,6 +1,7 @@ | |||
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md). |
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.
(Ditto on Cristian adding this change)
src/python/pants/backend/python/lint/isort/rules_integration_test.py
Outdated
Show resolved
Hide resolved
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 leaving it as "comment" in case you want to make any of the changes e.g. to add any of the comments I suggested, but to be clear, I think you can safely consider all of these suggestions as mostly optional. I tried to be useful and precise with the text suggestions, but those should also be optional. Once you mark each of them as resolved I will approve.
I wasn't trying to be pedantic here, but I am very unfamiliar with how type stubs work and only a little more familiar with dependency inference. Anything that seems like it should be common enough knowledge, you can feel free to ignore.
src/python/pants/backend/python/dependency_inference/module_mapper.py
Outdated
Show resolved
Hide resolved
def addresses_for_module(self, module: str) -> Tuple[Address, ...]: | ||
targets = self.mapping.get(module) | ||
if targets: | ||
return targets | ||
# If the module is not found, try the parent, if any. This is to accommodate `from` | ||
# imports, where we don't care about the specific symbol, but only the module. For example, | ||
# with `from typing import List`, we only care about `typing`. | ||
# Unlike with third party modules, we do not look past the direct parent. |
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 say "unlike with third party modules", but then provide an example with typing
above -- could we provide a more relevant example? It's also not clear to me why the parent module is only significant for from
imports (wouldn't we need to check the parent for import pants.x.y
too?).
src/python/pants/backend/python/lint/black/rules_integration_test.py
Outdated
Show resolved
Hide resolved
src/python/pants/backend/python/lint/black/rules_integration_test.py
Outdated
Show resolved
Hide resolved
"test_*.py", | ||
"*_test.py", | ||
"tests.py", | ||
"conftest.py", | ||
"test_*.pyi", | ||
"*_test.pyi", | ||
"tests.pyi", |
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 don't love this.
Is it just the number of lines to express it? Or you don't like how much it globs?
I might recommend putting the .pyi
globs right after the .py
globs, I think it might make it easier to read. But it's fine either way.
@@ -119,6 +119,25 @@ def config_path_globs(mypy: MyPy) -> PathGlobs: | |||
) | |||
|
|||
|
|||
def determine_python_files(files: Iterable[str]) -> Tuple[str, ...]: | |||
"""We run over all .py and .pyi files, but .pyi files take precedence. |
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, why is this? Why can we do py_file = f[:-1]
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.
Why can we do py_file = f[:-1] below?
This gets the filename with the i
removed.
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, why is this?
MyPy will error if we say to run over the same module with both its .py and .pyi files, so we
must be careful to only use the .pyi stub.
MyPy only lets you choose one. Type stubs are supposed to take precedence over implementation files.
src/python/pants/backend/python/typecheck/mypy/rules_integration_test.py
Outdated
Show resolved
Hide resolved
…place # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels]
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 great suggestions, Danny!
return FirstPartyModuleToAddressMapping( | ||
FrozenDict( | ||
{ | ||
module: tuple(sorted(addresses)) |
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.
Yes, that's correct. sorted()
returns a list, so we have to convert into a hashable/immutable type.
# requirement. Note that `colors.pyi` is at the top-level of the source root so that it strips | ||
# to the module `colors`. | ||
rule_runner.create_file("source_root1/colors.pyi") | ||
rule_runner.add_to_build_file("source_root1", "python_library()\n") |
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.
Doesn't look like it. It would be necessary if we wanted to call this method again on the same BUILD file though.
@@ -51,6 +51,7 @@ def test_infer_python_imports() -> None: | |||
) | |||
|
|||
rule_runner.create_file("src/python/str_import/subdir/f.py") | |||
rule_runner.create_file("src/python/str_import/subdir/f.pyi") |
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.
Yes, particularly when we have .py
and .pyi
for the same module name. I can add a comment.
src/python/pants/backend/python/lint/black/rules_integration_test.py
Outdated
Show resolved
Hide resolved
"test_*.py", | ||
"*_test.py", | ||
"tests.py", | ||
"conftest.py", | ||
"test_*.pyi", | ||
"*_test.pyi", | ||
"tests.pyi", |
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 don't like how many globs we now have. But I do think that this is the correct behavior. Just clunky.
@@ -119,6 +119,25 @@ def config_path_globs(mypy: MyPy) -> PathGlobs: | |||
) | |||
|
|||
|
|||
def determine_python_files(files: Iterable[str]) -> Tuple[str, ...]: | |||
"""We run over all .py and .pyi files, but .pyi files take precedence. |
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 can we do py_file = f[:-1] below?
This gets the filename with the i
removed.
@@ -119,6 +119,25 @@ def config_path_globs(mypy: MyPy) -> PathGlobs: | |||
) | |||
|
|||
|
|||
def determine_python_files(files: Iterable[str]) -> Tuple[str, ...]: | |||
"""We run over all .py and .pyi files, but .pyi files take precedence. |
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, why is this?
MyPy will error if we say to run over the same module with both its .py and .pyi files, so we
must be careful to only use the .pyi stub.
MyPy only lets you choose one. Type stubs are supposed to take precedence over implementation files.
[ci skip-rust] [ci skip-build-wheels]
[ci skip-rust] [ci skip-build-wheels]
All the tests are now added: this is now ready for final reviews. Thanks! |
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.
Looks great, thanks!
Too much copy pasta [ci skip-rust] [ci skip-build-wheels]
There are two use cases for writing
.pyi
type stubs:This PR adds support for both use cases. We allow Python targets to now include
.pyi
files, and update the default globs forpython_library
andpython_tests
to include them.We also teach dependency inference about
.pyi
files. For third party reqs, we infer a dep on both the req and the.pyi
file. For first party stubs, we infer a dep on both the.py
and.pyi
files.Most of our Python functionality works correctly without changes for
.pyi
files, but we have to teach Pytest to not try running over.pyi
files if they show up inpython_tests
targets, just like we special caseconftest.py
. We also update how we call MyPy; if there is a.pyi
file and.py
file, the.pyi
file takes precedence; otherwise, MyPy would error.[ci skip-rust]
[ci skip-build-wheels]