Skip to content

MAINT Enable ruff PGH rule for pygrep-hooks linting#1416

Open
romanlutz wants to merge 1 commit intoAzure:mainfrom
romanlutz:romanlutz/ruff_pgh
Open

MAINT Enable ruff PGH rule for pygrep-hooks linting#1416
romanlutz wants to merge 1 commit intoAzure:mainfrom
romanlutz:romanlutz/ruff_pgh

Conversation

@romanlutz
Copy link
Contributor

  • Add PGH to ruff select list
  • Fix PGH003: replace bare # type: ignore\ with specific mypy error codes
  • Fix PGH004: use colon syntax for # noqa:\ directives
  • Add docstring to MessagePiece.to_message (exposed by fixing blanket noqa)
  • Remove obsolete flake8 directive in test_unicode_confusable_converter.py

Copilot AI review requested due to automatic review settings February 27, 2026 22:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables Ruff’s PGH (pygrep-hooks) lint rules and updates existing # type: ignore / # noqa directives across the codebase to satisfy the new lint requirements, plus a small docstring/cleanup touch-up.

Changes:

  • Enable PGH in Ruff’s selected rule set.
  • Replace bare # type: ignore with code-specific ignores and normalize # noqa: syntax.
  • Minor hygiene updates (docstring addition, remove obsolete flake8: noqa).

Reviewed changes

Copilot reviewed 48 out of 48 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pyproject.toml Enable Ruff PGH rules
pyrit/auth/copilot_authenticator.py Make type: ignore codes explicit
pyrit/cli/frontend_core.py Narrow type: ignore codes
pyrit/common/display_response.py Normalize noqa / ignore syntax
pyrit/common/notebook_utils.py Normalize type: ignore code
pyrit/common/yaml_loadable.py Use specific type: ignore
pyrit/datasets/seed_datasets/remote/red_team_social_bias_dataset.py Type-ignore **kwargs expansion
pyrit/memory/azure_sql_memory.py Narrow ignore on __tablename__
pyrit/memory/memory_interface.py Narrow ignore in query conditions
pyrit/memory/memory_models.py Narrow ignores on entry field access
pyrit/memory/sqlite_memory.py Narrow ignores on ORM attrs/export
pyrit/models/message_piece.py Add docstring; normalize ignores/noqa
pyrit/models/seeds/seed.py Narrow ignore on Jinja env init
pyrit/prompt_converter/selective_text_converter.py Narrow ignore on strategy call
pyrit/prompt_target/azure_ml_chat_target.py Narrow ignore in exception path
pyrit/prompt_target/openai/openai_chat_target.py Narrow ignore for multimodal dict parts
pyrit/prompt_target/openai/openai_target.py Narrow ignore for token provider wrapper
pyrit/prompt_target/text_target.py Narrow ignores in CSV import
pyrit/scenario/core/scenario.py Narrow ignore for REQUIRED_VALUE default
pyrit/scenario/scenarios/airt/psychosocial_scenario.py Narrow ignore on scorer wrapping
pyrit/score/conversation_scorer.py Narrow ignore on dynamic class bases
tests/integration/ai_recruiter/test_ai_recruiter.py Narrow ignore on async call
tests/unit/common/test_helper_functions.py Narrow ignore for invalid arg test
tests/unit/converter/test_add_image_text_converter.py Narrow ignore for invalid arg test
tests/unit/converter/test_add_text_image_converter.py Narrow ignore for invalid arg test
tests/unit/converter/test_azure_speech_converter.py Narrow ignore for invalid arg test
tests/unit/converter/test_bin_ascii_converter.py Narrow ignore for invalid attr/arg tests
tests/unit/converter/test_image_compression_converter.py Narrow ignore for invalid arg tests
tests/unit/converter/test_math_prompt_converter.py Narrow ignore for invalid arg test
tests/unit/converter/test_prompt_converter.py Normalize noqa: + narrow ignore
tests/unit/converter/test_unicode_confusable_converter.py Remove obsolete flake8: noqa
tests/unit/executor/workflow/test_xpia.py Narrow ignore for invalid arg tests
tests/unit/memory/memory_interface/test_interface_attack_results.py Narrow ignore for collection comparisons
tests/unit/memory/test_azure_sql_memory.py Narrow ignores around sessions
tests/unit/models/test_message_piece.py Narrow ignore for invalid arg tests
tests/unit/prompt_normalizer/test_prompt_normalizer.py Narrow ignore for invalid arg/attr tests
tests/unit/scenarios/test_cyber.py Narrow ignore for scorer target access
tests/unit/scenarios/test_jailbreak.py Narrow ignore for scorer target access
tests/unit/scenarios/test_leakage_scenario.py Narrow ignore for scorer internals
tests/unit/scenarios/test_scam.py Narrow ignore for scorer target access
tests/unit/score/test_conversation_history_scorer.py Narrow ignore for dynamic attrs
tests/unit/score/test_true_false_composite_scorer.py Narrow ignore for invalid scorer test
tests/unit/setup/test_configuration_loader.py Narrow ignore for invalid arg test
tests/unit/setup/test_initialization.py Narrow ignore for invalid arg test
tests/unit/setup/test_pyrit_initializer.py Narrow ignores for abstract/None usage
tests/unit/target/test_huggingface_chat_target.py Narrow ignore for arg type
tests/unit/target/test_openai_chat_target.py Narrow ignores in multimodal assertions/mocks
tests/unit/target/test_openai_response_target.py Narrow ignores in multimodal assertions/mocks

