Skip to content

Commit

Permalink
pip_audit: ratchet down typing (#272)
Browse files Browse the repository at this point in the history
* pip_audit: ratchet down typing

* pypi_provider: fix type
  • Loading branch information
woodruffw committed May 9, 2022
1 parent 0dda49a commit 469cc0b
Show file tree
Hide file tree
Showing 14 changed files with 86 additions and 51 deletions.
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ endif

env/pyvenv.cfg: setup.py pyproject.toml
# Create our Python 3 virtual environment
rm -rf env
python3 -m venv env
[[ ! -d env ]] || python3 -m venv env
./env/bin/python -m pip install --upgrade pip
./env/bin/python -m pip install -e .[dev]

Expand All @@ -47,7 +46,7 @@ lint: env/pyvenv.cfg
black $(ALL_PY_SRCS) && \
isort $(ALL_PY_SRCS) && \
flake8 $(ALL_PY_SRCS) && \
mypy $(PY_MODULE) test/ && \
mypy $(PY_MODULE) && \
interrogate -c pyproject.toml .

.PHONY: test tests
Expand Down
9 changes: 0 additions & 9 deletions mypy.ini

This file was deleted.

8 changes: 4 additions & 4 deletions pip_audit/_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

import pip_api
import requests
from cachecontrol import CacheControl # type: ignore
from cachecontrol.caches import FileCache # type: ignore
from cachecontrol import CacheControl
from cachecontrol.caches import FileCache
from packaging.version import Version

from pip_audit._service.interface import ServiceError
Expand Down Expand Up @@ -74,7 +74,7 @@ class _SafeFileCache(FileCache):
caching directory as a running `pip` process).
"""

def __init__(self, directory):
def __init__(self, directory: Path):
self._logged_warning = False
super().__init__(directory)

Expand Down Expand Up @@ -134,7 +134,7 @@ def delete(self, key: str) -> None: # pragma: no cover
self._logged_warning = True


def caching_session(cache_dir: Optional[Path], *, use_pip=False) -> CacheControl:
def caching_session(cache_dir: Optional[Path], *, use_pip: bool = False) -> CacheControl:
"""
Return a `requests` style session, with suitable caching middleware.
Expand Down
8 changes: 4 additions & 4 deletions pip_audit/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def to_format(self, output_desc: bool) -> VulnerabilityFormat:
else:
assert_never(self)

def __str__(self):
def __str__(self) -> str:
return self.value


Expand All @@ -77,7 +77,7 @@ def to_service(self, timeout: int, cache_dir: Optional[Path]) -> VulnerabilitySe
else:
assert_never(self)

def __str__(self):
def __str__(self) -> str:
return self.value


Expand All @@ -101,7 +101,7 @@ def to_bool(self, format_: OutputFormatChoice) -> bool:
else:
assert_never(self)

def __str__(self):
def __str__(self) -> str:
return self.value


Expand All @@ -117,7 +117,7 @@ class ProgressSpinnerChoice(str, enum.Enum):
def __bool__(self) -> bool:
return self is ProgressSpinnerChoice.On

def __str__(self):
def __str__(self) -> str:
return self.value


