From e011ab4fa24e3af207d676c0dea848b513e42c65 Mon Sep 17 00:00:00 2001 From: Roman Lutz Date: Fri, 27 Feb 2026 07:08:38 -0800 Subject: [PATCH 1/2] Enable ruff rule E (pycodestyle errors) Enable the E rule category covering: - E402: module import not at top of file (fixed docstring placement, noqa for intentional) - E501: line too long (wrapped long strings/docstrings to 120 chars) - E721: type comparison (use 'is' instead of '==') - E731: lambda assignment (converted to def) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyproject.toml | 1 + pyrit/backend/mappers/attack_mappers.py | 4 +- pyrit/backend/routes/attacks.py | 4 +- pyrit/cli/pyrit_backend.py | 2 +- pyrit/cli/pyrit_shell.py | 38 +++++++++++++------ pyrit/datasets/jailbreak/text_jailbreak.py | 3 +- .../remote/vlsu_multimodal_dataset.py | 6 ++- .../attack/multi_turn/chunked_request.py | 3 +- .../openai/openai_image_target.py | 4 +- pyrit/prompt_target/openai/openai_target.py | 3 +- pyrit/registry/discovery.py | 4 +- pyrit/scenario/core/scenario_strategy.py | 4 +- pyrit/scenario/scenarios/airt/jailbreak.py | 3 +- pyrit/score/audio_transcript_scorer.py | 3 +- .../scorer_evaluation/scorer_metrics_io.py | 3 +- .../attack/multi_turn/test_red_teaming.py | 2 +- .../score/test_scorer_prompt_validator.py | 2 +- 17 files changed, 61 insertions(+), 28 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d87582c389..73f06338cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -257,6 +257,7 @@ select = [ "CPY001", # missing-copyright-notice "D", # https://docs.astral.sh/ruff/rules/#pydocstyle-d "DOC", # https://docs.astral.sh/ruff/rules/#pydoclint-doc + "E", # https://docs.astral.sh/ruff/rules/#pycodestyle-e "F401", # unused-import "I", # isort "PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie diff --git a/pyrit/backend/mappers/attack_mappers.py b/pyrit/backend/mappers/attack_mappers.py index c9c8dc7af2..fa1ea39a8d 100644 --- a/pyrit/backend/mappers/attack_mappers.py +++ b/pyrit/backend/mappers/attack_mappers.py @@ -1,8 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. -from __future__ import annotations - """ Attack mappers – domain ↔ DTO translation for attack-related models. @@ -10,6 +8,8 @@ The one exception is `attack_result_to_summary` which receives pre-fetched pieces. """ +from __future__ import annotations + import mimetypes import uuid from datetime import datetime, timezone diff --git a/pyrit/backend/routes/attacks.py b/pyrit/backend/routes/attacks.py index ed6fc4c029..ba90cc66cb 100644 --- a/pyrit/backend/routes/attacks.py +++ b/pyrit/backend/routes/attacks.py @@ -55,7 +55,9 @@ async def list_attacks( attack_class: Optional[str] = Query(None, description="Filter by exact attack class name"), converter_classes: Optional[list[str]] = Query( None, - description="Filter by converter class names (repeatable, AND logic). Pass empty to match no-converter attacks.", + description=( + "Filter by converter class names (repeatable, AND logic). Pass empty to match no-converter attacks." + ), ), outcome: Optional[Literal["undetermined", "success", "failure"]] = Query(None, description="Filter by outcome"), label: Optional[list[str]] = Query(None, description="Filter by labels (format: key:value, repeatable)"), diff --git a/pyrit/cli/pyrit_backend.py b/pyrit/cli/pyrit_backend.py index a3e3fe647f..07cde5ee74 100644 --- a/pyrit/cli/pyrit_backend.py +++ b/pyrit/cli/pyrit_backend.py @@ -17,7 +17,7 @@ sys.stdout.reconfigure(errors="replace") # type: ignore[union-attr] sys.stderr.reconfigure(errors="replace") # type: ignore[union-attr] -from pyrit.cli import frontend_core +from pyrit.cli import frontend_core # noqa: E402 def parse_args(*, args: Optional[list[str]] = None) -> Namespace: diff --git a/pyrit/cli/pyrit_shell.py b/pyrit/cli/pyrit_shell.py index 2c218f237b..14c3b57f9f 100644 --- a/pyrit/cli/pyrit_shell.py +++ b/pyrit/cli/pyrit_shell.py @@ -161,12 +161,19 @@ def do_run(self, line: str) -> None: --log-level Override default log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) Examples: - run garak.encoding --initializers openai_objective_target load_default_datasets - run garak.encoding --initializers custom_target load_default_datasets --strategies base64 rot13 - run foundry --initializers openai_objective_target load_default_datasets --max-concurrency 10 --max-retries 3 - run garak.encoding --initializers custom_target load_default_datasets --memory-labels '{"run_id":"test123","env":"dev"}' - run foundry --initializers openai_objective_target load_default_datasets -s jailbreak crescendo - run garak.encoding --initializers openai_objective_target load_default_datasets --database InMemory --log-level DEBUG + run garak.encoding --initializers openai_objective_target \ + load_default_datasets + run garak.encoding --initializers custom_target \ + load_default_datasets --strategies base64 rot13 + run foundry --initializers openai_objective_target \ + load_default_datasets --max-concurrency 10 --max-retries 3 + run garak.encoding --initializers custom_target \ + load_default_datasets \ + --memory-labels '{"run_id":"test123","env":"dev"}' + run foundry --initializers openai_objective_target \ + load_default_datasets -s jailbreak crescendo + run garak.encoding --initializers openai_objective_target \ + load_default_datasets --database InMemory --log-level DEBUG run foundry --initialization-scripts ./my_custom_init.py -s all Note: @@ -182,17 +189,19 @@ def do_run(self, line: str) -> None: print("\nOptions:") print(f" --initializers ... {frontend_core.ARG_HELP['initializers']} (REQUIRED)") print( - f" --initialization-scripts <...> {frontend_core.ARG_HELP['initialization_scripts']} (alternative to --initializers)" + f" --initialization-scripts <...> {frontend_core.ARG_HELP['initialization_scripts']}" + " (alternative to --initializers)" ) print(f" --strategies, -s ... {frontend_core.ARG_HELP['scenario_strategies']}") print(f" --max-concurrency {frontend_core.ARG_HELP['max_concurrency']}") print(f" --max-retries {frontend_core.ARG_HELP['max_retries']}") print(f" --memory-labels {frontend_core.ARG_HELP['memory_labels']}") print( - f" --database Override default database ({frontend_core.IN_MEMORY}, {frontend_core.SQLITE}, {frontend_core.AZURE_SQL})" + f" --database Override default database" + f" ({frontend_core.IN_MEMORY}, {frontend_core.SQLITE}, {frontend_core.AZURE_SQL})" ) print( - f" --log-level Override default log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)" + " --log-level Override default log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)" ) print("\nExample:") print(" run foundry --initializers openai_objective_target load_default_datasets") @@ -464,7 +473,11 @@ def main() -> int: "--database", choices=[frontend_core.IN_MEMORY, frontend_core.SQLITE, frontend_core.AZURE_SQL], default=frontend_core.SQLITE, - help=f"Default database type to use ({frontend_core.IN_MEMORY}, {frontend_core.SQLITE}, {frontend_core.AZURE_SQL}) (default: {frontend_core.SQLITE}, can be overridden per-run)", + help=( + f"Default database type to use" + f" ({frontend_core.IN_MEMORY}, {frontend_core.SQLITE}, {frontend_core.AZURE_SQL})" + f" (default: {frontend_core.SQLITE}, can be overridden per-run)" + ), ) parser.add_argument( @@ -472,7 +485,10 @@ def main() -> int: type=str, choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="WARNING", - help="Default logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) (default: WARNING, can be overridden per-run)", + help=( + "Default logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)" + " (default: WARNING, can be overridden per-run)" + ), ) parser.add_argument( diff --git a/pyrit/datasets/jailbreak/text_jailbreak.py b/pyrit/datasets/jailbreak/text_jailbreak.py index 94e317d368..87e7b40b99 100644 --- a/pyrit/datasets/jailbreak/text_jailbreak.py +++ b/pyrit/datasets/jailbreak/text_jailbreak.py @@ -229,7 +229,8 @@ def get_jailbreak_templates(cls, num_templates: Optional[int] = None) -> list[st if num_templates: if num_templates > len(jailbreak_template_names): raise ValueError( - f"Attempted to pull {num_templates} jailbreaks from a dataset with only {len(jailbreak_template_names)} jailbreaks!" + f"Attempted to pull {num_templates} jailbreaks from a dataset" + f" with only {len(jailbreak_template_names)} jailbreaks!" ) jailbreak_template_names = random.choices(jailbreak_template_names, k=num_templates) return jailbreak_template_names diff --git a/pyrit/datasets/seed_datasets/remote/vlsu_multimodal_dataset.py b/pyrit/datasets/seed_datasets/remote/vlsu_multimodal_dataset.py index 59bc8406b4..2a0f2dba7d 100644 --- a/pyrit/datasets/seed_datasets/remote/vlsu_multimodal_dataset.py +++ b/pyrit/datasets/seed_datasets/remote/vlsu_multimodal_dataset.py @@ -254,7 +254,11 @@ async def _fetch_and_save_image_async(self, image_url: str, group_id: str) -> st # Add browser-like headers for better success rate headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "User-Agent": ( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)" + " AppleWebKit/537.36 (KHTML, like Gecko)" + " Chrome/120.0.0.0 Safari/537.36" + ), "Accept": "image/webp,image/apng,image/*,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br", diff --git a/pyrit/executor/attack/multi_turn/chunked_request.py b/pyrit/executor/attack/multi_turn/chunked_request.py index 392aa0333e..19c62b1996 100644 --- a/pyrit/executor/attack/multi_turn/chunked_request.py +++ b/pyrit/executor/attack/multi_turn/chunked_request.py @@ -102,7 +102,8 @@ def __init__( chunk_size (int): Size of each chunk to request (default: 50). total_length (int): Estimated total length of the target value (default: 200). chunk_type (str): Type of chunk to request (e.g., "characters", "bytes", "words"). - request_template (str): Template for generating chunk requests (default: "Give me {chunk_type} {start}-{end} of '{objective}'"). + request_template (str): Template for generating chunk requests + (default: "Give me {chunk_type} {start}-{end} of '{objective}'"). attack_converter_config (Optional[AttackConverterConfig]): Configuration for prompt converters. attack_scoring_config (Optional[AttackScoringConfig]): Configuration for scoring components. prompt_normalizer (Optional[PromptNormalizer]): Normalizer for handling prompts. diff --git a/pyrit/prompt_target/openai/openai_image_target.py b/pyrit/prompt_target/openai/openai_image_target.py index 1c65ed6030..cafc8c71f1 100644 --- a/pyrit/prompt_target/openai/openai_image_target.py +++ b/pyrit/prompt_target/openai/openai_image_target.py @@ -54,7 +54,9 @@ def __init__( max_requests_per_minute (int, Optional): Number of requests the target can handle per minute before hitting a rate limit. The number of requests sent to the target will be capped at the value provided. - image_size (Literal["256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "1792x1024", "1024x1792"], Optional): The size of the generated image. + image_size: The size of the generated image. + Accepts "256x256", "512x512", "1024x1024", "1536x1024", + "1024x1536", "1792x1024", or "1024x1792". Different models support different image sizes. GPT image models support "1024x1024", "1536x1024" and "1024x1536". DALL-E-3 supports "1024x1024", "1792x1024" and "1024x1792". diff --git a/pyrit/prompt_target/openai/openai_target.py b/pyrit/prompt_target/openai/openai_target.py index bf9c46bf6e..04d6c759a2 100644 --- a/pyrit/prompt_target/openai/openai_target.py +++ b/pyrit/prompt_target/openai/openai_target.py @@ -63,7 +63,8 @@ def _ensure_async_token_provider( # Wrap synchronous token provider in async function logger.info( - "Detected synchronous token provider. Automatically wrapping in async function for compatibility with AsyncOpenAI." + "Detected synchronous token provider." + " Automatically wrapping in async function for compatibility with AsyncOpenAI." ) async def async_token_provider() -> str: diff --git a/pyrit/registry/discovery.py b/pyrit/registry/discovery.py index 3ba7174f2b..5df0c14fee 100644 --- a/pyrit/registry/discovery.py +++ b/pyrit/registry/discovery.py @@ -114,7 +114,9 @@ def discover_in_package( Tuples of (registry_name, class) for each discovered subclass. """ if name_builder is None: - name_builder = lambda prefix, name: name if not prefix else f"{prefix}.{name}" + + def name_builder(prefix: str, name: str) -> str: + return name if not prefix else f"{prefix}.{name}" for _, module_name, is_pkg in pkgutil.iter_modules([str(package_path)]): if module_name.startswith("_"): diff --git a/pyrit/scenario/core/scenario_strategy.py b/pyrit/scenario/core/scenario_strategy.py index c18ed60c14..b965992af4 100644 --- a/pyrit/scenario/core/scenario_strategy.py +++ b/pyrit/scenario/core/scenario_strategy.py @@ -1,8 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. -from __future__ import annotations - """ Base class for scenario attack strategies with group-based aggregation. @@ -13,6 +11,8 @@ It also provides ScenarioCompositeStrategy for representing composed attack strategies. """ +from __future__ import annotations + from enum import Enum from typing import TYPE_CHECKING, TypeVar diff --git a/pyrit/scenario/scenarios/airt/jailbreak.py b/pyrit/scenario/scenarios/airt/jailbreak.py index 22632064b5..b26c462a3b 100644 --- a/pyrit/scenario/scenarios/airt/jailbreak.py +++ b/pyrit/scenario/scenarios/airt/jailbreak.py @@ -151,7 +151,8 @@ def __init__( jailbreak_names = [] if jailbreak_names and num_templates: raise ValueError( - "Please provide only one of `num_templates` (random selection) or `jailbreak_names` (specific selection)." + "Please provide only one of `num_templates` (random selection)" + " or `jailbreak_names` (specific selection)." ) if not objective_scorer: diff --git a/pyrit/score/audio_transcript_scorer.py b/pyrit/score/audio_transcript_scorer.py index 92de08def0..b0d0ad2a92 100644 --- a/pyrit/score/audio_transcript_scorer.py +++ b/pyrit/score/audio_transcript_scorer.py @@ -248,7 +248,8 @@ def extract_audio_from_video(video_path: str) -> Optional[str]: # Ensure 16-bit audio if audio.sample_width != AudioTranscriptHelper._DEFAULT_SAMPLE_WIDTH: logger.info( - f"Converting sample width from {audio.sample_width * 8}-bit to {AudioTranscriptHelper._DEFAULT_SAMPLE_WIDTH * 8}-bit" + f"Converting sample width from {audio.sample_width * 8}-bit" + f" to {AudioTranscriptHelper._DEFAULT_SAMPLE_WIDTH * 8}-bit" ) audio = audio.set_sample_width(AudioTranscriptHelper._DEFAULT_SAMPLE_WIDTH) diff --git a/pyrit/score/scorer_evaluation/scorer_metrics_io.py b/pyrit/score/scorer_evaluation/scorer_metrics_io.py index 07c9f83bd9..d77bae82f9 100644 --- a/pyrit/score/scorer_evaluation/scorer_metrics_io.py +++ b/pyrit/score/scorer_evaluation/scorer_metrics_io.py @@ -436,7 +436,8 @@ def replace_evaluation_results( replaced = len(existing_entries) != len(filtered_entries) action = "Replaced" if replaced else "Added" logger.info( - f"{action} metrics for {scorer_identifier.class_name} (eval_hash={eval_hash[:8]}...) in {file_path.name}" + f"{action} metrics for {scorer_identifier.class_name}" + f" (eval_hash={eval_hash[:8]}...) in {file_path.name}" ) except Exception as e: diff --git a/tests/unit/executor/attack/multi_turn/test_red_teaming.py b/tests/unit/executor/attack/multi_turn/test_red_teaming.py index 7344206e08..f0a4827b34 100644 --- a/tests/unit/executor/attack/multi_turn/test_red_teaming.py +++ b/tests/unit/executor/attack/multi_turn/test_red_teaming.py @@ -231,7 +231,7 @@ def test_init_with_seed_prompt_variations( ) assert attack._adversarial_chat_seed_prompt.value == expected_value - if expected_type == str: + if expected_type is str: assert attack._adversarial_chat_seed_prompt.data_type == "text" def test_init_with_invalid_system_prompt_path_raises_error( diff --git a/tests/unit/score/test_scorer_prompt_validator.py b/tests/unit/score/test_scorer_prompt_validator.py index 3d461016bd..289233de17 100644 --- a/tests/unit/score/test_scorer_prompt_validator.py +++ b/tests/unit/score/test_scorer_prompt_validator.py @@ -348,7 +348,7 @@ def test_max_text_length_default_none_allows_all(self): assert validator.is_message_piece_supported(very_long_piece) is True def test_validate_raises_when_all_pieces_filtered_by_length(self): - """Test that validate raises error when all pieces are filtered due to length and raise_on_no_valid_pieces=True.""" + """Test validate raises error when all pieces filtered by length and raise_on_no_valid_pieces=True.""" validator = ScorerPromptValidator( supported_data_types=["text"], max_text_length=100, raise_on_no_valid_pieces=True ) From 54b66d2dcdfad5c6459308b60920a9658199647c Mon Sep 17 00:00:00 2001 From: Roman Lutz Date: Mon, 2 Mar 2026 16:08:57 -0800 Subject: [PATCH 2/2] fix: restore type info in image_size docstring, move reconfigure into main() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyrit/cli/pyrit_backend.py | 12 ++++++------ pyrit/prompt_target/openai/openai_image_target.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrit/cli/pyrit_backend.py b/pyrit/cli/pyrit_backend.py index 07cde5ee74..8ec6ae872a 100644 --- a/pyrit/cli/pyrit_backend.py +++ b/pyrit/cli/pyrit_backend.py @@ -12,12 +12,7 @@ from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter from typing import Optional -# Ensure emoji and other Unicode characters don't crash on Windows consoles -# that use legacy encodings like cp1252. -sys.stdout.reconfigure(errors="replace") # type: ignore[union-attr] -sys.stderr.reconfigure(errors="replace") # type: ignore[union-attr] - -from pyrit.cli import frontend_core # noqa: E402 +from pyrit.cli import frontend_core def parse_args(*, args: Optional[list[str]] = None) -> Namespace: @@ -196,6 +191,11 @@ def main(*, args: Optional[list[str]] = None) -> int: Returns: int: Exit code (0 for success, 1 for error). """ + # Ensure emoji and other Unicode characters don't crash on Windows consoles + # that use legacy encodings like cp1252. + sys.stdout.reconfigure(errors="replace") # type: ignore[union-attr] + sys.stderr.reconfigure(errors="replace") # type: ignore[union-attr] + try: parsed_args = parse_args(args=args) except SystemExit as e: diff --git a/pyrit/prompt_target/openai/openai_image_target.py b/pyrit/prompt_target/openai/openai_image_target.py index cafc8c71f1..4eef78ca9f 100644 --- a/pyrit/prompt_target/openai/openai_image_target.py +++ b/pyrit/prompt_target/openai/openai_image_target.py @@ -54,7 +54,7 @@ def __init__( max_requests_per_minute (int, Optional): Number of requests the target can handle per minute before hitting a rate limit. The number of requests sent to the target will be capped at the value provided. - image_size: The size of the generated image. + image_size (Literal, Optional): The size of the generated image. Accepts "256x256", "512x512", "1024x1024", "1536x1024", "1024x1536", "1792x1024", or "1024x1792". Different models support different image sizes.