Skip to content

Commit cfd4724

Browse files
dependabot[bot]Behnam Ousat
andauthored
MAINT: Bump https://github.com/astral-sh/ruff-pre-commit from v0.16.4 to 0.16.5 (#2541)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Behnam Ousat <behnamousat@microsoft.com> Copilot-Session: d95039f6-57f0-4751-91fb-acea24e234a8
1 parent 7045bad commit cfd4724

15 files changed

Lines changed: 71 additions & 70 deletions

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Please provide the following information:
4747
- version of Python packages: please run the following snippet and paste the output:
4848
```python
4949
import pyrit
50+
5051
pyrit.show_versions()
5152
```
5253
-->

.github/instructions/converters.instructions.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ All converters MUST inherit from `Converter` and implement:
1414

1515
```python
1616
class MyConverter(Converter):
17-
SUPPORTED_INPUT_TYPES = ("text",) # Required — non-empty tuple of PromptDataType values
18-
SUPPORTED_OUTPUT_TYPES = ("text",) # Required — non-empty tuple of PromptDataType values
17+
SUPPORTED_INPUT_TYPES = ("text",) # Required — non-empty tuple of PromptDataType values
18+
SUPPORTED_OUTPUT_TYPES = ("text",) # Required — non-empty tuple of PromptDataType values
1919

20-
async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult:
21-
...
20+
async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: ...
2221
```
2322

2423
Missing or empty `SUPPORTED_INPUT_TYPES` / `SUPPORTED_OUTPUT_TYPES` raises `TypeError` at class definition time via `__init_subclass__`.
@@ -51,8 +50,8 @@ All converters inherit `Identifiable`. Override `_build_identifier()` to include
5150
```python
5251
def _build_identifier(self) -> ComponentIdentifier:
5352
return self._create_identifier(
54-
params={"encoding": self._encoding}, # Behavioral params only
55-
children={"target": self._target.get_identifier()} # If converter wraps a target
53+
params={"encoding": self._encoding}, # Behavioral params only
54+
children={"target": self._target.get_identifier()}, # If converter wraps a target
5655
)
5756
```
5857

@@ -78,10 +77,10 @@ Use keyword-only arguments. Use `@apply_defaults` if the converter accepts targe
7877
```python
7978
from pyrit.common.apply_defaults import apply_defaults
8079

80+
8181
class MyConverter(Converter):
8282
@apply_defaults
83-
def __init__(self, *, target: PromptTarget, template: str = "default") -> None:
84-
...
83+
def __init__(self, *, target: PromptTarget, template: str = "default") -> None: ...
8584
```
8685

8786
### Keyword-only ``__init__`` is enforced
@@ -97,13 +96,14 @@ The check is satisfied by either of:
9796
```python
9897
def __init__(self, *, foo: str, bar: int = 0) -> None: ...
9998

99+
100100
def __init__(self, *args: str, foo: str = "") -> None: ... # *args after self
101101
```
102102

103103
It rejects:
104104

105105
```python
106-
def __init__(self, foo: str, bar: int = 0) -> None: ... # missing *
106+
def __init__(self, foo: str, bar: int = 0) -> None: ... # missing *
107107
```
108108

109109
## Exports and External Updates

.github/instructions/datasets.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class _MyDataset(_RemoteDatasetLoader):
7373
HF_DATASET_NAME: str = "owner/my-dataset"
7474
harm_categories: list[str] = ["harassment", "violence"]
7575
modalities: list[str] = ["text"]
76-
size: str = "medium" # tiny <10, small 10-99, medium 100-499, large 500-4999, huge 5000+
76+
size: str = "medium" # tiny <10, small 10-99, medium 100-499, large 500-4999, huge 5000+
7777
tags: set[str] = {"default", "safety"}
7878
```
7979

.github/instructions/output.instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Every new domain printer **must** have a corresponding convenience function adde
6161

6262
```python
6363
from pyrit.output.helpers import output_attack_async
64+
6465
await output_attack_async(result, format="pretty")
6566
```
6667

.github/instructions/scenarios.instructions.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ Technique members should represent **attack techniques** — the *how* of an att
144144

145145
```python
146146
class MyTechnique(ScenarioTechnique):
147-
ALL = ("all", {"all"}) # Required aggregate
148-
DEFAULT = ("default", {"default"}) # Recommended default aggregate
147+
ALL = ("all", {"all"}) # Required aggregate
148+
DEFAULT = ("default", {"default"}) # Recommended default aggregate
149149
SINGLE_TURN = ("single_turn", {"single_turn"}) # Category aggregate
150150

