Skip to content

Commit e73110e

Browse files
committed
feat: create cli extra group
BREAKING CHANGE
1 parent d0b5857 commit e73110e

8 files changed

Lines changed: 67 additions & 34 deletions

File tree

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ help:
1616
venv:
1717
@if ! {{ venv-exists }}; \
1818
then \
19-
uv sync --frozen --all-groups; \
19+
uv sync --frozen --all-extras --all-groups; \
2020
fi
2121

2222
# Cleans all artifacts generated while running this project, including the virtualenv.

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ maintainers = [
1212
requires-python = "<4,>=3.9"
1313
dependencies = [
1414
"requests<3.0.0,>=2.32.4",
15-
"click<9.0.0,>=8.1.8",
16-
"rich<15.0.0,>=14.0.0",
1715
"rapidfuzz<4.0.0,>=2.13.7",
1816
"pyparsing<4.0.0,>=3.2.3",
1917
"tomlkit<0.14.0,>=0.11.6",
@@ -26,6 +24,13 @@ description = "Security tool against dependency typosquatting attacks"
2624
readme = "README.md"
2725
dynamic = ["version"]
2826

27+
[project.optional-dependencies]
28+
cli = [
29+
"click<9.0.0,>=8.1.8",
30+
"rich<15.0.0,>=14.0.0",
31+
]
32+
33+
2934
[tool.hatch.version]
3035
path = "VERSION"
3136
pattern = "v(?P<version>[^\\s]+)"

src/twyn/base/exceptions.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import logging
22
from typing import IO, Any, Optional
33

4-
import click
5-
64
logger = logging.getLogger("twyn")
75

86

@@ -19,14 +17,20 @@ def __init__(self, message: str = "") -> None:
1917
super().__init__(message or self.message)
2018

2119

22-
class CliError(click.ClickException):
23-
"""Error that will populate application errors to stdout. It does not inherit from `TwynError`."""
20+
try:
21+
import click
2422

25-
message = "CLI error"
23+
class CliError(click.ClickException):
24+
"""Error that will populate application errors to stdout. It does not inherit from `TwynError`."""
2625

27-
def __init__(self, message: str = "") -> None:
28-
super().__init__(message)
26+
message = "CLI error"
27+
28+
def __init__(self, message: str = "") -> None:
29+
super().__init__(message)
30+
31+
def show(self, file: Optional[IO[Any]] = None) -> None:
32+
logger.debug(self.format_message(), exc_info=True)
33+
logger.error(self.format_message(), exc_info=False)
2934

30-
def show(self, file: Optional[IO[Any]] = None) -> None:
31-
logger.debug(self.format_message(), exc_info=True)
32-
logger.error(self.format_message(), exc_info=False)
35+
except ModuleNotFoundError:
36+
pass

src/twyn/cli.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,38 @@
22
import sys
33
from typing import Optional
44

5-
import click
6-
from rich.console import Console
7-
from rich.logging import RichHandler
8-
95
from twyn.__version__ import __version__
106
from twyn.base.constants import (
117
DEFAULT_PROJECT_TOML_FILE,
128
DEPENDENCY_FILE_MAPPING,
139
SELECTOR_METHOD_MAPPING,
1410
)
15-
from twyn.base.exceptions import CliError, TwynError
11+
from twyn.base.exceptions import TwynError
1612
from twyn.config.config_handler import ConfigHandler
1713
from twyn.file_handler.file_handler import FileHandler
1814
from twyn.main import check_dependencies
1915
from twyn.trusted_packages.cache_handler import CacheHandler
2016
from twyn.trusted_packages.constants import CACHE_DIR
2117

18+
try:
19+
import click
20+
from rich.console import Console
21+
from rich.logging import RichHandler
22+
23+
from twyn.base.exceptions import CliError
24+
except ImportError:
25+
print("Could not run twyn as a cli tool, some dependencies are missing! Run `pip install twyn[cli]`.")
26+
import sys
27+
28+
sys.exit(1)
29+
30+
logger = logging.getLogger("twyn")
31+
2232
logging.basicConfig(
2333
format="%(message)s",
2434
datefmt="[%X]",
2535
handlers=[RichHandler(rich_tracebacks=True, show_path=False, console=Console(stderr=True))],
2636
)
27-
logger = logging.getLogger("twyn")
2837

2938

3039
@click.group()

src/twyn/main.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import logging
2+
from collections.abc import Iterable
23
from typing import Optional, Union
34

4-
from rich.progress import track
5-
65
from twyn.base.constants import (
76
SELECTOR_METHOD_MAPPING,
87
PackageEcosystems,
@@ -88,10 +87,8 @@ def check_dependencies(
8887
normalized_dependencies = top_package_reference.normalize_packages(dependencies_to_check)
8988

9089
typos_list = TyposquatCheckResultList()
91-
dependencies_list = (
92-
track(normalized_dependencies, description="Processing...") if show_progress_bar else normalized_dependencies
93-
)
94-
for dependency in dependencies_list:
90+
91+
for dependency in _get_dependencies_list(normalized_dependencies, show_progress_bar):
9592
if dependency in normalized_allowlist_packages:
9693
logger.info("Dependency %s is in the allowlist", dependency)
9794
continue
@@ -103,6 +100,19 @@ def check_dependencies(
103100
return typos_list
104101

105102

103+
def _get_dependencies_list(normalized_dependencies: set[str], show_progress_bar: bool) -> Iterable[str]:
104+
try:
105+
from rich.progress import track # noqa: PLC0415
106+
107+
return (
108+
track(normalized_dependencies, description="Processing...")
109+
if show_progress_bar
110+
else normalized_dependencies
111+
)
112+
except ImportError:
113+
return normalized_dependencies
114+
115+
106116
def _get_selector_method(selector_method: str) -> SelectorMethod:
107117
if selector_method not in SELECTOR_METHOD_MAPPING:
108118
InvalidSelectorMethodError("Invalid selector method")

tests/main/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
@pytest.fixture(scope="module")
99
def disable_track() -> Generator[None, Any, None]:
1010
"""Disables the track UI for running tests."""
11-
with patch("twyn.main.track") as m_track:
11+
with patch("rich.progress.track") as m_track:
1212
m_track.side_effect = lambda iterable, description: iterable
1313
yield

tests/main/test_main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def test_track_is_disabled_by_default_when_used_as_package(
312312
package_ecosystem=None,
313313
)
314314
mock_get_packages.return_value = {"requests"}
315-
with patch("twyn.main.track") as m_track:
315+
with patch("rich.progress.track") as m_track:
316316
check_dependencies()
317317
assert m_track.call_count == 0
318318

@@ -328,7 +328,7 @@ def test_track_is_shown_when_enabled(self, mock_config: Mock, mock_get_packages:
328328
package_ecosystem=None,
329329
)
330330
mock_get_packages.return_value = {"requests"}
331-
with patch("twyn.main.track") as m_track:
331+
with patch("rich.progress.track") as m_track:
332332
check_dependencies(show_progress_bar=True)
333333
assert m_track.call_count == 1
334334

uv.lock

Lines changed: 11 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)