Skip to content

Commit

Permalink
Make uv-secure executable (#3)
Browse files Browse the repository at this point in the history
* Reset version number and bumped dependencies

* Making package executable

Switched to version defined in code and added --version CLI option. Updated project script to run the Typer app. Made the uv.lock path an option that defaults to the working directory. Added and updated tests.

* Tweaking some of the option help text

* Added Usage section to the README.md
  • Loading branch information
owenlamont authored Dec 21, 2024
1 parent 807afe7 commit f68c73a
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 149 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ repos:
additional_dependencies:
- pydantic
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.8.2
rev: v0.8.4
hooks:
- id: ruff-format
types_or: [python, pyi, jupyter]
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi, jupyter]
- repo: https://github.com/crate-ci/typos
rev: v1.28.2
rev: v1.28.4
hooks:
- id: typos
args: [
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@

Scan your uv.lock file for dependencies with known vulnerabilities

## Usage

After installation you can run uv-secure --help to see the options.

```text
>> uv-secure --help
Usage: uv-secure [OPTIONS]
Parse a uv.lock file, check vulnerabilities, and display summary.
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
│ --uv-lock-path -p PATH Path to the uv.lock file [default: uv.lock] │
│ --ignore -i TEXT Comma-separated list of vulnerability IDs to │
│ ignore, e.g. VULN-123,VULN-456 │
│ --version Show the application's version │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy │
│ it or customize the installation. │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────╯
```

By default if run with no options uv-secure will look for a uv.lock file in the current
working directory and scan that for known vulnerabilities. E.g.

```text
>> uv-secure
Checking dependencies for vulnerabilities...
╭──────────────────────────────────╮
│ No vulnerabilities detected! │
│ Checked: 160 dependencies │
│ All dependencies appear safe! 🎉 │
╰──────────────────────────────────╯
```

## Related Work and Motivation

I created this package as I wanted a dependency vulnerability scanner but I wasn't
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uv-secure"
version = "0.2.0"
dynamic = ["version"]
description = "Scan your uv.lock file for dependencies with known vulnerabilities"
readme = "README.md"
authors = [
Expand Down Expand Up @@ -29,7 +29,7 @@ jupyter = [
]

[project.scripts]
uv-secure = "uv_secure:main"
uv-secure = "uv_secure.run:app"

[build-system]
requires = ["hatchling"]
Expand All @@ -38,6 +38,9 @@ build-backend = "hatchling.build"
[tool.bandit]
exclude_dirs = ["tests"]

[tool.hatch.version]
path = "src/uv_secure/__version__.py"

[tool.mypy]
plugins = [
"pydantic.mypy"
Expand Down
1 change: 1 addition & 0 deletions src/uv_secure/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.0"
28 changes: 27 additions & 1 deletion src/uv_secure/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from rich.text import Text
import typer

from uv_secure.__version__ import __version__


app = typer.Typer()

Expand Down Expand Up @@ -115,6 +117,7 @@ def check_dependencies(uv_lock_path: Path, ignore_ids: list[str]) -> int:
vulnerable_count += 1
vulnerabilities_found.append((dep, filtered_vulnerabilities))

inf = inflect.engine()
total_plural = inf.plural("dependency", total_dependencies)
vulnerable_plural = inf.plural("dependency", vulnerable_count)

Expand Down Expand Up @@ -159,16 +162,39 @@ def check_dependencies(uv_lock_path: Path, ignore_ids: list[str]) -> int:
return 0 # Exit successfully


def version_callback(value: bool) -> None:
if value:
typer.echo(f"uv-secure {__version__}")
raise typer.Exit()


_uv_lock_path_option = typer.Option(
Path("./uv.lock"), "--uv-lock-path", "-p", help="Path to the uv.lock file"
)


_ignore_option = typer.Option(
"",
"--ignore",
"-i",
help="Comma-separated list of vulnerability IDs to ignore, e.g. VULN-123,VULN-456",
)

_version_option = typer.Option(
None,
"--version",
callback=version_callback,
is_eager=True,
help="Show the application's version",
)


@app.command()
def main(uv_lock_path: Path, ignore: str = _ignore_option) -> int:
def main(
uv_lock_path: Path = _uv_lock_path_option,
ignore: str = _ignore_option,
version: bool = _version_option,
) -> int:
"""Parse a uv.lock file, check vulnerabilities, and display summary."""
ignore_ids = [vuln_id.strip() for vuln_id in ignore.split(",") if vuln_id.strip()]
return check_dependencies(uv_lock_path, ignore_ids)
Expand Down
8 changes: 7 additions & 1 deletion tests/uv_secure/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ def temp_uv_lock_file(tmp_path: Path) -> Path:

def test_app(mocker: MockFixture) -> None:
mock_check_dependencies = mocker.patch("uv_secure.run.check_dependencies")
result = runner.invoke(app, "uv.lock")
result = runner.invoke(app, ("--uv-lock-path", "uv.lock"))
mock_check_dependencies.assert_called_once_with(Path("uv.lock"), [])
assert result.exit_code == 0


def test_app_version() -> None:
result = runner.invoke(app, "--version")
assert result.exit_code == 0
assert "uv-secure " in result.output


def test_check_dependencies_no_vulnerabilities(
temp_uv_lock_file: Path, httpx_mock: HTTPXMock, capsys: CaptureFixture[str]
) -> None:
Expand Down
Loading

0 comments on commit f68c73a

Please sign in to comment.