diff --git a/pyproject.toml b/pyproject.toml index d87582c38..9f3e7cf56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -252,16 +252,28 @@ fixable = [ "YTT", ] select = [ + "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a "B", # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b "C4", # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 "CPY001", # missing-copyright-notice "D", # https://docs.astral.sh/ruff/rules/#pydocstyle-d "DOC", # https://docs.astral.sh/ruff/rules/#pydoclint-doc + "ERA", # https://docs.astral.sh/ruff/rules/#eradicate-era + "EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe "F401", # unused-import + "FA", # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + "FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly "I", # isort + "ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + "LOG", # https://docs.astral.sh/ruff/rules/#flake8-logging-log "PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "PLE", # https://docs.astral.sh/ruff/rules/#pylint-ple + "Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q "RET", # https://docs.astral.sh/ruff/rules/#flake8-return-ret + "RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse "SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "SLOT", # https://docs.astral.sh/ruff/rules/#flake8-slots-slot "TCH", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch "UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-w @@ -300,7 +312,7 @@ notice-rgx = "Copyright \\(c\\) Microsoft Corporation\\.\\s*\\n.*Licensed under # Temporary ignores for pyrit/ subdirectories until issue #1176 # https://github.com/Azure/PyRIT/issues/1176 is fully resolved # TODO: Remove these ignores once the issues are fixed -"pyrit/{auxiliary_attacks,ui}/**/*.py" = ["B905", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D401", "D404", "D417", "D418", "DOC102", "DOC201", "DOC202", "DOC402", "DOC501", "SIM101", "SIM108"] +"pyrit/{auxiliary_attacks,ui}/**/*.py" = ["A", "B905", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D401", "D404", "D417", "D418", "DOC102", "DOC201", "DOC202", "DOC402", "DOC501", "SIM101", "SIM108"] # Backend API routes raise HTTPException handled by FastAPI, not true exceptions "pyrit/backend/**/*.py" = ["DOC501", "B008"] "pyrit/__init__.py" = ["D104"] diff --git a/pyrit/auth/azure_auth.py b/pyrit/auth/azure_auth.py index 519237938..b60618963 100644 --- a/pyrit/auth/azure_auth.py +++ b/pyrit/auth/azure_auth.py @@ -325,7 +325,7 @@ def get_speech_config(resource_id: Union[str, None], key: Union[str, None], regi except ModuleNotFoundError as e: logger.error( "Could not import azure.cognitiveservices.speech. " - + "You may need to install it via 'pip install pyrit[speech]'" + "You may need to install it via 'pip install pyrit[speech]'" ) raise e @@ -361,7 +361,7 @@ def get_speech_config_from_default_azure_credential(resource_id: str, region: st except ModuleNotFoundError as e: logger.error( "Could not import azure.cognitiveservices.speech. " - + "You may need to install it via 'pip install pyrit[speech]'" + "You may need to install it via 'pip install pyrit[speech]'" ) raise e diff --git a/pyrit/backend/middleware/error_handlers.py b/pyrit/backend/middleware/error_handlers.py index 446db678e..169c80f75 100644 --- a/pyrit/backend/middleware/error_handlers.py +++ b/pyrit/backend/middleware/error_handlers.py @@ -165,7 +165,7 @@ async def generic_exception_handler( # Log the full exception for debugging logger.error( f"Unhandled exception on {request.method} {request.url.path}: {exc}", - exc_info=True, + exc_info=True, # noqa: LOG014 ) problem = ProblemDetail( diff --git a/pyrit/common/path.py b/pyrit/common/path.py index f04718cf2..976ddff62 100644 --- a/pyrit/common/path.py +++ b/pyrit/common/path.py @@ -6,7 +6,7 @@ from appdirs import user_data_dir -def get_default_data_path(dir: str) -> pathlib.Path: +def get_default_data_path(dir: str) -> pathlib.Path: # noqa: A002 """ Retrieve the default data path for PyRIT. diff --git a/pyrit/exceptions/exception_classes.py b/pyrit/exceptions/exception_classes.py index eeb9e1653..ec7d8fbfb 100644 --- a/pyrit/exceptions/exception_classes.py +++ b/pyrit/exceptions/exception_classes.py @@ -377,6 +377,6 @@ def handle_bad_request_exception( request=request, response_text_pieces=[resp_text], response_type="error", error="blocked" ) else: - raise + raise # noqa: PLE0704 return response_entry diff --git a/pyrit/exceptions/exceptions_helpers.py b/pyrit/exceptions/exceptions_helpers.py index 5f396ceb7..73df4e258 100644 --- a/pyrit/exceptions/exceptions_helpers.py +++ b/pyrit/exceptions/exceptions_helpers.py @@ -49,7 +49,7 @@ def log_exception(retry_state: RetryCallState) -> None: try: exec_context = get_execution_context() if exec_context: - # Format: "objective scorer; TrueFalseScorer::_score_value_with_llm" + # Format: "objective scorer; TrueFalseScorer::_score_value_with_llm" # noqa: ERA001 role_display = exec_context.component_role.value.replace("_", " ") if exec_context.component_name: for_clause = f"{role_display}. {exec_context.component_name}::{fn_name}" diff --git a/pyrit/memory/memory_interface.py b/pyrit/memory/memory_interface.py index 67e6dcfb6..71235870a 100644 --- a/pyrit/memory/memory_interface.py +++ b/pyrit/memory/memory_interface.py @@ -392,7 +392,7 @@ def add_scores_to_memory(self, *, scores: Sequence[Score]) -> None: message_piece_id = score.message_piece_id pieces = self.get_message_pieces(prompt_ids=[str(message_piece_id)]) if not pieces: - logging.error(f"MessagePiece with ID {message_piece_id} not found in memory.") + logger.error(f"MessagePiece with ID {message_piece_id} not found in memory.") continue # auto-link score to the original prompt id if the prompt is a duplicate if pieces[0].original_prompt_id != pieces[0].id: diff --git a/pyrit/models/message_piece.py b/pyrit/models/message_piece.py index 62a6e4890..e8cc2504a 100644 --- a/pyrit/models/message_piece.py +++ b/pyrit/models/message_piece.py @@ -34,7 +34,7 @@ def __init__( original_value_sha256: Optional[str] = None, converted_value: Optional[str] = None, converted_value_sha256: Optional[str] = None, - id: Optional[uuid.UUID | str] = None, + id: Optional[uuid.UUID | str] = None, # noqa: A002 conversation_id: Optional[str] = None, sequence: int = -1, labels: Optional[dict[str, str]] = None, diff --git a/pyrit/models/scenario_result.py b/pyrit/models/scenario_result.py index 52669fc03..71d48adc0 100644 --- a/pyrit/models/scenario_result.py +++ b/pyrit/models/scenario_result.py @@ -67,7 +67,7 @@ def __init__( labels: Optional[dict[str, str]] = None, completion_time: Optional[datetime] = None, number_tries: int = 0, - id: Optional[uuid.UUID] = None, + id: Optional[uuid.UUID] = None, # noqa: A002 # Deprecated parameter - will be removed in 0.13.0 objective_scorer: Optional["Scorer"] = None, ) -> None: diff --git a/pyrit/models/score.py b/pyrit/models/score.py index fa01d5432..9660af71a 100644 --- a/pyrit/models/score.py +++ b/pyrit/models/score.py @@ -59,7 +59,7 @@ def __init__( score_type: ScoreType, score_rationale: str, message_piece_id: str | uuid.UUID, - id: Optional[uuid.UUID | str] = None, + id: Optional[uuid.UUID | str] = None, # noqa: A002 score_category: Optional[list[str]] = None, score_metadata: Optional[dict[str, Union[str, int, float]]] = None, scorer_class_identifier: Union[ComponentIdentifier, dict[str, Any]], diff --git a/pyrit/models/seeds/seed.py b/pyrit/models/seeds/seed.py index c506a49a1..0544c8dc7 100644 --- a/pyrit/models/seeds/seed.py +++ b/pyrit/models/seeds/seed.py @@ -201,7 +201,7 @@ def render_template_value_silent(self, **kwargs: Any) -> str: # Render the template with the provided kwargs return jinja_template.render(**kwargs) except Exception as e: - logging.error("Error rendering template: %s", e) + logger.error("Error rendering template: %s", e) return self.value async def set_sha256_value_async(self) -> None: diff --git a/pyrit/models/storage_io.py b/pyrit/models/storage_io.py index 211441965..3555a3648 100644 --- a/pyrit/models/storage_io.py +++ b/pyrit/models/storage_io.py @@ -225,9 +225,9 @@ async def _upload_blob_async(self, file_name: str, data: bytes, content_type: st if isinstance(exc, ClientAuthenticationError): logger.exception( msg="Authentication failed. Please check that the container existence in the " - + "Azure Storage Account and ensure the validity of the provided SAS token. If you " - + "haven't set the SAS token as an environment variable use `az login` to " - + "enable delegation-based SAS authentication to connect to the storage account" + "Azure Storage Account and ensure the validity of the provided SAS token. If you " + "haven't set the SAS token as an environment variable use `az login` to " + "enable delegation-based SAS authentication to connect to the storage account" ) raise logger.exception(msg=f"An unexpected error occurred: {exc}") diff --git a/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py b/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py index 1d890a0b9..e2a7b6a84 100644 --- a/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py +++ b/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py @@ -150,7 +150,7 @@ def recognize_audio(self, audio_bytes: bytes) -> str: except ModuleNotFoundError as e: logger.error( "Could not import azure.cognitiveservices.speech. " - + "You may need to install it via 'pip install pyrit[speech]'" + "You may need to install it via 'pip install pyrit[speech]'" ) raise e @@ -219,7 +219,7 @@ def stop_cb(self, evt: Any, recognizer: Any) -> None: except ModuleNotFoundError as e: logger.error( "Could not import azure.cognitiveservices.speech. " - + "You may need to install it via 'pip install pyrit[speech]'" + "You may need to install it via 'pip install pyrit[speech]'" ) raise e diff --git a/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py b/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py index e602def6d..b3bf06427 100644 --- a/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py +++ b/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py @@ -130,7 +130,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text except ModuleNotFoundError as e: logger.error( "Could not import azure.cognitiveservices.speech. " - + "You may need to install it via 'pip install pyrit[speech]'" + "You may need to install it via 'pip install pyrit[speech]'" ) raise e @@ -176,7 +176,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text logger.error(f"Error details: {cancellation_details.error_details}") raise RuntimeError( f"Speech synthesis canceled: {cancellation_details.reason}" - + f"Error details: {cancellation_details.error_details}" + f"Error details: {cancellation_details.error_details}" ) except Exception as e: logger.error("Failed to convert prompt to audio: %s", str(e)) diff --git a/pyrit/prompt_converter/insert_punctuation_converter.py b/pyrit/prompt_converter/insert_punctuation_converter.py index 759781449..6296e595f 100644 --- a/pyrit/prompt_converter/insert_punctuation_converter.py +++ b/pyrit/prompt_converter/insert_punctuation_converter.py @@ -70,7 +70,7 @@ def _is_valid_punctuation(self, punctuation_list: list[str]) -> bool: Returns: bool: valid list and valid punctuations """ - return all(str in string.punctuation for str in punctuation_list) + return all(char in string.punctuation for char in punctuation_list) async def convert_async( self, *, prompt: str, input_type: PromptDataType = "text", punctuation_list: Optional[list[str]] = None diff --git a/pyrit/prompt_converter/transparency_attack_converter.py b/pyrit/prompt_converter/transparency_attack_converter.py index 9897f3a10..d0b37dce0 100644 --- a/pyrit/prompt_converter/transparency_attack_converter.py +++ b/pyrit/prompt_converter/transparency_attack_converter.py @@ -6,7 +6,7 @@ from io import BytesIO from pathlib import Path -import numpy +import numpy as np from PIL import Image from pyrit.identifiers import ComponentIdentifier @@ -18,7 +18,7 @@ class _AdamOptimizer: """ - Implementation of the Adam Optimizer using NumPy. Adam optimization is a stochastic gradient + Implementation of the Adam Optimizer using np. Adam optimization is a stochastic gradient descent method that is based on adaptive estimation of first-order and second-order moments. For further details, see the original paper: `"Adam: A Method for Stochastic Optimization"` by D. P. Kingma and J. Ba, 2014: https://arxiv.org/abs/1412.6980. @@ -44,24 +44,24 @@ def __init__( self.beta_1 = beta_1 self.beta_2 = beta_2 self.epsilon = epsilon - self.m: numpy.ndarray # type: ignore[type-arg, unused-ignore] # first moment vector - self.v: numpy.ndarray # type: ignore[type-arg, unused-ignore] # second moment vector + self.m: np.ndarray # type: ignore[type-arg, unused-ignore] # first moment vector + self.v: np.ndarray # type: ignore[type-arg, unused-ignore] # second moment vector self.t = 0 # initialize timestep - def update(self, *, params: numpy.ndarray, grads: numpy.ndarray) -> numpy.ndarray: # type: ignore[type-arg, unused-ignore] + def update(self, *, params: np.ndarray, grads: np.ndarray) -> np.ndarray: # type: ignore[type-arg, unused-ignore] """ Perform a single update step using the Adam optimization algorithm. Args: - params (numpy.ndarray): Current parameter values to be optimized. - grads (numpy.ndarray): Gradients w.r.t. stochastic objective. + params (np.ndarray): Current parameter values to be optimized. + grads (np.ndarray): Gradients w.r.t. stochastic objective. Returns: - numpy.ndarray: Updated parameter values after applying the Adam optimization step. + np.ndarray: Updated parameter values after applying the Adam optimization step. """ if self.t == 0: - self.m = numpy.zeros_like(params) - self.v = numpy.zeros_like(params) + self.m = np.zeros_like(params) + self.v = np.zeros_like(params) self.t += 1 # Update biased first and second raw moment estimates @@ -72,7 +72,7 @@ def update(self, *, params: numpy.ndarray, grads: numpy.ndarray) -> numpy.ndarra m_hat = self.m / (1 - self.beta_1**self.t) v_hat = self.v / (1 - self.beta_2**self.t) - params -= self.learning_rate * m_hat / (numpy.sqrt(v_hat) + self.epsilon) + params -= self.learning_rate * m_hat / (np.sqrt(v_hat) + self.epsilon) return params @@ -200,7 +200,7 @@ def _build_identifier(self) -> ComponentIdentifier: } ) - def _load_and_preprocess_image(self, path: str) -> numpy.ndarray: # type: ignore[type-arg, unused-ignore] + def _load_and_preprocess_image(self, path: str) -> np.ndarray: # type: ignore[type-arg, unused-ignore] """ Load image, convert to grayscale, resize, and normalize for optimization. @@ -208,7 +208,7 @@ def _load_and_preprocess_image(self, path: str) -> numpy.ndarray: # type: ignor path (str): The file path to the image. Returns: - numpy.ndarray: Preprocessed image as a normalized NumPy array. + np.ndarray: Preprocessed image as a normalized NumPy array. Raises: ValueError: If the image cannot be loaded or processed. @@ -217,52 +217,52 @@ def _load_and_preprocess_image(self, path: str) -> numpy.ndarray: # type: ignor with Image.open(path) as img: img_gray = img.convert("L") if img.mode != "L" else img # read as grayscale img_resized = img_gray.resize(self.size, Image.Resampling.LANCZOS) - return numpy.array(img_resized, dtype=numpy.float32) / 255.0 # normalize to [0, 1] + return np.array(img_resized, dtype=np.float32) / 255.0 # normalize to [0, 1] except Exception as e: raise ValueError(f"Failed to load and preprocess image from {path}: {e}") from e - def _compute_mse_loss(self, blended_image: numpy.ndarray, target_tensor: numpy.ndarray) -> float: # type: ignore[type-arg, unused-ignore] + def _compute_mse_loss(self, blended_image: np.ndarray, target_tensor: np.ndarray) -> float: # type: ignore[type-arg, unused-ignore] """ Compute Mean Squared Error (MSE) loss between blended and target images. Args: - blended_image (numpy.ndarray): The blended image array. - target_tensor (numpy.ndarray): The target benign image array. + blended_image (np.ndarray): The blended image array. + target_tensor (np.ndarray): The target benign image array. Returns: float: The computed MSE loss value. """ - return float(numpy.mean(numpy.square(blended_image - target_tensor))) + return float(np.mean(np.square(blended_image - target_tensor))) - def _create_blended_image(self, attack_image: numpy.ndarray, alpha: numpy.ndarray) -> numpy.ndarray: # type: ignore[type-arg, unused-ignore] + def _create_blended_image(self, attack_image: np.ndarray, alpha: np.ndarray) -> np.ndarray: # type: ignore[type-arg, unused-ignore] """ Create a blended image using the attack image and alpha transparency. Args: - attack_image (numpy.ndarray): The attack image array. - alpha (numpy.ndarray): The alpha transparency array. + attack_image (np.ndarray): The attack image array. + alpha (np.ndarray): The alpha transparency array. Returns: - numpy.ndarray: The blended image in LA mode. + np.ndarray: The blended image in LA mode. """ - attack_image_uint8 = (attack_image * 255).astype(numpy.uint8) - transparency_uint8 = (alpha * 255).astype(numpy.uint8) + attack_image_uint8 = (attack_image * 255).astype(np.uint8) + transparency_uint8 = (alpha * 255).astype(np.uint8) # Create LA image: Luminance + Alpha (grayscale with transparency) height, width = attack_image_uint8.shape[:2] - la_image = numpy.zeros((height, width, 2), dtype=numpy.uint8) + la_image = np.zeros((height, width, 2), dtype=np.uint8) la_image[:, :, 0] = attack_image_uint8 # L (Luminance) la_image[:, :, 1] = transparency_uint8 # A (Alpha) return la_image - async def _save_blended_image(self, attack_image: numpy.ndarray, alpha: numpy.ndarray) -> str: # type: ignore[type-arg, unused-ignore] + async def _save_blended_image(self, attack_image: np.ndarray, alpha: np.ndarray) -> str: # type: ignore[type-arg, unused-ignore] """ Save the blended image with transparency as a PNG file. Args: - attack_image (numpy.ndarray): The attack image array. - alpha (numpy.ndarray): The alpha transparency array. + attack_image (np.ndarray): The attack image array. + alpha (np.ndarray): The alpha transparency array. Returns: str: The file path to the saved blended image. @@ -309,8 +309,8 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "imag background_image = self._load_and_preprocess_image(prompt) background_tensor = background_image * 0.5 # darkening for better blending optimization - alpha = numpy.ones_like(background_tensor) # optimized to determine transparency pattern - white_background = numpy.ones_like(background_tensor) # white canvas for blending simulation + alpha = np.ones_like(background_tensor) # optimized to determine transparency pattern + white_background = np.ones_like(background_tensor) # white canvas for blending simulation optimizer = _AdamOptimizer(learning_rate=self.learning_rate) grad_blended_alpha_constant = background_tensor - white_background @@ -340,7 +340,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "imag grad_loss_blended = 2 * (blended_image - self._cached_benign_image) / blended_image.size grad_alpha = grad_loss_blended * grad_blended_alpha_constant alpha = optimizer.update(params=alpha, grads=grad_alpha) - alpha = numpy.clip(alpha, 0.0, 1.0) + alpha = np.clip(alpha, 0.0, 1.0) image_path = await self._save_blended_image(background_tensor, alpha) return ConverterResult(output_text=image_path, output_type="image_path") diff --git a/pyrit/prompt_target/azure_blob_storage_target.py b/pyrit/prompt_target/azure_blob_storage_target.py index 586913eca..7303865c1 100644 --- a/pyrit/prompt_target/azure_blob_storage_target.py +++ b/pyrit/prompt_target/azure_blob_storage_target.py @@ -144,9 +144,9 @@ async def _upload_blob_async(self, file_name: str, data: bytes, content_type: st if isinstance(exc, ClientAuthenticationError): logger.exception( msg="Authentication failed. Please check that the container existence in the " - + "Azure Storage Account and ensure the validity of the provided SAS token. If you " - + "haven't set the SAS token as an environment variable use `az login` to " - + "enable delegation-based SAS authentication to connect to the storage account" + "Azure Storage Account and ensure the validity of the provided SAS token. If you " + "haven't set the SAS token as an environment variable use `az login` to " + "enable delegation-based SAS authentication to connect to the storage account" ) raise logger.exception(msg=f"An unexpected error occurred: {exc}") diff --git a/pyrit/prompt_target/azure_ml_chat_target.py b/pyrit/prompt_target/azure_ml_chat_target.py index 4daac0ee1..a823e4afa 100644 --- a/pyrit/prompt_target/azure_ml_chat_target.py +++ b/pyrit/prompt_target/azure_ml_chat_target.py @@ -180,7 +180,7 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: # Handle Bad Request response_entry = handle_bad_request_exception(response_text=hse.response.text, request=request) elif hse.response.status_code == 429: - raise RateLimitException() from hse + raise RateLimitException from hse else: raise hse @@ -221,7 +221,7 @@ async def _complete_chat_async( 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 ) from e async def _construct_http_body_async( diff --git a/pyrit/prompt_target/crucible_target.py b/pyrit/prompt_target/crucible_target.py index 1f1fe974c..f096bf926 100644 --- a/pyrit/prompt_target/crucible_target.py +++ b/pyrit/prompt_target/crucible_target.py @@ -103,7 +103,7 @@ async def _complete_text_async(self, text: str) -> str: ) if not resp.text: - raise EmptyResponseException() + raise EmptyResponseException logger.info(f'Received the following response from the prompt target "{resp.text}"') return resp.text diff --git a/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py b/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py index 15f1cc80b..85da9e084 100644 --- a/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py +++ b/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py @@ -336,7 +336,7 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: ).strip() if not assistant_response: - raise EmptyResponseException() + raise EmptyResponseException logger.info(f"Assistant's response: {assistant_response}") diff --git a/pyrit/prompt_target/openai/openai_target.py b/pyrit/prompt_target/openai/openai_target.py index bf9c46bf6..96d4f56ac 100644 --- a/pyrit/prompt_target/openai/openai_target.py +++ b/pyrit/prompt_target/openai/openai_target.py @@ -496,14 +496,14 @@ def model_dump_json(self) -> str: request_id = _extract_request_id_from_exception(e) retry_after = _extract_retry_after_from_exception(e) logger.warning(f"RateLimitError request_id={request_id} retry_after={retry_after} error={e}") - raise RateLimitException() from None + raise RateLimitException from None except APIStatusError as e: # Other API status errors - check for 429 here as well request_id = _extract_request_id_from_exception(e) if getattr(e, "status_code", None) == 429: retry_after = _extract_retry_after_from_exception(e) logger.warning(f"429 via APIStatusError request_id={request_id} retry_after={retry_after}") - raise RateLimitException() from None + raise RateLimitException from None logger.exception( f"APIStatusError request_id={request_id} status={getattr(e, 'status_code', None)} error={e}" ) diff --git a/pyrit/prompt_target/rpc_client.py b/pyrit/prompt_target/rpc_client.py index b7e000b0d..f3012a39f 100644 --- a/pyrit/prompt_target/rpc_client.py +++ b/pyrit/prompt_target/rpc_client.py @@ -79,7 +79,7 @@ def wait_for_prompt(self) -> MessagePiece: self._prompt_received_sem.acquire() if self._is_running: return self._prompt_received - raise RPCClientStoppedException() + raise RPCClientStoppedException def send_message(self, response: bool) -> None: """ diff --git a/pyrit/scenario/scenarios/airt/psychosocial_scenario.py b/pyrit/scenario/scenarios/airt/psychosocial_scenario.py index 44c1720f8..4fe242a90 100644 --- a/pyrit/scenario/scenarios/airt/psychosocial_scenario.py +++ b/pyrit/scenario/scenarios/airt/psychosocial_scenario.py @@ -242,7 +242,7 @@ def __init__( Defaults to 5. Increase for more gradual escalation, decrease for faster testing. """ if objectives is not None: - logging.warning( + logger.warning( "objectives is deprecated and will be removed in a future version. " "Use dataset_config in initialize_async instead." ) diff --git a/pyrit/score/scorer.py b/pyrit/score/scorer.py index 4b501b43a..3428c11a9 100644 --- a/pyrit/score/scorer.py +++ b/pyrit/score/scorer.py @@ -223,7 +223,7 @@ async def _score_async(self, message: Message, *, objective: Optional[str] = Non @abstractmethod async def _score_piece_async(self, message_piece: MessagePiece, *, objective: Optional[str] = None) -> list[Score]: - raise NotImplementedError() + raise NotImplementedError def _get_supported_pieces(self, message: Message) -> list[MessagePiece]: """ @@ -245,7 +245,7 @@ def validate_return_scores(self, scores: list[Score]) -> None: Args: scores (list[Score]): The scores to be validated. """ - raise NotImplementedError() + raise NotImplementedError async def evaluate_async( self, diff --git a/pyrit/score/scorer_evaluation/scorer_metrics_io.py b/pyrit/score/scorer_evaluation/scorer_metrics_io.py index 07c9f83bd..59d4f1aff 100644 --- a/pyrit/score/scorer_evaluation/scorer_metrics_io.py +++ b/pyrit/score/scorer_evaluation/scorer_metrics_io.py @@ -223,7 +223,7 @@ def _load_metrics_from_file( def find_objective_metrics_by_hash( *, - hash: str, + hash: str, # noqa: A002 file_path: Optional[Path] = None, ) -> Optional[ObjectiveScorerMetrics]: """ @@ -246,7 +246,7 @@ def find_objective_metrics_by_hash( def find_harm_metrics_by_hash( *, - hash: str, + hash: str, # noqa: A002 harm_category: str, ) -> Optional[HarmScorerMetrics]: """ @@ -266,7 +266,7 @@ def find_harm_metrics_by_hash( def _find_metrics_by_hash( *, file_path: Path, - hash: str, + hash: str, # noqa: A002 metrics_class: type[M], ) -> Optional[M]: """ diff --git a/pyrit/ui/rpc.py b/pyrit/ui/rpc.py index 2586edcaf..bb9828c11 100644 --- a/pyrit/ui/rpc.py +++ b/pyrit/ui/rpc.py @@ -96,7 +96,7 @@ def is_client_ready(self) -> bool: def send_score_prompt(self, prompt: MessagePiece, task: Optional[str] = None) -> None: if not self.is_client_ready(): - raise RPCClientNotReadyException() + raise RPCClientNotReadyException self._callback_score_prompt(prompt, task) def is_ping_missed(self) -> bool: @@ -129,7 +129,7 @@ def start(self) -> None: # Check if the server is already running by checking if the port is already in use. # If the port is already in use, throw an exception. if self._is_instance_running(): - raise RPCAlreadyRunningException() + raise RPCAlreadyRunningException self._score_received_semaphore = Semaphore(0) self._client_ready_semaphore = Semaphore(0) @@ -211,7 +211,7 @@ def wait_for_score(self) -> Score: self._score_received_semaphore.acquire() if not self._server_is_running: - raise RPCServerStoppedException() + raise RPCServerStoppedException score_ref = self._rpc_service.pop_score_received() self._client_ready_semaphore.release() @@ -243,7 +243,7 @@ def wait_for_client(self) -> None: self._client_ready_semaphore.acquire() if not self._server_is_running: - raise RPCServerStoppedException() + raise RPCServerStoppedException logger.info("Client is ready") diff --git a/pyrit/ui/rpc_client.py b/pyrit/ui/rpc_client.py index 7a4312620..d6cae64e3 100644 --- a/pyrit/ui/rpc_client.py +++ b/pyrit/ui/rpc_client.py @@ -55,7 +55,7 @@ def wait_for_prompt(self) -> MessagePiece: self._prompt_received_sem.acquire() if self._is_running: return self._prompt_received - raise RPCClientStoppedException() + raise RPCClientStoppedException def send_message(self, response: bool) -> None: score = Score( diff --git a/tests/integration/mocks.py b/tests/integration/mocks.py index a15db00b6..fedeb95e1 100644 --- a/tests/integration/mocks.py +++ b/tests/integration/mocks.py @@ -39,9 +39,9 @@ def get_sqlite_memory() -> Generator[SQLiteMemory, None, None]: class MockPromptTarget(PromptChatTarget): prompt_sent: list[str] - def __init__(self, id=None, rpm=None) -> None: + def __init__(self, id=None, rpm=None) -> None: # noqa: A002 super().__init__(max_requests_per_minute=rpm) - self.id = id + self.id = id # noqa: A003 self.prompt_sent = [] def set_system_prompt( diff --git a/tests/integration/targets/test_openai_responses_gpt5.py b/tests/integration/targets/test_openai_responses_gpt5.py index 8f6bf3ae4..9e599d3bf 100644 --- a/tests/integration/targets/test_openai_responses_gpt5.py +++ b/tests/integration/targets/test_openai_responses_gpt5.py @@ -9,7 +9,6 @@ import jsonschema import pytest -# from pyrit.auth import get_azure_openai_auth from pyrit.models import MessagePiece from pyrit.prompt_target import OpenAIResponseTarget @@ -21,7 +20,6 @@ def gpt5_args(): "endpoint": endpoint_value, "model_name": os.getenv("AZURE_OPENAI_GPT5_MODEL"), "api_key": os.getenv("AZURE_OPENAI_GPT5_KEY"), - # "api_key": get_azure_openai_auth(endpoint_value), } diff --git a/tests/integration/targets/test_targets_and_secrets.py b/tests/integration/targets/test_targets_and_secrets.py index 86ee6cf15..8aa4dad1f 100644 --- a/tests/integration/targets/test_targets_and_secrets.py +++ b/tests/integration/targets/test_targets_and_secrets.py @@ -460,7 +460,7 @@ async def test_connect_tts(sqlite_instance, endpoint, api_key, model_name): [ ("AZURE_OPENAI_VIDEO_ENDPOINT", "AZURE_OPENAI_VIDEO_KEY", "AZURE_OPENAI_VIDEO_MODEL"), # OpenAI Platform endpoint returns HTTP 401 "Missing scopes: api.videos.write" for all requests - # ("PLATFORM_OPENAI_VIDEO_ENDPOINT", "PLATFORM_OPENAI_VIDEO_KEY", "PLATFORM_OPENAI_VIDEO_MODEL"), + # ("PLATFORM_OPENAI_VIDEO_ENDPOINT", "PLATFORM_OPENAI_VIDEO_KEY", "PLATFORM_OPENAI_VIDEO_MODEL"), # noqa: ERA001 ], ) async def test_connect_video(sqlite_instance, endpoint, api_key, model_name): diff --git a/tests/unit/converter/test_ansi_attack_converter.py b/tests/unit/converter/test_ansi_attack_converter.py index 7f5f00888..cbd577cad 100644 --- a/tests/unit/converter/test_ansi_attack_converter.py +++ b/tests/unit/converter/test_ansi_attack_converter.py @@ -66,7 +66,7 @@ async def test_convert_async_unescape_scenario(): UNESCAPE_STUBS[0], # from scenario 3 ESCAPED_PAYLOADS[0], # from scenario 3 # Finally pick scenario 3 (unescape scenario) as final scenario - # scenario_choices: [scenario1, scenario2, scenario3] + # scenario_choices: [scenario1, scenario2, scenario3] # noqa: ERA001 # scenario 1: "ASKS ..." # scenario 2: "REPEAT..." # scenario 3: "UNESCAPE..." diff --git a/tests/unit/converter/test_image_compression_converter.py b/tests/unit/converter/test_image_compression_converter.py index 413a0fc31..f6e7a3e0f 100644 --- a/tests/unit/converter/test_image_compression_converter.py +++ b/tests/unit/converter/test_image_compression_converter.py @@ -14,7 +14,7 @@ def sample_image_bytes(): """Sample RGB image for testing with configurable format and size.""" - def _create_image(format="PNG", size=(200, 200)): + def _create_image(format="PNG", size=(200, 200)): # noqa: A002 img = Image.new("RGB", size, color=(125, 125, 125)) img_bytes = BytesIO() img.save(img_bytes, format=format) @@ -27,7 +27,7 @@ def _create_image(format="PNG", size=(200, 200)): def sample_transparent_image_bytes(): """Sample RGBA image with transparency for testing with configurable format.""" - def _create_image(format="PNG"): + def _create_image(format="PNG"): # noqa: A002 img = Image.new("RGBA", (200, 200), color=(125, 125, 125, 128)) img_bytes = BytesIO() img.save(img_bytes, format=format) diff --git a/tests/unit/converter/test_math_obfuscation_converter.py b/tests/unit/converter/test_math_obfuscation_converter.py index d60311de7..f7699756d 100644 --- a/tests/unit/converter/test_math_obfuscation_converter.py +++ b/tests/unit/converter/test_math_obfuscation_converter.py @@ -39,7 +39,7 @@ def test_math_obfuscation_spaces_and_newlines(): result = asyncio.run(converter.convert_async(prompt="A B\nC")) lines = result.output_text.splitlines() - # "A", blank (space), "B", blank (newline), "C" + # "A", blank (space), "B", blank (newline), "C" # noqa: ERA001 assert lines[0].startswith("A =") assert lines[1] == "" assert lines[2].startswith("B =") diff --git a/tests/unit/converter/test_selective_text_converter.py b/tests/unit/converter/test_selective_text_converter.py index 2ea624b05..dbe1ccb7a 100644 --- a/tests/unit/converter/test_selective_text_converter.py +++ b/tests/unit/converter/test_selective_text_converter.py @@ -70,7 +70,7 @@ async def test_convert_async_with_index_strategy(self): selection_strategy=IndexSelectionStrategy(start=0, end=5), ) result = await converter.convert_async(prompt="Hello World", input_type="text") - # "Hello" in base64 is "SGVsbG8=" + # "Hello" in base64 is "SGVsbG8=" # noqa: ERA001 assert result.output_text == "SGVsbG8= World" assert result.output_type == "text" @@ -80,7 +80,7 @@ async def test_convert_async_with_regex_strategy(self): selection_strategy=RegexSelectionStrategy(pattern=r"\d+"), ) result = await converter.convert_async(prompt="The code is 12345 here", input_type="text") - # "12345" in base64 is "MTIzNDU=" + # "12345" in base64 is "MTIzNDU=" # noqa: ERA001 assert result.output_text == "The code is MTIzNDU= here" assert result.output_type == "text" @@ -90,7 +90,7 @@ async def test_convert_async_with_keyword_strategy(self): selection_strategy=KeywordSelectionStrategy(keyword="secret"), ) result = await converter.convert_async(prompt="The secret is here", input_type="text") - # "secret" in base64 is "c2VjcmV0" + # "secret" in base64 is "c2VjcmV0" # noqa: ERA001 assert result.output_text == "The c2VjcmV0 is here" assert result.output_type == "text" @@ -100,7 +100,7 @@ async def test_convert_async_with_position_strategy(self): selection_strategy=PositionSelectionStrategy(start_proportion=0.0, end_proportion=0.5), ) result = await converter.convert_async(prompt="0123456789", input_type="text") - # "01234" in base64 is "MDEyMzQ=" + # "01234" in base64 is "MDEyMzQ=" # noqa: ERA001 assert result.output_text == "MDEyMzQ=56789" assert result.output_type == "text" @@ -110,7 +110,7 @@ async def test_convert_async_with_proportion_strategy(self): selection_strategy=ProportionSelectionStrategy(proportion=0.5, anchor="start"), ) result = await converter.convert_async(prompt="0123456789", input_type="text") - # "01234" in base64 is "MDEyMzQ=" + # "01234" in base64 is "MDEyMzQ=" # noqa: ERA001 assert result.output_text == "MDEyMzQ=56789" assert result.output_type == "text" @@ -120,7 +120,7 @@ async def test_convert_async_with_range_strategy(self): selection_strategy=RangeSelectionStrategy(start_proportion=0.0, end_proportion=0.5), ) result = await converter.convert_async(prompt="0123456789", input_type="text") - # "01234" in base64 is "MDEyMzQ=" + # "01234" in base64 is "MDEyMzQ=" # noqa: ERA001 assert result.output_text == "MDEyMzQ=56789" assert result.output_type == "text" @@ -131,7 +131,7 @@ async def test_convert_async_with_preserve_tokens(self): preserve_tokens=True, ) result = await converter.convert_async(prompt="Hello World", input_type="text") - # "Hello" in base64 is "SGVsbG8=" + # "Hello" in base64 is "SGVsbG8=" # noqa: ERA001 assert result.output_text == "⟪SGVsbG8=⟫ World" assert result.output_type == "text" @@ -144,7 +144,7 @@ async def test_convert_async_with_custom_tokens(self): end_token=">>", ) result = await converter.convert_async(prompt="Hello World", input_type="text") - # "Hello" in base64 is "SGVsbG8=" + # "Hello" in base64 is "SGVsbG8=" # noqa: ERA001 assert result.output_text == "<> World" assert result.output_type == "text" @@ -194,7 +194,7 @@ async def test_convert_async_middle_section(self): selection_strategy=IndexSelectionStrategy(start=4, end=10), ) result = await converter.convert_async(prompt="The secret code", input_type="text") - # "secret" in base64 is "c2VjcmV0" + # "secret" in base64 is "c2VjcmV0" # noqa: ERA001 assert result.output_text == "The c2VjcmV0 code" assert result.output_type == "text" @@ -204,7 +204,7 @@ async def test_convert_async_end_section(self): selection_strategy=IndexSelectionStrategy(start=11, end=None), ) result = await converter.convert_async(prompt="Hello World", input_type="text") - # "" (empty string) in base64 is "" + # "" (empty string) in base64 is "" # noqa: ERA001 assert result.output_text == "Hello World" assert result.output_type == "text" @@ -230,7 +230,7 @@ async def test_convert_async_with_keyword_and_context(self): selection_strategy=KeywordSelectionStrategy(keyword="secret", context_before=4, context_after=3), ) result = await converter.convert_async(prompt="The secret is here", input_type="text") - # "The secret is" in base64 is "VGhlIHNlY3JldCBpcw==" + # "The secret is" in base64 is "VGhlIHNlY3JldCBpcw==" # noqa: ERA001 assert result.output_text == "VGhlIHNlY3JldCBpcw== here" assert result.output_type == "text" diff --git a/tests/unit/converter/test_transparency_attack_converter.py b/tests/unit/converter/test_transparency_attack_converter.py index 413df2b61..e110b19d6 100644 --- a/tests/unit/converter/test_transparency_attack_converter.py +++ b/tests/unit/converter/test_transparency_attack_converter.py @@ -5,7 +5,7 @@ import tempfile from unittest.mock import AsyncMock, MagicMock, patch -import numpy +import numpy as np import pytest from PIL import Image @@ -102,8 +102,8 @@ def test_load_and_preprocess_image(self, sample_benign_image): processed_image = converter._load_and_preprocess_image(sample_benign_image) assert processed_image.shape == (50, 50) # height, width (single channel grayscale) - assert processed_image.dtype == numpy.float32 - assert numpy.all(processed_image >= 0.0) and numpy.all(processed_image <= 1.0) + assert processed_image.dtype == np.float32 + assert np.all(processed_image >= 0.0) and np.all(processed_image <= 1.0) for invalid_path in [None, "", "invalid_path.txt", "image.png", "image.gif"]: with pytest.raises(ValueError): @@ -114,8 +114,8 @@ def test_load_and_preprocess_image(self, sample_benign_image): def test_compute_mse_loss(self, sample_benign_image): converter = TransparencyAttackConverter(benign_image_path=sample_benign_image) - blended = numpy.array([[1.0, 2.0], [3.0, 4.0]]) - target = numpy.array([[2.0, 3.0], [4.0, 5.0]]) + blended = np.array([[1.0, 2.0], [3.0, 4.0]]) + target = np.array([[2.0, 3.0], [4.0, 5.0]]) expected_loss = 1.0 loss = converter._compute_mse_loss(blended, target) @@ -124,13 +124,13 @@ def test_compute_mse_loss(self, sample_benign_image): def test_create_blended_image(self, sample_benign_image): converter = TransparencyAttackConverter(benign_image_path=sample_benign_image) - attack_image = numpy.array([[0.2]], dtype=numpy.float32) # 1x1 grayscale image - alpha = numpy.array([[0.8]], dtype=numpy.float32) # 1x1 alpha + attack_image = np.array([[0.2]], dtype=np.float32) # 1x1 grayscale image + alpha = np.array([[0.8]], dtype=np.float32) # 1x1 alpha la_image = converter._create_blended_image(attack_image, alpha) assert la_image.shape == (1, 1, 2) # LA (Luminance + Alpha) - assert la_image.dtype == numpy.uint8 + assert la_image.dtype == np.uint8 expected_gray_value = int(0.2 * 255) assert la_image[0, 0, 0] == expected_gray_value # L (Luminance) assert la_image[0, 0, 1] == int(0.8 * 255) # A (Alpha) @@ -145,8 +145,8 @@ async def test_save_blended_image(self, sample_benign_image): mock_factory.return_value = mock_serializer converter = TransparencyAttackConverter(benign_image_path=sample_benign_image) - attack_image = numpy.ones((10, 10), dtype=numpy.float32) * 0.5 - alpha = numpy.ones((10, 10), dtype=numpy.float32) * 0.7 + attack_image = np.ones((10, 10), dtype=np.float32) * 0.5 + alpha = np.ones((10, 10), dtype=np.float32) * 0.7 result_path = await converter._save_blended_image(attack_image, alpha) diff --git a/tests/unit/exceptions/test_exceptions.py b/tests/unit/exceptions/test_exceptions.py index 516b6e9f3..f95527371 100644 --- a/tests/unit/exceptions/test_exceptions.py +++ b/tests/unit/exceptions/test_exceptions.py @@ -125,7 +125,7 @@ def test_pyrit_target_retry_respects_runtime_env_var(self): def failing_function(): nonlocal call_count call_count += 1 - raise EmptyResponseException() + raise EmptyResponseException # Change the env var AFTER the decorator has been applied original_value = os.environ.get("RETRY_MAX_NUM_ATTEMPTS") @@ -156,7 +156,7 @@ def test_pyrit_json_retry_respects_runtime_env_var(self): def failing_function(): nonlocal call_count call_count += 1 - raise InvalidJsonException() + raise InvalidJsonException # Change the env var AFTER the decorator has been applied original_value = os.environ.get("RETRY_MAX_NUM_ATTEMPTS") @@ -190,7 +190,7 @@ def test_pyrit_placeholder_retry_respects_runtime_env_var(self): def failing_function(): nonlocal call_count call_count += 1 - raise MissingPromptPlaceholderException() + raise MissingPromptPlaceholderException # Change the env var AFTER the decorator has been applied original_value = os.environ.get("RETRY_MAX_NUM_ATTEMPTS") diff --git a/tests/unit/mocks.py b/tests/unit/mocks.py index 7da46c5fe..2c0064cee 100644 --- a/tests/unit/mocks.py +++ b/tests/unit/mocks.py @@ -122,7 +122,7 @@ def raise_for_status(self): class MockPromptTarget(PromptChatTarget): prompt_sent: list[str] - def __init__(self, id=None, rpm=None) -> None: + def __init__(self, id=None, rpm=None) -> None: # noqa: A002 super().__init__(max_requests_per_minute=rpm) self.id = id self.prompt_sent = [] diff --git a/tests/unit/models/test_message_piece.py b/tests/unit/models/test_message_piece.py index 469c94e87..8142ecd5d 100644 --- a/tests/unit/models/test_message_piece.py +++ b/tests/unit/models/test_message_piece.py @@ -601,8 +601,8 @@ def test_order_message_pieces_by_conversation_same_timestamp(): ), ] - sorted = sort_message_pieces(pieces) - assert sorted == expected + sorted_pieces = sort_message_pieces(pieces) + assert sorted_pieces == expected def test_order_message_pieces_by_conversation_empty_list(): diff --git a/tests/unit/models/test_seed.py b/tests/unit/models/test_seed.py index 31f820fe1..32414e0f7 100644 --- a/tests/unit/models/test_seed.py +++ b/tests/unit/models/test_seed.py @@ -288,16 +288,16 @@ def test_group_id_from_empty_group_set_equally(): def test_group_id_set_equally_success(): - id = uuid.uuid4() + group_id = uuid.uuid4() group = SeedGroup( seeds=[ - SeedPrompt(value="Hello", data_type="text", prompt_group_id=id), - SeedPrompt(value="World", data_type="text", prompt_group_id=id), + SeedPrompt(value="Hello", data_type="text", prompt_group_id=group_id), + SeedPrompt(value="World", data_type="text", prompt_group_id=group_id), ] ) assert len(group.prompts) == 2 - assert group.prompts[0].prompt_group_id == id + assert group.prompts[0].prompt_group_id == group_id def test_group_id_set_unequally_raises(): diff --git a/tests/unit/score/test_scorer.py b/tests/unit/score/test_scorer.py index a2e11a91a..a7ec650fd 100644 --- a/tests/unit/score/test_scorer.py +++ b/tests/unit/score/test_scorer.py @@ -829,9 +829,9 @@ async def test_score_response_async_multiple_pieces(): # The following commented-out lines should be uncommented when the permanent solution is implemented # # Should have all auxiliary scores - # assert len(result["auxiliary_scores"]) == 4 - # for score in aux_scores: - # assert score in result["auxiliary_scores"] + # assert len(result["auxiliary_scores"]) == 4 # noqa: ERA001 + # for score in aux_scores: # noqa: ERA001 + # assert score in result["auxiliary_scores"] # noqa: ERA001 # Should have only one objective score (first success) assert len(result["objective_scores"]) == 1 @@ -909,13 +909,13 @@ async def test_score_response_async_skip_on_error_false(): assert len(result["auxiliary_scores"]) == 1 # The following commented-out lines should be uncommented when the permanent solution is implemented # # Should score both pieces for auxiliary - # assert len(result["auxiliary_scores"]) == 2 + # assert len(result["auxiliary_scores"]) == 2 # noqa: ERA001 # But only one objective score (first success) assert len(result["objective_scores"]) == 1 # # Verify both pieces were scored for auxiliary - # assert aux_scorer.score_async.call_count == 2 + # assert aux_scorer.score_async.call_count == 2 # noqa: ERA001 @pytest.mark.asyncio diff --git a/tests/unit/score/test_self_ask_true_false.py b/tests/unit/score/test_self_ask_true_false.py index 58cc2361b..17a10048c 100644 --- a/tests/unit/score/test_self_ask_true_false.py +++ b/tests/unit/score/test_self_ask_true_false.py @@ -183,7 +183,7 @@ def test_self_ask_true_false_get_identifier_long_prompt_hashed(patch_central_dat assert len(identifier.params["system_prompt_template"]) > 100 # GROUNDED prompt is long # But when serialized via to_dict(), long prompts are truncated - # Format: "... [sha256:]" + # Format: "... [sha256:]" # noqa: ERA001 id_dict = identifier.to_dict() sys_prompt_in_dict = id_dict.get("params", {}).get("system_prompt_template", "") if sys_prompt_in_dict: diff --git a/tests/unit/target/test_http_target.py b/tests/unit/target/test_http_target.py index 39d3e6cd3..6e977edf7 100644 --- a/tests/unit/target/test_http_target.py +++ b/tests/unit/target/test_http_target.py @@ -94,7 +94,7 @@ def test_parse_raw_http_request_ignores_content_length(patch_central_database): def test_parse_raw_http_respects_url_path(patch_central_database): request1 = ( "POST https://diffsite.com/test/ HTTP/1.1\nHost: example.com\nContent-Type: " - + "application/json\nContent-Length: 100\n\n" + "application/json\nContent-Length: 100\n\n" ) target = HTTPTarget(http_request=request1) headers, _, url, _, _ = target.parse_raw_http_request(request1)