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

Allow http(s) as constraint file #2040

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion piptools/scripts/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from piptools.locations import CACHE_DIR, DEFAULT_CONFIG_FILE_NAMES
from piptools.utils import UNSAFE_PACKAGES, override_defaults_from_config_file

from .types import EnhancedPath

BuildTargetT = Literal["sdist", "wheel", "editable"]
ALL_BUILD_TARGETS: tuple[BuildTargetT, ...] = (
"editable",
Expand Down Expand Up @@ -337,7 +339,7 @@ def _get_default_option(option_name: str) -> Any:
constraint = click.option(
"-c",
"--constraint",
type=click.Path(
type=EnhancedPath(
exists=True,
file_okay=True,
dir_okay=False,
Expand Down
100 changes: 100 additions & 0 deletions piptools/scripts/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from __future__ import annotations

import os
import typing
from gettext import gettext
from typing import Any, Union

import click


class EnhancedPath(click.Path):
"""The ``EnhancedPath`` type extends the built-in ``click.Path`` type, to
also support URLs, for example HTTP(S). Note that ``file://`` support
"""

def __init__(self, **kwargs: Any):
super().__init__(**kwargs)

def convert(
self,
value: str | os.PathLike[str],
param: click.Parameter | None,
ctx: click.Context | None,
) -> str | bytes | os.PathLike[str]:
if isinstance(value, os.PathLike) or not EnhancedPath.is_url(value):
return typing.cast(
Union[str, bytes, "os.PathLike[str]"],
super().convert(value, param, ctx),
)

if EnhancedPath.is_file_scheme(value):
super().convert(EnhancedPath.file_url_to_path(value), param, ctx)
return value

Check warning on line 33 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L32-L33

Added lines #L32 - L33 were not covered by tests

from urllib.error import HTTPError, URLError
from urllib.request import urlopen

Check warning on line 36 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L35-L36

Added lines #L35 - L36 were not covered by tests

def handle_http_error(

Check warning on line 38 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L38

Added line #L38 was not covered by tests
http_error: HTTPError,
) -> None:
if http_error.code == 404 and self.exists:
self.fail(

Check warning on line 42 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L42

Added line #L42 was not covered by tests
gettext("{name} {filename!r} does not exist.").format(
name=self.name.title(), filename=value
),
param,
ctx,
)

if http_error.code == 403 and self.readable:
self.fail(

Check warning on line 51 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L51

Added line #L51 was not covered by tests
gettext("{name} {filename!r} is not readable.").format(
name=self.name.title(), filename=value
),
param,
ctx,
)
self.fail(

Check warning on line 58 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L58

Added line #L58 was not covered by tests
gettext(
"failed checking {name} {filename!r} with error code {code}."
).format(
name=self.name.title(),
filename=value,
code=http_error.code,
),
param,
ctx,
)

try:
urlopen(value) # nosec
return value
except URLError as e:

Check warning on line 73 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L70-L73

Added lines #L70 - L73 were not covered by tests
if isinstance(e, HTTPError):
handle_http_error(e)

Check warning on line 75 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L75

Added line #L75 was not covered by tests

self.fail(

Check warning on line 77 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L77

Added line #L77 was not covered by tests
gettext("failed checking {name} {filename!r}.").format(
name=self.name.title(),
filename=value,
),
param,
ctx,
)

@staticmethod
def is_url(value: str) -> bool:
return True if "://" in value else False

@staticmethod
def is_file_scheme(url: str) -> bool:
from urllib.parse import urlparse

Check warning on line 92 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L92

Added line #L92 was not covered by tests

return urlparse(url).scheme == "file"

Check warning on line 94 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L94

Added line #L94 was not covered by tests

@staticmethod
def file_url_to_path(url: str) -> str:
from urllib.parse import unquote_plus, urlparse

Check warning on line 98 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L98

Added line #L98 was not covered by tests

return unquote_plus(urlparse(url).path)

Check warning on line 100 in piptools/scripts/types.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/types.py#L100

Added line #L100 was not covered by tests