151151
PromptSending = ("prompt_sending", {"single_turn", "default"})
@@ -208,8 +208,7 @@ Note: `atomic_attack_name` must remain unique per `AtomicAttack` for correct res
208208
Every scenario implements the single abstract extension point:
209209

210210
```python
211-
async def _build_atomic_attacks_async(self, *, context: ScenarioContext) -> list[AtomicAttack]:
212-
...
211+
async def _build_atomic_attacks_async(self, *, context: ScenarioContext) -> list[AtomicAttack]: ...
213212
```
214213

215214
`initialize_async` resolves the run's inputs once (objective target, techniques, dataset
@@ -226,6 +225,7 @@ Scenarios whose construction is the plain technique × dataset cross-product del
226225
```python
227226
from pyrit.scenario.core.matrix_atomic_attack_builder import build_matrix_atomic_attacks
228227

228+
229229
async def _build_atomic_attacks_async(self, *, context: ScenarioContext) -> list[AtomicAttack]:
230230
return build_matrix_atomic_attacks(
231231
context=context,
@@ -260,13 +260,13 @@ and is loaded into the registry by `TechniqueInitializer`.
260260
from pyrit.scenario.core.attack_technique_factory import AttackTechniqueFactory
261261

262262
AttackTechniqueFactory(
263-
name="prompt_sending", # REQUIRED — must match the technique enum value
263+
name="prompt_sending", # REQUIRED — must match the technique enum value
264264
attack_class=PromptSendingAttack,
265265
technique_tags=["core", "single_turn", "default"],
266266
attack_kwargs={"max_turns": 5},
267-
adversarial_chat=None, # None = resolve adversarial target lazily at create()
267+
adversarial_chat=None, # None = resolve adversarial target lazily at create()
268268
seed_technique=None,
269-
uses_adversarial=None, # None = auto-derive from attack signature/seeds
269+
uses_adversarial=None, # None = auto-derive from attack signature/seeds
270270
scorer_override_policy=ScorerOverridePolicy.WARN,
271271
)
272272
```
@@ -318,10 +318,10 @@ population — that reintroduces baseline-vs-technique population divergence und
318318

319319
```python
320320
AtomicAttack(
321-
atomic_attack_name=technique_name, # groups related attacks
321+
atomic_attack_name=technique_name, # groups related attacks
322322
attack_technique=AttackTechnique(attack=attack_instance), # bundles the AttackStrategy
323-
seed_groups=list(seed_groups), # must be non-empty
324-
memory_labels=context.memory_labels, # from the context snapshot
323+
seed_groups=list(seed_groups), # must be non-empty
324+
memory_labels=context.memory_labels, # from the context snapshot
325325
)
326326
```
327327

.github/instructions/style-guide.instructions.md

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,25 @@ async def _send_async(self):
1919
with open(self.file_path, "rb") as fp:
2020
return fp.read()
2121

22+
2223
# CORRECT — async file read
2324
async def _send_async(self):
2425
async with aiofiles.open(self.file_path, "rb") as fp:
2526
return await fp.read()
2627

28+
2729
# WRONG — sync-only library called directly
2830
async def _read_audio_async(self, path):
2931
with wave.open(path, "rb") as wav:
3032
return wav.readframes(wav.getnframes())
3133

34+
3235
# CORRECT — wrap blocking lib in to_thread
3336
def _read_wav_sync(path):
3437
with wave.open(path, "rb") as wav:
3538
return wav.readframes(wav.getnframes())
3639

40+
3741
async def _read_audio_async(self, path):
3842
return await asyncio.to_thread(_read_wav_sync, path)
3943
```
@@ -47,8 +51,8 @@ async def _read_audio_async(self, path):
4751

4852
```python
4953
# CORRECT
50-
async def send_prompt_async(self, prompt: str) -> Message:
51-
...
54+
async def send_prompt_async(self, prompt: str) -> Message: ...
55+
5256

