-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Teach stubsabot to be smarter about the required locations of py.typed
files
#11053
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
Teach stubsabot to be smarter about the required locations of py.typed
files
#11053
Conversation
Would this approach work?
|
Yes, that does sound like a promising alternative. I'll give it a try and see how it compares. |
That made it a lot simpler, thanks! |
Have you backtested this on a set of known py.typed packages? It's possible things like shipping an untyped tests/ are not uncommon. (to be clear, I'm fine with some false negatives, but curious if we know what they would look like in practice) |
I'm probably not going to review this. I'm not super familiar with the internal layout of various Python packages or stubsabot. |
I haven't done exhaustive testing (mostly because I became exhausted). There are definitely distributions in typeshed that have some "interesting" things shipped in their sdists: take a look at >>>
with tarfile.open(r"C:\Users\alexw\Downloads\docopt-0.6.2.tar.gz", mode="r:gz") as tf:
... tf.list(verbose=False)
...
docopt-0.6.2/
docopt-0.6.2/docopt.egg-info/
docopt-0.6.2/docopt.egg-info/dependency_links.txt
docopt-0.6.2/docopt.egg-info/PKG-INFO
docopt-0.6.2/docopt.egg-info/SOURCES.txt
docopt-0.6.2/docopt.egg-info/top_level.txt
docopt-0.6.2/docopt.py
docopt-0.6.2/examples/
docopt-0.6.2/examples/arguments_example.py
docopt-0.6.2/examples/calculator_example.py
docopt-0.6.2/examples/counted_example.py
docopt-0.6.2/examples/git/
docopt-0.6.2/examples/git/git.py
docopt-0.6.2/examples/git/git_add.py
docopt-0.6.2/examples/git/git_branch.py
docopt-0.6.2/examples/git/git_checkout.py
docopt-0.6.2/examples/git/git_clone.py
docopt-0.6.2/examples/git/git_commit.py
docopt-0.6.2/examples/git/git_push.py
docopt-0.6.2/examples/git/git_remote.py
docopt-0.6.2/examples/naval_fate.py
docopt-0.6.2/examples/odd_even_example.py
docopt-0.6.2/examples/options_example.py
docopt-0.6.2/examples/options_shortcut_example.py
docopt-0.6.2/examples/quick_example.py
docopt-0.6.2/examples/validation_example.py
docopt-0.6.2/LICENSE-MIT
docopt-0.6.2/MANIFEST.in
docopt-0.6.2/PKG-INFO
docopt-0.6.2/README.rst
docopt-0.6.2/setup.cfg
docopt-0.6.2/setup.py Presumably if |
very reasonable! |
I'll try to do more manual testing of this tomorrow. |
I made this diff locally, committed it, and then ran Diffdiff --git a/stubs/flake8-plugin-utils/METADATA.toml b/stubs/flake8-plugin-utils/METADATA.toml
index 4874bcd4d..36cbf8860 100644
--- a/stubs/flake8-plugin-utils/METADATA.toml
+++ b/stubs/flake8-plugin-utils/METADATA.toml
@@ -1,7 +1,6 @@
version = "1.3.*"
upstream_repository = "https://github.com/afonasev/flake8-plugin-utils"
partial_stub = true
-obsolete_since = "1.3.3" # Released on 2023-06-26
[tool.stubtest]
ignore_missing_stub = true
diff --git a/stubs/pluggy/METADATA.toml b/stubs/pluggy/METADATA.toml
index 69173ea16..29b734085 100644
--- a/stubs/pluggy/METADATA.toml
+++ b/stubs/pluggy/METADATA.toml
@@ -1,3 +1,2 @@
version = "1.2.0"
upstream_repository = "https://github.com/pytest-dev/pluggy"
-obsolete_since = "1.3.0" # Released on 2023-08-26
diff --git a/stubs/stdlib-list/METADATA.toml b/stubs/stdlib-list/METADATA.toml
index 4d6dcf39d..f9f553a2c 100644
--- a/stubs/stdlib-list/METADATA.toml
+++ b/stubs/stdlib-list/METADATA.toml
@@ -1,3 +1,2 @@
version = "0.8.*"
upstream_repository = "https://github.com/pypi/stdlib-list"
-obsolete_since = "0.9.0" # Released on 2023-06-22
diff --git a/stubs/stripe/METADATA.toml b/stubs/stripe/METADATA.toml
index 59f103da7..b0a9497ad 100644
--- a/stubs/stripe/METADATA.toml
+++ b/stubs/stripe/METADATA.toml
@@ -1,7 +1,6 @@
version = "3.5.*"
upstream_repository = "https://github.com/stripe/stripe-python"
partial_stub = true
-obsolete_since = "7.1.0" # Released on 2023-10-27
[tool.stubtest]
ignore_missing_stub = true
diff --git a/stubs/tree-sitter/METADATA.toml b/stubs/tree-sitter/METADATA.toml
index 30d271954..350844ff0 100644
--- a/stubs/tree-sitter/METADATA.toml
+++ b/stubs/tree-sitter/METADATA.toml
@@ -1,3 +1,2 @@
version = "0.20.1"
upstream_repository = "https://github.com/tree-sitter/py-tree-sitter"
-obsolete_since = "0.20.3" # Released on 2023-11-13
diff --git a/stubs/tzlocal/METADATA.toml b/stubs/tzlocal/METADATA.toml
index f3e200e94..a5ef047ab 100644
--- a/stubs/tzlocal/METADATA.toml
+++ b/stubs/tzlocal/METADATA.toml
@@ -1,4 +1,3 @@
version = "5.1"
upstream_repository = "https://github.com/regebro/tzlocal"
requires = ["types-pytz"]
-obsolete_since = "5.2" # Released on 2023-10-22 This was the output (all looks good as far as I can see):
|
I wrote this script (and saved it inside the Scriptimport asyncio
import aiohttp
from stubsabot import fetch_pypi_info, release_contains_py_typed
py_typed_packages = [
# "mypy",
"Flask-SQLAlchemy",
"SQLAlchemy",
"typeshed-stats",
"urllib3",
"annoy",
"freezegun",
"certifi",
"cryptography",
"selenium",
"emoji",
"dj-database-url",
# "pyvmomi",
"invoke",
"babel",
"chardet",
"prettytable",
"termcolor",
"xxhash",
"orjson",
"attrs",
]
async def check_package_detected_as_py_typed(distribution: str, session: aiohttp.ClientSession) -> None:
pypi_info = await fetch_pypi_info(distribution, session=session)
latest_release = next(release for release in pypi_info.releases_in_descending_order() if not release.version.is_prerelease)
assert await release_contains_py_typed(latest_release, session=session), distribution
async def check_all_packages() -> None:
async with aiohttp.ClientSession() as session:
tasks = (check_package_detected_as_py_typed(package, session) for package in py_typed_packages)
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(check_all_packages()) The mypy assertion fails because the The pyvmomi assertion fails because the |
So, answering @hauntsaninja's question from #11053 (comment) -- overall, I think this improves the balance between false positives and false negatives. It gets rid of the clear false positive from #11049, and I've only found one possible false negative ( |
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 looking into it!
An attempt to fix the bug that resulted in #11049.
The approach is this:If there are no directories at the top level of a wheel or sdist (except for the.dist-info
directory), none of the packages for this distribution can bepy.typed
Else, return True if all top-level directories (except for.dist-info
) are marked aspy.typed
:for each directory, if it contains at least one file, consider it marked aspy.typed
if it has apy.typed
file at the top-levelElse, if it only contains subdirectories, consider it marked aspy.typed
if all subdirectories are marked aspy.typed
(applying the same logic recursively).Edit: all the above is out of date. The PR now uses the better approach suggested by Akuli in #11053 (comment).
I've tested this a fair bit locally, and I think it does the right thing, but would appreciate careful eyes on this and additional testing