Skip to content
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 typings for the scripts.compile module #1322

Merged
merged 15 commits into from Mar 12, 2021
Merged
21 changes: 21 additions & 0 deletions piptools/repositories/base.py
@@ -1,11 +1,17 @@
import optparse
from abc import ABCMeta, abstractmethod
from contextlib import contextmanager
from typing import Iterator, Optional, Set

from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.index import PyPI
from pip._internal.network.session import PipSession
from pip._internal.req import InstallRequirement


class BaseRepository(metaclass=ABCMeta):
DEFAULT_INDEX_URL = PyPI.simple_url

def clear_caches(self) -> None:
"""Should clear any caches used by the implementation."""

Expand Down Expand Up @@ -51,3 +57,18 @@ def copy_ireq_dependencies(
it its name, we would lose track of those dependencies on combining
that ireq with others.
"""

@property
@abstractmethod
def options(self) -> optparse.Values:
"""Returns parsed pip options"""

@property
@abstractmethod
def session(self) -> PipSession:
"""Returns a session to make requests"""

@property
@abstractmethod
def finder(self) -> PackageFinder:
"""Returns a package finder to interact with simple repository API (PEP 503)"""
12 changes: 4 additions & 8 deletions piptools/repositories/local.py
@@ -1,6 +1,6 @@
import optparse
from contextlib import contextmanager
from typing import Dict, Iterator, List, Optional, Set, cast
from typing import Iterator, Mapping, Optional, Set, cast

from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.candidate import InstallationCandidate
Expand Down Expand Up @@ -41,7 +41,7 @@ class LocalRequirementsRepository(BaseRepository):

def __init__(
self,
existing_pins: Dict[str, InstallationCandidate],
existing_pins: Mapping[str, InstallationCandidate],
proxied_repository: PyPIRepository,
reuse_hashes: bool = True,
):
Expand All @@ -50,8 +50,8 @@ def __init__(
self.existing_pins = existing_pins

@property
def options(self) -> List[optparse.Option]:
return cast(List[optparse.Option], self.repository.options)
def options(self) -> optparse.Values:
return self.repository.options

@property
def finder(self) -> PackageFinder:
Expand All @@ -61,10 +61,6 @@ def finder(self) -> PackageFinder:
def session(self) -> Session:
return self.repository.session

@property
def DEFAULT_INDEX_URL(self) -> str:
return cast(str, self.repository.DEFAULT_INDEX_URL)

def clear_caches(self) -> None:
self.repository.clear_caches()

Expand Down
38 changes: 27 additions & 11 deletions piptools/repositories/pypi.py
Expand Up @@ -2,6 +2,7 @@
import hashlib
import itertools
import logging
import optparse
import os
from contextlib import contextmanager
from shutil import rmtree
Expand All @@ -11,10 +12,13 @@
from pip._internal.cache import WheelCache
from pip._internal.cli.progress_bars import BAR_TYPES
from pip._internal.commands import create_command
from pip._internal.commands.install import InstallCommand
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.models.index import PackageIndex, PyPI
from pip._internal.models.index import PackageIndex
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
from pip._internal.network.session import PipSession
from pip._internal.req import InstallRequirement, RequirementSet
from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.utils.hashes import FAVORITE_HASH
Expand Down Expand Up @@ -43,7 +47,6 @@


class PyPIRepository(BaseRepository):
DEFAULT_INDEX_URL = PyPI.simple_url
HASHABLE_PACKAGE_TYPES = {"bdist_wheel", "sdist"}

"""
Expand All @@ -57,18 +60,19 @@ def __init__(self, pip_args: List[str], cache_dir: str):
# Use pip's parser for pip.conf management and defaults.
# General options (find_links, index_url, extra_index_url, trusted_host,
# and pre) are deferred to pip.
self.command = create_command("install")
self.command: InstallCommand = create_command("install")
extra_pip_args = ["--use-deprecated", "legacy-resolver"]
self.options, _ = self.command.parse_args(pip_args + extra_pip_args)
if self.options.cache_dir:
self.options.cache_dir = normalize_path(self.options.cache_dir)

self.options.require_hashes = False
self.options.ignore_dependencies = False
options, _ = self.command.parse_args(pip_args + extra_pip_args)
if options.cache_dir:
options.cache_dir = normalize_path(options.cache_dir)
options.require_hashes = False
options.ignore_dependencies = False

self.session = self.command._build_session(self.options)
self.finder = self.command._build_package_finder(
options=self.options, session=self.session
self._options: optparse.Values = options
self._session = self.command._build_session(options)
self._finder = self.command._build_package_finder(
options=options, session=self.session
)

# Caches
Expand All @@ -91,6 +95,18 @@ def __init__(self, pip_args: List[str], cache_dir: str):
def clear_caches(self) -> None:
rmtree(self._download_dir, ignore_errors=True)

@property
def options(self) -> optparse.Values:
return self._options

@property
def session(self) -> PipSession:
return self._session

@property
def finder(self) -> PackageFinder:
return self._finder

def find_all_candidates(self, req_name: str) -> List[InstallationCandidate]:
if req_name not in self._available_candidates_cache:
candidates = self.finder.find_all_candidates(req_name)
Expand Down
78 changes: 40 additions & 38 deletions piptools/scripts/compile.py
Expand Up @@ -2,10 +2,10 @@
import shlex
import sys
import tempfile
from typing import Any
from typing import Any, BinaryIO, Optional, Tuple, cast

import click
from click.utils import safecall
from click.utils import LazyFile, safecall
from pip._internal.commands import create_command
from pip._internal.req.constructors import install_req_from_line
from pip._internal.utils.misc import redact_auth_from_url
Expand All @@ -17,6 +17,7 @@
from ..locations import CACHE_DIR
from ..logging import log
from ..repositories import LocalRequirementsRepository, PyPIRepository
from ..repositories.base import BaseRepository
from ..resolver import Resolver
from ..utils import UNSAFE_PACKAGES, dedup, is_pinned_requirement, key_from_ireq
from ..writer import OutputWriter
Expand Down Expand Up @@ -186,43 +187,45 @@ def _get_default_option(option_name: str) -> Any:
show_default=True,
type=click.Path(file_okay=False, writable=True),
)
@click.option("--pip-args", help="Arguments to pass directly to the pip command.")
@click.option(
"--pip-args", "pip_args_str", help="Arguments to pass directly to the pip command."
)
@click.option(
"--emit-index-url/--no-emit-index-url",
is_flag=True,
default=True,
help="Add index URL to generated file",
)
def cli(
ctx,
verbose,
quiet,
dry_run,
pre,
rebuild,
find_links,
index_url,
extra_index_url,
cert,
client_cert,
trusted_host,
header,
emit_trusted_host,
annotate,
upgrade,
upgrade_packages,
output_file,
allow_unsafe,
generate_hashes,
reuse_hashes,
src_files,
max_rounds,
build_isolation,
emit_find_links,
cache_dir,
pip_args,
emit_index_url,
):
ctx: click.Context,
verbose: int,
quiet: int,
dry_run: bool,
pre: bool,
rebuild: bool,
find_links: Tuple[str],
index_url: str,
extra_index_url: Tuple[str],
cert: Optional[str],
client_cert: Optional[str],
trusted_host: Tuple[str],
header: bool,
emit_trusted_host: bool,
annotate: bool,
upgrade: bool,
upgrade_packages: Tuple[str],
output_file: Optional[LazyFile],
allow_unsafe: bool,
generate_hashes: bool,
reuse_hashes: bool,
src_files: Tuple[str],
max_rounds: int,
build_isolation: bool,
emit_find_links: bool,
cache_dir: str,
pip_args_str: Optional[str],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you rename it?

Copy link
Member Author

@atugushev atugushev Feb 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, the CLI option is untouched , only variable name’s changed. See option decorator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of changes and renaming variables on top of everything else is hard to follow. It's usually better to make separate patches that are easier to digest for reviewers.

Copy link
Member Author

@atugushev atugushev Feb 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention was to make diff less noisy and make PR easier for reviewers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like me to revert the variable name back? I'm okay with both variants.

emit_index_url: bool,
) -> None:
"""Compiles requirements.txt from requirements.in specs."""
log.verbosity = verbose - quiet