5357
# INCORRECT
5458
async def send_prompt(self, prompt: str) -> Message: # Missing _async suffix
@@ -68,8 +72,8 @@ async def send_prompt(self, prompt: str) -> Message: # Missing _async suffix
6872

6973
```python
7074
# CORRECT
71-
def _validate_input(self, data: dict) -> None:
72-
...
75+
def _validate_input(self, data: dict) -> None: ...
76+
7377

7478
# INCORRECT
7579
def validate_input(self, data: dict) -> None: # Should be private
@@ -94,11 +98,11 @@ def validate_input(self, data: dict) -> None: # Should be private
9498

9599
```python
96100
# CORRECT
97-
def process_data(self, *, data: list[str], threshold: float = 0.5) -> dict[str, Any]:
98-
...
101+
def process_data(self, *, data: list[str], threshold: float = 0.5) -> dict[str, Any]: ...
102+
103+
104+
def get_name(self) -> str | None: ...
99105

100-
def get_name(self) -> str | None:
101-
...
102106

103107
# INCORRECT
104108
def process_data(self, data, threshold=0.5): # Missing all type annotations
@@ -113,18 +117,11 @@ def process_data(self, data, threshold=0.5): # Missing all type annotations
113117

114118
```python
115119
# CORRECT
116-
def __init__(
117-
self,
118-
*,
119-
target: PromptTarget,
120-
scorer: Scorer | None = None,
121-
max_retries: int = 3
122-
) -> None:
123-
...
120+
def __init__(self, *, target: PromptTarget, scorer: Scorer | None = None, max_retries: int = 3) -> None: ...
121+
124122

125123
# INCORRECT
126-
def __init__(self, target: PromptTarget, scorer: Scorer | None = None, max_retries: int = 3):
127-
...
124+
def __init__(self, target: PromptTarget, scorer: Scorer | None = None, max_retries: int = 3): ...
128125
```
129126