Expand Down
56 changes: 40 additions & 16 deletions pip_audit/_dependency_source/resolvelib/pypi_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@
"""

import itertools
from email.message import EmailMessage
from email.message import EmailMessage, Message
from email.parser import BytesParser
from io import BytesIO
from operator import attrgetter
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import BinaryIO, Iterator, List, Optional, Set, cast
from typing import Any, BinaryIO, Iterator, List, Mapping, Optional, Set, Union, cast
from urllib.parse import urlparse
from zipfile import ZipFile

import html5lib
import requests
from cachecontrol import CacheControl # type: ignore
from cachecontrol import CacheControl
from packaging.requirements import Requirement
from packaging.specifiers import SpecifierSet
from packaging.utils import canonicalize_name, parse_sdist_filename, parse_wheel_filename
from packaging.version import Version
from resolvelib.providers import AbstractProvider
from resolvelib.resolvers import RequirementInformation

from pip_audit._cache import caching_session
from pip_audit._state import AuditState
Expand Down Expand Up @@ -68,10 +69,10 @@ def __init__(
self._timeout = timeout
self._state = state

self._metadata: Optional[EmailMessage] = None
self._metadata: Optional[Message] = None
self._dependencies: Optional[List[Requirement]] = None

def __repr__(self): # pragma: no cover
def __repr__(self) -> str: # pragma: no cover
"""
A string representation for `Candidate`.
"""
Expand All @@ -80,7 +81,7 @@ def __repr__(self): # pragma: no cover
return f"<{self.name}[{','.join(self.extras)}]=={self.version} wheel={self.is_wheel}>"

@property
def metadata(self) -> EmailMessage:
def metadata(self) -> Message:
"""
Return the package metadata for this candidate.
"""
Expand All @@ -94,7 +95,7 @@ def metadata(self) -> EmailMessage:
self._metadata = self._get_metadata_for_sdist()
return self._metadata

def _get_dependencies(self):
def _get_dependencies(self) -> Iterator[Requirement]:
"""
Computes the dependency set for this candidate.
"""
Expand All @@ -119,7 +120,7 @@ def dependencies(self) -> List[Requirement]:
self._dependencies = list(self._get_dependencies())
return self._dependencies

def _get_metadata_for_wheel(self):
def _get_metadata_for_wheel(self) -> Message:
"""
Extracts the metadata for this candidate, if it's a wheel.
"""
Expand All @@ -138,7 +139,7 @@ def _get_metadata_for_wheel(self):
# If we didn't find the metadata, return an empty dict
return EmailMessage() # pragma: no cover

def _get_metadata_for_sdist(self):
def _get_metadata_for_sdist(self) -> Message:
"""
Extracts the metadata for this candidate, if it's a source distribution.
"""
Expand Down Expand Up @@ -173,7 +174,12 @@ def _get_metadata_for_sdist(self):


def get_project_from_indexes(
index_urls: List[str], session, project, extras, timeout: Optional[int], state: AuditState
index_urls: List[str],
session: CacheControl,
project: str,
extras: Set[str],
timeout: Optional[int],
state: AuditState,
) -> Iterator[Candidate]:
"""Return candidates from all indexes created from the project name and extras."""
project_found = False
Expand All @@ -192,7 +198,12 @@ def get_project_from_indexes(


def get_project_from_index(
index_url: str, session, project, extras, timeout: Optional[int], state: AuditState
index_url: str,
session: CacheControl,
project: str,
extras: Set[str],
timeout: Optional[int],
state: AuditState,
) -> Iterator[Candidate]:
"""Return candidates from an index created from the project name and extras."""
url = index_url + "/" + project
Expand Down Expand Up @@ -272,19 +283,32 @@ def __init__(
self.session = caching_session(cache_dir, use_pip=True)
self._state = state

def identify(self, requirement_or_candidate):
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
"""
See `resolvelib.providers.AbstractProvider.identify`.
"""
return canonicalize_name(requirement_or_candidate.name)

def get_preference(self, identifier, resolutions, candidates, information, backtrack_causes):
# TODO: Typing. See: https://github.com/sarugaku/resolvelib/issues/104
def get_preference( # type: ignore[override, no-untyped-def]
self,
identifier: Any,
resolutions: Mapping[Any, Any],
candidates: Mapping[Any, Iterator[Any]],
information: Mapping[Any, Iterator[RequirementInformation]],
backtrack_causes: Any,
):
"""
See `resolvelib.providers.AbstractProvider.get_preference`.
"""
return sum(1 for _ in candidates[identifier])

def find_matches(self, identifier, requirements, incompatibilities):
def find_matches(
self,
identifier: Any,
requirements: Mapping[Any, Iterator[Any]],
incompatibilities: Mapping[Any, Iterator[Any]],
) -> Iterator[Any]:
"""
See `resolvelib.providers.AbstractProvider.find_matches`.
"""
Expand Down Expand Up @@ -333,13 +357,13 @@ def find_matches(self, identifier, requirements, incompatibilities):
else:
yield from candidates

def is_satisfied_by(self, requirement, candidate):
def is_satisfied_by(self, requirement: Any, candidate: Any) -> bool:
"""
See `resolvelib.providers.AbstractProvider.is_satisfied_by`.
"""
return candidate.version in requirement.specifier

def get_dependencies(self, candidate):
def get_dependencies(self, candidate: Any) -> Any:
"""
See `resolvelib.providers.AbstractProvider.get_dependencies`.
"""
Expand Down
8 changes: 5 additions & 3 deletions pip_audit/_dependency_source/resolvelib/resolvelib.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

import logging
from pathlib import Path
from typing import List, Optional, cast
from typing import List, Optional, Union

from packaging.requirements import Requirement
from packaging.requirements import Requirement as _Requirement
from pip_api import Requirement as ParsedRequirement
from requests.exceptions import HTTPError
from resolvelib import BaseReporter, Resolver
Expand All @@ -23,6 +23,9 @@
PYPI_URL = "https://pypi.org/simple"


Requirement = Union[_Requirement, ParsedRequirement]


class ResolveLibResolver(DependencyResolver):
"""
An implementation of `DependencyResolver` that uses `resolvelib` as its
Expand Down Expand Up @@ -62,7 +65,6 @@ def resolve(self, req: Requirement) -> List[Dependency]:
# since the latter is a subclass. But only the latter knows whether the
# requirement is editable, so we need to check for it here.
if isinstance(req, ParsedRequirement):
req = cast(ParsedRequirement, req)
if req.editable and self._skip_editable:
return [
SkippedDependency(name=req.name, skip_reason="requirement marked as editable")
Expand Down
4 changes: 2 additions & 2 deletions pip_audit/_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
from dataclasses import dataclass
from typing import Dict, Iterator, List, cast
from typing import Any, Dict, Iterator, List, cast

from packaging.version import Version

Expand All @@ -29,7 +29,7 @@ class FixVersion:

dep: ResolvedDependency

def __init__(self, *_args, **_kwargs) -> None: # pragma: no cover
def __init__(self, *_args: Any, **_kwargs: Any) -> None: # pragma: no cover
"""
A stub constructor that always fails.
"""
Expand Down
2 changes: 1 addition & 1 deletion pip_audit/_format/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, output_desc: bool):
self.output_desc = output_desc

