From b6469b6970e403e0e923c8312bc65af6ce3aa9ac Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 13 Jan 2025 18:27:08 +0100 Subject: [PATCH] refactor: move ScannerUI outside of core.ui This fixes import loop errors when trying to import Commit. --- ggshield/cmd/secret/scan/archive.py | 3 +- ggshield/cmd/secret/scan/docset.py | 3 +- ggshield/cmd/secret/scan/path.py | 3 +- ggshield/cmd/secret/scan/precommit.py | 3 +- ggshield/cmd/secret/scan/pypi.py | 3 +- ggshield/core/scanner_ui/__init__.py | 33 +++++++++++++++++++ .../plain_text_scanner_ui.py | 2 +- .../rich => scanner_ui}/rich_scanner_ui.py | 2 +- .../core/{ui => scanner_ui}/scanner_ui.py | 0 ggshield/core/ui/__init__.py | 13 +++----- ggshield/core/ui/ggshield_ui.py | 20 ----------- .../ui/plain_text/plain_text_ggshield_ui.py | 8 ----- ggshield/core/ui/rich/rich_ggshield_ui.py | 9 ----- ggshield/verticals/secret/docker.py | 5 +-- ggshield/verticals/secret/repo.py | 3 +- ggshield/verticals/secret/secret_scanner.py | 2 +- .../verticals/secret/test_secret_scanner.py | 2 +- 17 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 ggshield/core/scanner_ui/__init__.py rename ggshield/core/{ui/plain_text => scanner_ui}/plain_text_scanner_ui.py (91%) rename ggshield/core/{ui/rich => scanner_ui}/rich_scanner_ui.py (96%) rename ggshield/core/{ui => scanner_ui}/scanner_ui.py (100%) diff --git a/ggshield/cmd/secret/scan/archive.py b/ggshield/cmd/secret/scan/archive.py index 217be8ba00..d28554c027 100644 --- a/ggshield/cmd/secret/scan/archive.py +++ b/ggshield/cmd/secret/scan/archive.py @@ -15,6 +15,7 @@ from ggshield.core.errors import UnexpectedError from ggshield.core.scan import ScanContext, ScanMode from ggshield.core.scan.file import create_files_from_paths +from ggshield.core.scanner_ui import create_scanner_ui from ggshield.utils.archive import safe_unpack from ggshield.utils.click import RealPath from ggshield.utils.files import ListFilesMode @@ -54,7 +55,7 @@ def archive_cmd( print_file_list(files, binary_paths) ui.display_heading("Starting scan") - with ui.create_scanner_ui(len(files)) as scanner_ui: + with create_scanner_ui(len(files)) as scanner_ui: scan_context = ScanContext( scan_mode=ScanMode.ARCHIVE, command_path=ctx.command_path, diff --git a/ggshield/cmd/secret/scan/docset.py b/ggshield/cmd/secret/scan/docset.py index 2bfbf93ceb..4d9e568656 100644 --- a/ggshield/cmd/secret/scan/docset.py +++ b/ggshield/cmd/secret/scan/docset.py @@ -12,6 +12,7 @@ from ggshield.core import ui from ggshield.core.client import create_client_from_config from ggshield.core.scan import ScanContext, ScanMode, Scannable, StringScannable +from ggshield.core.scanner_ui import create_message_only_scanner_ui from ggshield.core.ui.ggshield_ui import GGShieldProgress from ggshield.verticals.secret import SecretScanCollection, SecretScanner @@ -34,7 +35,7 @@ def create_scans_from_docset_files( ui.display_verbose(f"- {click.format_filename(input_file.name)}") files = generate_files_from_docsets(input_file) - with ui.create_message_only_scanner_ui() as scanner_ui: + with create_message_only_scanner_ui() as scanner_ui: results = scanner.scan(files, scanner_ui=scanner_ui) scans.append( SecretScanCollection(id=input_file.name, type="docset", results=results) diff --git a/ggshield/cmd/secret/scan/path.py b/ggshield/cmd/secret/scan/path.py index 92dce03a7d..78c66160b7 100644 --- a/ggshield/cmd/secret/scan/path.py +++ b/ggshield/cmd/secret/scan/path.py @@ -15,6 +15,7 @@ from ggshield.core.client import create_client_from_config from ggshield.core.scan import ScanContext, ScanMode, Scannable from ggshield.core.scan.file import create_files_from_paths +from ggshield.core.scanner_ui import create_scanner_ui from ggshield.utils.click import RealPath from ggshield.utils.files import ListFilesMode from ggshield.verticals.secret import SecretScanCollection, SecretScanner @@ -71,7 +72,7 @@ def path_cmd( ui.display_heading("Starting scan") target = paths[0] if len(paths) == 1 else Path.cwd() target_path = target if target.is_dir() else target.parent - with ui.create_scanner_ui(len(files)) as scanner_ui: + with create_scanner_ui(len(files)) as scanner_ui: scan_context = ScanContext( scan_mode=ScanMode.PATH, command_path=ctx.command_path, diff --git a/ggshield/cmd/secret/scan/precommit.py b/ggshield/cmd/secret/scan/precommit.py index 9f28c107df..3e63b9584b 100644 --- a/ggshield/cmd/secret/scan/precommit.py +++ b/ggshield/cmd/secret/scan/precommit.py @@ -14,6 +14,7 @@ from ggshield.core import ui from ggshield.core.client import create_client_from_config from ggshield.core.scan import Commit, ScanContext, ScanMode +from ggshield.core.scanner_ui import create_scanner_ui from ggshield.utils.git_shell import check_git_dir, git from ggshield.verticals.secret import SecretScanCollection, SecretScanner from ggshield.verticals.secret.output import SecretTextOutputHandler @@ -95,7 +96,7 @@ def precommit_cmd( scan_context=scan_context, secret_config=config.user_config.secret, ) - with ui.create_scanner_ui(len(commit.urls)) as scanner_ui: + with create_scanner_ui(len(commit.urls)) as scanner_ui: results = scanner.scan(commit.get_files(), scanner_ui) return_code = output_handler.process_scan( diff --git a/ggshield/cmd/secret/scan/pypi.py b/ggshield/cmd/secret/scan/pypi.py index e6cfb74dfd..70618bdb13 100644 --- a/ggshield/cmd/secret/scan/pypi.py +++ b/ggshield/cmd/secret/scan/pypi.py @@ -18,6 +18,7 @@ from ggshield.core.errors import UnexpectedError from ggshield.core.scan import ScanContext, ScanMode, Scannable from ggshield.core.scan.file import create_files_from_paths +from ggshield.core.scanner_ui import create_scanner_ui from ggshield.utils.archive import safe_unpack from ggshield.utils.files import ListFilesMode from ggshield.verticals.secret import SecretScanCollection, SecretScanner @@ -113,7 +114,7 @@ def pypi_cmd( print_file_list(files, binary_paths) ui.display_heading("Starting scan") - with ui.create_scanner_ui(len(files)) as scanner_ui: + with create_scanner_ui(len(files)) as scanner_ui: scan_context = ScanContext( scan_mode=ScanMode.PYPI, command_path=ctx.command_path, diff --git a/ggshield/core/scanner_ui/__init__.py b/ggshield/core/scanner_ui/__init__.py new file mode 100644 index 0000000000..125dedbd01 --- /dev/null +++ b/ggshield/core/scanner_ui/__init__.py @@ -0,0 +1,33 @@ +from ggshield.core.scanner_ui.plain_text_scanner_ui import PlainTextScannerUI +from ggshield.core.scanner_ui.rich_scanner_ui import ( + RichMessageOnlyScannerUI, + RichProgressScannerUI, +) +from ggshield.core.ui import get_ui +from ggshield.core.ui.rich import RichGGShieldUI + +from .scanner_ui import ScannerUI + + +def create_scanner_ui(total: int) -> ScannerUI: + """ + Creates a ScannerUI instance. This is used to show progress on scanning + Scannables. + """ + if isinstance(get_ui(), RichGGShieldUI): + return RichProgressScannerUI(get_ui(), total) + else: + return PlainTextScannerUI() + + +def create_message_only_scanner_ui(): + """ + Creates a ScannerUI instance without a progress bar. This is used when the scan + itself is part of a larger scan. For example when scanning a commit range, each + commit gets a message-only ScannerUI. Progress of the commit range scan is + represented by a progress bar created using `create_progress()`. + """ + if isinstance(get_ui(), RichGGShieldUI): + return RichMessageOnlyScannerUI(get_ui()) + else: + return PlainTextScannerUI() diff --git a/ggshield/core/ui/plain_text/plain_text_scanner_ui.py b/ggshield/core/scanner_ui/plain_text_scanner_ui.py similarity index 91% rename from ggshield/core/ui/plain_text/plain_text_scanner_ui.py rename to ggshield/core/scanner_ui/plain_text_scanner_ui.py index e241095e2a..043d3e7cda 100644 --- a/ggshield/core/ui/plain_text/plain_text_scanner_ui.py +++ b/ggshield/core/scanner_ui/plain_text_scanner_ui.py @@ -4,7 +4,7 @@ from ggshield.core import ui from ggshield.core.scan import Scannable -from ggshield.core.ui.scanner_ui import ScannerUI +from ggshield.core.scanner_ui.scanner_ui import ScannerUI class PlainTextScannerUI(ScannerUI): diff --git a/ggshield/core/ui/rich/rich_scanner_ui.py b/ggshield/core/scanner_ui/rich_scanner_ui.py similarity index 96% rename from ggshield/core/ui/rich/rich_scanner_ui.py rename to ggshield/core/scanner_ui/rich_scanner_ui.py index 1f7fed080a..d38c1bdb63 100644 --- a/ggshield/core/ui/rich/rich_scanner_ui.py +++ b/ggshield/core/scanner_ui/rich_scanner_ui.py @@ -3,8 +3,8 @@ from typing_extensions import Self from ggshield.core.scan import Scannable +from ggshield.core.scanner_ui.scanner_ui import ScannerUI from ggshield.core.ui.ggshield_ui import GGShieldUI -from ggshield.core.ui.scanner_ui import ScannerUI class RichMessageOnlyScannerUI(ScannerUI): diff --git a/ggshield/core/ui/scanner_ui.py b/ggshield/core/scanner_ui/scanner_ui.py similarity index 100% rename from ggshield/core/ui/scanner_ui.py rename to ggshield/core/scanner_ui/scanner_ui.py diff --git a/ggshield/core/ui/__init__.py b/ggshield/core/ui/__init__.py index eaa7a61309..90bd4c2151 100644 --- a/ggshield/core/ui/__init__.py +++ b/ggshield/core/ui/__init__.py @@ -9,7 +9,6 @@ from .ggshield_ui import GGShieldProgress, GGShieldUI, Level from .plain_text import PlainTextGGShieldUI -from .scanner_ui import ScannerUI # GGShieldUI instance to which top-level functions forward their output. @@ -49,6 +48,10 @@ def set_ui(ui: GGShieldUI) -> None: _ui = ui +def get_ui() -> GGShieldUI: + return _ui + + def display_debug(message: str) -> None: _ui.display_debug(message) @@ -81,14 +84,6 @@ def create_progress(total: int) -> GGShieldProgress: return _ui.create_progress(total) -def create_scanner_ui(total: int) -> ScannerUI: - return _ui.create_scanner_ui(total) - - -def create_message_only_scanner_ui() -> ScannerUI: - return _ui.create_message_only_scanner_ui() - - def _reset_ui(): """Reset the module to its startup state. Used by reset.reset().""" global _ui diff --git a/ggshield/core/ui/ggshield_ui.py b/ggshield/core/ui/ggshield_ui.py index 3ed93e0648..ad7f711a74 100644 --- a/ggshield/core/ui/ggshield_ui.py +++ b/ggshield/core/ui/ggshield_ui.py @@ -8,8 +8,6 @@ from typing_extensions import Self -from .scanner_ui import ScannerUI - class Level(IntEnum): ERROR = auto() @@ -56,24 +54,6 @@ class GGShieldUI(ABC): def __init__(self): self.level = Level.INFO - @abstractmethod - def create_scanner_ui(self, total: int) -> ScannerUI: - """ - Creates a ScannerUI instance. This is used to show progress on scanning - Scannables. - """ - ... - - @abstractmethod - def create_message_only_scanner_ui(self) -> ScannerUI: - """ - Creates a ScannerUI instance without a progress bar. This is used when the scan - itself is part of a larger scan. For example when scanning a commit range, each - commit gets a message-only ScannerUI. Progress of the commit range scan is - represented by a progress bar created using `create_progress()`. - """ - ... - @abstractmethod def create_progress(self, total: int) -> GGShieldProgress: """ diff --git a/ggshield/core/ui/plain_text/plain_text_ggshield_ui.py b/ggshield/core/ui/plain_text/plain_text_ggshield_ui.py index 0578f1583e..9db8414686 100644 --- a/ggshield/core/ui/plain_text/plain_text_ggshield_ui.py +++ b/ggshield/core/ui/plain_text/plain_text_ggshield_ui.py @@ -8,8 +8,6 @@ GGShieldUI, Level, ) -from ggshield.core.ui.plain_text.plain_text_scanner_ui import PlainTextScannerUI -from ggshield.core.ui.scanner_ui import ScannerUI class PlainTextGGShieldProgress(GGShieldProgress): @@ -59,12 +57,6 @@ def log(self, record: logging.LogRecord) -> None: self.display_warning(f"Unsupported log level {level}") self.display_error(msg) - def create_scanner_ui(self, total: int) -> ScannerUI: - return PlainTextScannerUI() - - def create_message_only_scanner_ui(self) -> ScannerUI: - return PlainTextScannerUI() - def create_progress(self, total: int) -> GGShieldProgress: return PlainTextGGShieldProgress() diff --git a/ggshield/core/ui/rich/rich_ggshield_ui.py b/ggshield/core/ui/rich/rich_ggshield_ui.py index a86f93a89a..6db5425a04 100644 --- a/ggshield/core/ui/rich/rich_ggshield_ui.py +++ b/ggshield/core/ui/rich/rich_ggshield_ui.py @@ -7,10 +7,7 @@ from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn from typing_extensions import Self -from ggshield.core.ui.scanner_ui import ScannerUI - from ..ggshield_ui import NAME_BY_LEVEL, DebugInfo, GGShieldProgress, GGShieldUI, Level -from .rich_scanner_ui import RichMessageOnlyScannerUI, RichProgressScannerUI COLOR_BY_LEVEL = { @@ -63,12 +60,6 @@ def __init__(self): self.console = Console(file=sys.stderr) self._previous_timestamp = "" - def create_scanner_ui(self, total: int) -> ScannerUI: - return RichProgressScannerUI(self, total) - - def create_message_only_scanner_ui(self) -> ScannerUI: - return RichMessageOnlyScannerUI(self) - def create_progress(self, total: int) -> GGShieldProgress: return RichGGShieldProgress(self.console, total) diff --git a/ggshield/verticals/secret/docker.py b/ggshield/verticals/secret/docker.py index 85deae6fad..157709e6f9 100644 --- a/ggshield/verticals/secret/docker.py +++ b/ggshield/verticals/secret/docker.py @@ -17,6 +17,7 @@ from ggshield.core.errors import UnexpectedError from ggshield.core.scan import ScanContext, Scannable, StringScannable from ggshield.core.scan.id_cache import IDCache +from ggshield.core.scanner_ui import create_scanner_ui from ggshield.utils.files import is_path_binary from .secret_scan_collection import SecretScanCollection @@ -340,7 +341,7 @@ def docker_scan_archive( with DockerImage.open(archive_path) as docker_image: ui.display_heading("Scanning Docker config") - with ui.create_scanner_ui(1) as scanner_ui: + with create_scanner_ui(1) as scanner_ui: results = scanner.scan( [docker_image.config_scannable], scanner_ui=scanner_ui ) @@ -356,7 +357,7 @@ def docker_scan_archive( ui.display_heading(f"Skipping layer {layer_id}: already scanned") else: ui.display_heading(f"Scanning layer {info.diff_id}") - with ui.create_scanner_ui(file_count) as scanner_ui: + with create_scanner_ui(file_count) as scanner_ui: layer_results = scanner.scan(files, scanner_ui=scanner_ui) if not layer_results.has_secrets: layer_id_cache.add(layer_id) diff --git a/ggshield/verticals/secret/repo.py b/ggshield/verticals/secret/repo.py index 6d13c2c529..4e0f95c688 100644 --- a/ggshield/verticals/secret/repo.py +++ b/ggshield/verticals/secret/repo.py @@ -16,6 +16,7 @@ from ggshield.core.constants import MAX_WORKERS from ggshield.core.errors import ExitCode, QuotaLimitReachedError, handle_exception from ggshield.core.scan import Commit, ScanContext +from ggshield.core.scanner_ui import create_message_only_scanner_ui from ggshield.core.text_utils import STYLE, format_text from ggshield.utils.git_shell import get_list_commit_SHA, is_git_dir from ggshield.utils.os import cd @@ -78,7 +79,7 @@ def scan_commits_content( check_api_key=False, # Key has been checked in `scan_commit_range()` secret_config=secret_config, ) - with ui.create_message_only_scanner_ui() as scanner_ui: + with create_message_only_scanner_ui() as scanner_ui: results = scanner.scan( commit_files, scan_threads=SCAN_THREADS, scanner_ui=scanner_ui ) diff --git a/ggshield/verticals/secret/secret_scanner.py b/ggshield/verticals/secret/secret_scanner.py index b56bce2800..f092766340 100644 --- a/ggshield/verticals/secret/secret_scanner.py +++ b/ggshield/verticals/secret/secret_scanner.py @@ -16,8 +16,8 @@ from ggshield.core.constants import MAX_WORKERS from ggshield.core.errors import MissingScopesError, UnexpectedError, handle_api_error from ggshield.core.scan import DecodeError, ScanContext, Scannable +from ggshield.core.scanner_ui.scanner_ui import ScannerUI from ggshield.core.text_utils import pluralize -from ggshield.core.ui.scanner_ui import ScannerUI from .secret_scan_collection import Error, Result, Results diff --git a/tests/unit/verticals/secret/test_secret_scanner.py b/tests/unit/verticals/secret/test_secret_scanner.py index 8dd83f2637..a0626ec2cc 100644 --- a/tests/unit/verticals/secret/test_secret_scanner.py +++ b/tests/unit/verticals/secret/test_secret_scanner.py @@ -31,7 +31,7 @@ Scannable, StringScannable, ) -from ggshield.core.ui.scanner_ui import ScannerUI +from ggshield.core.scanner_ui.scanner_ui import ScannerUI from ggshield.utils.git_shell import Filemode from ggshield.utils.os import get_os_info from ggshield.verticals.secret import SecretScanner