Skip to content

Commit

Permalink
Implemented directory scanning for requirements.txt files (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
owenlamont authored Jan 9, 2025
1 parent 389be2b commit e786680
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ repos:
# --write-changes (Don't use this to stop typos making auto-corrections)
]
- repo: https://github.com/owenlamont/uv-secure
rev: 0.3.2
rev: 0.4.0
hooks:
- id: uv-secure
- repo: https://github.com/adrienverge/yamllint.git
Expand Down
2 changes: 1 addition & 1 deletion src/uv_secure/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.0"
__version__ = "0.4.1"
8 changes: 5 additions & 3 deletions src/uv_secure/dependency_checker/dependency_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
config_file_factory,
Configuration,
)
from uv_secure.directory_scanner import get_lock_to_config_map
from uv_secure.directory_scanner import get_dependency_file_to_config_map
from uv_secure.package_info import (
download_vulnerabilities,
parse_requirements_txt_file,
Expand Down Expand Up @@ -145,7 +145,9 @@ async def check_lock_files(

console = Console()
if len(file_paths) == 1 and file_paths[0].is_dir():
lock_to_config_map = await get_lock_to_config_map(APath(file_paths[0]))
lock_to_config_map = await get_dependency_file_to_config_map(
APath(file_paths[0])
)
file_paths = tuple(lock_to_config_map.keys())
else:
if config_path is not None:
Expand All @@ -156,7 +158,7 @@ async def check_lock_files(
file_path.name in {"requirements.txt", "uv.lock"}
for file_path in file_paths
):
lock_to_config_map = await get_lock_to_config_map(
lock_to_config_map = await get_dependency_file_to_config_map(
[APath(file_path) for file_path in file_paths]
)
file_paths = tuple(lock_to_config_map.keys())
Expand Down
6 changes: 4 additions & 2 deletions src/uv_secure/directory_scanner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from uv_secure.directory_scanner.directory_scanner import get_lock_to_config_map
from uv_secure.directory_scanner.directory_scanner import (
get_dependency_file_to_config_map,
)


__all__ = ["get_lock_to_config_map"]
__all__ = ["get_dependency_file_to_config_map"]
32 changes: 23 additions & 9 deletions src/uv_secure/directory_scanner/directory_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _get_root_dir(file_paths: Sequence[Path]) -> Path:
return Path(*common_parts)


async def get_lock_to_config_map(
async def get_dependency_file_to_config_map(
file_paths: Union[Path, list[Path]],
) -> dict[Path, Configuration]:
"""Get map of uv.lock files to their configurations.
Expand All @@ -65,15 +65,27 @@ async def get_lock_to_config_map(
if type(file_paths) is Path:
root_dir = await file_paths.resolve()
config_and_lock_files = await _find_files(
root_dir, ["pyproject.toml", "uv-secure.toml", ".uv-secure.toml", "uv.lock"]
root_dir,
[
"pyproject.toml",
"uv-secure.toml",
".uv-secure.toml",
"uv.lock",
"requirements.txt",
],
)
else:
resolved_paths = await _resolve_paths(file_paths)
root_dir = _get_root_dir(resolved_paths)
config_and_lock_files = await _find_files(
root_dir, ["pyproject.toml", "uv-secure.toml", ".uv-secure.toml"]
)
config_and_lock_files["uv.lock"] = resolved_paths
config_and_lock_files["uv.lock"] = [
path for path in resolved_paths if path.name == "uv.lock"
]
config_and_lock_files["requirements.txt"] = [
path for path in resolved_paths if path.name == "requirements.txt"
]

config_file_paths = (
config_and_lock_files["pyproject.toml"]
Expand All @@ -90,11 +102,13 @@ async def get_lock_to_config_map(
p.parent: c for p, c in zip(config_file_paths, configs) if c is not None
}

lock_file_paths = config_and_lock_files.get("uv.lock", [])
lock_to_config_map: dict[Path, Configuration] = {}
dependency_file_paths = config_and_lock_files.get(
"uv.lock", []
) + config_and_lock_files.get("requirements.txt", [])
dependency_file_to_config_map: dict[Path, Configuration] = {}
default_config = Configuration()
for lock_file in lock_file_paths:
current_dir = lock_file.parent
for dependency_file in dependency_file_paths:
current_dir = dependency_file.parent
while True:
found_config = path_config_map.get(current_dir)
if found_config is not None or current_dir == root_dir:
Expand All @@ -103,5 +117,5 @@ async def get_lock_to_config_map(

if found_config is None:
found_config = default_config
lock_to_config_map[lock_file] = found_config
return lock_to_config_map
dependency_file_to_config_map[dependency_file] = found_config
return dependency_file_to_config_map
14 changes: 14 additions & 0 deletions tests/uv_secure/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ def test_app_no_vulnerabilities_requirements_txt(
assert "All dependencies appear safe!" in result.output


def test_app_no_vulnerabilities_requirements_txt_no_specified_path(
tmp_path: Path,
temp_uv_requirements_txt_file: Path,
no_vulnerabilities_response: HTTPXMock,
) -> None:
os.chdir(tmp_path)
result = runner.invoke(app)

assert result.exit_code == 0
assert "No vulnerabilities detected!" in result.output
assert "Checked: 1 dependency" in result.output
assert "All dependencies appear safe!" in result.output


def test_app_no_vulnerabilities_relative_lock_file_path(
tmp_path: Path, temp_uv_lock_file: Path, no_vulnerabilities_response: HTTPXMock
) -> None:
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e786680

Please sign in to comment.