@property
def is_manifest(self):
def is_manifest(self) -> bool:
"""
See `VulnerabilityFormat.is_manifest`.
"""
Expand Down
2 changes: 1 addition & 1 deletion pip_audit/_format/cyclonedx.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(self, inner_format: "CycloneDxFormat.InnerFormat"):
self._inner_format = inner_format

@property
def is_manifest(self):
def is_manifest(self) -> bool:
"""
See `VulnerabilityFormat.is_manifest`.
"""
Expand Down
2 changes: 1 addition & 1 deletion pip_audit/_format/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, output_desc: bool):
self.output_desc = output_desc

@property
def is_manifest(self):
def is_manifest(self) -> bool:
"""
See `VulnerabilityFormat.is_manifest`.
"""
Expand Down
4 changes: 2 additions & 2 deletions pip_audit/_service/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, Iterator, List, Set, Tuple
from typing import Any, Dict, Iterator, List, Set, Tuple

from packaging.utils import canonicalize_name
from packaging.version import Version
Expand All @@ -28,7 +28,7 @@ class Dependency:
Use the `canonicalized_name` property when a canonicalized form is necessary.
"""

def __init__(self, *_args, **_kwargs) -> None:
def __init__(self, *_args: Any, **_kwargs: Any) -> None:
"""
A stub constructor that always fails.
"""
Expand Down
10 changes: 6 additions & 4 deletions pip_audit/_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from logging.handlers import MemoryHandler
from typing import Any, Dict, List, Sequence

from progress.spinner import Spinner as BaseSpinner # type: ignore
from progress.spinner import Spinner as BaseSpinner


class AuditState:
Expand All @@ -30,7 +30,7 @@ def __init__(self, *, members: Sequence["_StateActor"] = []):

self._members = members

def update_state(self, message: str):
def update_state(self, message: str) -> None:
"""
Called whenever `pip_audit`'s internal state changes in a way that's meaningful to
expose to a user.
Expand Down Expand Up @@ -64,7 +64,9 @@ def __enter__(self) -> "AuditState": # pragma: no cover
self.initialize()
return self

def __exit__(self, _exc_type, _exc_value, _exc_traceback): # pragma: no cover
def __exit__(
self, _exc_type: Any, _exc_value: Any, _exc_traceback: Any
) -> None: # pragma: no cover
"""
Helper to ensure `finalize` gets called when the `pip-audit` state falls out of scope of a
`with` statement.
Expand Down Expand Up @@ -122,7 +124,7 @@ def __init__(self, message: str = "", **kwargs: Dict[str, Any]):
)
self.prev_handlers: List[logging.Handler] = []

def _writeln_truncated(self, line: str):
def _writeln_truncated(self, line: str) -> None:
"""
Wraps `BaseSpinner.writeln`, providing reasonable truncation behavior
when a line would otherwise overflow its terminal row and cause the progress
Expand Down
3 changes: 2 additions & 1 deletion pip_audit/_virtual_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import logging
import venv
from types import SimpleNamespace
from typing import Iterator, List, Optional, Tuple

from packaging.version import Version
Expand Down Expand Up @@ -54,7 +55,7 @@ def __init__(self, install_args: List[str], state: AuditState = AuditState()):
self._packages: Optional[List[Tuple[str, Version]]] = None
self._state = state

def post_setup(self, context):
def post_setup(self, context: SimpleNamespace) -> None:
"""
Install the custom package and populate the list of installed packages.
Expand Down
Loading

0 comments on commit 469cc0b

Please sign in to comment.