130127
### Forwarded Constructor Parameters
@@ -140,8 +137,7 @@ def __init__(self, target: PromptTarget, scorer: Scorer | None = None, max_retri
140137

141138
```python
142139
# CORRECT
143-
def process(self, data: str) -> str:
144-
...
140+
def process(self, data: str) -> str: ...
145141
```
146142

147143
## Imports
@@ -163,10 +159,13 @@ third-party packages (`transformers`, `azure.storage.blob`, `alembic`, `openai`,
163159
def main() -> int:
164160
parsed_args = parse_args()
165161
from pyrit.cli import frontend_core # deferred: heavy
162+
166163
...
167164

165+
168166
async def _create_container_client_async(self):
169167
from azure.storage.blob.aio import ContainerClient # deferred: heavy
168+
170169
...
171170
```
172171

@@ -237,12 +236,7 @@ from typing import Self, override
237236

238237
```python
239238
def calculate_score(
240-
self,
241-
*,
242-
response: str,
243-
objective: str,
244-
threshold: float = 0.8,
245-
max_attempts: int | None = None
239+
self, *, response: str, objective: str, threshold: float = 0.8, max_attempts: int | None = None
246240
) -> Score:
247241
"""
248242
Calculate the score for a response against an objective.
@@ -281,6 +275,7 @@ navigation in the rendered docs without any extra markup.
281275
# WRONG — reST roles render as literal `:class:\`SeedPrompt\`` under MyST,
282276
# and the pre-commit guard will reject them
283277
"""Returns a :class:`SeedPrompt` instance."""
278+
284279
"""Delegate to :func:`download_files_async` (deprecated alias)."""
285280
"""See :meth:`PromptTarget.apply_capabilities` for details."""
286281

@@ -318,6 +313,7 @@ class TreeOfAttacksAttack(AttackStrategy):
318313
DEFAULT_TREE_DEPTH: int = 5
319314
MIN_CONFIDENCE_THRESHOLD: float = 0.7
320315

316+
321317
# INCORRECT
322318
DEFAULT_TREE_WIDTH = 3 # Should be inside class
323319
DEFAULT_TREE_DEPTH = 5
@@ -343,11 +339,13 @@ async def execute_attack_async(self, *, context: AttackContext) -> AttackResult:
343339

344340
return result
345341

342+
346343
def _validate_context(self, context: AttackContext) -> None:
347344
"""Validate the attack context."""
348345
if not context.objective:
349346
raise ValueError("Context must have an objective")
350347

348+
351349
# INCORRECT - Too long and doing too many things
352350
async def execute_attack_async(self, *, context: AttackContext) -> AttackResult:
353351
# 50+ lines of mixed validation, preparation, sending, and evaluation logic
@@ -372,9 +370,7 @@ async def execute_attack_async(self, *, context: AttackContext) -> AttackResult:
372370
```python
373371
# CORRECT
374372
if not self._model:
375-
raise ValueError(
376-
"Model not initialized. Call initialize_model() before executing attack."
377-
)
373+
raise ValueError("Model not initialized. Call initialize_model() before executing attack.")
378374

379375
# INCORRECT
380376
if not self._model:
@@ -397,6 +393,7 @@ def process_items(self, *, items: list[str]) -> list[str]:
397393
# Main logic for multiple items
398394
return [self._process_single(item) for item in items]
399395

396+
400397
# INCORRECT - Excessive nesting
401398
def process_items(self, *, items: list[str]) -> list[str]:
402399
if items:
@@ -417,6 +414,7 @@ Set `removed_in` to **current version + 2 minor versions** (e.g. `0.14.x` → `r
417414
```python
418415
from pyrit.common.deprecation import print_deprecation_message
419416

417+
420418
def old_method(self, *, foo: str) -> None:
421419
print_deprecation_message(
422420
old_item="MyClass.old_method",
@@ -431,6 +429,7 @@ def old_method(self, *, foo: str) -> None:
431429
```python
432430
# INCORRECT - bypasses the helper, breaks consistent formatting and filtering
433431
import warnings
432+
434433
warnings.warn("foo is deprecated, use bar", DeprecationWarning, stacklevel=2)
435434
```
436435

@@ -484,6 +483,7 @@ async with self._get_client() as client:
484483
# For custom resources
485484
from contextlib import asynccontextmanager
486485

486+
487487
@asynccontextmanager
488488
async def temporary_config(self, **kwargs):
489489
old_config = self._config.copy()
@@ -508,12 +508,14 @@ def is_complete(self) -> bool:
508508
"""Whether the attack is complete."""
509509
return self._status == AttackStatus.COMPLETE
510510

511+
511512
# INCORRECT - verb-phrase docstring, flagged by Ruff D421
512513
@property
513514
def is_complete(self) -> bool:
514515
"""Check if the attack is complete."""
515516
return self._status == AttackStatus.COMPLETE
516517

518+
517519
# INCORRECT - Too complex for property
518520
@property
519521
def analysis_report(self) -> str:
@@ -531,17 +533,12 @@ def analysis_report(self) -> str:
531533
```python
532534
# CORRECT
533535
class AttackExecutor:
534-
def __init__(
535-
self,
536-
*,
537-
target: PromptTarget,
538-
scorer: Scorer,
539-
logger: logging.Logger | None = None
540-
) -> None:
536+
def __init__(self, *, target: PromptTarget, scorer: Scorer, logger: logging.Logger | None = None) -> None:
541537
self._target = target
542538
self._scorer = scorer
543539
self._logger = logger or logging.getLogger(__name__)
544540

541+
545542
# INCORRECT
546543
class AttackExecutor:
547544
def __init__(self):
@@ -560,6 +557,7 @@ def calculate_score(response: str, objective: str) -> float:
560557
# Logic without side effects
561558
return score
562559

560+
563561
async def evaluate_response_async(self, *, response: str) -> Score:
564562
"""I/O function that uses the pure function."""
565563
score_value = calculate_score(response, self._objective)
@@ -580,6 +578,7 @@ def process_large_dataset(self, *, file_path: Path) -> Generator[Result, None, N
580578
for line in f:
581579
yield self._process_line(line)
582580

581+
583582
# INCORRECT
584583
def process_large_dataset(self, *, file_path: Path) -> list[Result]:
585584
with open(file_path) as f:

0 commit comments

Comments
 (0)