Skip to content

Commit

Permalink
Merge branch 'add-api-docs' of github.com:chrysle/pip-tools into add-…
Browse files Browse the repository at this point in the history
…api-docs
  • Loading branch information
chrysle committed May 12, 2024
2 parents 34e9b20 + 290ad20 commit 893f758
Show file tree
Hide file tree
Showing 23 changed files with 458 additions and 56 deletions.
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 24.2.0
rev: 24.4.2
hooks:
- id: black
args: [--target-version=py38]
Expand All @@ -9,7 +9,7 @@ repos:
hooks:
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.1
rev: v3.15.2
hooks:
- id: pyupgrade
args: [--py38-plus]
Expand All @@ -20,7 +20,7 @@ repos:
additional_dependencies:
- flake8-pytest-style
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.10.0
hooks:
- id: mypy
# Avoid error: Duplicate module named 'setup'
Expand All @@ -37,7 +37,7 @@ repos:
- pytest==7.4.2
language_version: python3.8
- repo: https://github.com/PyCQA/bandit
rev: 1.7.7
rev: 1.7.8
hooks:
- id: bandit
args: [--ini, .bandit]
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
## v7.4.1

05 Mar 2024

Bug Fixes:

- Skip constraint path check ([#2038](https://github.com/jazzband/pip-tools/pull/2038)).
Thanks @honnix
- Fix collecting deps for all extras in multiple input packages
([#1981](https://github.com/jazzband/pip-tools/pull/1981)). Thanks @dragly

## v7.4.0

16 Feb 2024

Features:

- Allow force-enabling or force-disabling colorized output
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ Sample `.pre-commit-config.yaml`:
```yaml
repos:
- repo: https://github.com/jazzband/pip-tools
rev: 7.4.0
rev: 7.4.1
hooks:
- id: pip-compile
```
Expand All @@ -457,7 +457,7 @@ You might want to customize `pip-compile` args by configuring `args` and/or `fil
```yaml
repos:
- repo: https://github.com/jazzband/pip-tools
rev: 7.4.0
rev: 7.4.1
hooks:
- id: pip-compile
files: ^requirements/production\.(in|txt)$
Expand All @@ -469,7 +469,7 @@ If you have multiple requirement files make sure you create a hook for each file
```yaml
repos:
- repo: https://github.com/jazzband/pip-tools
rev: 7.4.0
rev: 7.4.1
hooks:
- id: pip-compile
name: pip-compile setup.py
Expand Down
5 changes: 4 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
# Stdlib extensions:
"sphinx.ext.intersphinx",
# Third-party extensions:
"myst_parser",
"sphinxcontrib.apidoc",
"sphinx.ext.intersphinx",
"sphinxcontrib.programoutput",
]

Expand Down Expand Up @@ -79,6 +81,7 @@
("py:class", "_ImportLibDist"),
("py:class", "PackageMetadata"),
("py:class", "importlib.*"),
("py:class", "IndexContent"),
("py:exc", "click.*"),
]

Expand Down
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ docutils==0.20.1
# sphinx
furo==2023.8.17
# via -r requirements.in
idna==3.4
idna==3.7
# via requests
imagesize==1.4.1
# via sphinx
Expand Down
29 changes: 25 additions & 4 deletions piptools/_compat/pip_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pip._internal.metadata import BaseDistribution
from pip._internal.metadata.pkg_resources import Distribution as _PkgResourcesDist
from pip._internal.models.direct_url import DirectUrl
from pip._internal.models.link import Link
from pip._internal.network.session import PipSession
from pip._internal.req import InstallRequirement
from pip._internal.req import parse_requirements as _parse_requirements
Expand Down Expand Up @@ -51,9 +52,12 @@ def _from_pkg_resources(cls, dist: _PkgResourcesDist) -> Distribution:

@classmethod
def _from_importlib(cls, dist: _ImportLibDist) -> Distribution:
"""Mimics pkg_resources.Distribution.requires for the case of no
extras. This doesn't fulfill that API's `extras` parameter but
satisfies the needs of pip-tools."""
"""Mimic pkg_resources.Distribution.requires for the case of no
extras.
This doesn't fulfill that API's ``extras`` parameter but
satisfies the needs of pip-tools.
"""
reqs = (Requirement.parse(req) for req in (dist._dist.requires or ()))
requires = [
req
Expand All @@ -63,6 +67,16 @@ def _from_importlib(cls, dist: _ImportLibDist) -> Distribution:
return cls(dist._dist.name, dist._dist.version, requires, dist.direct_url)


class FileLink(Link): # type: ignore[misc]
"""Wrapper for ``pip``'s ``Link`` class."""
_url: str

@property
def file_path(self) -> str:
# overriding the actual property to bypass some validation
return self._url


def parse_requirements(
filename: str,
session: PipSession,
Expand All @@ -74,7 +88,14 @@ def parse_requirements(
for parsed_req in _parse_requirements(
filename, session, finder=finder, options=options, constraint=constraint
):
yield install_req_from_parsed_requirement(parsed_req, isolated=isolated)
install_req = install_req_from_parsed_requirement(parsed_req, isolated=isolated)
if install_req.editable and not parsed_req.requirement.startswith("file://"):
# ``Link.url`` is what is saved to the output file
# we set the url directly to undo the transformation in pip's Link class
file_link = FileLink(install_req.link.url)
file_link._url = parsed_req.requirement
install_req.link = file_link
yield install_req


def create_wheel_cache(cache_dir: str, format_control: str | None = None) -> WheelCache:
Expand Down
94 changes: 93 additions & 1 deletion piptools/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
import pyproject_hooks
from pip._internal.req import InstallRequirement
from pip._internal.req.constructors import install_req_from_line, parse_req_from_line
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import Requirement

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

PYPROJECT_TOML = "pyproject.toml"

Expand All @@ -32,23 +39,95 @@ def get_all(self, name: str, failobj: None = None) -> list[Any] | None: ...
def get_all(self, name: str, failobj: _T) -> list[Any] | _T: ...


@dataclass
class StaticProjectMetadata:
extras: tuple[str, ...]
requirements: tuple[InstallRequirement, ...]


@dataclass
class ProjectMetadata:
extras: tuple[str, ...]
requirements: tuple[InstallRequirement, ...]
build_requirements: tuple[InstallRequirement, ...]


def maybe_statically_parse_project_metadata(
src_file: pathlib.Path,
) -> StaticProjectMetadata | None:
"""
Return the metadata for a project, if it can be statically parsed from ``pyproject.toml``.
This function is typically significantly faster than invoking a build backend.
Returns None if the project metadata cannot be statically parsed.
"""
if src_file.name != PYPROJECT_TOML:
return None

try:
with open(src_file, "rb") as f:
pyproject_contents = tomllib.load(f)
except tomllib.TOMLDecodeError:
return None

# Not valid PEP 621 metadata
if (
"project" not in pyproject_contents
or "name" not in pyproject_contents["project"]
):
return None

project_table = pyproject_contents["project"]

# Dynamic dependencies require build backend invocation
dynamic = project_table.get("dynamic", [])
if "dependencies" in dynamic or "optional-dependencies" in dynamic:
return None

package_name = project_table["name"]
comes_from = f"{package_name} ({src_file})"

extras = project_table.get("optional-dependencies", {}).keys()
install_requirements = [
InstallRequirement(Requirement(req), comes_from)
for req in project_table.get("dependencies", [])
]
for extra, reqs in (
pyproject_contents.get("project", {}).get("optional-dependencies", {}).items()
):
for req in reqs:
requirement = Requirement(req)
if requirement.name == package_name:
# Similar to logic for handling self-referential requirements
# from _prepare_requirements
requirement.url = src_file.parent.as_uri()
# Note we don't need to modify `requirement` to include this extra
marker = Marker(f"extra == '{extra}'")
install_requirements.append(
InstallRequirement(requirement, comes_from, markers=marker)
)

return StaticProjectMetadata(
extras=tuple(extras),
requirements=tuple(install_requirements),
)


def build_project_metadata(
src_file: pathlib.Path,
build_targets: tuple[str, ...],
*,
attempt_static_parse: bool,
isolated: bool,
quiet: bool,
) -> ProjectMetadata:
) -> ProjectMetadata | StaticProjectMetadata:
"""
Return the metadata for a project.
First, optionally attempt to determine the metadata statically from the
``pyproject.toml`` file. This will not work if build_targets are specified,
since we cannot determine build requirements statically.
Uses the ``prepare_metadata_for_build_wheel`` hook for the wheel metadata
if available, otherwise ``build_wheel``.
Expand All @@ -58,12 +137,25 @@ def build_project_metadata(
:param src_file: Project source file
:param build_targets: A tuple of build targets to get the dependencies
of (``sdist`` or ``wheel`` or ``editable``).
:param attempt_static_parse: Whether to attempt to statically parse the
project metadata from ``pyproject.toml``.
Cannot be used with ``build_targets``.
:param isolated: Whether to run invoke the backend in the current
environment or to create an isolated one and invoke it
there.
:param quiet: Whether to suppress the output of subprocesses.
"""

if attempt_static_parse:
if build_targets:
raise ValueError(
"Cannot execute the PEP 517 optional get_requires_for_build* "
"hooks statically, as build requirements are requested"
)
project_metadata = maybe_statically_parse_project_metadata(src_file)
if project_metadata is not None:
return project_metadata

src_dir = src_file.parent
with _create_project_builder(src_dir, isolated=isolated, quiet=quiet) as builder:
metadata = _build_project_wheel_metadata(builder)
Expand Down
5 changes: 4 additions & 1 deletion piptools/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import operator
from typing import Iterable

from pip._internal.index.package_finder import PackageFinder
Expand Down Expand Up @@ -27,7 +28,9 @@ def __str__(self) -> str:
versions = []
pre_versions = []

for candidate in sorted(self.candidates_tried):
for candidate in sorted(
self.candidates_tried, key=operator.attrgetter("version")
):
version = str(candidate.version)
if candidate.version.is_prerelease:
pre_versions.append(version)
Expand Down
Loading

0 comments on commit 893f758

Please sign in to comment.