Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
bruno-fs committed Oct 8, 2023
1 parent 5cfeb34 commit bd5d683
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 25 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ select = [
"UP", # pyupgrade
"W", # pycodestyle
]
ignore = ["D203", "D107"]
ignore = ["D203", "D107", "D212"]
src = ["src", "tests"]

[tool.ruff.per-file-ignores]
Expand Down
8 changes: 8 additions & 0 deletions src/pybuild_deps/compile_build_dependencies.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"""
compile build dependencies module.
Heavily rely on pip-tools BacktrackingResolver and our own find_build_deps
to recursively find all build dependencies and generate a pinned list
of build dependencies.
"""

from __future__ import annotations

from typing import Iterable
Expand Down
2 changes: 2 additions & 0 deletions src/pybuild_deps/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""constants for pybuild deps."""

from pip._internal.utils.appdirs import user_cache_dir
from xdg import xdg_cache_home

Expand Down
5 changes: 4 additions & 1 deletion src/pybuild_deps/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
"""custom exceptions for pybuild-deps."""


class PyBuildDepsError(Exception):
...
"""Custom exception for pybuild-deps."""
10 changes: 9 additions & 1 deletion src/pybuild_deps/logger.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""custom logger for pybuild-deps."""
# ruff: noqa: D102

from __future__ import annotations

import logging
Expand All @@ -10,7 +13,12 @@


class Logger:
"""Custom logger for pybuild-deps."""
"""
Custom logger for pybuild-deps.
When invoked as a CLI, will use click to log messages. Otherwise
will use default python logger.
"""

def __init__(self, verbosity: int = 0):
self._verbosity = verbosity
Expand Down
3 changes: 3 additions & 0 deletions src/pybuild_deps/parsers/requirements.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""parser for requirement.txt files."""

from __future__ import annotations

import optparse
Expand Down Expand Up @@ -25,6 +27,7 @@ def parse_requirements(
constraint: bool = False,
isolated: bool = False,
) -> Generator[InstallRequirement]:
"""Thin wrapper around pip's `parse_requirements`."""
for parsed_req in _parse_requirements(
filename, session, finder=finder, options=options, constraint=constraint
):
Expand Down
55 changes: 33 additions & 22 deletions src/pybuild_deps/scripts/compile.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""compile script."""

from __future__ import annotations

import os
Expand Down Expand Up @@ -78,7 +80,7 @@ def get_compile_command(click_ctx):
default=False,
help="Generate pip 8 style hashes in the resulting requirements file.",
)
@click.argument("src_files", nargs=-1, type=click.Path(exists=True, allow_dash=True))
@click.argument("src_files", nargs=-1, type=click.Path(exists=True, allow_dash=False))
@click.option(
"--cache-dir",
help="Store the cache data in DIRECTORY.",
Expand All @@ -104,34 +106,16 @@ def cli(
"""Compiles build_requirements.txt from requirements.txt."""
log.verbosity = verbose - quiet
if len(src_files) == 0:
if Path(REQUIREMENTS_TXT).exists():
src_files = (REQUIREMENTS_TXT,)
else:
raise click.BadParameter(
f"Couldn't find a '{REQUIREMENTS_TXT}'. "
"You must specify at least one input file."
)
src_files = _handle_src_files()
if not output_file and not dry_run:
log.warning("No output file (-o) specified. Defaulting to 'dry run' mode.")
dry_run = True

pip_args = []
repository = PyPIRepository(pip_args, cache_dir=cache_dir)
repository = PyPIRepository([], cache_dir=cache_dir)

dependencies: list[InstallRequirement] = []
for src_file in src_files:
try:
dependencies.extend(
parse_requirements(
src_file,
finder=repository.finder,
session=repository.session,
options=repository.options,
)
)
except PyBuildDepsError as err:
log.error(str(err))
sys.exit(2)
dependencies.extend(_parse_requirements(repository, src_file))

compiler = BuildDependencyCompiler(repository)
try:
Expand Down Expand Up @@ -178,3 +162,30 @@ def cli(

if dry_run:
log.info("Dry-run, so no file created/updated.")


def _parse_requirements(repository, src_file):
try:
return list(
parse_requirements(
src_file,
finder=repository.finder,
session=repository.session,
options=repository.options,
)
)
except PyBuildDepsError as err:
log.error(str(err))
sys.exit(2)


def _handle_src_files():
if Path(REQUIREMENTS_TXT).exists():
src_files = (REQUIREMENTS_TXT,)
else:
raise click.BadParameter(
f"Couldn't find a '{REQUIREMENTS_TXT}'. "
"You must specify at least one input file."
)

return src_files
5 changes: 5 additions & 0 deletions tests/test_compile_build_dependencies.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""test compile_build_dependencies module."""