Expand Down Expand Up @@ -261,13 +264,14 @@ def cli(
output_file = click.open_file(file_name, "w+b", atomic=True, lazy=True)

# Close the file at the end of the context execution
assert output_file is not None
ctx.call_on_close(safecall(output_file.close_intelligently))

###
# Setup
###

right_args = shlex.split(pip_args or "")
right_args = shlex.split(pip_args_str or "")
pip_args = []
for link in find_links:
pip_args.extend(["-f", link])
Expand All @@ -288,6 +292,7 @@ def cli(
pip_args.append("--no-build-isolation")
pip_args.extend(right_args)

repository: BaseRepository
repository = PyPIRepository(pip_args, cache_dir=cache_dir)

# Parse all constraints coming from --upgrade-package/-P
Expand Down Expand Up @@ -412,10 +417,7 @@ def cli(
allow_unsafe=allow_unsafe,
)
results = resolver.resolve(max_rounds=max_rounds)
if generate_hashes:
hashes = resolver.resolve_hashes(results)
else:
hashes = None
hashes = resolver.resolve_hashes(results) if generate_hashes else None
except PipToolsError as e:
log.error(str(e))
sys.exit(2)
Expand All @@ -427,7 +429,7 @@ def cli(
##

writer = OutputWriter(
output_file,
cast(BinaryIO, output_file),
click_ctx=ctx,
dry_run=dry_run,
emit_header=header,
Expand Down
2 changes: 1 addition & 1 deletion piptools/utils.py
Expand Up @@ -297,7 +297,7 @@ def get_compile_command(click_ctx: click.Context) -> str:
else:
if isinstance(val, str) and is_url(val):
val = redact_auth_from_url(val)
if option.name == "pip_args":
if option.name == "pip_args_str":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't look related to typing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to #1322 (comment)

# shlex.quote() would produce functional but noisily quoted results,
# e.g. --pip-args='--cache-dir='"'"'/tmp/with spaces'"'"''
# Instead, we try to get more legible quoting via repr:
Expand Down
6 changes: 3 additions & 3 deletions piptools/writer.py
@@ -1,7 +1,7 @@
import os
import re
from itertools import chain
from typing import BinaryIO, Dict, Iterator, List, Optional, Sequence, Set, Tuple
from typing import BinaryIO, Dict, Iterable, Iterator, List, Optional, Set, Tuple

from click import unstyle
from click.core import Context
Expand Down Expand Up @@ -62,8 +62,8 @@ def __init__(
annotate: bool,
generate_hashes: bool,
default_index_url: str,
index_urls: Sequence[str],
trusted_hosts: Sequence[str],
index_urls: Iterable[str],
trusted_hosts: Iterable[str],
format_control: FormatControl,
allow_unsafe: bool,
find_links: List[str],
Expand Down
15 changes: 15 additions & 0 deletions tests/conftest.py
@@ -1,4 +1,5 @@
import json
import optparse
import os
import subprocess
import sys
Expand All @@ -8,7 +9,9 @@

import pytest
from click.testing import CliRunner
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.network.session import PipSession
from pip._internal.req.constructors import (
install_req_from_editable,
install_req_from_line,
Expand Down Expand Up @@ -90,6 +93,18 @@ def copy_ireq_dependencies(self, source, dest):
# No state to update.
pass

@property
def options(self) -> optparse.Values:
"""Not used"""

@property
def session(self) -> PipSession:
"""Not used"""

@property
def finder(self) -> PackageFinder:
"""Not used"""


class FakeInstalledDistribution:
def __init__(self, line, deps=None):
Expand Down