self,
*,
objective_target: PromptTarget = REQUIRED_VALUE, # type: ignore
objective_target: PromptTarget = REQUIRED_VALUE, # type: ignore[attr-defined]
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

objective_target is typed as PromptTarget but the default value is the REQUIRED_VALUE sentinel. The ignore code attr-defined won’t match the mypy error here (typically assignment for an incompatible default), which will break the mypy --strict pre-commit hook. Use the correct ignore code (or adjust the type to include the sentinel) so mypy is actually suppressed.

Suggested change
objective_target: PromptTarget = REQUIRED_VALUE, # type: ignore[attr-defined]
objective_target: PromptTarget = REQUIRED_VALUE, # type: ignore[assignment]

Copilot uses AI. Check for mistakes.
Comment on lines 217 to 225
try:
return str(response.json()["output"])
except Exception as e:
if response.json() == {}:
raise EmptyResponseException(message="The chat returned an empty response.") from e
raise e(
f"Exception obtaining response from the target. Returned response: {response.json()}. "
+ f"Exception: {str(e)}" # type: ignore
+ f"Exception: {str(e)}" # type: ignore[union-attr]
) from e
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the exception handler, e is an exception instance, so raise e(...) will raise TypeError and mask the real parsing error. Raise a concrete exception type instead (e.g., raise RuntimeError(...) from e or raise type(e)(...) from e). The current type: ignore[...] on the string concatenation also looks unrelated to the actual issue.

Copilot uses AI. Check for mistakes.
Comment on lines 233 to 237
minutes_left = (expiry_time - current_time).total_seconds() / 60
logger.info(f"Cached token is valid for another {minutes_left:.2f} minutes")

return token_data # type: ignore
return token_data # type: ignore[union-attr]

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This # type: ignore[union-attr] on the return statement is unlikely to match the actual mypy error under --strict (commonly no-any-return here, since json.loads(...) yields Any). If the ignore code doesn’t match, mypy will both report the real error and treat this ignore as unused. Prefer typing/casting the parsed JSON to dict[str, Any] (or update the ignore to the correct mypy error code).

Copilot uses AI. Check for mistakes.
Comment on lines 449 to 454
self._save_token_to_cache(token=bearer_token, expires_in=token_expires_in)
else:
logger.error(f"Failed to retrieve bearer token within {self._token_capture_timeout} seconds.")

return bearer_token # type: ignore
return bearer_token # type: ignore[union-attr]
except Exception as e:
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here: bearer_token is derived from json.loads, so it is easy for mypy to infer Any and raise no-any-return under --strict. # type: ignore[union-attr] won’t suppress that and may be flagged as an unused ignore. Consider casting data["access_token"] to str (and expires_in to int | None) or use the correct ignore code.

Copilot uses AI. Check for mistakes.
Comment on lines 440 to 442
# Wrap with conversation scorer to evaluate full conversation history
conversation_scorer: FloatScaleScorer = create_conversation_scorer(scorer=psych_scorer) # type: ignore
conversation_scorer: FloatScaleScorer = create_conversation_scorer(scorer=psych_scorer) # type: ignore[union-attr]

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_conversation_scorer is annotated to return Scorer, so assigning it to a FloatScaleScorer-typed variable typically triggers a mypy assignment error (not union-attr). With mypy --strict, the current ignore code may not suppress the real error and can be reported as an unused ignore. Prefer cast(FloatScaleScorer, ...) here (or improve create_conversation_scorer typing via overloads) instead of type: ignore[union-attr].

