@@ -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
2324async 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
2830async 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
3336def _read_wav_sync (path ):
3437 with wave.open(path, " rb" ) as wav:
3538 return wav.readframes(wav.getnframes())
3639
40+
3741async 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
5458async 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
7579def 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
104108def 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`,
163159def main () -> int :
164160 parsed_args = parse_args()
165161 from pyrit.cli import frontend_core # deferred: heavy
162+
166163 ...
167164
165+
168166async 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
239238def 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
322318DEFAULT_TREE_WIDTH = 3 # Should be inside class
323319DEFAULT_TREE_DEPTH = 5
@@ -343,11 +339,13 @@ async def execute_attack_async(self, *, context: AttackContext) -> AttackResult:
343339
344340 return result
345341
342+
346343def _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
352350async 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
374372if 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
380376if 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
401398def 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
418415from pyrit.common.deprecation import print_deprecation_message
419416
417+
420418def 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
433431import warnings
432+
434433warnings.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
485484from contextlib import asynccontextmanager
486485
486+
487487@asynccontextmanager
488488async 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
513514def 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
519521def analysis_report (self ) -> str :
@@ -531,17 +533,12 @@ def analysis_report(self) -> str:
531533``` python
532534# CORRECT
533535class 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
546543class 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+
563561async 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
584583def process_large_dataset (self , * , file_path : Path) -> list[Result]:
585584 with open (file_path) as f:
0 commit comments