import pytest
from pip._internal.req.constructors import install_req_from_req_string
from piptools.repositories import PyPIRepository
Expand All @@ -9,11 +11,13 @@

@pytest.fixture
def compiler() -> BuildDependencyCompiler:
"""BuildDependencyCompiler instance."""
repo = PyPIRepository([], cache_dir=PIP_CACHE_DIR)
return BuildDependencyCompiler(repo)


def test_compile_greenpath(compiler):
"""Test compiling build dependencies happy path."""
ireq = install_req_from_req_string("cryptography==40.0.0")
dependencies = compiler.resolve([ireq])
deps_per_name = {req.name: req for req in dependencies}
Expand All @@ -23,6 +27,7 @@ def test_compile_greenpath(compiler):


def test_unpinned_dependency(compiler):
"""Ensure unpinned dependencies will raise the appropriate error."""
ireq = install_req_from_req_string("cryptography<40")
with pytest.raises(PyBuildDepsError):
compiler.resolve([ireq])
6 changes: 6 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

@pytest.fixture
def pypi_repo():
"""PyPIRepository instance for testing."""
return PyPIRepository([], cache_dir=PIP_CACHE_DIR)


Expand Down Expand Up @@ -65,6 +66,7 @@ def test_find_build_deps(

@pytest.mark.e2e
def test_compile_greenpath(runner: CliRunner, tmp_path: Path, pypi_repo):
"""Test happy path for compile command."""
output = tmp_path / "requirements-build.txt"
requirements_path: Path = tmp_path / "requirements.txt"
requirements_path.write_text("cryptography==39.0.0")
Expand All @@ -78,6 +80,7 @@ def test_compile_greenpath(runner: CliRunner, tmp_path: Path, pypi_repo):


def test_compile_missing_requirements_txt(runner: CliRunner, tmp_path: Path):
"""Test compile without a requirements.txt."""
chdir(tmp_path)
result = runner.invoke(main.cli, args=["compile"])
assert result.exit_code != 0
Expand All @@ -97,6 +100,7 @@ def test_compile_implicit_requirements_txt_and_non_default_options(
cache: Path,
args,
):
"""Exercise some options to ensure they are working."""
chdir(tmp_path)
requirements_path: Path = tmp_path / "requirements.txt"
requirements_path.write_text("setuptools-rust==1.6.0")
Expand All @@ -106,6 +110,7 @@ def test_compile_implicit_requirements_txt_and_non_default_options(


def test_compile_not_pinned_requirements_txt(runner: CliRunner, tmp_path: Path):
"""Ensure the appropriate error is thrown for non pinned requirements."""
chdir(tmp_path)
requirements_path: Path = tmp_path / "requirements.txt"
requirements_path.write_text("setuptools-rust<1")
Expand All @@ -119,6 +124,7 @@ def test_compile_not_pinned_requirements_txt(runner: CliRunner, tmp_path: Path):


def test_compile_unsolvable_dependencies(runner: CliRunner, tmp_path: Path, mocker):
"""Test error handling for unsolvable dependencies."""
mocker.patch.object(
BuildDependencyCompiler, "resolve", side_effect=PipToolsError("SOME ERROR")
)
Expand Down
4 changes: 4 additions & 0 deletions tests/test_parsers/test_requirements.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""test requirements parser."""

from pathlib import Path

import pytest
Expand All @@ -7,13 +9,15 @@


def test_pinned_requirements(tmp_path, mocker):
"""Test parsing requirements with pinned dependencies."""
requirements_path: Path = tmp_path / "requirements.txt"
requirements_path.write_text("cryptography==40.0.0")
requirements_list = list(parse_requirements(str(requirements_path), mocker.Mock()))
assert [r.name for r in requirements_list] == ["cryptography"]


def test_unpinned_requirements(tmp_path, mocker):
"""Test parsing requirements with unpinned dependencies."""
requirements_path: Path = tmp_path / "requirements.txt"
requirements_path.write_text("cryptography>40")
with pytest.raises(PyBuildDepsError):
Expand Down

0 comments on commit bd5d683

Please sign in to comment.