From 3565f701a377115b1fc50fa398a44eaa921f09e8 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 13:42:50 -0400 Subject: [PATCH 01/19] Apply monkeytype to twine.repository --- twine/repository.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/twine/repository.py b/twine/repository.py index ef13e3f9..b9120130 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -12,20 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. import sys +from typing import IO from typing import Dict from typing import List from typing import Optional +from typing import Sequence +from typing import Set from typing import Tuple +from typing import Union import requests import requests_toolbelt import tqdm import urllib3 +from pretend import stub from requests import adapters +from requests.adapters import HTTPAdapter +from requests.models import Response from requests_toolbelt.utils import user_agent import twine from twine import package as package_file +from twine.package import PackageFile KEYWORDS_TO_NOT_FLATTEN = {"gpg_signature", "content"} @@ -38,7 +46,7 @@ class ProgressBar(tqdm.tqdm): - def update_to(self, n): + def update_to(self, n: int) -> None: """Update the bar in the way compatible with requests-toolbelt. This is identical to tqdm.update, except ``n`` will be the current @@ -232,7 +240,7 @@ def package_is_uploaded( return False - def release_urls(self, packages): + def release_urls(self, packages: List[stub]) -> Set[str]: if self.url.startswith(WAREHOUSE): url = WAREHOUSE_WEB elif self.url.startswith(TEST_WAREHOUSE): From 94603fd5bed57aebff2a257c3750eac39f0e3ea9 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 13:48:41 -0400 Subject: [PATCH 02/19] Clean up after monkeytype --- twine/repository.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/twine/repository.py b/twine/repository.py index b9120130..cfd8f355 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -12,28 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. import sys -from typing import IO from typing import Dict from typing import List from typing import Optional -from typing import Sequence from typing import Set from typing import Tuple -from typing import Union import requests import requests_toolbelt import tqdm import urllib3 -from pretend import stub from requests import adapters -from requests.adapters import HTTPAdapter -from requests.models import Response from requests_toolbelt.utils import user_agent import twine from twine import package as package_file -from twine.package import PackageFile KEYWORDS_TO_NOT_FLATTEN = {"gpg_signature", "content"} @@ -240,7 +233,7 @@ def package_is_uploaded( return False - def release_urls(self, packages: List[stub]) -> Set[str]: + def release_urls(self, packages: List[package_file.PackageFile]) -> Set[str]: if self.url.startswith(WAREHOUSE): url = WAREHOUSE_WEB elif self.url.startswith(TEST_WAREHOUSE): From 77f748eb9e5d6cd06a28208958ee208d5d520d10 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 13:58:09 -0400 Subject: [PATCH 03/19] Apply monkeytype to twine.utils --- twine/utils.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/twine/utils.py b/twine/utils.py index 70cd0ae5..f4a23082 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -17,14 +17,19 @@ import functools import os import os.path +from argparse import ArgumentParser +from argparse import Namespace +from typing import Any from typing import Callable from typing import DefaultDict from typing import Dict +from typing import List from typing import Optional from urllib.parse import urlparse from urllib.parse import urlunparse import requests +from requests.models import Response from twine import exceptions @@ -241,11 +246,17 @@ def __init__(self, env: str, **kwargs) -> None: self.env = env super().__init__(default=default, nargs=0, **kwargs) - def __call__(self, parser, namespace, values, option_string=None): + def __call__( + self, + parser: ArgumentParser, + namespace: Namespace, + values: List[Any], + option_string: Optional[str] = None, + ) -> None: setattr(namespace, self.dest, True) @staticmethod - def bool_from_env(val): + def bool_from_env(val: Optional[str]) -> Optional[bool]: """ Allow '0' and 'false' and 'no' to be False """ From 32d9a1a8a54410d2f2fcd3ece8c46aaec458b982 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:05:07 -0400 Subject: [PATCH 04/19] Clean up after monkeytype --- twine/utils.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/twine/utils.py b/twine/utils.py index f4a23082..f05da9a5 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -17,19 +17,17 @@ import functools import os import os.path -from argparse import ArgumentParser -from argparse import Namespace from typing import Any from typing import Callable from typing import DefaultDict from typing import Dict -from typing import List from typing import Optional +from typing import Union +from typing import Sequence from urllib.parse import urlparse from urllib.parse import urlunparse import requests -from requests.models import Response from twine import exceptions @@ -234,7 +232,13 @@ def __init__( required = False super().__init__(default=default, required=required, **kwargs) - def __call__(self, parser, namespace, values, option_string=None): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], + option_string: Optional[str] = None, + ) -> None: setattr(namespace, self.dest, values) @@ -248,17 +252,17 @@ def __init__(self, env: str, **kwargs) -> None: def __call__( self, - parser: ArgumentParser, - namespace: Namespace, - values: List[Any], + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[Any], None], option_string: Optional[str] = None, ) -> None: setattr(namespace, self.dest, True) @staticmethod - def bool_from_env(val: Optional[str]) -> Optional[bool]: + def bool_from_env(val: Optional[str]) -> bool: """ Allow '0' and 'false' and 'no' to be False """ falsey = {"0", "false", "no"} - return val and val.lower() not in falsey + return bool(val and val.lower() not in falsey) From 1e8db135a9a633735fca61392ef5595aa787cc68 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:12:24 -0400 Subject: [PATCH 05/19] Apply monkeytype to twine.package --- twine/package.py | 6 +++++- twine/utils.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/twine/package.py b/twine/package.py index 7d9fa388..4bcd3f57 100644 --- a/twine/package.py +++ b/twine/package.py @@ -25,6 +25,7 @@ import pkg_resources import pkginfo +from pkginfo.distribution import Distribution from twine import exceptions from twine import wheel @@ -177,7 +178,10 @@ def sign(self, sign_with: str, identity: Optional[str]): self.add_gpg_signature(self.signed_filename, self.signed_basefilename) @classmethod - def run_gpg(cls, gpg_args): + def run_gpg( + cls, + gpg_args: Union[Tuple[str, str, str, str, str, str], Tuple[str, str, str, str]], + ) -> None: try: subprocess.check_call(gpg_args) return diff --git a/twine/utils.py b/twine/utils.py index f05da9a5..4d992f5b 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -22,8 +22,8 @@ from typing import DefaultDict from typing import Dict from typing import Optional -from typing import Union from typing import Sequence +from typing import Union from urllib.parse import urlparse from urllib.parse import urlunparse From 8c5907b821e26f08d27cec10ded2e5a8528f9e05 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:15:21 -0400 Subject: [PATCH 06/19] Clean up after monkeytype --- twine/package.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/twine/package.py b/twine/package.py index 4bcd3f57..8c94fbbc 100644 --- a/twine/package.py +++ b/twine/package.py @@ -25,7 +25,6 @@ import pkg_resources import pkginfo -from pkginfo.distribution import Distribution from twine import exceptions from twine import wheel @@ -178,10 +177,7 @@ def sign(self, sign_with: str, identity: Optional[str]): self.add_gpg_signature(self.signed_filename, self.signed_basefilename) @classmethod - def run_gpg( - cls, - gpg_args: Union[Tuple[str, str, str, str, str, str], Tuple[str, str, str, str]], - ) -> None: + def run_gpg(cls, gpg_args: Tuple[str, ...]) -> None: try: subprocess.check_call(gpg_args) return From 25d4d42b629a8268ea76dd23ffc311b26bca085c Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:20:10 -0400 Subject: [PATCH 07/19] Apply monkeytype to twine.auth --- twine/auth.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/twine/auth.py b/twine/auth.py index 26ca0c70..67fd59e4 100644 --- a/twine/auth.py +++ b/twine/auth.py @@ -2,7 +2,9 @@ import getpass import warnings from typing import Callable +from typing import Dict from typing import Optional +from typing import Type import keyring @@ -11,18 +13,18 @@ class CredentialInput: - def __init__(self, username: str = None, password: str = None): + def __init__(self, username: str = None, password: str = None) -> None: self.username = username self.password = password class Resolver: - def __init__(self, config: utils.RepositoryConfig, input: CredentialInput): + def __init__(self, config: utils.RepositoryConfig, input: CredentialInput) -> None: self.config = config self.input = input @classmethod - def choose(cls, interactive): + def choose(cls, interactive: bool) -> Type[Resolver]: return cls if interactive else Private @property # type: ignore # https://github.com/python/mypy/issues/1362 From d786e29cd54cdadc9ac766452b1a0b5ce49e6560 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:22:00 -0400 Subject: [PATCH 08/19] Clean up after monkeytype --- twine/auth.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/twine/auth.py b/twine/auth.py index 67fd59e4..dd94d809 100644 --- a/twine/auth.py +++ b/twine/auth.py @@ -2,7 +2,6 @@ import getpass import warnings from typing import Callable -from typing import Dict from typing import Optional from typing import Type @@ -24,7 +23,7 @@ def __init__(self, config: utils.RepositoryConfig, input: CredentialInput) -> No self.input = input @classmethod - def choose(cls, interactive: bool) -> Type[Resolver]: + def choose(cls, interactive: bool) -> Type["Resolver"]: return cls if interactive else Private @property # type: ignore # https://github.com/python/mypy/issues/1362 From 9ab7d6f6a389d1766b0d61a1acb591d525636cb3 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:36:18 -0400 Subject: [PATCH 09/19] Add annotations to twine.settings --- twine/settings.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/twine/settings.py b/twine/settings.py index 33376bfc..76e5cf09 100644 --- a/twine/settings.py +++ b/twine/settings.py @@ -135,12 +135,17 @@ def __init__( ) @property - def username(self): - return self.auth.username + def username(self) -> Optional[str]: + # Workaround for https://github.com/python/mypy/issues/5858 + return cast(Optional[str], self.auth.username) @property - def password(self): - return None if self.client_cert else self.auth.password + def password(self) -> Optional[str]: + if self.client_cert: + return None + + # Workaround for https://github.com/python/mypy/issues/5858 + return cast(Optional[str], self.auth.password) @staticmethod def register_argparse_arguments(parser: argparse.ArgumentParser) -> None: From bec1ed8a60eef2e60424dea363a64188092b8e11 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:53:26 -0400 Subject: [PATCH 10/19] Add monkeytype to tox --- tox.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tox.ini b/tox.ini index a9b7e83b..8c42b00f 100644 --- a/tox.ini +++ b/tox.ini @@ -51,6 +51,14 @@ commands = # TODO: Consider checking the tests after the source is fully typed mypy twine +[testenv:monkeytype] +deps = + {[testenv]deps} + {[testenv:typing]deps} + monkeytype +commands = + monkeytype {posargs:run -m pytest} + [testenv:lint] deps = {[testenv:code-style]deps} From f9c395e1f2edd0628fbfa1eb811be1b3cf89b893 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:53:37 -0400 Subject: [PATCH 11/19] Apply monkeytype to twine.cli --- twine/cli.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/twine/cli.py b/twine/cli.py index e1bf11e3..dc39b943 100644 --- a/twine/cli.py +++ b/twine/cli.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. import argparse +from typing import Dict +from typing import List +from typing import Tuple import pkg_resources import pkginfo @@ -19,17 +22,20 @@ import requests_toolbelt import setuptools import tqdm +from pkg_resources import EntryPoint import twine from twine import _installed -def _registered_commands(group="twine.registered_commands"): +def _registered_commands( + group: str = "twine.registered_commands", +) -> Dict[str, EntryPoint]: registered_commands = pkg_resources.iter_entry_points(group=group) return {c.name: c for c in registered_commands} -def list_dependencies_and_versions(): +def list_dependencies_and_versions() -> List[Tuple[str, str]]: return [ ("pkginfo", _installed.Installed(pkginfo).version), ("requests", requests.__version__), @@ -39,13 +45,13 @@ def list_dependencies_and_versions(): ] -def dep_versions(): +def dep_versions() -> str: return ", ".join( "{}: {}".format(*dependency) for dependency in list_dependencies_and_versions() ) -def dispatch(argv): +def dispatch(argv: List[str]) -> None: registered_commands = _registered_commands() parser = argparse.ArgumentParser(prog="twine") parser.add_argument( From 3ecec4191f55f54c175a16d0b236036a1c2921a4 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 14:57:17 -0400 Subject: [PATCH 12/19] Clean up after monkeytype --- twine/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/twine/cli.py b/twine/cli.py index dc39b943..1c38543d 100644 --- a/twine/cli.py +++ b/twine/cli.py @@ -22,7 +22,6 @@ import requests_toolbelt import setuptools import tqdm -from pkg_resources import EntryPoint import twine from twine import _installed @@ -30,7 +29,7 @@ def _registered_commands( group: str = "twine.registered_commands", -) -> Dict[str, EntryPoint]: +) -> Dict[str, pkg_resources.EntryPoint]: registered_commands = pkg_resources.iter_entry_points(group=group) return {c.name: c for c in registered_commands} From c9390041731f930127b3dc239ee6062d8014ffec Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 16:34:20 -0400 Subject: [PATCH 13/19] Add TODOs to mypy config --- mypy.ini | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/mypy.ini b/mypy.ini index c165eb23..5ffa6804 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,13 +1,31 @@ [mypy] -; TODO: check_untyped_defs = True ; TODO: Make this more granular; docs recommend it as a "last resort" -; https://mypy.readthedocs.io/en/latest/existing_code.html#start-small +; https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-type-hints-for-third-party-library ignore_missing_imports = True -; NOTE: Docs recommend `normal` or `silent` for an existing codebase -; https://mypy.readthedocs.io/en/latest/running_mypy.html#following-imports -; follow_imports = skip + +warn_redundant_casts = True +warn_unused_configs = True +warn_unused_ignores = True + +; Reporting show_traceback = True html_report = mypy linecount_report = mypy lineprecision_report = mypy txt_report = mypy + +; TODO: Adopt --strict settings, iterating towards something like: +; https://github.com/pypa/packaging/blob/master/setup.cfg +; Starting with modules that have annotations applied via MonkeyType +[mypy-twine.auth,twine.cli,twine.package,twine.repository,twine.utils] +; disallow_any_generics = True +; disallow_subclassing_any = True +; disallow_untyped_calls = True +; disallow_untyped_defs = True +; disallow_incomplete_defs = True +; check_untyped_defs = True +; disallow_untyped_decorators = True +; no_implicit_optional = True +; warn_return_any = True +; no_implicit_reexport = True +; strict_equality = True From 40acce8103343c34f1848c09223c899207dc79aa Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 16:58:46 -0400 Subject: [PATCH 14/19] Enable strict settings that already pass --- mypy.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy.ini b/mypy.ini index 5ffa6804..9deca0ac 100644 --- a/mypy.ini +++ b/mypy.ini @@ -20,12 +20,12 @@ txt_report = mypy [mypy-twine.auth,twine.cli,twine.package,twine.repository,twine.utils] ; disallow_any_generics = True ; disallow_subclassing_any = True -; disallow_untyped_calls = True +disallow_untyped_calls = True ; disallow_untyped_defs = True ; disallow_incomplete_defs = True -; check_untyped_defs = True -; disallow_untyped_decorators = True -; no_implicit_optional = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True ; warn_return_any = True -; no_implicit_reexport = True -; strict_equality = True +no_implicit_reexport = True +strict_equality = True From 444c9c7451360ca79ee1a8ba25ef2c3d9b3f5cc0 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 17:04:49 -0400 Subject: [PATCH 15/19] Fix incomplete defs --- mypy.ini | 2 +- twine/package.py | 6 ++++-- twine/repository.py | 2 +- twine/utils.py | 8 ++++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/mypy.ini b/mypy.ini index 9deca0ac..72f9a05d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -22,7 +22,7 @@ txt_report = mypy ; disallow_subclassing_any = True disallow_untyped_calls = True ; disallow_untyped_defs = True -; disallow_incomplete_defs = True +disallow_incomplete_defs = True check_untyped_defs = True disallow_untyped_decorators = True no_implicit_optional = True diff --git a/twine/package.py b/twine/package.py index 8c94fbbc..9a8ec93e 100644 --- a/twine/package.py +++ b/twine/package.py @@ -159,14 +159,16 @@ def metadata_dictionary(self) -> Dict[str, MetadataValue]: return data - def add_gpg_signature(self, signature_filepath: str, signature_filename: str): + def add_gpg_signature( + self, signature_filepath: str, signature_filename: str + ) -> None: if self.gpg_signature is not None: raise exceptions.InvalidDistribution("GPG Signature can only be added once") with open(signature_filepath, "rb") as gpg: self.gpg_signature = (signature_filename, gpg.read()) - def sign(self, sign_with: str, identity: Optional[str]): + def sign(self, sign_with: str, identity: Optional[str]) -> None: print(f"Signing {self.basefilename}") gpg_args: Tuple[str, ...] = (sign_with, "--detach-sign") if identity: diff --git a/twine/repository.py b/twine/repository.py index cfd8f355..1f028cb8 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -246,7 +246,7 @@ def release_urls(self, packages: List[package_file.PackageFile]) -> Set[str]: for package in packages } - def verify_package_integrity(self, package: package_file.PackageFile): + def verify_package_integrity(self, package: package_file.PackageFile) -> None: # TODO(sigmavirus24): Add a way for users to download the package and # check it's hash against what it has locally. pass diff --git a/twine/utils.py b/twine/utils.py index 4d992f5b..080e8ffc 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -224,7 +224,11 @@ class EnvironmentDefault(argparse.Action): """Get values from environment variable.""" def __init__( - self, env: str, required: bool = True, default: Optional[str] = None, **kwargs + self, + env: str, + required: bool = True, + default: Optional[str] = None, + **kwargs: Any, ) -> None: default = os.environ.get(env, default) self.env = env @@ -245,7 +249,7 @@ def __call__( class EnvironmentFlag(argparse.Action): """Set boolean flag from environment variable.""" - def __init__(self, env: str, **kwargs) -> None: + def __init__(self, env: str, **kwargs: Any) -> None: default = self.bool_from_env(os.environ.get(env)) self.env = env super().__init__(default=default, nargs=0, **kwargs) From 157d93a0e2e71fd95e692e4ff807aa99980da0e8 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 17:06:23 -0400 Subject: [PATCH 16/19] Fix untyped defs --- mypy.ini | 2 +- twine/repository.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index 72f9a05d..2200f49f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -21,7 +21,7 @@ txt_report = mypy ; disallow_any_generics = True ; disallow_subclassing_any = True disallow_untyped_calls = True -; disallow_untyped_defs = True +disallow_untyped_defs = True disallow_incomplete_defs = True check_untyped_defs = True disallow_untyped_decorators = True diff --git a/twine/repository.py b/twine/repository.py index 1f028cb8..6dc234d8 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -94,7 +94,7 @@ def _make_user_agent_string() -> str: .build() ) - def close(self): + def close(self) -> None: self.session.close() @staticmethod @@ -118,7 +118,7 @@ def set_client_certificate(self, clientcert: Optional[str]) -> None: if clientcert: self.session.cert = clientcert - def register(self, package): + def register(self, package: package_file.PackageFile) -> requests.Response: data = package.metadata_dictionary() data.update({":action": "submit", "protocol_version": "1"}) From 1d3ab55ac130683bfd2527c775696399ee7406cc Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 17:47:54 -0400 Subject: [PATCH 17/19] Fix any generics --- mypy.ini | 2 +- twine/auth.py | 4 ++-- twine/repository.py | 4 +++- twine/utils.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy.ini b/mypy.ini index 2200f49f..fb2dfd14 100644 --- a/mypy.ini +++ b/mypy.ini @@ -18,7 +18,7 @@ txt_report = mypy ; https://github.com/pypa/packaging/blob/master/setup.cfg ; Starting with modules that have annotations applied via MonkeyType [mypy-twine.auth,twine.cli,twine.package,twine.repository,twine.utils] -; disallow_any_generics = True +disallow_any_generics = True ; disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_defs = True diff --git a/twine/auth.py b/twine/auth.py index dd94d809..3854ddc2 100644 --- a/twine/auth.py +++ b/twine/auth.py @@ -77,10 +77,10 @@ def password_from_keyring_or_prompt(self) -> str: "password", getpass.getpass ) - def prompt(self, what: str, how: Callable) -> str: + def prompt(self, what: str, how: Callable[..., str]) -> str: return how(f"Enter your {what}: ") class Private(Resolver): - def prompt(self, what: str, how: Optional[Callable] = None) -> str: + def prompt(self, what: str, how: Optional[Callable[..., str]] = None) -> str: raise exceptions.NonInteractive(f"Credential not found for {what}.") diff --git a/twine/repository.py b/twine/repository.py index 6dc234d8..b309388f 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import sys +from typing import Any from typing import Dict from typing import List from typing import Optional @@ -69,7 +70,8 @@ def __init__( for scheme in ("http://", "https://"): self.session.mount(scheme, self._make_adapter_with_retries()) - self._releases_json_data: Dict[str, Dict] = {} + # Working around https://github.com/python/typing/issues/182 + self._releases_json_data: Dict[str, Dict[str, Any]] = {} self.disable_progress_bar = disable_progress_bar @staticmethod diff --git a/twine/utils.py b/twine/utils.py index 080e8ffc..81b935ed 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -184,7 +184,7 @@ def get_userpass_value( cli_value: Optional[str], config: RepositoryConfig, key: str, - prompt_strategy: Optional[Callable] = None, + prompt_strategy: Optional[Callable[[], str]] = None, ) -> Optional[str]: """Gets the username / password from config. From 174a3c56b4fdd84202b714061056a05649b34855 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 17:58:34 -0400 Subject: [PATCH 18/19] Fix return any --- mypy.ini | 2 +- twine/auth.py | 5 +++-- twine/cli.py | 3 ++- twine/repository.py | 7 +++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mypy.ini b/mypy.ini index fb2dfd14..b6f1d2c5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -26,6 +26,6 @@ disallow_incomplete_defs = True check_untyped_defs = True disallow_untyped_decorators = True no_implicit_optional = True -; warn_return_any = True +warn_return_any = True no_implicit_reexport = True strict_equality = True diff --git a/twine/auth.py b/twine/auth.py index 3854ddc2..8ff9db78 100644 --- a/twine/auth.py +++ b/twine/auth.py @@ -4,6 +4,7 @@ from typing import Callable from typing import Optional from typing import Type +from typing import cast import keyring @@ -54,7 +55,7 @@ def get_username_from_keyring(self) -> Optional[str]: try: creds = keyring.get_credential(self.system, None) if creds: - return creds.username + return cast(str, creds.username) except AttributeError: # To support keyring prior to 15.2 pass @@ -64,7 +65,7 @@ def get_username_from_keyring(self) -> Optional[str]: def get_password_from_keyring(self) -> Optional[str]: try: - return keyring.get_password(self.system, self.username) + return cast(str, keyring.get_password(self.system, self.username)) except Exception as exc: warnings.warn(str(exc)) return None diff --git a/twine/cli.py b/twine/cli.py index 1c38543d..b650f461 100644 --- a/twine/cli.py +++ b/twine/cli.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import argparse +from typing import Any from typing import Dict from typing import List from typing import Tuple @@ -50,7 +51,7 @@ def dep_versions() -> str: ) -def dispatch(argv: List[str]) -> None: +def dispatch(argv: List[str]) -> Any: registered_commands = _registered_commands() parser = argparse.ArgumentParser(prog="twine") parser.add_argument( diff --git a/twine/repository.py b/twine/repository.py index b309388f..8a82baa4 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -18,6 +18,7 @@ from typing import Optional from typing import Set from typing import Tuple +from typing import cast import requests import requests_toolbelt @@ -89,13 +90,15 @@ def _make_user_agent_string() -> str: from twine import cli dependencies = cli.list_dependencies_and_versions() - return ( - user_agent.UserAgentBuilder("twine", twine.__version__,) + user_agent_string = ( + user_agent.UserAgentBuilder("twine", twine.__version__) .include_extras(dependencies) .include_implementation() .build() ) + return cast(str, user_agent_string) + def close(self) -> None: self.session.close() From 16956006757b6192b50c27e4e3fa0ba8dbf56c14 Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Mon, 20 Apr 2020 18:00:53 -0400 Subject: [PATCH 19/19] Add comment re: subclassing any --- mypy.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index b6f1d2c5..d8eb262f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -18,8 +18,9 @@ txt_report = mypy ; https://github.com/pypa/packaging/blob/master/setup.cfg ; Starting with modules that have annotations applied via MonkeyType [mypy-twine.auth,twine.cli,twine.package,twine.repository,twine.utils] -disallow_any_generics = True +; Enabling this will fail on subclasses of untype imports, e.g. tqdm ; disallow_subclassing_any = True +disallow_any_generics = True disallow_untyped_calls = True disallow_untyped_defs = True disallow_incomplete_defs = True