Copilot uses AI. Check for mistakes.
entries: Sequence[SeedEntry] = self._query_entries(
SeedEntry,
conditions=and_(SeedEntry.dataset_name is not None, SeedEntry.dataset_name != ""), # type: ignore
conditions=and_(SeedEntry.dataset_name is not None, SeedEntry.dataset_name != ""), # type: ignore[union-attr]
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SeedEntry.dataset_name is not None is a Python identity check and evaluates to a plain bool, not a SQLAlchemy expression, so it doesn’t do what it appears to and also tends to cause a mypy arg-type error inside and_(...). Consider removing that clause or using the SQLAlchemy form (e.g., SeedEntry.dataset_name.isnot(None)), and update/remove the type: ignore[...] accordingly.

Suggested change
conditions=and_(SeedEntry.dataset_name is not None, SeedEntry.dataset_name != ""), # type: ignore[union-attr]
conditions=and_(SeedEntry.dataset_name.isnot(None), SeedEntry.dataset_name != ""),

Copilot uses AI. Check for mistakes.
Comment on lines 74 to 82
message_piece = MessagePiece(
role=row["role"], # type: ignore
role=row["role"], # type: ignore[union-attr]
original_value=row["value"],
original_value_data_type=row.get["data_type", None], # type: ignore
original_value_data_type=row.get["data_type", None], # type: ignore[union-attr]
conversation_id=row.get("conversation_id", None),
sequence=int(sequence_str) if sequence_str else None,
labels=labels,
response_error=row.get("response_error", None), # type: ignore
response_error=row.get("response_error", None), # type: ignore[union-attr]
prompt_target_identifier=self.get_identifier(),
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

row.get["data_type", None] is not valid for a dict (it subscripts the get method) and will raise TypeError at runtime. Use row.get("data_type")/row.get("data_type", None) instead, and consider validating/casting CSV strings (e.g., role/data_type) rather than suppressing with type: ignore here.

Copilot uses AI. Check for mistakes.
Comment on lines 196 to 201
words = prompt.split(self._word_separator)

# Get selected word indices
selected_indices = self._selection_strategy.select_words(words=words) # type: ignore
selected_indices = self._selection_strategy.select_words(words=words) # type: ignore[return-value]

# If no words selected, return original prompt
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The # type: ignore[return-value] on select_words is likely the wrong error code (mypy typically reports this as attr-defined since TextSelectionStrategy has no select_words). With mypy --strict, this can fail CI due to an unsuppressed error and/or an unused-ignore. Prefer casting self._selection_strategy to WordSelectionStrategy inside the word-level branch (or change the ignore to the correct code).

Copilot uses AI. Check for mistakes.
- Add PGH to ruff select list
- Fix PGH003: replace bare \# type: ignore\ with specific mypy error codes
- Fix PGH004: use colon syntax for \# noqa:\ directives
- Add docstring to MessagePiece.to_message (exposed by fixing blanket noqa)
- Remove obsolete flake8 directive in test_unicode_confusable_converter.py

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 28, 2026 15:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 48 changed files in this pull request and generated 7 comments.

Comment on lines 364 to 374
@@ -371,7 +371,7 @@ def test_factory_preserves_wrapped_scorer():
assert hasattr(conv_scorer, "_wrapped_scorer")
wrapped = conv_scorer._wrapped_scorer
assert wrapped is original_scorer
assert wrapped.custom_attr == "test_value" # type: ignore
assert wrapped.custom_attr == "test_value" # type: ignore[abstract]
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# type: ignore[abstract] on custom_attr is the wrong mypy error category here (this is dynamically adding/accessing an attribute that isn’t declared on MockFloatScaleScorer). Use the appropriate ignore code (typically attr-defined) or avoid the ignore by using setattr/a typed protocol if you want the test to be type-checker-friendly.

Copilot uses AI. Check for mistakes.
Comment on lines 160 to +162
assert messages[0]["role"] == "user"
assert messages[0]["content"][0]["type"] == "text" # type: ignore
assert messages[0]["content"][1]["type"] == "image_url" # type: ignore
assert messages[0]["content"][0]["type"] == "text" # type: ignore[method-assign]
assert messages[0]["content"][1]["type"] == "image_url" # type: ignore[method-assign]
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These # type: ignore[method-assign] comments don’t match what the lines are doing (they’re reads/indexing into messages[...], not assigning to a method). If you want these ignores to be meaningful, update them to the correct mypy error code for the underlying typing issue (e.g., an index/TypedDict/union-content error) or refactor with cast to avoid the ignore.

Copilot uses AI. Check for mistakes.
Comment on lines 541 to +543
def test_validate_request_unsupported_data_types(target: OpenAIChatTarget):
image_piece = get_image_message_piece()
image_piece.converted_value_data_type = "new_unknown_type" # type: ignore
image_piece.converted_value_data_type = "new_unknown_type" # type: ignore[method-assign]
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image_piece.converted_value_data_type = ... is an attribute assignment; # type: ignore[method-assign] is intended for assignments to methods and likely won’t suppress the intended type-checking error. Use the correct ignore code for an incompatible attribute assignment (commonly assignment) or adjust the test to avoid mutating the typed field directly.

Copilot uses AI. Check for mistakes.
Comment on lines 571 to +573
def test_validate_request_unsupported_data_types(target: OpenAIResponseTarget):
image_piece = get_image_message_piece()
image_piece.converted_value_data_type = "new_unknown_type" # type: ignore
image_piece.converted_value_data_type = "new_unknown_type" # type: ignore[method-assign]
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image_piece.converted_value_data_type = ... is an attribute assignment; # type: ignore[method-assign] is intended for assignments to methods and likely won’t suppress the intended type-checking error. Use the correct ignore code for an incompatible attribute assignment (commonly assignment) or adjust the test to avoid mutating the typed field directly.

Copilot uses AI. Check for mistakes.
Comment on lines 74 to 82
message_piece = MessagePiece(
role=row["role"], # type: ignore
role=row["role"], # type: ignore[arg-type]
original_value=row["value"],
original_value_data_type=row.get["data_type", None], # type: ignore
original_value_data_type=row.get("data_type", None), # type: ignore[arg-type]
conversation_id=row.get("conversation_id", None),
sequence=int(sequence_str) if sequence_str else None,
labels=labels,
response_error=row.get("response_error", None), # type: ignore
response_error=row.get("response_error", None), # type: ignore[arg-type]
prompt_target_identifier=self.get_identifier(),
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import_scores_from_csv currently passes None for fields that are not Optional in MessagePiece (original_value_data_type, response_error, and sequence). This will raise at runtime for the documented minimal CSV format (e.g., only role,value). Use sensible defaults when keys are missing (e.g., default data type to "text", response_error to "none", and sequence to -1 or omit the argument) and consider validating/normalizing the CSV values before constructing MessagePiece.

Copilot uses AI. Check for mistakes.
Comment on lines 184 to +191
assert messages[0]["role"] == "user"
assert messages[0]["content"][0]["type"] == "input_text" # type: ignore
assert messages[0]["content"][1]["type"] == "input_image" # type: ignore
assert messages[0]["content"][0]["type"] == "input_text" # type: ignore[method-assign]
assert messages[0]["content"][1]["type"] == "input_image" # type: ignore[method-assign]
assert messages[1]["role"] == "assistant"
assert messages[1]["content"][0]["type"] == "output_text" # type: ignore
assert messages[1]["content"][0]["type"] == "output_text" # type: ignore[method-assign]
assert messages[2]["role"] == "user"
assert messages[2]["content"][0]["type"] == "input_text" # type: ignore
assert messages[2]["content"][1]["type"] == "input_image" # type: ignore
assert messages[2]["content"][0]["type"] == "input_text" # type: ignore[method-assign]
assert messages[2]["content"][1]["type"] == "input_image" # type: ignore[method-assign]
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These # type: ignore[method-assign] comments don’t match what the lines are doing (they’re reads/indexing into messages[...], not assigning to a method). If you want these ignores to be meaningful, update them to the correct mypy error code for the underlying typing issue (e.g., an index/TypedDict/union-content error) or refactor with cast to avoid the ignore.

Copilot uses AI. Check for mistakes.

Returns:
Message: A Message containing this piece.
"""
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MessagePiece.to_message uses a local import. If this is to avoid a circular import (likely, given pyrit.models.message imports MessagePiece), please add a brief comment explaining that rationale so future refactors don’t “fix” it back to a top-level import.

Suggested change
"""
"""
# Local import is required to avoid a circular dependency because
# pyrit.models.message imports MessagePiece.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants