From caccbe9d4ce0c9bd99731c3a6dfd7a009e7c219f Mon Sep 17 00:00:00 2001 From: romanlutz Date: Mon, 18 May 2026 05:29:54 -0700 Subject: [PATCH 1/4] Add _async suffix to async helpers in pyrit/common Renames the following async helpers to comply with the PyRIT style guide requirement that all async functions end with _async. Old names are kept as thin deprecation wrappers that delegate to the new names and emit DeprecationWarning. They will be removed in v0.16.0. - convert_local_image_to_data_url -> convert_local_image_to_data_url_async - display_image_response -> display_image_response_async - download_specific_files -> download_specific_files_async - download_chunk -> download_chunk_async - download_file -> download_file_async - download_files -> download_files_async - download_with_limit -> download_with_limit_async (nested helper, renamed in place) Updates all internal call sites and test patches to use the new names, and adds deprecation-warning tests for each module-level wrapper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyrit/common/data_url_converter.py | 18 ++++- pyrit/common/display_response.py | 13 +++- pyrit/common/download_hf_model.py | 71 ++++++++++++++++--- .../attack/printer/console_printer.py | 4 +- .../chat_message_normalizer.py | 4 +- .../hugging_face/hugging_face_chat_target.py | 11 ++- .../openai/openai_chat_target.py | 4 +- .../openai/openai_response_target.py | 4 +- .../prompt_target/websocket_copilot_target.py | 4 +- .../test_convert_local_image_to_data_url.py | 12 ++-- tests/unit/common/test_data_url_converter.py | 24 ++++++- tests/unit/common/test_display_response.py | 28 +++++--- tests/unit/common/test_hf_model_downloads.py | 16 +++-- .../attack/printer/test_console_printer.py | 10 +-- .../test_chat_message_normalizer.py | 2 +- .../target/test_huggingface_chat_target.py | 9 +-- .../target/test_openai_chat_target.py | 8 +-- .../target/test_openai_response_target.py | 12 ++-- .../target/test_websocket_copilot_target.py | 6 +- 19 files changed, 190 insertions(+), 70 deletions(-) diff --git a/pyrit/common/data_url_converter.py b/pyrit/common/data_url_converter.py index 9c0fd72651..deef38b621 100644 --- a/pyrit/common/data_url_converter.py +++ b/pyrit/common/data_url_converter.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +from pyrit.common.deprecation import print_deprecation_message from pyrit.models import DataTypeSerializer, data_serializer_factory # Supported image formats for Azure OpenAI GPT-4o, @@ -8,7 +9,7 @@ AZURE_OPENAI_GPT4O_SUPPORTED_IMAGE_FORMATS = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif"] -async def convert_local_image_to_data_url(image_path: str) -> str: +async def convert_local_image_to_data_url_async(image_path: str) -> str: """ Convert a local image file to a data URL encoded in base64. @@ -42,3 +43,18 @@ async def convert_local_image_to_data_url(image_path: str) -> str: # Construct the data URL, as per Azure OpenAI GPT-4 Turbo local image format # https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/gpt-with-vision?tabs=rest%2Csystem-assigned%2Cresource#call-the-chat-completion-apis return f"data:{mime_type};base64,{base64_encoded_data}" + + +async def convert_local_image_to_data_url(image_path: str) -> str: + """ + Delegate to :func:`convert_local_image_to_data_url_async` (deprecated alias). + + Returns: + str: A string containing the MIME type and the base64-encoded data of the image, formatted as a data URL. + """ + print_deprecation_message( + old_item="pyrit.common.data_url_converter.convert_local_image_to_data_url", + new_item=convert_local_image_to_data_url_async, + removed_in="v0.16.0", + ) + return await convert_local_image_to_data_url_async(image_path) diff --git a/pyrit/common/display_response.py b/pyrit/common/display_response.py index 2010aa4532..a4ce298265 100644 --- a/pyrit/common/display_response.py +++ b/pyrit/common/display_response.py @@ -6,6 +6,7 @@ from PIL import Image +from pyrit.common.deprecation import print_deprecation_message from pyrit.common.notebook_utils import is_in_ipython_session from pyrit.memory import CentralMemory from pyrit.models import AzureBlobStorageIO, DiskStorageIO, MessagePiece @@ -13,7 +14,7 @@ logger = logging.getLogger(__name__) -async def display_image_response(response_piece: MessagePiece) -> None: +async def display_image_response_async(response_piece: MessagePiece) -> None: """ Display response images if running in notebook environment. @@ -54,3 +55,13 @@ async def display_image_response(response_piece: MessagePiece) -> None: display(image) # type: ignore[ty:unresolved-reference] # noqa: F821 if response_piece.response_error == "blocked": logger.info("---\nContent blocked, cannot show a response.\n---") + + +async def display_image_response(response_piece: MessagePiece) -> None: + """Delegate to :func:`display_image_response_async` (deprecated alias).""" + print_deprecation_message( + old_item="pyrit.common.display_response.display_image_response", + new_item=display_image_response_async, + removed_in="v0.16.0", + ) + await display_image_response_async(response_piece) diff --git a/pyrit/common/download_hf_model.py b/pyrit/common/download_hf_model.py index 10095b526b..1ba8cb8a8e 100644 --- a/pyrit/common/download_hf_model.py +++ b/pyrit/common/download_hf_model.py @@ -9,6 +9,8 @@ import httpx from huggingface_hub import HfApi +from pyrit.common.deprecation import print_deprecation_message + logger = logging.getLogger(__name__) @@ -37,7 +39,9 @@ def get_available_files(model_id: str, token: str) -> list[str]: return [] -async def download_specific_files(model_id: str, file_patterns: list[str] | None, token: str, cache_dir: Path) -> None: +async def download_specific_files_async( + model_id: str, file_patterns: list[str] | None, token: str, cache_dir: Path +) -> None: """ Download specific files from a Hugging Face model repository. If file_patterns is None, downloads all files. @@ -61,10 +65,12 @@ async def download_specific_files(model_id: str, file_patterns: list[str] | None urls = [base_url + file for file in files_to_download] # Download the files - await download_files(urls, token, cache_dir) + await download_files_async(urls, token, cache_dir) -async def download_chunk(url: str, headers: dict[str, str], start: int, end: int, client: httpx.AsyncClient) -> bytes: +async def download_chunk_async( + url: str, headers: dict[str, str], start: int, end: int, client: httpx.AsyncClient +) -> bytes: """ Download a chunk of the file with a specified byte range. @@ -77,7 +83,7 @@ async def download_chunk(url: str, headers: dict[str, str], start: int, end: int return response.content -async def download_file(url: str, token: str, download_dir: Path, num_splits: int) -> None: +async def download_file_async(url: str, token: str, download_dir: Path, num_splits: int) -> None: """Download a file in multiple segments (splits) using byte-range requests.""" headers = {"Authorization": f"Bearer {token}"} async with httpx.AsyncClient(follow_redirects=True) as client: @@ -95,7 +101,7 @@ async def download_file(url: str, token: str, download_dir: Path, num_splits: in for i in range(num_splits): start = i * chunk_size end = start + chunk_size - 1 if i < num_splits - 1 else file_size - 1 - tasks.append(download_chunk(url, headers, start, end, client)) + tasks.append(download_chunk_async(url, headers, start, end, client)) # Download all chunks concurrently chunks = await asyncio.gather(*tasks) @@ -107,16 +113,63 @@ async def download_file(url: str, token: str, download_dir: Path, num_splits: in logger.info(f"Downloaded {file_name} to {file_path}") -async def download_files( +async def download_files_async( urls: list[str], token: str, download_dir: Path, num_splits: int = 3, parallel_downloads: int = 4 ) -> None: """Download multiple files with parallel downloads and segmented downloading.""" # Limit the number of parallel downloads semaphore = asyncio.Semaphore(parallel_downloads) - async def download_with_limit(url: str) -> None: + async def download_with_limit_async(url: str) -> None: async with semaphore: - await download_file(url, token, download_dir, num_splits) + await download_file_async(url, token, download_dir, num_splits) # Run downloads concurrently, but limit to parallel_downloads at a time - await asyncio.gather(*(download_with_limit(url) for url in urls)) + await asyncio.gather(*(download_with_limit_async(url) for url in urls)) + + +async def download_specific_files(model_id: str, file_patterns: list[str] | None, token: str, cache_dir: Path) -> None: + """Delegate to :func:`download_specific_files_async` (deprecated alias).""" + print_deprecation_message( + old_item="pyrit.common.download_hf_model.download_specific_files", + new_item=download_specific_files_async, + removed_in="v0.16.0", + ) + await download_specific_files_async(model_id, file_patterns, token, cache_dir) + + +async def download_chunk(url: str, headers: dict[str, str], start: int, end: int, client: httpx.AsyncClient) -> bytes: + """ + Delegate to :func:`download_chunk_async` (deprecated alias). + + Returns: + The content of the downloaded chunk. + """ + print_deprecation_message( + old_item="pyrit.common.download_hf_model.download_chunk", + new_item=download_chunk_async, + removed_in="v0.16.0", + ) + return await download_chunk_async(url, headers, start, end, client) + + +async def download_file(url: str, token: str, download_dir: Path, num_splits: int) -> None: + """Delegate to :func:`download_file_async` (deprecated alias).""" + print_deprecation_message( + old_item="pyrit.common.download_hf_model.download_file", + new_item=download_file_async, + removed_in="v0.16.0", + ) + await download_file_async(url, token, download_dir, num_splits) + + +async def download_files( + urls: list[str], token: str, download_dir: Path, num_splits: int = 3, parallel_downloads: int = 4 +) -> None: + """Delegate to :func:`download_files_async` (deprecated alias).""" + print_deprecation_message( + old_item="pyrit.common.download_hf_model.download_files", + new_item=download_files_async, + removed_in="v0.16.0", + ) + await download_files_async(urls, token, download_dir, num_splits, parallel_downloads) diff --git a/pyrit/executor/attack/printer/console_printer.py b/pyrit/executor/attack/printer/console_printer.py index 8c4cb9190d..f61c7b45a9 100644 --- a/pyrit/executor/attack/printer/console_printer.py +++ b/pyrit/executor/attack/printer/console_printer.py @@ -8,7 +8,7 @@ from colorama import Back, Fore, Style -from pyrit.common.display_response import display_image_response +from pyrit.common.display_response import display_image_response_async from pyrit.executor.attack.printer.attack_result_printer import AttackResultPrinter from pyrit.memory import CentralMemory from pyrit.models import AttackOutcome, AttackResult, ConversationType, Score @@ -245,7 +245,7 @@ async def print_messages_async( self._print_wrapped_text(piece.converted_value, Fore.YELLOW) # Display images if present - await display_image_response(piece) + await display_image_response_async(piece) # Print scores with better formatting (only if scores are requested) if include_scores: diff --git a/pyrit/message_normalizer/chat_message_normalizer.py b/pyrit/message_normalizer/chat_message_normalizer.py index e090c6ade9..c5d3547e80 100644 --- a/pyrit/message_normalizer/chat_message_normalizer.py +++ b/pyrit/message_normalizer/chat_message_normalizer.py @@ -6,7 +6,7 @@ import os from typing import TYPE_CHECKING, Any, Union -from pyrit.common.data_url_converter import convert_local_image_to_data_url +from pyrit.common.data_url_converter import convert_local_image_to_data_url_async from pyrit.message_normalizer.message_normalizer import ( MessageListNormalizer, MessageStringNormalizer, @@ -140,7 +140,7 @@ async def _piece_to_content_dict_async(self, piece: MessagePiece) -> dict[str, A return {"type": "text", "text": content} if data_type == "image_path": # Convert local image to base64 data URL - data_url = await convert_local_image_to_data_url(content) + data_url = await convert_local_image_to_data_url_async(content) return {"type": "image_url", "image_url": {"url": data_url}} if data_type == "audio_path": # Convert local audio to base64 for input_audio format 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 472ff0c807..3a0e52bafa 100644 --- a/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py +++ b/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py @@ -17,7 +17,7 @@ ) from pyrit.common import default_values -from pyrit.common.download_hf_model import download_specific_files +from pyrit.common.download_hf_model import download_specific_files_async from pyrit.exceptions import EmptyResponseException, pyrit_target_retry from pyrit.identifiers import ComponentIdentifier from pyrit.models import Message, construct_response_from_request @@ -280,11 +280,16 @@ async def load_model_and_tokenizer(self) -> None: if self.necessary_files is None: # Download all files if no specific files are provided logger.info(f"Downloading all files for {self.model_id}...") - await download_specific_files(self.model_id or "", None, self.huggingface_token, Path(cache_dir)) # type: ignore[ty:invalid-argument-type] + await download_specific_files_async( + self.model_id or "", + None, + self.huggingface_token, # type: ignore[ty:invalid-argument-type] + Path(cache_dir), + ) else: # Download only the necessary files logger.info(f"Downloading specific files for {self.model_id}...") - await download_specific_files( + await download_specific_files_async( self.model_id or "", self.necessary_files, self.huggingface_token, # type: ignore[ty:invalid-argument-type] diff --git a/pyrit/prompt_target/openai/openai_chat_target.py b/pyrit/prompt_target/openai/openai_chat_target.py index db506ec37e..e5a0abde3b 100644 --- a/pyrit/prompt_target/openai/openai_chat_target.py +++ b/pyrit/prompt_target/openai/openai_chat_target.py @@ -8,7 +8,7 @@ from dataclasses import replace from typing import Any, Optional -from pyrit.common.data_url_converter import convert_local_image_to_data_url +from pyrit.common.data_url_converter import convert_local_image_to_data_url_async from pyrit.exceptions import ( EmptyResponseException, PyritException, @@ -641,7 +641,7 @@ async def _build_chat_messages_for_multi_modal_async( entry = {"type": "text", "text": message_piece.converted_value} content.append(entry) elif message_piece.converted_value_data_type == "image_path": - data_base64_encoded_url = await convert_local_image_to_data_url(message_piece.converted_value) + data_base64_encoded_url = await convert_local_image_to_data_url_async(message_piece.converted_value) image_url_entry = {"url": data_base64_encoded_url} entry = {"type": "image_url", "image_url": image_url_entry} content.append(entry) diff --git a/pyrit/prompt_target/openai/openai_response_target.py b/pyrit/prompt_target/openai/openai_response_target.py index b2b5f87c06..2302f0e2ac 100644 --- a/pyrit/prompt_target/openai/openai_response_target.py +++ b/pyrit/prompt_target/openai/openai_response_target.py @@ -14,7 +14,7 @@ from openai.types.shared import ReasoningEffort -from pyrit.common.data_url_converter import convert_local_image_to_data_url +from pyrit.common.data_url_converter import convert_local_image_to_data_url_async from pyrit.exceptions import ( EmptyResponseException, PyritException, @@ -247,7 +247,7 @@ async def _construct_input_item_from_piece(self, piece: MessagePiece) -> dict[st "text": piece.converted_value, } if piece.converted_value_data_type == "image_path": - data_url = await convert_local_image_to_data_url(piece.converted_value) + data_url = await convert_local_image_to_data_url_async(piece.converted_value) return {"type": "input_image", "image_url": {"url": data_url}} raise ValueError(f"Unsupported piece type for inline content: {piece.converted_value_data_type}") diff --git a/pyrit/prompt_target/websocket_copilot_target.py b/pyrit/prompt_target/websocket_copilot_target.py index 174b8e6453..ed8ab07376 100644 --- a/pyrit/prompt_target/websocket_copilot_target.py +++ b/pyrit/prompt_target/websocket_copilot_target.py @@ -14,7 +14,7 @@ from websockets.exceptions import InvalidStatus from pyrit.auth import CopilotAuthenticator, ManualCopilotAuthenticator -from pyrit.common.data_url_converter import convert_local_image_to_data_url +from pyrit.common.data_url_converter import convert_local_image_to_data_url_async from pyrit.exceptions import ( EmptyResponseException, pyrit_target_retry, @@ -332,7 +332,7 @@ async def _process_image_piece_async(self, *, image_path: str, copilot_conversat Returns: dict: Message annotation structure for the uploaded image. """ - data_url = await convert_local_image_to_data_url(image_path) + data_url = await convert_local_image_to_data_url_async(image_path) normalized_image_path = image_path.replace("\\", "/") file_name = pathlib.Path(normalized_image_path).name diff --git a/tests/unit/common/test_convert_local_image_to_data_url.py b/tests/unit/common/test_convert_local_image_to_data_url.py index 375c31e610..b509dc0ebd 100644 --- a/tests/unit/common/test_convert_local_image_to_data_url.py +++ b/tests/unit/common/test_convert_local_image_to_data_url.py @@ -7,13 +7,13 @@ import pytest -from pyrit.common.data_url_converter import convert_local_image_to_data_url +from pyrit.common.data_url_converter import convert_local_image_to_data_url_async from pyrit.memory.sqlite_memory import SQLiteMemory async def test_convert_image_to_data_url_file_not_found(): with pytest.raises(FileNotFoundError): - await convert_local_image_to_data_url("nonexistent.jpg") + await convert_local_image_to_data_url_async("nonexistent.jpg") async def test_convert_image_with_unsupported_extension(): @@ -23,7 +23,7 @@ async def test_convert_image_with_unsupported_extension(): assert os.path.exists(tmp_file_name) with pytest.raises(ValueError) as exc_info: - await convert_local_image_to_data_url(tmp_file_name) + await convert_local_image_to_data_url_async(tmp_file_name) assert "Unsupported image format" in str(exc_info.value) @@ -36,7 +36,7 @@ async def test_convert_local_image_to_data_url_unsupported_format(): tmp_file_name = tmp_file.name try: with pytest.raises(ValueError) as excinfo: - await convert_local_image_to_data_url(tmp_file_name) + await convert_local_image_to_data_url_async(tmp_file_name) assert "Unsupported image format" in str(excinfo.value) finally: os.remove(tmp_file_name) @@ -45,7 +45,7 @@ async def test_convert_local_image_to_data_url_unsupported_format(): async def test_convert_local_image_to_data_url_missing_file(): # Should raise FileNotFoundError for missing file with pytest.raises(FileNotFoundError): - await convert_local_image_to_data_url("not_a_real_file.jpg") + await convert_local_image_to_data_url_async("not_a_real_file.jpg") @patch("os.path.exists", return_value=True) @@ -63,7 +63,7 @@ async def test_convert_image_to_data_url_success( assert os.path.exists(tmp_file_name) - result = await convert_local_image_to_data_url(tmp_file_name) + result = await convert_local_image_to_data_url_async(tmp_file_name) assert "data:image/jpeg;base64,encoded_base64_string" in result # Assertions for the mocks diff --git a/tests/unit/common/test_data_url_converter.py b/tests/unit/common/test_data_url_converter.py index d756c8a232..c18a06336d 100644 --- a/tests/unit/common/test_data_url_converter.py +++ b/tests/unit/common/test_data_url_converter.py @@ -10,6 +10,7 @@ from pyrit.common.data_url_converter import ( AZURE_OPENAI_GPT4O_SUPPORTED_IMAGE_FORMATS, convert_local_image_to_data_url, + convert_local_image_to_data_url_async, ) @@ -21,7 +22,7 @@ def test_supported_image_formats_contains_common_types(): async def test_convert_raises_file_not_found(): with pytest.raises(FileNotFoundError): - await convert_local_image_to_data_url("nonexistent_image.jpg") + await convert_local_image_to_data_url_async("nonexistent_image.jpg") async def test_convert_raises_for_unsupported_format(): @@ -29,7 +30,7 @@ async def test_convert_raises_for_unsupported_format(): tmp = f.name try: with pytest.raises(ValueError, match="Unsupported image format"): - await convert_local_image_to_data_url(tmp) + await convert_local_image_to_data_url_async(tmp) finally: os.remove(tmp) @@ -42,7 +43,24 @@ async def test_convert_returns_data_url(): mock_serializer.read_data_base64 = AsyncMock(return_value="AAAA") with patch("pyrit.common.data_url_converter.data_serializer_factory", return_value=mock_serializer): - result = await convert_local_image_to_data_url(tmp) + result = await convert_local_image_to_data_url_async(tmp) + + assert result.startswith("data:image/png;base64,") + assert result.endswith("AAAA") + finally: + os.remove(tmp) + + +async def test_deprecated_alias_emits_warning_and_delegates(): + with NamedTemporaryFile(suffix=".png", delete=False) as f: + tmp = f.name + try: + mock_serializer = AsyncMock() + mock_serializer.read_data_base64 = AsyncMock(return_value="AAAA") + + with patch("pyrit.common.data_url_converter.data_serializer_factory", return_value=mock_serializer): + with pytest.warns(DeprecationWarning, match="convert_local_image_to_data_url"): + result = await convert_local_image_to_data_url(tmp) assert result.startswith("data:image/png;base64,") assert result.endswith("AAAA") diff --git a/tests/unit/common/test_display_response.py b/tests/unit/common/test_display_response.py index 57aeaf3b7b..f07696fd1a 100644 --- a/tests/unit/common/test_display_response.py +++ b/tests/unit/common/test_display_response.py @@ -6,7 +6,7 @@ import pytest -from pyrit.common.display_response import display_image_response +from pyrit.common.display_response import display_image_response, display_image_response_async @pytest.fixture() @@ -23,7 +23,7 @@ async def test_display_image_skips_when_not_notebook(mock_ipython, _mock_central piece.response_error = "none" piece.converted_value_data_type = "image_path" piece.converted_value = "some/image.png" - await display_image_response(piece) + await display_image_response_async(piece) # No error — function should silently skip display outside notebook @@ -32,7 +32,7 @@ async def test_display_image_logs_blocked_response(_mock_central_memory, caplog) piece.response_error = "blocked" piece.converted_value_data_type = "text" with caplog.at_level(logging.INFO, logger="pyrit.common.display_response"): - await display_image_response(piece) + await display_image_response_async(piece) assert "Content blocked" in caplog.text @@ -40,7 +40,7 @@ async def test_display_image_no_action_for_text_type(_mock_central_memory): piece = MagicMock() piece.response_error = "none" piece.converted_value_data_type = "text" - await display_image_response(piece) + await display_image_response_async(piece) @patch("pyrit.common.display_response.is_in_ipython_session", return_value=True) @@ -55,7 +55,7 @@ async def test_display_image_reads_and_displays(mock_display, mock_image, mock_i mock_img_obj = MagicMock() mock_image.open.return_value = mock_img_obj - await display_image_response(piece) + await display_image_response_async(piece) _mock_central_memory.results_storage_io.read_file.assert_awaited_once_with("path/to/img.png") mock_image.open.assert_called_once() @@ -72,13 +72,13 @@ async def test_display_image_logs_error_on_read_failure(mock_ipython, _mock_cent _mock_central_memory.results_storage_io.read_file = AsyncMock(side_effect=Exception("disk error")) with caplog.at_level(logging.ERROR, logger="pyrit.common.display_response"): - await display_image_response(piece) + await display_image_response_async(piece) assert "Failed to read image" in caplog.text @patch("pyrit.common.display_response.is_in_ipython_session", return_value=True) async def test_display_image_logs_error_when_storage_io_is_none(mock_ipython, caplog): - """Test that display_image_response logs error and returns when results_storage_io is None.""" + """Test that display_image_response_async logs error and returns when results_storage_io is None.""" mock_memory = MagicMock() mock_memory.results_storage_io = None with patch("pyrit.memory.CentralMemory.get_memory_instance", return_value=mock_memory): @@ -88,7 +88,7 @@ async def test_display_image_logs_error_when_storage_io_is_none(mock_ipython, ca piece.converted_value = "some/image.png" with caplog.at_level(logging.ERROR, logger="pyrit.common.display_response"): - await display_image_response(piece) + await display_image_response_async(piece) assert "Failed to read image" in caplog.text @@ -115,7 +115,7 @@ async def test_display_image_azure_fallback_to_disk(mock_display, mock_image, mo piece.converted_value_data_type = "image_path" piece.converted_value = "some/image.png" - await display_image_response(piece) + await display_image_response_async(piece) mock_disk_instance.read_file.assert_awaited_once_with("some/image.png") mock_image.open.assert_called_once() @@ -144,6 +144,14 @@ async def test_display_image_azure_and_disk_both_fail(mock_disk_io_cls, mock_ipy piece.converted_value = "some/image.png" with caplog.at_level(logging.ERROR, logger="pyrit.common.display_response"): - await display_image_response(piece) + await display_image_response_async(piece) assert "Failed to read image" in caplog.text + + +async def test_deprecated_alias_emits_warning_and_delegates(_mock_central_memory): + piece = MagicMock() + piece.response_error = "blocked" + piece.converted_value_data_type = "text" + with pytest.warns(DeprecationWarning, match="display_image_response"): + await display_image_response(piece) diff --git a/tests/unit/common/test_hf_model_downloads.py b/tests/unit/common/test_hf_model_downloads.py index ecb920a336..e2e28d7222 100644 --- a/tests/unit/common/test_hf_model_downloads.py +++ b/tests/unit/common/test_hf_model_downloads.py @@ -8,7 +8,7 @@ import pytest # Import functions to test from local application files -from pyrit.common.download_hf_model import download_specific_files +from pyrit.common.download_hf_model import download_specific_files, download_specific_files_async # Define constants for testing MODEL_ID = "microsoft/Phi-3-mini-4k-instruct" @@ -32,9 +32,17 @@ def setup_environment(): yield token -async def test_download_specific_files(setup_environment): +async def test_download_specific_files_async(setup_environment): """Test downloading specific files""" token = setup_environment # Get the token from the fixture - with patch("os.makedirs"), patch("pyrit.common.download_hf_model.download_files"): - await download_specific_files(MODEL_ID, FILE_PATTERNS, token, Path("")) + with patch("os.makedirs"), patch("pyrit.common.download_hf_model.download_files_async"): + await download_specific_files_async(MODEL_ID, FILE_PATTERNS, token, Path("")) + + +async def test_deprecated_alias_emits_warning_and_delegates(setup_environment): + token = setup_environment + + with patch("os.makedirs"), patch("pyrit.common.download_hf_model.download_files_async"): + with pytest.warns(DeprecationWarning, match="download_specific_files"): + await download_specific_files(MODEL_ID, FILE_PATTERNS, token, Path("")) diff --git a/tests/unit/executor/attack/printer/test_console_printer.py b/tests/unit/executor/attack/printer/test_console_printer.py index b8195db5ba..6965414ef1 100644 --- a/tests/unit/executor/attack/printer/test_console_printer.py +++ b/tests/unit/executor/attack/printer/test_console_printer.py @@ -227,7 +227,7 @@ async def test_print_messages_async_empty_list(printer, capsys): assert "No messages to display" in captured.out -@patch("pyrit.executor.attack.printer.console_printer.display_image_response", new_callable=AsyncMock) +@patch("pyrit.executor.attack.printer.console_printer.display_image_response_async", new_callable=AsyncMock) async def test_print_messages_async_user_message(mock_display, printer, sample_message, capsys): await printer.print_messages_async(messages=[sample_message]) captured = capsys.readouterr() @@ -236,7 +236,7 @@ async def test_print_messages_async_user_message(mock_display, printer, sample_m assert "Hello world" in captured.out -@patch("pyrit.executor.attack.printer.console_printer.display_image_response", new_callable=AsyncMock) +@patch("pyrit.executor.attack.printer.console_printer.display_image_response_async", new_callable=AsyncMock) async def test_print_messages_async_assistant_message(mock_display, printer, capsys): piece = MessagePiece( role="assistant", @@ -250,7 +250,7 @@ async def test_print_messages_async_assistant_message(mock_display, printer, cap assert "Response" in captured.out -@patch("pyrit.executor.attack.printer.console_printer.display_image_response", new_callable=AsyncMock) +@patch("pyrit.executor.attack.printer.console_printer.display_image_response_async", new_callable=AsyncMock) async def test_print_messages_async_converted_differs(mock_display, printer, capsys): piece = MessagePiece( role="user", @@ -347,7 +347,7 @@ def test_print_wrapped_text_with_newlines(printer, capsys): assert "Line four" in captured.out -@patch("pyrit.executor.attack.printer.console_printer.display_image_response", new_callable=AsyncMock) +@patch("pyrit.executor.attack.printer.console_printer.display_image_response_async", new_callable=AsyncMock) async def test_print_messages_async_blocked_without_partial_content(mock_display, printer, capsys): piece = MessagePiece( role="assistant", @@ -364,7 +364,7 @@ async def test_print_messages_async_blocked_without_partial_content(mock_display assert "status_code" not in captured.out -@patch("pyrit.executor.attack.printer.console_printer.display_image_response", new_callable=AsyncMock) +@patch("pyrit.executor.attack.printer.console_printer.display_image_response_async", new_callable=AsyncMock) async def test_print_messages_async_blocked_with_partial_content(mock_display, printer, capsys): piece = MessagePiece( role="assistant", diff --git a/tests/unit/message_normalizer/test_chat_message_normalizer.py b/tests/unit/message_normalizer/test_chat_message_normalizer.py index a8a402d82f..b9a7cec57b 100644 --- a/tests/unit/message_normalizer/test_chat_message_normalizer.py +++ b/tests/unit/message_normalizer/test_chat_message_normalizer.py @@ -131,7 +131,7 @@ async def test_image_path_conversion(self): normalizer = ChatMessageNormalizer() with patch( - "pyrit.message_normalizer.chat_message_normalizer.convert_local_image_to_data_url", + "pyrit.message_normalizer.chat_message_normalizer.convert_local_image_to_data_url_async", new_callable=AsyncMock, ) as mock_convert: mock_convert.return_value = "data:image/png;base64,abc123" diff --git a/tests/unit/prompt_target/target/test_huggingface_chat_target.py b/tests/unit/prompt_target/target/test_huggingface_chat_target.py index c47d2ebdc2..6d5ddcf367 100644 --- a/tests/unit/prompt_target/target/test_huggingface_chat_target.py +++ b/tests/unit/prompt_target/target/test_huggingface_chat_target.py @@ -99,9 +99,10 @@ def mock_create_task(): @pytest.fixture(autouse=True) -def mock_download_specific_files(): +def mock_download_specific_files_async(): with patch( - "pyrit.prompt_target.hugging_face.hugging_face_chat_target.download_specific_files", new_callable=AsyncMock + "pyrit.prompt_target.hugging_face.hugging_face_chat_target.download_specific_files_async", + new_callable=AsyncMock, ) as mock: yield mock @@ -118,7 +119,7 @@ def test_init_with_no_token_var_raises(monkeypatch, patch_central_database): @pytest.mark.skipif(not is_torch_installed(), reason="torch is not installed") -async def test_hf_initialization(patch_central_database, mock_download_specific_files): +async def test_hf_initialization(patch_central_database, mock_download_specific_files_async): # Test the initialization without loading the actual models hf_chat = HuggingFaceChatTarget(model_id="test_model", use_cuda=False) assert hf_chat.model_id == "test_model" @@ -128,7 +129,7 @@ async def test_hf_initialization(patch_central_database, mock_download_specific_ await hf_chat.load_model_and_tokenizer() assert hf_chat.model is not None assert hf_chat.tokenizer is not None - mock_download_specific_files.assert_awaited_once() + mock_download_specific_files_async.assert_awaited_once() @pytest.mark.skipif(not is_torch_installed(), reason="torch is not installed") diff --git a/tests/unit/prompt_target/target/test_openai_chat_target.py b/tests/unit/prompt_target/target/test_openai_chat_target.py index 0540bb68ca..9a5881b3aa 100644 --- a/tests/unit/prompt_target/target/test_openai_chat_target.py +++ b/tests/unit/prompt_target/target/test_openai_chat_target.py @@ -123,7 +123,7 @@ async def test_build_chat_messages_for_multi_modal(target: OpenAIChatTarget): ) ] with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): messages = await target._build_chat_messages_for_multi_modal_async(entries) @@ -303,7 +303,7 @@ async def test_send_prompt_async_empty_response_adds_to_memory(openai_response_j ) # Make assistant response empty with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): # Mock the OpenAI SDK client to return empty content @@ -412,7 +412,7 @@ async def test_send_prompt_async(openai_response_json: dict, patch_central_datab ] ) with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): # Mock the OpenAI SDK client to return a completion @@ -478,7 +478,7 @@ async def test_send_prompt_async_empty_response_retries(openai_response_json: di ) # Make assistant response empty with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): # Mock the OpenAI SDK client to return empty content diff --git a/tests/unit/prompt_target/target/test_openai_response_target.py b/tests/unit/prompt_target/target/test_openai_response_target.py index 1355f66478..10e1d0036d 100644 --- a/tests/unit/prompt_target/target/test_openai_response_target.py +++ b/tests/unit/prompt_target/target/test_openai_response_target.py @@ -174,7 +174,7 @@ async def test_build_input_for_multi_modal(target: OpenAIResponseTarget): ), ] with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): messages = await target._build_input_for_multi_modal_async(entries) @@ -329,7 +329,7 @@ async def test_send_prompt_async_empty_response_adds_to_memory( mock_response = create_mock_response(openai_response_json) with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): target._async_client.responses.create = AsyncMock(return_value=mock_response) # type: ignore[method-assign] @@ -420,7 +420,7 @@ async def test_send_prompt_async(openai_response_json: dict, target: OpenAIRespo mock_response = create_mock_response(openai_response_json) with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): target._async_client.responses.create = AsyncMock(return_value=mock_response) # type: ignore[method-assign] @@ -468,7 +468,7 @@ async def test_send_prompt_async_empty_response_retries(openai_response_json: di mock_response = create_mock_response(openai_response_json) with patch( - "pyrit.common.data_url_converter.convert_local_image_to_data_url", + "pyrit.common.data_url_converter.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,encoded_string", ): target._async_client.responses.create = AsyncMock(return_value=mock_response) # type: ignore[method-assign] @@ -678,7 +678,7 @@ async def test_build_input_for_multi_modal_async_image_and_text(target: OpenAIRe ) req = Message(message_pieces=[text_piece, image_piece]) with patch( - "pyrit.prompt_target.openai.openai_response_target.convert_local_image_to_data_url", + "pyrit.prompt_target.openai.openai_response_target.convert_local_image_to_data_url_async", return_value="data:image/jpeg;base64,abc", ): result = await target._build_input_for_multi_modal_async([req]) @@ -754,7 +754,7 @@ async def test_build_input_for_multi_modal_async_filters_reasoning(target: OpenA ] # Patch image conversion (should not be called) - with patch("pyrit.common.data_url_converter.convert_local_image_to_data_url", new_callable=AsyncMock): + with patch("pyrit.common.data_url_converter.convert_local_image_to_data_url_async", new_callable=AsyncMock): result = await target._build_input_for_multi_modal_async(conversation) # Reasoning is now filtered out (not sent to API), so we have 3 items: diff --git a/tests/unit/prompt_target/target/test_websocket_copilot_target.py b/tests/unit/prompt_target/target/test_websocket_copilot_target.py index ef855b668c..31cf99254e 100644 --- a/tests/unit/prompt_target/target/test_websocket_copilot_target.py +++ b/tests/unit/prompt_target/target/test_websocket_copilot_target.py @@ -112,7 +112,7 @@ def sample_mixed_pieces(make_message_piece): @pytest.fixture def patch_convert_local_image_to_data_url(): with patch( - "pyrit.prompt_target.websocket_copilot_target.convert_local_image_to_data_url", + "pyrit.prompt_target.websocket_copilot_target.convert_local_image_to_data_url_async", new=AsyncMock(return_value="data:image/png;base64,abc123"), ): yield @@ -666,7 +666,7 @@ async def test_connect_and_send_with_image_pieces(self, mock_authenticator, samp with patch("websockets.connect", return_value=mock_websocket): with patch.object(target, "_upload_image_async", new=AsyncMock(return_value="doc_id_123")): with patch( - "pyrit.prompt_target.websocket_copilot_target.convert_local_image_to_data_url", + "pyrit.prompt_target.websocket_copilot_target.convert_local_image_to_data_url_async", new=AsyncMock(return_value="data:image/png;base64,abc123"), ): response = await target._connect_and_send( @@ -691,7 +691,7 @@ async def test_connect_and_send_with_mixed_content(self, mock_authenticator, sam with patch("websockets.connect", return_value=mock_websocket): with patch.object(target, "_upload_image_async", new=AsyncMock(side_effect=["doc_id_1", "doc_id_2"])): with patch( - "pyrit.prompt_target.websocket_copilot_target.convert_local_image_to_data_url", + "pyrit.prompt_target.websocket_copilot_target.convert_local_image_to_data_url_async", new=AsyncMock(return_value="data:image/png;base64,abc123"), ): response = await target._connect_and_send( From 17085c572eb0c6df6b9ebe08f0cfbb1c7f596537 Mon Sep 17 00:00:00 2001 From: romanlutz Date: Mon, 18 May 2026 06:46:49 -0700 Subject: [PATCH 2/4] Add tests covering new deprecation wrappers and HF necessary_files branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unit/common/test_hf_model_downloads.py | 114 +++++++++++++++++- .../target/test_huggingface_chat_target.py | 11 ++ 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/tests/unit/common/test_hf_model_downloads.py b/tests/unit/common/test_hf_model_downloads.py index e2e28d7222..e30add2421 100644 --- a/tests/unit/common/test_hf_model_downloads.py +++ b/tests/unit/common/test_hf_model_downloads.py @@ -3,12 +3,21 @@ import os from pathlib import Path -from unittest.mock import patch +from unittest.mock import AsyncMock, MagicMock, patch import pytest # Import functions to test from local application files -from pyrit.common.download_hf_model import download_specific_files, download_specific_files_async +from pyrit.common.download_hf_model import ( + download_chunk, + download_chunk_async, + download_file, + download_file_async, + download_files, + download_files_async, + download_specific_files, + download_specific_files_async, +) # Define constants for testing MODEL_ID = "microsoft/Phi-3-mini-4k-instruct" @@ -46,3 +55,104 @@ async def test_deprecated_alias_emits_warning_and_delegates(setup_environment): with patch("os.makedirs"), patch("pyrit.common.download_hf_model.download_files_async"): with pytest.warns(DeprecationWarning, match="download_specific_files"): await download_specific_files(MODEL_ID, FILE_PATTERNS, token, Path("")) + + +async def test_download_chunk_deprecated_alias_emits_warning_and_delegates(): + client = MagicMock() + seen: dict[str, tuple] = {} + + async def fake_chunk_async(url, headers, start, end, c): + seen["args"] = (url, headers, start, end, c) + return b"data" + + with patch("pyrit.common.download_hf_model.download_chunk_async", new=fake_chunk_async): + with pytest.warns(DeprecationWarning, match="download_chunk"): + result = await download_chunk("https://example/file", {"k": "v"}, 0, 9, client) + + assert seen["args"] == ("https://example/file", {"k": "v"}, 0, 9, client) + assert result == b"data" + + +async def test_download_file_deprecated_alias_emits_warning_and_delegates(): + seen: dict[str, tuple] = {} + + async def fake_file_async(url, token, download_dir, num_splits): + seen["args"] = (url, token, download_dir, num_splits) + + with patch("pyrit.common.download_hf_model.download_file_async", new=fake_file_async): + with pytest.warns(DeprecationWarning, match="download_file"): + await download_file("https://example/file", "token", Path(""), 3) + + assert seen["args"] == ("https://example/file", "token", Path(""), 3) + + +async def test_download_files_deprecated_alias_emits_warning_and_delegates(): + seen: dict[str, tuple] = {} + + async def fake_files_async(urls, token, download_dir, num_splits, parallel_downloads): + seen["args"] = (urls, token, download_dir, num_splits, parallel_downloads) + + with patch("pyrit.common.download_hf_model.download_files_async", new=fake_files_async): + with pytest.warns(DeprecationWarning, match="download_files"): + await download_files(["https://example/file"], "token", Path(""), 3, 4) + + assert seen["args"] == (["https://example/file"], "token", Path(""), 3, 4) + + +async def test_download_files_async_dispatches_one_call_per_url(): + """Exercise the nested download_with_limit_async helper plus asyncio.gather.""" + seen_urls: list[str] = [] + + async def fake_file_async(url, token, download_dir, num_splits): + seen_urls.append(url) + + with patch("pyrit.common.download_hf_model.download_file_async", new=fake_file_async): + urls = ["https://example/a", "https://example/b", "https://example/c"] + await download_files_async(urls, "token", Path("/tmp"), num_splits=2, parallel_downloads=2) + + assert sorted(seen_urls) == sorted(urls) + + +async def test_download_file_async_schedules_one_chunk_per_split(tmp_path): + """Exercise the chunk-task assembly inside download_file_async.""" + num_splits = 3 + file_size = 30 + chunk_bytes = b"abcdefghij" + + head_response = MagicMock() + head_response.raise_for_status = MagicMock() + head_response.headers = {"Content-Length": str(file_size)} + + client_instance = MagicMock() + client_instance.head = AsyncMock(return_value=head_response) + + class _ClientCM: + async def __aenter__(self): + return client_instance + + async def __aexit__(self, exc_type, exc, tb): + return False + + with ( + patch("pyrit.common.download_hf_model.httpx.AsyncClient", return_value=_ClientCM()), + patch("pyrit.common.download_hf_model.download_chunk_async", new_callable=AsyncMock) as mock_chunk_async, + ): + mock_chunk_async.return_value = chunk_bytes + await download_file_async("https://example/myfile.bin", "token", tmp_path, num_splits) + + assert mock_chunk_async.await_count == num_splits + assert (tmp_path / "myfile.bin").read_bytes() == chunk_bytes * num_splits + + +async def test_download_chunk_async_returns_response_content(): + """Sanity-check the real download_chunk_async implementation.""" + response = MagicMock() + response.raise_for_status = MagicMock() + response.content = b"chunk-payload" + client = MagicMock() + client.get = AsyncMock(return_value=response) + + result = await download_chunk_async("https://example/file", {"Authorization": "Bearer t"}, 0, 9, client) + + assert result == b"chunk-payload" + client.get.assert_awaited_once() diff --git a/tests/unit/prompt_target/target/test_huggingface_chat_target.py b/tests/unit/prompt_target/target/test_huggingface_chat_target.py index 6d5ddcf367..1a28a26531 100644 --- a/tests/unit/prompt_target/target/test_huggingface_chat_target.py +++ b/tests/unit/prompt_target/target/test_huggingface_chat_target.py @@ -132,6 +132,17 @@ async def test_hf_initialization(patch_central_database, mock_download_specific_ mock_download_specific_files_async.assert_awaited_once() +@pytest.mark.skipif(not is_torch_installed(), reason="torch is not installed") +async def test_hf_initialization_with_necessary_files(patch_central_database, mock_download_specific_files_async): + hf_chat = HuggingFaceChatTarget( + model_id="test_model", use_cuda=False, necessary_files=["config.json", "tokenizer.json"] + ) + await hf_chat.load_model_and_tokenizer() + mock_download_specific_files_async.assert_awaited_once() + args = mock_download_specific_files_async.await_args.args + assert args[1] == ["config.json", "tokenizer.json"] + + @pytest.mark.skipif(not is_torch_installed(), reason="torch is not installed") async def test_is_model_id_valid_true(): # Simulate valid model ID From 0fb107137220aeff874b1a52c0be8ae8644d3fea Mon Sep 17 00:00:00 2001 From: romanlutz Date: Mon, 18 May 2026 12:29:25 -0700 Subject: [PATCH 3/4] Clear HF cache and use unique model_id in necessary_files test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../target/test_huggingface_chat_target.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/unit/prompt_target/target/test_huggingface_chat_target.py b/tests/unit/prompt_target/target/test_huggingface_chat_target.py index 1a28a26531..93a4ca912f 100644 --- a/tests/unit/prompt_target/target/test_huggingface_chat_target.py +++ b/tests/unit/prompt_target/target/test_huggingface_chat_target.py @@ -134,13 +134,17 @@ async def test_hf_initialization(patch_central_database, mock_download_specific_ @pytest.mark.skipif(not is_torch_installed(), reason="torch is not installed") async def test_hf_initialization_with_necessary_files(patch_central_database, mock_download_specific_files_async): - hf_chat = HuggingFaceChatTarget( - model_id="test_model", use_cuda=False, necessary_files=["config.json", "tokenizer.json"] - ) - await hf_chat.load_model_and_tokenizer() - mock_download_specific_files_async.assert_awaited_once() - args = mock_download_specific_files_async.await_args.args - assert args[1] == ["config.json", "tokenizer.json"] + HuggingFaceChatTarget.disable_cache() + try: + hf_chat = HuggingFaceChatTarget( + model_id="test_model_necessary_files", use_cuda=False, necessary_files=["config.json", "tokenizer.json"] + ) + await hf_chat.load_model_and_tokenizer() + mock_download_specific_files_async.assert_awaited_once() + args = mock_download_specific_files_async.await_args.args + assert args[1] == ["config.json", "tokenizer.json"] + finally: + HuggingFaceChatTarget.enable_cache() @pytest.mark.skipif(not is_torch_installed(), reason="torch is not installed") From 7fa502bbf1549cefc97ea9377f57fc86376beb3a Mon Sep 17 00:00:00 2001 From: romanlutz Date: Tue, 19 May 2026 16:17:59 -0700 Subject: [PATCH 4/4] Address PR review: drop v prefix and use full string paths for new_item Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyrit/common/data_url_converter.py | 4 ++-- pyrit/common/display_response.py | 4 ++-- pyrit/common/download_hf_model.py | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pyrit/common/data_url_converter.py b/pyrit/common/data_url_converter.py index deef38b621..1e3e52f87b 100644 --- a/pyrit/common/data_url_converter.py +++ b/pyrit/common/data_url_converter.py @@ -54,7 +54,7 @@ async def convert_local_image_to_data_url(image_path: str) -> str: """ print_deprecation_message( old_item="pyrit.common.data_url_converter.convert_local_image_to_data_url", - new_item=convert_local_image_to_data_url_async, - removed_in="v0.16.0", + new_item="pyrit.common.data_url_converter.convert_local_image_to_data_url_async", + removed_in="0.16.0", ) return await convert_local_image_to_data_url_async(image_path) diff --git a/pyrit/common/display_response.py b/pyrit/common/display_response.py index a4ce298265..be60649a72 100644 --- a/pyrit/common/display_response.py +++ b/pyrit/common/display_response.py @@ -61,7 +61,7 @@ async def display_image_response(response_piece: MessagePiece) -> None: """Delegate to :func:`display_image_response_async` (deprecated alias).""" print_deprecation_message( old_item="pyrit.common.display_response.display_image_response", - new_item=display_image_response_async, - removed_in="v0.16.0", + new_item="pyrit.common.display_response.display_image_response_async", + removed_in="0.16.0", ) await display_image_response_async(response_piece) diff --git a/pyrit/common/download_hf_model.py b/pyrit/common/download_hf_model.py index 1ba8cb8a8e..0699737e2a 100644 --- a/pyrit/common/download_hf_model.py +++ b/pyrit/common/download_hf_model.py @@ -132,8 +132,8 @@ async def download_specific_files(model_id: str, file_patterns: list[str] | None """Delegate to :func:`download_specific_files_async` (deprecated alias).""" print_deprecation_message( old_item="pyrit.common.download_hf_model.download_specific_files", - new_item=download_specific_files_async, - removed_in="v0.16.0", + new_item="pyrit.common.download_hf_model.download_specific_files_async", + removed_in="0.16.0", ) await download_specific_files_async(model_id, file_patterns, token, cache_dir) @@ -147,8 +147,8 @@ async def download_chunk(url: str, headers: dict[str, str], start: int, end: int """ print_deprecation_message( old_item="pyrit.common.download_hf_model.download_chunk", - new_item=download_chunk_async, - removed_in="v0.16.0", + new_item="pyrit.common.download_hf_model.download_chunk_async", + removed_in="0.16.0", ) return await download_chunk_async(url, headers, start, end, client) @@ -157,8 +157,8 @@ async def download_file(url: str, token: str, download_dir: Path, num_splits: in """Delegate to :func:`download_file_async` (deprecated alias).""" print_deprecation_message( old_item="pyrit.common.download_hf_model.download_file", - new_item=download_file_async, - removed_in="v0.16.0", + new_item="pyrit.common.download_hf_model.download_file_async", + removed_in="0.16.0", ) await download_file_async(url, token, download_dir, num_splits) @@ -169,7 +169,7 @@ async def download_files( """Delegate to :func:`download_files_async` (deprecated alias).""" print_deprecation_message( old_item="pyrit.common.download_hf_model.download_files", - new_item=download_files_async, - removed_in="v0.16.0", + new_item="pyrit.common.download_hf_model.download_files_async", + removed_in="0.16.0", ) await download_files_async(urls, token, download_dir, num_splits, parallel_downloads)