Skip to content

Commit

Permalink
Add new command to suggest stub packages for mypy.
Browse files Browse the repository at this point in the history
  • Loading branch information
domdfcoding committed Feb 11, 2021
1 parent 111e298 commit b92e4b5
Show file tree
Hide file tree
Showing 21 changed files with 592 additions and 28 deletions.
2 changes: 2 additions & 0 deletions formate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ known_third_party = [
"apeye",
"appdirs",
"attrs",
"betamax",
"bump2version",
"check_wheel_contents",
"click",
Expand Down Expand Up @@ -73,6 +74,7 @@ known_third_party = [
"sdjson",
"shippinglabel",
"southwark",
"tabulate",
"toml",
"tomlkit",
"tox",
Expand Down
115 changes: 114 additions & 1 deletion repo_helper/cli/commands/suggest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
# 3rd party
import click
from consolekit import CONTEXT_SETTINGS
from consolekit.options import auto_default_option, flag_option
from consolekit.input import confirm
from consolekit.options import auto_default_option, flag_option, no_pager_option

# this package
from repo_helper.cli import cli_group
Expand Down Expand Up @@ -205,3 +206,115 @@ def detect_languages(directory: pathlib.Path) -> Iterator[str]:
for _ in chain.from_iterable(directory.rglob(pattern) for pattern in patterns):
yield language
break


@no_pager_option()
@flag_option("-t", "--force-tty", help="Force repo-helper to treat stdout as a TTY")
@flag_option("--add/--no-add", help="Add the classifiers to the 'repo_helper.yml' file.", default=None)
@suggest_command()
def stubs(add: Optional[bool] = None, force_tty: bool = False, no_pager: bool = False):
"""
Suggest :pep:`561` type stubs.
"""

# stdlib
import shutil
import sys
from itertools import chain

# 3rd party
import tabulate
from apeye import URL, TrailingRequestsURL
from domdf_python_tools.paths import PathPlus
from domdf_python_tools.stringlist import StringList
from shippinglabel import normalize
from shippinglabel.pypi import PYPI_API
from shippinglabel.requirements import combine_requirements, read_requirements

# this package
from repo_helper.core import RepoHelper

rh = RepoHelper(PathPlus.cwd())
rh.load_settings()
config = rh.templates.globals

requirements_files = [rh.target_repo / "requirements.txt"]

if config["enable_tests"]:
requirements_files.append(rh.target_repo / config["tests_dir"] / "requirements.txt")

requirements_files.extend((rh.target_repo / config["import_name"]).iterchildren("**/requirements.txt"))

all_requirements = set(
chain.from_iterable(read_requirements(file, include_invalid=True)[0] for file in requirements_files)
)

stubs_file = rh.target_repo / "stubs.txt"

if stubs_file.is_file():
existing_stubs, stub_comments, invalid_stubs = read_requirements(stubs_file, include_invalid=True)
else:
existing_stubs = set()
stub_comments, invalid_stubs = [], []

suggestions = {}

for requirement in all_requirements:
if normalize(requirement.name) in {"typing-extensions"}:
continue

types_url = TrailingRequestsURL(PYPI_API / f"types-{requirement.name.lower()}" / "json/")
stubs_url = TrailingRequestsURL(PYPI_API / f"{requirement.name.lower()}-stubs" / "json/")

response = stubs_url.head()
if response.status_code == 404:
# No stubs found for -stubs
response = types_url.head()
if response.status_code == 404:
# No stubs found for types-
continue
else:
response_url = URL(response.url)
suggestions[str(requirement)] = response_url.parent.name
# print(requirement, response.url)
else:
response_url = URL(response.url)
suggestions[str(requirement)] = response_url.parent.name
# print(requirement, response.url)

if not suggestions:
if sys.stdout.isatty() or force_tty:
click.echo("No stubs to suggest.")
sys.exit(1)

if sys.stdout.isatty() or force_tty:

table = StringList([
"Suggestions",
"-----------",
tabulate.tabulate(suggestions.items(), headers=["Requirement", "Stubs"]),
])
table.blankline(ensure_single=True)

if no_pager or len(table) <= shutil.get_terminal_size().lines:
click.echo('\n'.join(table))
else:
click.echo_via_pager('\n'.join(table))

if add is None:
add = confirm("Do you want to add these to the 'stubs.txt' file?")

if add:
new_stubs = sorted(combine_requirements(*existing_stubs, *suggestions.values()))

stubs_file.write_lines([
*stub_comments,
*invalid_stubs,
*map(str, new_stubs),
])

else:
for stub in suggestions.values():
click.echo(stub)

sys.exit(0)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ requests>=2.25.1
ruamel-yaml>=0.16.12
shippinglabel>=0.8.2
southwark>=0.7.1
tabulate>=0.8.7
toml>=0.10.2
typing-extensions>=3.7.4.3
yapf-isort>=0.5.2
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# this package
from repo_helper.configuration import metadata

pytest_plugins = ("domdf_python_tools.testing", "repo_helper.testing", "coincidence")
pytest_plugins = ("coincidence", "repo_helper.testing")


@pytest.fixture()
Expand Down
32 changes: 6 additions & 26 deletions tests/test_cli/test_show.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# stdlib
import sys

# 3rd party
import pytest
from coincidence import check_file_regression, min_version, not_pypy
from coincidence import check_file_regression, min_version, not_pypy, only_version
from consolekit.testing import CliRunner, Result
from domdf_python_tools.paths import PathPlus, in_directory
from pytest_regressions.file_regression import FileRegressionFixture
Expand Down Expand Up @@ -60,28 +57,11 @@ def test_changelog(tmp_repo, file_regression: FileRegressionFixture):
version_specific = pytest.mark.parametrize(
"py_version",
[
pytest.param(
"3.6",
marks=pytest.mark.skipif(
condition=sys.version_info[:2] != (3, 6),
reason="Output differs on Python 3.6",
)
),
pytest.param(
"3.7",
marks=pytest.mark.skipif(
condition=sys.version_info[:2] != (3, 7),
reason="Output differs on Python 3.7",
)
),
pytest.param(
"3.8",
marks=pytest.mark.skipif(
condition=sys.version_info[:2] != (3, 8),
reason="Output differs on Python 3.8",
)
),
pytest.param("3.9+", marks=min_version(3.9, "Output differs on Python 3.9+")),
pytest.param("3.6", marks=only_version(3.6, reason="Output differs on Python 3.6")),
pytest.param("3.7", marks=only_version(3.7, reason="Output differs on Python 3.7")),
pytest.param("3.8", marks=only_version(3.8, reason="Output differs on Python 3.8")),
pytest.param("3.9", marks=only_version(3.9, reason="Output differs on Python 3.9")),
pytest.param("3.10+", marks=min_version("3.10", "Output differs on Python 3.10+")),
]
)

Expand Down
195 changes: 195 additions & 0 deletions tests/test_cli/test_show_/test_requirements_3_10__.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
repo_helper==2020.12.18
├── apeye>=0.4.0
│ ├── appdirs>=1.4.4
│ ├── cachecontrol[filecache]>=0.12.6
│ │ ├── lockfile>=0.9; extra == "filecache"
│ │ ├── msgpack>=0.5.2
│ │ └── requests
│ │ ├── certifi>=2017.4.17
│ │ ├── chardet<5,>=3.0.2
│ │ ├── idna<3,>=2.5
│ │ └── urllib3<1.27,>=1.21.1
│ ├── domdf-python-tools>=0.9.0
│ │ ├── natsort>=7.1.0
│ │ ├── pydash>=4.7.4
│ │ └── typing-extensions>=3.7.4.3
│ ├── lockfile>=0.12.2
│ ├── requests>=2.24.0
│ │ ├── certifi>=2017.4.17
│ │ ├── chardet<5,>=3.0.2
│ │ ├── idna<3,>=2.5
│ │ └── urllib3<1.27,>=1.21.1
│ └── tldextract>=2.2.0
│ ├── filelock>=3.0.8
│ ├── idna
│ ├── requests>=2.1.0
│ │ ├── certifi>=2017.4.17
│ │ ├── chardet<5,>=3.0.2
│ │ ├── idna<3,>=2.5
│ │ └── urllib3<1.27,>=1.21.1
│ └── requests-file>=1.4
│ ├── requests>=1.0.0
│ │ ├── certifi>=2017.4.17
│ │ ├── chardet<5,>=3.0.2
│ │ ├── idna<3,>=2.5
│ │ └── urllib3<1.27,>=1.21.1
│ └── six
├── attrs>=20.2.0
├── click==7.1.2
├── configconfig>=0.5.0
│ ├── domdf-python-tools>=0.10.0
│ │ ├── natsort>=7.1.0
│ │ ├── pydash>=4.7.4
│ │ └── typing-extensions>=3.7.4.3
│ ├── jsonschema>=3.2.0
│ │ ├── attrs>=17.4.0
│ │ ├── pyrsistent>=0.14.0
│ │ ├── setuptools
│ │ └── six>=1.11.0
│ ├── ruamel-yaml>=0.16.12
│ ├── typing-extensions>=3.7.4.3
│ └── typing-inspect>=0.6.0
│ ├── mypy-extensions>=0.3.0
│ └── typing-extensions>=3.7.4
├── consolekit>=1.0.0
│ ├── click>=7.1.2
│ ├── domdf-python-tools>=2.5.1
│ │ ├── natsort>=7.1.0
│ │ ├── pydash>=4.7.4
│ │ └── typing-extensions>=3.7.4.3
│ ├── mistletoe>=0.7.2
│ └── typing-extensions>=3.7.4.3
├── dict2css>=0.2.0
│ ├── css-parser==1.0.6
│ └── domdf-python-tools>=2.2.0
│ ├── natsort>=7.1.0
│ ├── pydash>=4.7.4
│ └── typing-extensions>=3.7.4.3
├── domdf-python-tools>=2.5.0
│ ├── natsort>=7.1.0
│ ├── pydash>=4.7.4
│ └── typing-extensions>=3.7.4.3
├── dulwich>=0.19.16
│ ├── certifi
│ └── urllib3>=1.24.1
├── email-validator==1.1.2
│ ├── dnspython>=1.15.0
│ └── idna>=2.0.0
├── idna==2.10
├── isort>=5.0.0
├── jinja2>=2.11.2
│ └── MarkupSafe>=0.23
├── natsort>=7.1.0
├── packaging>=20.4
│ └── pyparsing>=2.0.2
├── pre-commit>=2.7.1
│ ├── cfgv>=2.0.0
│ ├── identify>=1.0.0
│ ├── nodeenv>=0.11.1
│ ├── pyyaml>=5.1
│ ├── toml
│ └── virtualenv>=20.0.8
│ ├── appdirs<2,>=1.4.3
│ ├── distlib<1,>=0.3.1
│ ├── filelock<4,>=3.0.0
│ └── six<2,>=1.9.0
├── requests>=2.25.1
│ ├── certifi>=2017.4.17
│ ├── chardet<5,>=3.0.2
│ ├── idna<3,>=2.5
│ └── urllib3<1.27,>=1.21.1
├── ruamel-yaml>=0.16.12
├── shippinglabel>=0.8.2
│ ├── apeye>=0.4.0
│ │ ├── appdirs>=1.4.4
│ │ ├── cachecontrol[filecache]>=0.12.6
│ │ │ ├── lockfile>=0.9; extra == "filecache"
│ │ │ ├── msgpack>=0.5.2
│ │ │ └── requests
│ │ │ ├── certifi>=2017.4.17
│ │ │ ├── chardet<5,>=3.0.2
│ │ │ ├── idna<3,>=2.5
│ │ │ └── urllib3<1.27,>=1.21.1
│ │ ├── domdf-python-tools>=0.9.0
│ │ │ ├── natsort>=7.1.0
│ │ │ ├── pydash>=4.7.4
│ │ │ └── typing-extensions>=3.7.4.3
│ │ ├── lockfile>=0.12.2
│ │ ├── requests>=2.24.0
│ │ │ ├── certifi>=2017.4.17
│ │ │ ├── chardet<5,>=3.0.2
│ │ │ ├── idna<3,>=2.5
│ │ │ └── urllib3<1.27,>=1.21.1
│ │ └── tldextract>=2.2.0
│ │ ├── filelock>=3.0.8
│ │ ├── idna
│ │ ├── requests>=2.1.0
│ │ │ ├── certifi>=2017.4.17
│ │ │ ├── chardet<5,>=3.0.2
│ │ │ ├── idna<3,>=2.5
│ │ │ └── urllib3<1.27,>=1.21.1
│ │ └── requests-file>=1.4
│ │ ├── requests>=1.0.0
│ │ │ ├── certifi>=2017.4.17
│ │ │ ├── chardet<5,>=3.0.2
│ │ │ ├── idna<3,>=2.5
│ │ │ └── urllib3<1.27,>=1.21.1
│ │ └── six
│ ├── appdirs>=1.4.4
│ ├── consolekit>=0.1.2
│ │ ├── click>=7.1.2
│ │ ├── domdf-python-tools>=2.5.1
│ │ │ ├── natsort>=7.1.0
│ │ │ ├── pydash>=4.7.4
│ │ │ └── typing-extensions>=3.7.4.3
│ │ ├── mistletoe>=0.7.2
│ │ └── typing-extensions>=3.7.4.3
│ ├── domdf-python-tools>=2.0.1
│ │ ├── natsort>=7.1.0
│ │ ├── pydash>=4.7.4
│ │ └── typing-extensions>=3.7.4.3
│ ├── packaging>=20.7
│ │ └── pyparsing>=2.0.2
│ ├── trove-classifiers>=2020.10.21
│ ├── typing-extensions>=3.7.4.3
│ └── wheel-filename>=1.2.0
├── southwark>=0.7.1
│ ├── click>=7.1.2
│ ├── consolekit>=0.1.2
│ │ ├── click>=7.1.2
│ │ ├── domdf-python-tools>=2.5.1
│ │ │ ├── natsort>=7.1.0
│ │ │ ├── pydash>=4.7.4
│ │ │ └── typing-extensions>=3.7.4.3
│ │ ├── mistletoe>=0.7.2
│ │ └── typing-extensions>=3.7.4.3
│ ├── domdf-python-tools>=2.1.0
│ │ ├── natsort>=7.1.0
│ │ ├── pydash>=4.7.4
│ │ └── typing-extensions>=3.7.4.3
│ ├── dulwich!=0.20.7,!=0.20.8,!=0.20.9,>=0.20.5
│ │ ├── certifi
│ │ └── urllib3>=1.24.1
│ ├── filelock>=3.0.12
│ └── typing-extensions>=3.7.4.3
├── tabulate>=0.8.7
├── toml>=0.10.2
├── typing-extensions>=3.7.4.3
└── yapf-isort>=0.5.2
├── asttokens>=2.0.4
│ └── six
├── click>=7.1.2
├── consolekit>=0.3.0
│ ├── click>=7.1.2
│ ├── domdf-python-tools>=2.5.1
│ │ ├── natsort>=7.1.0
│ │ ├── pydash>=4.7.4
│ │ └── typing-extensions>=3.7.4.3
│ ├── mistletoe>=0.7.2
│ └── typing-extensions>=3.7.4.3
├── domdf-python-tools>=0.10.0
│ ├── natsort>=7.1.0
│ ├── pydash>=4.7.4
│ └── typing-extensions>=3.7.4.3
├── isort<=5.6.4,>=5.5.2
└── yapf==0.30.0
Loading

0 comments on commit b92e4b5

Please sign in to comment.