diff --git a/.github/instructions/style-guide.instructions.md b/.github/instructions/style-guide.instructions.md index 0a5e0bcd10..3ca0c7626c 100644 --- a/.github/instructions/style-guide.instructions.md +++ b/.github/instructions/style-guide.instructions.md @@ -175,14 +175,22 @@ paths stay fast. ### Lazy `__init__.py` Exports (PEP 562) -Public API packages (`pyrit.prompt_target`, `pyrit.converter`, `pyrit.score`) -use `__getattr__`-based lazy loading so heavy symbols can be imported from the -package without paying the cost at package load time. See -`pyrit/prompt_target/__init__.py` for the canonical example. Rules: - -- Lazy names must remain in `__all__` and have a `TYPE_CHECKING` import for IDE support. -- Internal utility packages (e.g., `pyrit.common`) simply omit heavy submodules - from `__init__.py` — consumers import directly from the specific file. +Public API packages use `__getattr__`-based lazy loading so package imports do +not load every implementation module. Use `pyrit.common.lazy_imports` and follow +the standard contract: + +- Add each public export to `_LAZY_EXPORTS`, which is the runtime source of truth. +- Use a module string when the public and source attribute names match. Use the + tuple form only for aliases or module-valued exports. +- Set `__all__ = list(_LAZY_EXPORTS)`. +- Put every public export under `if TYPE_CHECKING:` for editor and static-analysis support. +- Implement `__getattr__` with `resolve_lazy_export` and `__dir__` with `get_lazy_dir`. +- Do not add eager implementation imports to package initializers. + +The package contract tests in `tests/unit/common/test_lazy_package_imports.py` +enforce these rules. Internal heavy submodules that are not public exports +should remain omitted from `__init__.py`; consumers import them from the +specific module. ### Import Paths diff --git a/pyrit/__init__.py b/pyrit/__init__.py index 08873700b0..089dbd1b5a 100644 --- a/pyrit/__init__.py +++ b/pyrit/__init__.py @@ -1,12 +1,60 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -__name__ = "pyrit" -# Remove dev suffix when releasing and keep in sync with pyproject.toml -# NOTE: __version__ must be set before imports below to avoid circular import issues. -# Submodules (e.g., component_identifier, memory_models) reference pyrit.__version__ -# and get imported transitively during the .common import chain. -__version__ = "1.1.0.dev0" - -from .common import turn_off_transformers_warning # noqa: F401 -from .show_versions import show_versions # noqa: F401 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# ruff: noqa: F401 + +"""PyRIT public package API.""" + +import os +import sys +from types import ModuleType +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + import pyrit.common.turn_off_transformers_warning as turn_off_transformers_warning + from pyrit._version import __version__ + from pyrit.show_versions import show_versions + +# Most people install PyRIT without torch, so suppress the transformers advisory +# before any PyRIT submodule can import transformers. +os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "True" + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "__version__": "pyrit._version", + "show_versions": "pyrit.show_versions", + "turn_off_transformers_warning": ("pyrit.common.turn_off_transformers_warning", None), +} + +__all__ = list(_LAZY_EXPORTS) + + +class _LazyPyRITModule(ModuleType): + """Resolve exports that share a name with an imported child module.""" + + def __getattribute__(self, name: str) -> object: + if name == "show_versions": + module_globals = ModuleType.__getattribute__(self, "__dict__") + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=module_globals, + exports=_LAZY_EXPORTS, + ) + return ModuleType.__getattribute__(self, name) + + +sys.modules[__name__].__class__ = _LazyPyRITModule + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/_version.py b/pyrit/_version.py new file mode 100644 index 0000000000..081e04fad6 --- /dev/null +++ b/pyrit/_version.py @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""PyRIT package version.""" + +# Keep this module dependency-free to avoid circular imports. Submodules such +# as component identifiers and memory models reference ``pyrit.__version__`` +# and can be imported transitively while the package is still initializing. +# Remove the development suffix when releasing and keep this value in sync with pyproject.toml. +__version__ = "1.1.0.dev0" diff --git a/pyrit/analytics/__init__.py b/pyrit/analytics/__init__.py index 654923ce87..bbbd1def2b 100644 --- a/pyrit/analytics/__init__.py +++ b/pyrit/analytics/__init__.py @@ -1,26 +1,39 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Analytics module for PyRIT conversation and result analysis.""" -from pyrit.analytics.conversation_analytics import ConversationAnalytics -from pyrit.analytics.result_analysis import ( - AttackStats, - analyze_results, - get_cached_results_for_technique, -) -from pyrit.analytics.text_matching import ( - ApproximateTextMatching, - ExactTextMatching, - TextMatching, -) - -__all__ = [ - "analyze_results", - "ApproximateTextMatching", - "AttackStats", - "ConversationAnalytics", - "ExactTextMatching", - "get_cached_results_for_technique", - "TextMatching", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.analytics.conversation_analytics import ConversationAnalytics + from pyrit.analytics.result_analysis import AttackStats, analyze_results, get_cached_results_for_technique + from pyrit.analytics.text_matching import ApproximateTextMatching, ExactTextMatching, TextMatching + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "analyze_results": "pyrit.analytics.result_analysis", + "ApproximateTextMatching": "pyrit.analytics.text_matching", + "AttackStats": "pyrit.analytics.result_analysis", + "ConversationAnalytics": "pyrit.analytics.conversation_analytics", + "ExactTextMatching": "pyrit.analytics.text_matching", + "get_cached_results_for_technique": "pyrit.analytics.result_analysis", + "TextMatching": "pyrit.analytics.text_matching", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/auth/__init__.py b/pyrit/auth/__init__.py index 28746eecd7..e20000e0c8 100644 --- a/pyrit/auth/__init__.py +++ b/pyrit/auth/__init__.py @@ -1,42 +1,63 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -""" -Authentication functionality for a variety of services. -""" - -from pyrit.auth.authenticator import Authenticator -from pyrit.auth.azure_auth import ( - AsyncTokenProviderCredential, - AzureAuth, - TokenProviderCredential, - ensure_async_token_provider, - get_azure_async_token_provider, - get_azure_openai_auth, - get_azure_token_provider, - get_default_azure_scope, - is_azure_ml_endpoint, - is_azure_openai_endpoint, -) -from pyrit.auth.azure_storage_auth import AzureStorageAuth -from pyrit.auth.copilot_authenticator import CopilotAuthenticator -from pyrit.auth.manual_copilot_authenticator import ManualCopilotAuthenticator -from pyrit.auth.openai_auth import resolve_openai_auth - -__all__ = [ - "AsyncTokenProviderCredential", - "Authenticator", - "AzureAuth", - "AzureStorageAuth", - "CopilotAuthenticator", - "ManualCopilotAuthenticator", - "resolve_openai_auth", - "TokenProviderCredential", - "ensure_async_token_provider", - "get_azure_token_provider", - "get_azure_async_token_provider", - "get_default_azure_scope", - "get_azure_openai_auth", - "is_azure_ml_endpoint", - "is_azure_openai_endpoint", -] +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# ruff: noqa: F401 + +""" +Authentication functionality for a variety of services. +""" + +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.auth.authenticator import Authenticator + from pyrit.auth.azure_auth import ( + AsyncTokenProviderCredential, + AzureAuth, + TokenProviderCredential, + ensure_async_token_provider, + get_azure_async_token_provider, + get_azure_openai_auth, + get_azure_token_provider, + get_default_azure_scope, + is_azure_ml_endpoint, + is_azure_openai_endpoint, + ) + from pyrit.auth.azure_storage_auth import AzureStorageAuth + from pyrit.auth.copilot_authenticator import CopilotAuthenticator + from pyrit.auth.manual_copilot_authenticator import ManualCopilotAuthenticator + from pyrit.auth.openai_auth import resolve_openai_auth + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AsyncTokenProviderCredential": "pyrit.auth.azure_auth", + "Authenticator": "pyrit.auth.authenticator", + "AzureAuth": "pyrit.auth.azure_auth", + "AzureStorageAuth": "pyrit.auth.azure_storage_auth", + "CopilotAuthenticator": "pyrit.auth.copilot_authenticator", + "ManualCopilotAuthenticator": "pyrit.auth.manual_copilot_authenticator", + "resolve_openai_auth": "pyrit.auth.openai_auth", + "TokenProviderCredential": "pyrit.auth.azure_auth", + "ensure_async_token_provider": "pyrit.auth.azure_auth", + "get_azure_token_provider": "pyrit.auth.azure_auth", + "get_azure_async_token_provider": "pyrit.auth.azure_auth", + "get_default_azure_scope": "pyrit.auth.azure_auth", + "get_azure_openai_auth": "pyrit.auth.azure_auth", + "is_azure_ml_endpoint": "pyrit.auth.azure_auth", + "is_azure_openai_endpoint": "pyrit.auth.azure_auth", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/backend/mappers/__init__.py b/pyrit/backend/mappers/__init__.py index 15bb298838..30745428c7 100644 --- a/pyrit/backend/mappers/__init__.py +++ b/pyrit/backend/mappers/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Backend mappers module. @@ -8,26 +9,42 @@ Centralizes all translation logic so domain models can evolve independently of the API contract. """ -from pyrit.backend.mappers._preview import format_last_message_preview -from pyrit.backend.mappers.attack_mappers import ( - attack_result_to_summary_async, - pyrit_messages_to_dto_async, - request_piece_to_pyrit_message_piece, - request_to_pyrit_message, -) -from pyrit.backend.mappers.converter_mappers import ( - converter_object_to_instance, -) -from pyrit.backend.mappers.target_mappers import ( - target_object_to_instance, -) - -__all__ = [ - "attack_result_to_summary_async", - "converter_object_to_instance", - "format_last_message_preview", - "pyrit_messages_to_dto_async", - "request_piece_to_pyrit_message_piece", - "request_to_pyrit_message", - "target_object_to_instance", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.backend.mappers._preview import format_last_message_preview + from pyrit.backend.mappers.attack_mappers import ( + attack_result_to_summary_async, + pyrit_messages_to_dto_async, + request_piece_to_pyrit_message_piece, + request_to_pyrit_message, + ) + from pyrit.backend.mappers.converter_mappers import converter_object_to_instance + from pyrit.backend.mappers.target_mappers import target_object_to_instance + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "attack_result_to_summary_async": "pyrit.backend.mappers.attack_mappers", + "converter_object_to_instance": "pyrit.backend.mappers.converter_mappers", + "format_last_message_preview": "pyrit.backend.mappers._preview", + "pyrit_messages_to_dto_async": "pyrit.backend.mappers.attack_mappers", + "request_piece_to_pyrit_message_piece": "pyrit.backend.mappers.attack_mappers", + "request_to_pyrit_message": "pyrit.backend.mappers.attack_mappers", + "target_object_to_instance": "pyrit.backend.mappers.target_mappers", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/backend/middleware/__init__.py b/pyrit/backend/middleware/__init__.py index b73a95d524..3248026bc9 100644 --- a/pyrit/backend/middleware/__init__.py +++ b/pyrit/backend/middleware/__init__.py @@ -1,10 +1,35 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Middleware module for backend.""" -from pyrit.backend.middleware.error_handlers import register_error_handlers -from pyrit.backend.middleware.request_id import RequestIdMiddleware -from pyrit.backend.middleware.security_headers import SecurityHeadersMiddleware +from typing import TYPE_CHECKING -__all__ = ["register_error_handlers", "RequestIdMiddleware", "SecurityHeadersMiddleware"] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.backend.middleware.error_handlers import register_error_handlers + from pyrit.backend.middleware.request_id import RequestIdMiddleware + from pyrit.backend.middleware.security_headers import SecurityHeadersMiddleware + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "register_error_handlers": "pyrit.backend.middleware.error_handlers", + "RequestIdMiddleware": "pyrit.backend.middleware.request_id", + "SecurityHeadersMiddleware": "pyrit.backend.middleware.security_headers", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/backend/models/__init__.py b/pyrit/backend/models/__init__.py index 1d65fea9a8..c150f81a5a 100644 --- a/pyrit/backend/models/__init__.py +++ b/pyrit/backend/models/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Backend models package. @@ -7,114 +8,114 @@ Pydantic models for API requests and responses. """ -from pyrit.backend.models._media import DEFAULT_MEDIA_EXTENSIONS -from pyrit.backend.models.attacks import ( - AddMessageRequest, - AddMessageResponse, - AttackConversationsResponse, - AttackListResponse, - AttackOptionsResponse, - AttackSummary, - ConversationMessagesResponse, - ConversationSummary, - ConverterOptionsResponse, - CreateAttackRequest, - CreateAttackResponse, - CreateConversationRequest, - CreateConversationResponse, - MessagePieceRequest, - MessagePieceView, - MessageView, - PrependedMessageRequest, - ScoreView, - TargetInfo, - UpdateAttackRequest, - UpdateMainConversationRequest, - UpdateMainConversationResponse, -) -from pyrit.backend.models.common import ( - SENSITIVE_FIELD_PATTERNS, - FieldError, - PaginationInfo, - ProblemDetail, - filter_sensitive_fields, -) -from pyrit.backend.models.converters import ( - ConverterInstance, - ConverterInstanceListResponse, - ConverterPreviewRequest, - ConverterPreviewResponse, - CreateConverterRequest, - CreateConverterResponse, - PreviewStep, -) -from pyrit.backend.models.datasets import ( - DatasetInfo, - DatasetListResponse, -) -from pyrit.backend.models.initializers import ( - ListRegisteredInitializersResponse, - RegisterInitializerRequest, -) -from pyrit.backend.models.scenarios import ( - ListRegisteredScenariosResponse, - ScenarioRunListResponse, -) -from pyrit.backend.models.targets import ( - CreateTargetRequest, - TargetListResponse, -) +from typing import TYPE_CHECKING -__all__ = [ - # Media - "DEFAULT_MEDIA_EXTENSIONS", - # Attacks - "AddMessageRequest", - "AddMessageResponse", - "AttackConversationsResponse", - "AttackListResponse", - "AttackOptionsResponse", - "AttackSummary", - "UpdateMainConversationRequest", - "UpdateMainConversationResponse", - "ConversationMessagesResponse", - "ConversationSummary", - "ConverterOptionsResponse", - "CreateAttackRequest", - "CreateAttackResponse", - "CreateConversationRequest", - "CreateConversationResponse", - "MessagePieceRequest", - "MessagePieceView", - "MessageView", - "PrependedMessageRequest", - "ScoreView", - "TargetInfo", - "UpdateAttackRequest", - # Common - "SENSITIVE_FIELD_PATTERNS", - "FieldError", - "filter_sensitive_fields", - "PaginationInfo", - "ProblemDetail", - # Converters - "ConverterInstance", - "ConverterInstanceListResponse", - "ConverterPreviewRequest", - "ConverterPreviewResponse", - "CreateConverterRequest", - "CreateConverterResponse", - "PreviewStep", - # Datasets - "DatasetInfo", - "DatasetListResponse", - # Scenarios - "ListRegisteredScenariosResponse", - "ScenarioRunListResponse", - # Initializers - "ListRegisteredInitializersResponse", - "RegisterInitializerRequest", - # Targets - "CreateTargetRequest", - "TargetListResponse", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.backend.models._media import DEFAULT_MEDIA_EXTENSIONS + from pyrit.backend.models.attacks import ( + AddMessageRequest, + AddMessageResponse, + AttackConversationsResponse, + AttackListResponse, + AttackOptionsResponse, + AttackSummary, + ConversationMessagesResponse, + ConversationSummary, + ConverterOptionsResponse, + CreateAttackRequest, + CreateAttackResponse, + CreateConversationRequest, + CreateConversationResponse, + MessagePieceRequest, + MessagePieceView, + MessageView, + PrependedMessageRequest, + ScoreView, + TargetInfo, + UpdateAttackRequest, + UpdateMainConversationRequest, + UpdateMainConversationResponse, + ) + from pyrit.backend.models.common import ( + SENSITIVE_FIELD_PATTERNS, + FieldError, + PaginationInfo, + ProblemDetail, + filter_sensitive_fields, + ) + from pyrit.backend.models.converters import ( + ConverterInstance, + ConverterInstanceListResponse, + ConverterPreviewRequest, + ConverterPreviewResponse, + CreateConverterRequest, + CreateConverterResponse, + PreviewStep, + ) + from pyrit.backend.models.datasets import DatasetInfo, DatasetListResponse + from pyrit.backend.models.initializers import ListRegisteredInitializersResponse, RegisterInitializerRequest + from pyrit.backend.models.scenarios import ListRegisteredScenariosResponse, ScenarioRunListResponse + from pyrit.backend.models.targets import CreateTargetRequest, TargetListResponse + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "DEFAULT_MEDIA_EXTENSIONS": "pyrit.backend.models._media", + "AddMessageRequest": "pyrit.backend.models.attacks", + "AddMessageResponse": "pyrit.backend.models.attacks", + "AttackConversationsResponse": "pyrit.backend.models.attacks", + "AttackListResponse": "pyrit.backend.models.attacks", + "AttackOptionsResponse": "pyrit.backend.models.attacks", + "AttackSummary": "pyrit.backend.models.attacks", + "UpdateMainConversationRequest": "pyrit.backend.models.attacks", + "UpdateMainConversationResponse": "pyrit.backend.models.attacks", + "ConversationMessagesResponse": "pyrit.backend.models.attacks", + "ConversationSummary": "pyrit.backend.models.attacks", + "ConverterOptionsResponse": "pyrit.backend.models.attacks", + "CreateAttackRequest": "pyrit.backend.models.attacks", + "CreateAttackResponse": "pyrit.backend.models.attacks", + "CreateConversationRequest": "pyrit.backend.models.attacks", + "CreateConversationResponse": "pyrit.backend.models.attacks", + "MessagePieceRequest": "pyrit.backend.models.attacks", + "MessagePieceView": "pyrit.backend.models.attacks", + "MessageView": "pyrit.backend.models.attacks", + "PrependedMessageRequest": "pyrit.backend.models.attacks", + "ScoreView": "pyrit.backend.models.attacks", + "TargetInfo": "pyrit.backend.models.attacks", + "UpdateAttackRequest": "pyrit.backend.models.attacks", + "SENSITIVE_FIELD_PATTERNS": "pyrit.backend.models.common", + "FieldError": "pyrit.backend.models.common", + "filter_sensitive_fields": "pyrit.backend.models.common", + "PaginationInfo": "pyrit.backend.models.common", + "ProblemDetail": "pyrit.backend.models.common", + "ConverterInstance": "pyrit.backend.models.converters", + "ConverterInstanceListResponse": "pyrit.backend.models.converters", + "ConverterPreviewRequest": "pyrit.backend.models.converters", + "ConverterPreviewResponse": "pyrit.backend.models.converters", + "CreateConverterRequest": "pyrit.backend.models.converters", + "CreateConverterResponse": "pyrit.backend.models.converters", + "PreviewStep": "pyrit.backend.models.converters", + "DatasetInfo": "pyrit.backend.models.datasets", + "DatasetListResponse": "pyrit.backend.models.datasets", + "ListRegisteredScenariosResponse": "pyrit.backend.models.scenarios", + "ScenarioRunListResponse": "pyrit.backend.models.scenarios", + "ListRegisteredInitializersResponse": "pyrit.backend.models.initializers", + "RegisterInitializerRequest": "pyrit.backend.models.initializers", + "CreateTargetRequest": "pyrit.backend.models.targets", + "TargetListResponse": "pyrit.backend.models.targets", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/backend/routes/__init__.py b/pyrit/backend/routes/__init__.py index 1e9476a605..454f2b4111 100644 --- a/pyrit/backend/routes/__init__.py +++ b/pyrit/backend/routes/__init__.py @@ -1,32 +1,53 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ API route handlers. """ -from pyrit.backend.routes import ( - attacks, - converters, - datasets, - health, - initializers, - labels, - media, - scenarios, - targets, - version, -) - -__all__ = [ - "attacks", - "converters", - "datasets", - "health", - "initializers", - "labels", - "media", - "scenarios", - "targets", - "version", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.backend.routes import ( + attacks, + converters, + datasets, + health, + initializers, + labels, + media, + scenarios, + targets, + version, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "attacks": ("pyrit.backend.routes.attacks", None), + "converters": ("pyrit.backend.routes.converters", None), + "datasets": ("pyrit.backend.routes.datasets", None), + "health": ("pyrit.backend.routes.health", None), + "initializers": ("pyrit.backend.routes.initializers", None), + "labels": ("pyrit.backend.routes.labels", None), + "media": ("pyrit.backend.routes.media", None), + "scenarios": ("pyrit.backend.routes.scenarios", None), + "targets": ("pyrit.backend.routes.targets", None), + "version": ("pyrit.backend.routes.version", None), +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/backend/services/__init__.py b/pyrit/backend/services/__init__.py index 2590b70d41..918bdfcdb0 100644 --- a/pyrit/backend/services/__init__.py +++ b/pyrit/backend/services/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Backend services module. @@ -7,48 +8,47 @@ Provides business logic layer for API routes. """ -from pyrit.backend.services.attack_service import ( - AttackService, - get_attack_service, -) -from pyrit.backend.services.converter_service import ( - ConverterService, - get_converter_service, -) -from pyrit.backend.services.dataset_service import ( - DatasetService, - get_dataset_service, -) -from pyrit.backend.services.initializer_service import ( - InitializerService, - get_initializer_service, -) -from pyrit.backend.services.scenario_run_service import ( - ScenarioRunService, - get_scenario_run_service, -) -from pyrit.backend.services.scenario_service import ( - ScenarioService, - get_scenario_service, -) -from pyrit.backend.services.target_service import ( - TargetService, - get_target_service, -) - -__all__ = [ - "AttackService", - "get_attack_service", - "ConverterService", - "get_converter_service", - "DatasetService", - "get_dataset_service", - "InitializerService", - "get_initializer_service", - "ScenarioService", - "get_scenario_service", - "ScenarioRunService", - "get_scenario_run_service", - "TargetService", - "get_target_service", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.backend.services.attack_service import AttackService, get_attack_service + from pyrit.backend.services.converter_service import ConverterService, get_converter_service + from pyrit.backend.services.dataset_service import DatasetService, get_dataset_service + from pyrit.backend.services.initializer_service import InitializerService, get_initializer_service + from pyrit.backend.services.scenario_run_service import ScenarioRunService, get_scenario_run_service + from pyrit.backend.services.scenario_service import ScenarioService, get_scenario_service + from pyrit.backend.services.target_service import TargetService, get_target_service + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AttackService": "pyrit.backend.services.attack_service", + "get_attack_service": "pyrit.backend.services.attack_service", + "ConverterService": "pyrit.backend.services.converter_service", + "get_converter_service": "pyrit.backend.services.converter_service", + "DatasetService": "pyrit.backend.services.dataset_service", + "get_dataset_service": "pyrit.backend.services.dataset_service", + "InitializerService": "pyrit.backend.services.initializer_service", + "get_initializer_service": "pyrit.backend.services.initializer_service", + "ScenarioService": "pyrit.backend.services.scenario_service", + "get_scenario_service": "pyrit.backend.services.scenario_service", + "ScenarioRunService": "pyrit.backend.services.scenario_run_service", + "get_scenario_run_service": "pyrit.backend.services.scenario_run_service", + "TargetService": "pyrit.backend.services.target_service", + "get_target_service": "pyrit.backend.services.target_service", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/common/__init__.py b/pyrit/common/__init__.py index 53f313e625..748155ebdc 100644 --- a/pyrit/common/__init__.py +++ b/pyrit/common/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Common utilities and helpers for PyRIT. @@ -12,52 +13,92 @@ ``Parameter`` is not part of ``pyrit.common``; it lives in ``pyrit.models``. """ -from pyrit.common.apply_defaults import ( - REQUIRED_VALUE, - DefaultValueScope, - apply_defaults, - apply_defaults_to_method, - get_global_default_values, - reset_default_values, - set_default_value, -) -from pyrit.common.brick_contract import enforce_keyword_only_init, forward_init_parameters -from pyrit.common.default_values import get_non_required_value, get_required_value -from pyrit.common.deprecation import print_deprecation_message -from pyrit.common.mime_type import get_mime_type -from pyrit.common.notebook_utils import is_in_ipython_session -from pyrit.common.singleton import Singleton -from pyrit.common.utils import ( - combine_dict, - combine_list, - get_kwarg_param, - get_random_indices, - verify_and_resolve_path, - warn_if_set, -) -from pyrit.common.yaml_loadable import YamlLoadable - -__all__ = [ - "apply_defaults", - "apply_defaults_to_method", - "combine_dict", - "combine_list", - "DefaultValueScope", - "enforce_keyword_only_init", - "forward_init_parameters", - "get_global_default_values", - "get_kwarg_param", - "get_mime_type", - "get_non_required_value", - "get_random_indices", - "get_required_value", - "is_in_ipython_session", - "print_deprecation_message", - "REQUIRED_VALUE", - "reset_default_values", - "set_default_value", - "Singleton", - "verify_and_resolve_path", - "warn_if_set", - "YamlLoadable", -] +import sys +from types import ModuleType +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.common.apply_defaults import ( + REQUIRED_VALUE, + DefaultValueScope, + apply_defaults, + apply_defaults_to_method, + get_global_default_values, + reset_default_values, + set_default_value, + ) + from pyrit.common.brick_contract import enforce_keyword_only_init, forward_init_parameters + from pyrit.common.default_values import get_non_required_value, get_required_value + from pyrit.common.deprecation import print_deprecation_message + from pyrit.common.mime_type import get_mime_type + from pyrit.common.notebook_utils import is_in_ipython_session + from pyrit.common.singleton import Singleton + from pyrit.common.utils import ( + combine_dict, + combine_list, + get_kwarg_param, + get_random_indices, + verify_and_resolve_path, + warn_if_set, + ) + from pyrit.common.yaml_loadable import YamlLoadable + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "apply_defaults": "pyrit.common.apply_defaults", + "apply_defaults_to_method": "pyrit.common.apply_defaults", + "combine_dict": "pyrit.common.utils", + "combine_list": "pyrit.common.utils", + "DefaultValueScope": "pyrit.common.apply_defaults", + "enforce_keyword_only_init": "pyrit.common.brick_contract", + "forward_init_parameters": "pyrit.common.brick_contract", + "get_global_default_values": "pyrit.common.apply_defaults", + "get_kwarg_param": "pyrit.common.utils", + "get_mime_type": "pyrit.common.mime_type", + "get_non_required_value": "pyrit.common.default_values", + "get_random_indices": "pyrit.common.utils", + "get_required_value": "pyrit.common.default_values", + "is_in_ipython_session": "pyrit.common.notebook_utils", + "print_deprecation_message": "pyrit.common.deprecation", + "REQUIRED_VALUE": "pyrit.common.apply_defaults", + "reset_default_values": "pyrit.common.apply_defaults", + "set_default_value": "pyrit.common.apply_defaults", + "Singleton": "pyrit.common.singleton", + "verify_and_resolve_path": "pyrit.common.utils", + "warn_if_set": "pyrit.common.utils", + "YamlLoadable": "pyrit.common.yaml_loadable", +} + +__all__ = list(_LAZY_EXPORTS) + + +class _LazyCommonModule(ModuleType): + """Resolve exports that share a name with an imported child module.""" + + def __getattribute__(self, name: str) -> object: + if name == "apply_defaults": + module_globals = ModuleType.__getattribute__(self, "__dict__") + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=module_globals, + exports=_LAZY_EXPORTS, + ) + return ModuleType.__getattribute__(self, name) + + +sys.modules[__name__].__class__ = _LazyCommonModule + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/converter/__init__.py b/pyrit/converter/__init__.py index f6ec5f0236..57d2a84d02 100644 --- a/pyrit/converter/__init__.py +++ b/pyrit/converter/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Converters for transforming prompts before sending them to targets in red teaming workflows. @@ -11,248 +12,241 @@ transformation pipelines for testing AI system robustness. """ -import importlib from typing import TYPE_CHECKING -from pyrit.converter.acrostic_converter import AcrosticConverter -from pyrit.converter.add_image_text_converter import AddImageTextConverter -from pyrit.converter.add_image_to_video_converter import AddImageVideoConverter -from pyrit.converter.add_text_image_converter import AddTextImageConverter -from pyrit.converter.ansi_escape.ansi_attack_converter import AnsiAttackConverter -from pyrit.converter.arabic_presentation_form_converter import ArabicPresentationFormConverter -from pyrit.converter.arabizi_converter import ArabiziConverter -from pyrit.converter.ascii_art_converter import AsciiArtConverter -from pyrit.converter.ask_to_decode_converter import AskToDecodeConverter -from pyrit.converter.atbash_converter import AtbashConverter -from pyrit.converter.azure_speech_audio_to_text_converter import AzureSpeechAudioToTextConverter -from pyrit.converter.azure_speech_text_to_audio_converter import AzureSpeechTextToAudioConverter -from pyrit.converter.base64_converter import Base64Converter -from pyrit.converter.base2048_converter import Base2048Converter -from pyrit.converter.bidi_converter import BidiConverter -from pyrit.converter.bin_ascii_converter import BinAsciiConverter -from pyrit.converter.binary_converter import BinaryConverter -from pyrit.converter.braille_converter import BrailleConverter -from pyrit.converter.caesar_converter import CaesarConverter -from pyrit.converter.character_space_converter import CharacterSpaceConverter -from pyrit.converter.charswap_attack_converter import CharSwapConverter -from pyrit.converter.codechameleon_converter import CodeChameleonConverter -from pyrit.converter.colloquial_wordswap_converter import ColloquialWordswapConverter -from pyrit.converter.converter import Converter, ConverterResult, get_converter_modalities -from pyrit.converter.decomposition_converter import DecompositionConverter -from pyrit.converter.denylist_converter import DenylistConverter -from pyrit.converter.diacritic_converter import DiacriticConverter -from pyrit.converter.ecoji_converter import EcojiConverter -from pyrit.converter.emoji_converter import EmojiConverter -from pyrit.converter.first_letter_converter import FirstLetterConverter -from pyrit.converter.flip_converter import FlipConverter -from pyrit.converter.image_color_saturation_converter import ImageColorSaturationConverter -from pyrit.converter.image_compression_converter import ImageCompressionConverter -from pyrit.converter.image_overlay_converter import ImageOverlayConverter -from pyrit.converter.image_prompt_style_converter import ImagePromptStyleConverter -from pyrit.converter.image_resizing_converter import ImageResizingConverter -from pyrit.converter.image_rotation_converter import ImageRotationConverter -from pyrit.converter.insert_punctuation_converter import InsertPunctuationConverter -from pyrit.converter.ipa_converter import IPAConverter -from pyrit.converter.json_string_converter import JsonStringConverter -from pyrit.converter.leetspeak_converter import LeetspeakConverter -from pyrit.converter.llm_generic_text_converter import LLMGenericTextConverter -from pyrit.converter.malicious_question_generator_converter import MaliciousQuestionGeneratorConverter -from pyrit.converter.math_obfuscation_converter import MathObfuscationConverter -from pyrit.converter.math_prompt_converter import MathPromptConverter -from pyrit.converter.morse_converter import MorseConverter -from pyrit.converter.nato_converter import NatoConverter -from pyrit.converter.negation_trap_converter import NegationTrapConverter -from pyrit.converter.noise_converter import NoiseConverter -from pyrit.converter.pdf_converter import PDFConverter -from pyrit.converter.persuasion_converter import PersuasionConverter -from pyrit.converter.policy_puppetry_converter import PolicyPuppetryConverter, PolicyPuppetryTemplate -from pyrit.converter.qr_code_converter import QRCodeConverter -from pyrit.converter.random_capital_letters_converter import RandomCapitalLettersConverter -from pyrit.converter.random_translation_converter import RandomTranslationConverter -from pyrit.converter.repeat_token_converter import RepeatTokenConverter -from pyrit.converter.rot13_converter import ROT13Converter -from pyrit.converter.scientific_translation_converter import ScientificTranslationConverter -from pyrit.converter.search_replace_converter import SearchReplaceConverter -from pyrit.converter.selective_text_converter import SelectiveTextConverter -from pyrit.converter.string_join_converter import StringJoinConverter -from pyrit.converter.suffix_append_converter import SuffixAppendConverter -from pyrit.converter.superscript_converter import SuperscriptConverter -from pyrit.converter.task_framing_converter import TaskFramingConverter -from pyrit.converter.tatweel_converter import TatweelConverter -from pyrit.converter.template_segment_converter import TemplateSegmentConverter -from pyrit.converter.tense_converter import TenseConverter -from pyrit.converter.text_selection_strategy import ( - AllWordsSelectionStrategy, - IndexSelectionStrategy, - KeywordSelectionStrategy, - PositionSelectionStrategy, - ProportionSelectionStrategy, - RangeSelectionStrategy, - RegexSelectionStrategy, - TextSelectionStrategy, - TokenSelectionStrategy, - WordIndexSelectionStrategy, - WordKeywordSelectionStrategy, - WordPositionSelectionStrategy, - WordProportionSelectionStrategy, - WordRegexSelectionStrategy, - WordSelectionStrategy, -) -from pyrit.converter.token_smuggling import ( - AsciiSmugglerConverter, - SneakyBitsSmugglerConverter, - VariationSelectorSmugglerConverter, -) -from pyrit.converter.tone_converter import ToneConverter -from pyrit.converter.toxic_sentence_generator_converter import ToxicSentenceGeneratorConverter -from pyrit.converter.translation_converter import TranslationConverter -from pyrit.converter.transparency_attack_converter import TransparencyAttackConverter -from pyrit.converter.unicode_confusable_converter import UnicodeConfusableConverter -from pyrit.converter.unicode_replacement_converter import UnicodeReplacementConverter -from pyrit.converter.unicode_sub_converter import UnicodeSubstitutionConverter -from pyrit.converter.url_converter import UrlConverter -from pyrit.converter.variation_converter import VariationConverter -from pyrit.converter.vigenere_converter import VigenereConverter -from pyrit.converter.word_doc_converter import WordDocConverter -from pyrit.converter.zalgo_converter import ZalgoConverter -from pyrit.converter.zero_width_converter import ZeroWidthConverter +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export if TYPE_CHECKING: + from pyrit.converter.acrostic_converter import AcrosticConverter + from pyrit.converter.add_image_text_converter import AddImageTextConverter + from pyrit.converter.add_image_to_video_converter import AddImageVideoConverter + from pyrit.converter.add_text_image_converter import AddTextImageConverter + from pyrit.converter.ansi_escape.ansi_attack_converter import AnsiAttackConverter + from pyrit.converter.arabic_presentation_form_converter import ArabicPresentationFormConverter + from pyrit.converter.arabizi_converter import ArabiziConverter + from pyrit.converter.ascii_art_converter import AsciiArtConverter + from pyrit.converter.ask_to_decode_converter import AskToDecodeConverter + from pyrit.converter.atbash_converter import AtbashConverter from pyrit.converter.audio_echo_converter import AudioEchoConverter from pyrit.converter.audio_frequency_converter import AudioFrequencyConverter from pyrit.converter.audio_speed_converter import AudioSpeedConverter from pyrit.converter.audio_volume_converter import AudioVolumeConverter from pyrit.converter.audio_white_noise_converter import AudioWhiteNoiseConverter + from pyrit.converter.azure_speech_audio_to_text_converter import AzureSpeechAudioToTextConverter + from pyrit.converter.azure_speech_text_to_audio_converter import AzureSpeechTextToAudioConverter + from pyrit.converter.base64_converter import Base64Converter + from pyrit.converter.base2048_converter import Base2048Converter + from pyrit.converter.bidi_converter import BidiConverter + from pyrit.converter.bin_ascii_converter import BinAsciiConverter + from pyrit.converter.binary_converter import BinaryConverter + from pyrit.converter.braille_converter import BrailleConverter + from pyrit.converter.caesar_converter import CaesarConverter + from pyrit.converter.character_space_converter import CharacterSpaceConverter + from pyrit.converter.charswap_attack_converter import CharSwapConverter + from pyrit.converter.codechameleon_converter import CodeChameleonConverter + from pyrit.converter.colloquial_wordswap_converter import ColloquialWordswapConverter + from pyrit.converter.converter import Converter, ConverterResult, get_converter_modalities + from pyrit.converter.decomposition_converter import DecompositionConverter + from pyrit.converter.denylist_converter import DenylistConverter + from pyrit.converter.diacritic_converter import DiacriticConverter + from pyrit.converter.ecoji_converter import EcojiConverter + from pyrit.converter.emoji_converter import EmojiConverter + from pyrit.converter.first_letter_converter import FirstLetterConverter + from pyrit.converter.flip_converter import FlipConverter + from pyrit.converter.image_color_saturation_converter import ImageColorSaturationConverter + from pyrit.converter.image_compression_converter import ImageCompressionConverter + from pyrit.converter.image_overlay_converter import ImageOverlayConverter + from pyrit.converter.image_prompt_style_converter import ImagePromptStyleConverter + from pyrit.converter.image_resizing_converter import ImageResizingConverter + from pyrit.converter.image_rotation_converter import ImageRotationConverter + from pyrit.converter.insert_punctuation_converter import InsertPunctuationConverter + from pyrit.converter.ipa_converter import IPAConverter + from pyrit.converter.json_string_converter import JsonStringConverter + from pyrit.converter.leetspeak_converter import LeetspeakConverter + from pyrit.converter.llm_generic_text_converter import LLMGenericTextConverter + from pyrit.converter.malicious_question_generator_converter import MaliciousQuestionGeneratorConverter + from pyrit.converter.math_obfuscation_converter import MathObfuscationConverter + from pyrit.converter.math_prompt_converter import MathPromptConverter + from pyrit.converter.morse_converter import MorseConverter + from pyrit.converter.nato_converter import NatoConverter + from pyrit.converter.negation_trap_converter import NegationTrapConverter + from pyrit.converter.noise_converter import NoiseConverter + from pyrit.converter.pdf_converter import PDFConverter + from pyrit.converter.persuasion_converter import PersuasionConverter + from pyrit.converter.policy_puppetry_converter import PolicyPuppetryConverter, PolicyPuppetryTemplate + from pyrit.converter.qr_code_converter import QRCodeConverter + from pyrit.converter.random_capital_letters_converter import RandomCapitalLettersConverter + from pyrit.converter.random_translation_converter import RandomTranslationConverter + from pyrit.converter.repeat_token_converter import RepeatTokenConverter + from pyrit.converter.rot13_converter import ROT13Converter + from pyrit.converter.scientific_translation_converter import ScientificTranslationConverter + from pyrit.converter.search_replace_converter import SearchReplaceConverter + from pyrit.converter.selective_text_converter import SelectiveTextConverter + from pyrit.converter.string_join_converter import StringJoinConverter + from pyrit.converter.suffix_append_converter import SuffixAppendConverter + from pyrit.converter.superscript_converter import SuperscriptConverter + from pyrit.converter.task_framing_converter import TaskFramingConverter + from pyrit.converter.tatweel_converter import TatweelConverter + from pyrit.converter.template_segment_converter import TemplateSegmentConverter + from pyrit.converter.tense_converter import TenseConverter from pyrit.converter.text_jailbreak_converter import TextJailbreakConverter + from pyrit.converter.text_selection_strategy import ( + AllWordsSelectionStrategy, + IndexSelectionStrategy, + KeywordSelectionStrategy, + PositionSelectionStrategy, + ProportionSelectionStrategy, + RangeSelectionStrategy, + RegexSelectionStrategy, + TextSelectionStrategy, + TokenSelectionStrategy, + WordIndexSelectionStrategy, + WordKeywordSelectionStrategy, + WordPositionSelectionStrategy, + WordProportionSelectionStrategy, + WordRegexSelectionStrategy, + WordSelectionStrategy, + ) + from pyrit.converter.token_smuggling import ( + AsciiSmugglerConverter, + SneakyBitsSmugglerConverter, + VariationSelectorSmugglerConverter, + ) + from pyrit.converter.tone_converter import ToneConverter + from pyrit.converter.toxic_sentence_generator_converter import ToxicSentenceGeneratorConverter + from pyrit.converter.translation_converter import TranslationConverter + from pyrit.converter.transparency_attack_converter import TransparencyAttackConverter + from pyrit.converter.unicode_confusable_converter import UnicodeConfusableConverter + from pyrit.converter.unicode_replacement_converter import UnicodeReplacementConverter + from pyrit.converter.unicode_sub_converter import UnicodeSubstitutionConverter + from pyrit.converter.url_converter import UrlConverter + from pyrit.converter.variation_converter import VariationConverter + from pyrit.converter.vigenere_converter import VigenereConverter + from pyrit.converter.word_doc_converter import WordDocConverter + from pyrit.converter.zalgo_converter import ZalgoConverter + from pyrit.converter.zero_width_converter import ZeroWidthConverter -# Lazy imports for modules with heavy third-party dependencies (PEP 562). -# Audio converters import `scipy` which adds ~1.3s to startup. -# TextJailbreakConverter imports `pyrit.datasets` which triggers `datasets` → `pandas` (~1.6s). -_LAZY_IMPORTS: dict[str, str] = { +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AcrosticConverter": "pyrit.converter.acrostic_converter", + "AddImageTextConverter": "pyrit.converter.add_image_text_converter", + "AddImageVideoConverter": "pyrit.converter.add_image_to_video_converter", + "AddTextImageConverter": "pyrit.converter.add_text_image_converter", + "AllWordsSelectionStrategy": "pyrit.converter.text_selection_strategy", + "AnsiAttackConverter": "pyrit.converter.ansi_escape.ansi_attack_converter", + "ArabicPresentationFormConverter": "pyrit.converter.arabic_presentation_form_converter", + "ArabiziConverter": "pyrit.converter.arabizi_converter", + "AsciiArtConverter": "pyrit.converter.ascii_art_converter", + "AsciiSmugglerConverter": "pyrit.converter.token_smuggling", + "AskToDecodeConverter": "pyrit.converter.ask_to_decode_converter", + "AtbashConverter": "pyrit.converter.atbash_converter", "AudioEchoConverter": "pyrit.converter.audio_echo_converter", "AudioFrequencyConverter": "pyrit.converter.audio_frequency_converter", "AudioSpeedConverter": "pyrit.converter.audio_speed_converter", "AudioVolumeConverter": "pyrit.converter.audio_volume_converter", "AudioWhiteNoiseConverter": "pyrit.converter.audio_white_noise_converter", + "AzureSpeechAudioToTextConverter": "pyrit.converter.azure_speech_audio_to_text_converter", + "AzureSpeechTextToAudioConverter": "pyrit.converter.azure_speech_text_to_audio_converter", + "Base2048Converter": "pyrit.converter.base2048_converter", + "Base64Converter": "pyrit.converter.base64_converter", + "BidiConverter": "pyrit.converter.bidi_converter", + "BinAsciiConverter": "pyrit.converter.bin_ascii_converter", + "BinaryConverter": "pyrit.converter.binary_converter", + "BrailleConverter": "pyrit.converter.braille_converter", + "CaesarConverter": "pyrit.converter.caesar_converter", + "CharSwapConverter": "pyrit.converter.charswap_attack_converter", + "CharacterSpaceConverter": "pyrit.converter.character_space_converter", + "CodeChameleonConverter": "pyrit.converter.codechameleon_converter", + "ColloquialWordswapConverter": "pyrit.converter.colloquial_wordswap_converter", + "ConverterResult": "pyrit.converter.converter", + "DecompositionConverter": "pyrit.converter.decomposition_converter", + "DenylistConverter": "pyrit.converter.denylist_converter", + "DiacriticConverter": "pyrit.converter.diacritic_converter", + "EcojiConverter": "pyrit.converter.ecoji_converter", + "EmojiConverter": "pyrit.converter.emoji_converter", + "FirstLetterConverter": "pyrit.converter.first_letter_converter", + "FlipConverter": "pyrit.converter.flip_converter", + "ImageColorSaturationConverter": "pyrit.converter.image_color_saturation_converter", + "ImageCompressionConverter": "pyrit.converter.image_compression_converter", + "ImageOverlayConverter": "pyrit.converter.image_overlay_converter", + "ImagePromptStyleConverter": "pyrit.converter.image_prompt_style_converter", + "ImageResizingConverter": "pyrit.converter.image_resizing_converter", + "ImageRotationConverter": "pyrit.converter.image_rotation_converter", + "IndexSelectionStrategy": "pyrit.converter.text_selection_strategy", + "InsertPunctuationConverter": "pyrit.converter.insert_punctuation_converter", + "IPAConverter": "pyrit.converter.ipa_converter", + "JsonStringConverter": "pyrit.converter.json_string_converter", + "KeywordSelectionStrategy": "pyrit.converter.text_selection_strategy", + "LeetspeakConverter": "pyrit.converter.leetspeak_converter", + "LLMGenericTextConverter": "pyrit.converter.llm_generic_text_converter", + "MaliciousQuestionGeneratorConverter": "pyrit.converter.malicious_question_generator_converter", + "MathObfuscationConverter": "pyrit.converter.math_obfuscation_converter", + "MathPromptConverter": "pyrit.converter.math_prompt_converter", + "MorseConverter": "pyrit.converter.morse_converter", + "NatoConverter": "pyrit.converter.nato_converter", + "NegationTrapConverter": "pyrit.converter.negation_trap_converter", + "NoiseConverter": "pyrit.converter.noise_converter", + "PDFConverter": "pyrit.converter.pdf_converter", + "PersuasionConverter": "pyrit.converter.persuasion_converter", + "PolicyPuppetryConverter": "pyrit.converter.policy_puppetry_converter", + "PolicyPuppetryTemplate": "pyrit.converter.policy_puppetry_converter", + "PositionSelectionStrategy": "pyrit.converter.text_selection_strategy", + "Converter": "pyrit.converter.converter", + "ProportionSelectionStrategy": "pyrit.converter.text_selection_strategy", + "QRCodeConverter": "pyrit.converter.qr_code_converter", + "ROT13Converter": "pyrit.converter.rot13_converter", + "RandomCapitalLettersConverter": "pyrit.converter.random_capital_letters_converter", + "RandomTranslationConverter": "pyrit.converter.random_translation_converter", + "RangeSelectionStrategy": "pyrit.converter.text_selection_strategy", + "RegexSelectionStrategy": "pyrit.converter.text_selection_strategy", + "RepeatTokenConverter": "pyrit.converter.repeat_token_converter", + "ScientificTranslationConverter": "pyrit.converter.scientific_translation_converter", + "SearchReplaceConverter": "pyrit.converter.search_replace_converter", + "SelectiveTextConverter": "pyrit.converter.selective_text_converter", + "SneakyBitsSmugglerConverter": "pyrit.converter.token_smuggling", + "StringJoinConverter": "pyrit.converter.string_join_converter", + "SuffixAppendConverter": "pyrit.converter.suffix_append_converter", + "SuperscriptConverter": "pyrit.converter.superscript_converter", + "TaskFramingConverter": "pyrit.converter.task_framing_converter", + "TatweelConverter": "pyrit.converter.tatweel_converter", + "TemplateSegmentConverter": "pyrit.converter.template_segment_converter", + "TenseConverter": "pyrit.converter.tense_converter", "TextJailbreakConverter": "pyrit.converter.text_jailbreak_converter", + "TextSelectionStrategy": "pyrit.converter.text_selection_strategy", + "TokenSelectionStrategy": "pyrit.converter.text_selection_strategy", + "ToneConverter": "pyrit.converter.tone_converter", + "ToxicSentenceGeneratorConverter": "pyrit.converter.toxic_sentence_generator_converter", + "TranslationConverter": "pyrit.converter.translation_converter", + "TransparencyAttackConverter": "pyrit.converter.transparency_attack_converter", + "UnicodeConfusableConverter": "pyrit.converter.unicode_confusable_converter", + "UnicodeReplacementConverter": "pyrit.converter.unicode_replacement_converter", + "UnicodeSubstitutionConverter": "pyrit.converter.unicode_sub_converter", + "UrlConverter": "pyrit.converter.url_converter", + "VariationConverter": "pyrit.converter.variation_converter", + "VariationSelectorSmugglerConverter": "pyrit.converter.token_smuggling", + "VigenereConverter": "pyrit.converter.vigenere_converter", + "WordDocConverter": "pyrit.converter.word_doc_converter", + "WordIndexSelectionStrategy": "pyrit.converter.text_selection_strategy", + "WordKeywordSelectionStrategy": "pyrit.converter.text_selection_strategy", + "WordPositionSelectionStrategy": "pyrit.converter.text_selection_strategy", + "WordProportionSelectionStrategy": "pyrit.converter.text_selection_strategy", + "WordRegexSelectionStrategy": "pyrit.converter.text_selection_strategy", + "WordSelectionStrategy": "pyrit.converter.text_selection_strategy", + "ZalgoConverter": "pyrit.converter.zalgo_converter", + "ZeroWidthConverter": "pyrit.converter.zero_width_converter", + "get_converter_modalities": "pyrit.converter.converter", } +__all__ = list(_LAZY_EXPORTS) + def __getattr__(name: str) -> object: - if name in _LAZY_IMPORTS: - module = importlib.import_module(_LAZY_IMPORTS[name]) - attr = getattr(module, name) - globals()[name] = attr - return attr - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) -__all__ = [ - "AcrosticConverter", - "AddImageTextConverter", - "AddImageVideoConverter", - "AddTextImageConverter", - "AllWordsSelectionStrategy", - "AnsiAttackConverter", - "ArabicPresentationFormConverter", - "ArabiziConverter", - "AsciiArtConverter", - "AsciiSmugglerConverter", - "AskToDecodeConverter", - "AtbashConverter", - "AudioEchoConverter", - "AudioFrequencyConverter", - "AudioSpeedConverter", - "AudioVolumeConverter", - "AudioWhiteNoiseConverter", - "AzureSpeechAudioToTextConverter", - "AzureSpeechTextToAudioConverter", - "Base2048Converter", - "Base64Converter", - "BidiConverter", - "BinAsciiConverter", - "BinaryConverter", - "BrailleConverter", - "CaesarConverter", - "CharSwapConverter", - "CharacterSpaceConverter", - "CodeChameleonConverter", - "ColloquialWordswapConverter", - "ConverterResult", - "DecompositionConverter", - "DenylistConverter", - "DiacriticConverter", - "EcojiConverter", - "EmojiConverter", - "FirstLetterConverter", - "FlipConverter", - "ImageColorSaturationConverter", - "ImageCompressionConverter", - "ImageOverlayConverter", - "ImagePromptStyleConverter", - "ImageResizingConverter", - "ImageRotationConverter", - "IndexSelectionStrategy", - "InsertPunctuationConverter", - "IPAConverter", - "JsonStringConverter", - "KeywordSelectionStrategy", - "LeetspeakConverter", - "LLMGenericTextConverter", - "MaliciousQuestionGeneratorConverter", - "MathObfuscationConverter", - "MathPromptConverter", - "MorseConverter", - "NatoConverter", - "NegationTrapConverter", - "NoiseConverter", - "PDFConverter", - "PersuasionConverter", - "PolicyPuppetryConverter", - "PolicyPuppetryTemplate", - "PositionSelectionStrategy", - "Converter", - "ProportionSelectionStrategy", - "QRCodeConverter", - "ROT13Converter", - "RandomCapitalLettersConverter", - "RandomTranslationConverter", - "RangeSelectionStrategy", - "RegexSelectionStrategy", - "RepeatTokenConverter", - "ScientificTranslationConverter", - "SearchReplaceConverter", - "SelectiveTextConverter", - "SneakyBitsSmugglerConverter", - "StringJoinConverter", - "SuffixAppendConverter", - "SuperscriptConverter", - "TaskFramingConverter", - "TatweelConverter", - "TemplateSegmentConverter", - "TenseConverter", - "TextJailbreakConverter", - "TextSelectionStrategy", - "TokenSelectionStrategy", - "ToneConverter", - "ToxicSentenceGeneratorConverter", - "TranslationConverter", - "TransparencyAttackConverter", - "UnicodeConfusableConverter", - "UnicodeReplacementConverter", - "UnicodeSubstitutionConverter", - "UrlConverter", - "VariationConverter", - "VariationSelectorSmugglerConverter", - "VigenereConverter", - "WordDocConverter", - "WordIndexSelectionStrategy", - "WordKeywordSelectionStrategy", - "WordPositionSelectionStrategy", - "WordProportionSelectionStrategy", - "WordRegexSelectionStrategy", - "WordSelectionStrategy", - "ZalgoConverter", - "ZeroWidthConverter", - "get_converter_modalities", -] +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/converter/token_smuggling/__init__.py b/pyrit/converter/token_smuggling/__init__.py index 952191ca04..33f6cc8277 100644 --- a/pyrit/converter/token_smuggling/__init__.py +++ b/pyrit/converter/token_smuggling/__init__.py @@ -1,19 +1,38 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Token smuggling converters that use Unicode-based techniques to hide, encode, or obfuscate text content within prompts for security testing purposes. """ -from pyrit.converter.token_smuggling.ascii_smuggler_converter import AsciiSmugglerConverter -from pyrit.converter.token_smuggling.sneaky_bits_smuggler_converter import SneakyBitsSmugglerConverter -from pyrit.converter.token_smuggling.variation_selector_smuggler_converter import ( - VariationSelectorSmugglerConverter, -) - -__all__ = [ - "AsciiSmugglerConverter", - "SneakyBitsSmugglerConverter", - "VariationSelectorSmugglerConverter", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.converter.token_smuggling.ascii_smuggler_converter import AsciiSmugglerConverter + from pyrit.converter.token_smuggling.sneaky_bits_smuggler_converter import SneakyBitsSmugglerConverter + from pyrit.converter.token_smuggling.variation_selector_smuggler_converter import VariationSelectorSmugglerConverter + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AsciiSmugglerConverter": "pyrit.converter.token_smuggling.ascii_smuggler_converter", + "SneakyBitsSmugglerConverter": "pyrit.converter.token_smuggling.sneaky_bits_smuggler_converter", + "VariationSelectorSmugglerConverter": "pyrit.converter.token_smuggling.variation_selector_smuggler_converter", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/datasets/__init__.py b/pyrit/datasets/__init__.py index 35b6ba9f53..33802d7a9a 100644 --- a/pyrit/datasets/__init__.py +++ b/pyrit/datasets/__init__.py @@ -1,23 +1,41 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 -""" -Dataset fetching and loading utilities for various red teaming and safety evaluation datasets. -""" - -from pyrit.datasets.jailbreak.text_jailbreak import TextJailBreak -from pyrit.datasets.seed_datasets import local, remote # noqa: F401 -from pyrit.datasets.seed_datasets.seed_dataset_provider import SeedDatasetProvider -from pyrit.datasets.seed_datasets.seed_metadata import ( - SeedDatasetFilter, - SeedDatasetLoadTime, - SeedDatasetMetadata, -) - -__all__ = [ - "SeedDatasetFilter", - "SeedDatasetMetadata", - "SeedDatasetLoadTime", - "SeedDatasetProvider", - "TextJailBreak", -] +"""Dataset fetching and loading utilities for red teaming and safety evaluation datasets.""" + +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.datasets.jailbreak.text_jailbreak import TextJailBreak + from pyrit.datasets.seed_datasets.seed_dataset_provider import SeedDatasetProvider + from pyrit.datasets.seed_datasets.seed_metadata import ( + SeedDatasetFilter, + SeedDatasetLoadTime, + SeedDatasetMetadata, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "SeedDatasetFilter": "pyrit.datasets.seed_datasets.seed_metadata", + "SeedDatasetMetadata": "pyrit.datasets.seed_datasets.seed_metadata", + "SeedDatasetLoadTime": "pyrit.datasets.seed_datasets.seed_metadata", + "SeedDatasetProvider": "pyrit.datasets.seed_datasets.seed_dataset_provider", + "TextJailBreak": "pyrit.datasets.jailbreak.text_jailbreak", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/datasets/seed_datasets/local/__init__.py b/pyrit/datasets/seed_datasets/local/__init__.py index 90d6e5e31e..46d772414d 100644 --- a/pyrit/datasets/seed_datasets/local/__init__.py +++ b/pyrit/datasets/seed_datasets/local/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Local dataset loaders with automatic discovery. @@ -7,12 +8,30 @@ Automatically discovers and registers all YAML dataset files from the seed_datasets directory. """ -from pyrit.datasets.seed_datasets.local.jailbreak_dataset import ( - _JailbreakTemplatesDataset, -) -from pyrit.datasets.seed_datasets.local.local_dataset_loader import _LocalDatasetLoader +from typing import TYPE_CHECKING -__all__ = [ - "_JailbreakTemplatesDataset", - "_LocalDatasetLoader", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.datasets.seed_datasets.local.jailbreak_dataset import _JailbreakTemplatesDataset + from pyrit.datasets.seed_datasets.local.local_dataset_loader import _LocalDatasetLoader + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "_JailbreakTemplatesDataset": "pyrit.datasets.seed_datasets.local.jailbreak_dataset", + "_LocalDatasetLoader": "pyrit.datasets.seed_datasets.local.local_dataset_loader", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/datasets/seed_datasets/remote/__init__.py b/pyrit/datasets/seed_datasets/remote/__init__.py index 251bd94320..26fb028705 100644 --- a/pyrit/datasets/seed_datasets/remote/__init__.py +++ b/pyrit/datasets/seed_datasets/remote/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Remote dataset loaders with automatic discovery. @@ -7,244 +8,266 @@ Import concrete implementations to trigger registration. """ -from pyrit.datasets.seed_datasets.remote.aegis_ai_content_safety_dataset import ( - AegisHarmCategory, - _AegisContentSafetyDataset, -) -from pyrit.datasets.seed_datasets.remote.agent_threat_rules_dataset import ( - ATRCategory, - ATRDetectionField, - ATRVariationType, - _AgentThreatRulesDataset, -) -from pyrit.datasets.seed_datasets.remote.aya_redteaming_dataset import _AyaRedteamingDataset -from pyrit.datasets.seed_datasets.remote.babelscape_alert_dataset import _BabelscapeAlertDataset -from pyrit.datasets.seed_datasets.remote.beaver_tails_dataset import _BeaverTailsDataset -from pyrit.datasets.seed_datasets.remote.categorical_harmful_qa_dataset import _CategoricalHarmfulQADataset -from pyrit.datasets.seed_datasets.remote.cbt_bench_dataset import _CBTBenchDataset -from pyrit.datasets.seed_datasets.remote.ccp_sensitive_prompts_dataset import _CCPSensitivePromptsDataset -from pyrit.datasets.seed_datasets.remote.coconot_dataset import ( - CoCoNotCategory, - CoCoNotSplit, - _CoCoNotContrastDataset, - _CoCoNotRefusalDataset, -) -from pyrit.datasets.seed_datasets.remote.comic_jailbreak_dataset import ( - COMIC_JAILBREAK_TEMPLATES, - ComicJailbreakTemplateConfig, - _ComicJailbreakDataset, -) -from pyrit.datasets.seed_datasets.remote.dangerous_qa_dataset import _DangerousQADataset -from pyrit.datasets.seed_datasets.remote.darkbench_dataset import _DarkBenchDataset -from pyrit.datasets.seed_datasets.remote.decoding_trust_toxicity_dataset import ( - DecodingTrustToxicitySubset, - _DecodingTrustToxicityDataset, -) -from pyrit.datasets.seed_datasets.remote.equitymedqa_dataset import _EquityMedQADataset -from pyrit.datasets.seed_datasets.remote.figstep_dataset import ( - FigStepCategory, - FigStepVariant, - _FigStepDataset, - _FigStepProDataset, -) -from pyrit.datasets.seed_datasets.remote.forbidden_questions_dataset import _ForbiddenQuestionsDataset -from pyrit.datasets.seed_datasets.remote.garak_audio_dataset import _GarakAudioAchillesHeelDataset -from pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset import ( - _GarakCratesDataset, - _GarakDartDataset, - _GarakNpmDataset, - _GarakPerlDataset, - _GarakPypiDataset, - _GarakRakuDataset, - _GarakRubyGemsDataset, -) -from pyrit.datasets.seed_datasets.remote.garak_system_prompt_dataset import ( - _GarakDrhSystemPromptDataset, - _GarakTmSystemPromptDataset, -) -from pyrit.datasets.seed_datasets.remote.harmbench_dataset import _HarmBenchDataset -from pyrit.datasets.seed_datasets.remote.harmbench_multimodal_dataset import _HarmBenchMultimodalDataset -from pyrit.datasets.seed_datasets.remote.harmful_qa_dataset import _HarmfulQADataset -from pyrit.datasets.seed_datasets.remote.hixstest_dataset import HiXSTestLanguage, _HiXSTestDataset -from pyrit.datasets.seed_datasets.remote.jailbreakv_28k_dataset import _JailbreakV28KDataset -from pyrit.datasets.seed_datasets.remote.jailbreakv_redteam_2k_dataset import _JailbreakVRedteam2KDataset -from pyrit.datasets.seed_datasets.remote.jbb_behaviors_dataset import _JBBBehaviorsDataset -from pyrit.datasets.seed_datasets.remote.librai_do_not_answer_dataset import _LibrAIDoNotAnswerDataset -from pyrit.datasets.seed_datasets.remote.llm_latent_adversarial_training_dataset import ( - _LLMLatentAdversarialTrainingDataset, -) -from pyrit.datasets.seed_datasets.remote.medsafetybench_dataset import _MedSafetyBenchDataset -from pyrit.datasets.seed_datasets.remote.mlcommons_ailuminate_dataset import _MLCommonsAILuminateDataset -from pyrit.datasets.seed_datasets.remote.mm_safetybench_dataset import ( - MMSafetyBenchCategory, - MMSafetyBenchVariant, - _MMSafetyBenchDataset, -) -from pyrit.datasets.seed_datasets.remote.moral_integrity_corpus_dataset import _MICDataset -from pyrit.datasets.seed_datasets.remote.mossbench_dataset import MossBenchOversensitivityType, _MossBenchDataset -from pyrit.datasets.seed_datasets.remote.msts_dataset import _MSTSDataset -from pyrit.datasets.seed_datasets.remote.multilingual_vulnerability_dataset import _MultilingualVulnerabilityDataset -from pyrit.datasets.seed_datasets.remote.odin_dataset import ( - ODINSecurityBoundary, - ODINSeverity, - ODINTaxonomyCategory, - _ODINDataset, -) -from pyrit.datasets.seed_datasets.remote.or_bench_dataset import ( - _ORBench80KDataset, - _ORBenchHardDataset, - _ORBenchToxicDataset, -) -from pyrit.datasets.seed_datasets.remote.pku_safe_rlhf_dataset import _PKUSafeRLHFDataset -from pyrit.datasets.seed_datasets.remote.promptintel_dataset import ( - PromptIntelCategory, - PromptIntelSeverity, - _PromptIntelDataset, -) -from pyrit.datasets.seed_datasets.remote.red_team_social_bias_dataset import _RedTeamSocialBiasDataset -from pyrit.datasets.seed_datasets.remote.remote_dataset_loader import _RemoteDatasetLoader -from pyrit.datasets.seed_datasets.remote.salad_bench_dataset import _SaladBenchDataset -from pyrit.datasets.seed_datasets.remote.sgxstest_dataset import SGXSTestLabel, _SGXSTestDataset -from pyrit.datasets.seed_datasets.remote.simple_safety_tests_dataset import _SimpleSafetyTestsDataset -from pyrit.datasets.seed_datasets.remote.siuo_dataset import SIUOCategory, _SIUODataset -from pyrit.datasets.seed_datasets.remote.sorry_bench_dataset import _SorryBenchDataset -from pyrit.datasets.seed_datasets.remote.sosbench_dataset import _SOSBenchDataset -from pyrit.datasets.seed_datasets.remote.strong_reject_dataset import _StrongRejectDataset -from pyrit.datasets.seed_datasets.remote.tdc23_redteaming_dataset import _TDC23RedteamingDataset -from pyrit.datasets.seed_datasets.remote.toxic_chat_dataset import _ToxicChatDataset -from pyrit.datasets.seed_datasets.remote.transphobia_awareness_dataset import _TransphobiaAwarenessDataset -from pyrit.datasets.seed_datasets.remote.visual_leak_bench_dataset import ( - VisualLeakBenchCategory, - VisualLeakBenchPIIType, - _VisualLeakBenchDataset, -) -from pyrit.datasets.seed_datasets.remote.vlguard_dataset import ( - VLGuardCategory, - VLGuardSubcategory, - VLGuardSubset, - _VLGuardDataset, -) -from pyrit.datasets.seed_datasets.remote.vlsu_multimodal_dataset import _VLSUMultimodalDataset -from pyrit.datasets.seed_datasets.remote.wildguardmix_dataset import ( - WildGuardMixAdversarial, - WildGuardMixPromptHarmLabel, - WildGuardMixSplit, - _WildGuardMixDataset, -) -from pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset import ( - XLSafetyBenchCountry, - XLSafetyBenchCulturalCategory, - XLSafetyBenchJailbreakCategory, - XLSafetyBenchLanguageMode, - _XLSafetyBenchCulturalDataset, - _XLSafetyBenchJailbreakDataset, - _XLSafetyBenchJailbreakObjectivesDataset, -) -from pyrit.datasets.seed_datasets.remote.xstest_dataset import _XSTestDataset +from typing import TYPE_CHECKING -__all__ = [ - "AegisHarmCategory", - "CoCoNotCategory", - "CoCoNotSplit", - "DecodingTrustToxicitySubset", - "FigStepCategory", - "FigStepVariant", - "HiXSTestLanguage", - "MMSafetyBenchCategory", - "MMSafetyBenchVariant", - "MossBenchOversensitivityType", - "ODINSecurityBoundary", - "ODINSeverity", - "ODINTaxonomyCategory", - "PromptIntelCategory", - "PromptIntelSeverity", - "SGXSTestLabel", - "SIUOCategory", - "VLGuardCategory", - "VLGuardSubcategory", - "VLGuardSubset", - "WildGuardMixAdversarial", - "WildGuardMixPromptHarmLabel", - "WildGuardMixSplit", - "_AegisContentSafetyDataset", - "ATRCategory", - "ATRDetectionField", - "ATRVariationType", - "_AgentThreatRulesDataset", - "_AyaRedteamingDataset", - "_BabelscapeAlertDataset", - "_BeaverTailsDataset", - "_CBTBenchDataset", - "_CCPSensitivePromptsDataset", - "_CategoricalHarmfulQADataset", - "_CoCoNotContrastDataset", - "_CoCoNotRefusalDataset", - "_ComicJailbreakDataset", - "COMIC_JAILBREAK_TEMPLATES", - "ComicJailbreakTemplateConfig", - "_DangerousQADataset", - "_DarkBenchDataset", - "_DecodingTrustToxicityDataset", - "_EquityMedQADataset", - "_FigStepDataset", - "_FigStepProDataset", - "_ForbiddenQuestionsDataset", - "_GarakAudioAchillesHeelDataset", - "_GarakCratesDataset", - "_GarakDartDataset", - "_GarakDrhSystemPromptDataset", - "_GarakNpmDataset", - "_GarakPerlDataset", - "_GarakPypiDataset", - "_GarakRakuDataset", - "_GarakRubyGemsDataset", - "_GarakTmSystemPromptDataset", - "_HarmBenchDataset", - "_HarmBenchMultimodalDataset", - "_HarmfulQADataset", - "_HiXSTestDataset", - "_JailbreakV28KDataset", - "_JailbreakVRedteam2KDataset", - "_JBBBehaviorsDataset", - "_LibrAIDoNotAnswerDataset", - "_LLMLatentAdversarialTrainingDataset", - "_MedSafetyBenchDataset", - "_MICDataset", - "_MLCommonsAILuminateDataset", - "_MMSafetyBenchDataset", - "_MossBenchDataset", - "_MSTSDataset", - "_MultilingualVulnerabilityDataset", - "_ODINDataset", - "_ORBench80KDataset", - "_ORBenchHardDataset", - "_ORBenchToxicDataset", - "_PKUSafeRLHFDataset", - "_PromptIntelDataset", - "_RedTeamSocialBiasDataset", - "_RemoteDatasetLoader", - "_SGXSTestDataset", - "_SaladBenchDataset", - "_SimpleSafetyTestsDataset", - "_SIUODataset", - "_SOSBenchDataset", - "_SorryBenchDataset", - "_StrongRejectDataset", - "_TDC23RedteamingDataset", - "_ToxicChatDataset", - "_TransphobiaAwarenessDataset", - "_VLGuardDataset", - "_VLSUMultimodalDataset", - "_VisualLeakBenchDataset", - "VisualLeakBenchCategory", - "VisualLeakBenchPIIType", - "_WildGuardMixDataset", - "XLSafetyBenchCountry", - "XLSafetyBenchCulturalCategory", - "XLSafetyBenchJailbreakCategory", - "XLSafetyBenchLanguageMode", - "_XLSafetyBenchCulturalDataset", - "_XLSafetyBenchJailbreakDataset", - "_XLSafetyBenchJailbreakObjectivesDataset", - "_XSTestDataset", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.datasets.seed_datasets.remote.aegis_ai_content_safety_dataset import ( + AegisHarmCategory, + _AegisContentSafetyDataset, + ) + from pyrit.datasets.seed_datasets.remote.agent_threat_rules_dataset import ( + ATRCategory, + ATRDetectionField, + ATRVariationType, + _AgentThreatRulesDataset, + ) + from pyrit.datasets.seed_datasets.remote.aya_redteaming_dataset import _AyaRedteamingDataset + from pyrit.datasets.seed_datasets.remote.babelscape_alert_dataset import _BabelscapeAlertDataset + from pyrit.datasets.seed_datasets.remote.beaver_tails_dataset import _BeaverTailsDataset + from pyrit.datasets.seed_datasets.remote.categorical_harmful_qa_dataset import _CategoricalHarmfulQADataset + from pyrit.datasets.seed_datasets.remote.cbt_bench_dataset import _CBTBenchDataset + from pyrit.datasets.seed_datasets.remote.ccp_sensitive_prompts_dataset import _CCPSensitivePromptsDataset + from pyrit.datasets.seed_datasets.remote.coconot_dataset import ( + CoCoNotCategory, + CoCoNotSplit, + _CoCoNotContrastDataset, + _CoCoNotRefusalDataset, + ) + from pyrit.datasets.seed_datasets.remote.comic_jailbreak_dataset import ( + COMIC_JAILBREAK_TEMPLATES, + ComicJailbreakTemplateConfig, + _ComicJailbreakDataset, + ) + from pyrit.datasets.seed_datasets.remote.dangerous_qa_dataset import _DangerousQADataset + from pyrit.datasets.seed_datasets.remote.darkbench_dataset import _DarkBenchDataset + from pyrit.datasets.seed_datasets.remote.decoding_trust_toxicity_dataset import ( + DecodingTrustToxicitySubset, + _DecodingTrustToxicityDataset, + ) + from pyrit.datasets.seed_datasets.remote.equitymedqa_dataset import _EquityMedQADataset + from pyrit.datasets.seed_datasets.remote.figstep_dataset import ( + FigStepCategory, + FigStepVariant, + _FigStepDataset, + _FigStepProDataset, + ) + from pyrit.datasets.seed_datasets.remote.forbidden_questions_dataset import _ForbiddenQuestionsDataset + from pyrit.datasets.seed_datasets.remote.garak_audio_dataset import _GarakAudioAchillesHeelDataset + from pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset import ( + _GarakCratesDataset, + _GarakDartDataset, + _GarakNpmDataset, + _GarakPerlDataset, + _GarakPypiDataset, + _GarakRakuDataset, + _GarakRubyGemsDataset, + ) + from pyrit.datasets.seed_datasets.remote.garak_system_prompt_dataset import ( + _GarakDrhSystemPromptDataset, + _GarakTmSystemPromptDataset, + ) + from pyrit.datasets.seed_datasets.remote.harmbench_dataset import _HarmBenchDataset + from pyrit.datasets.seed_datasets.remote.harmbench_multimodal_dataset import _HarmBenchMultimodalDataset + from pyrit.datasets.seed_datasets.remote.harmful_qa_dataset import _HarmfulQADataset + from pyrit.datasets.seed_datasets.remote.hixstest_dataset import HiXSTestLanguage, _HiXSTestDataset + from pyrit.datasets.seed_datasets.remote.jailbreakv_28k_dataset import _JailbreakV28KDataset + from pyrit.datasets.seed_datasets.remote.jailbreakv_redteam_2k_dataset import _JailbreakVRedteam2KDataset + from pyrit.datasets.seed_datasets.remote.jbb_behaviors_dataset import _JBBBehaviorsDataset + from pyrit.datasets.seed_datasets.remote.librai_do_not_answer_dataset import _LibrAIDoNotAnswerDataset + from pyrit.datasets.seed_datasets.remote.llm_latent_adversarial_training_dataset import ( + _LLMLatentAdversarialTrainingDataset, + ) + from pyrit.datasets.seed_datasets.remote.medsafetybench_dataset import _MedSafetyBenchDataset + from pyrit.datasets.seed_datasets.remote.mlcommons_ailuminate_dataset import _MLCommonsAILuminateDataset + from pyrit.datasets.seed_datasets.remote.mm_safetybench_dataset import ( + MMSafetyBenchCategory, + MMSafetyBenchVariant, + _MMSafetyBenchDataset, + ) + from pyrit.datasets.seed_datasets.remote.moral_integrity_corpus_dataset import _MICDataset + from pyrit.datasets.seed_datasets.remote.mossbench_dataset import MossBenchOversensitivityType, _MossBenchDataset + from pyrit.datasets.seed_datasets.remote.msts_dataset import _MSTSDataset + from pyrit.datasets.seed_datasets.remote.multilingual_vulnerability_dataset import _MultilingualVulnerabilityDataset + from pyrit.datasets.seed_datasets.remote.odin_dataset import ( + ODINSecurityBoundary, + ODINSeverity, + ODINTaxonomyCategory, + _ODINDataset, + ) + from pyrit.datasets.seed_datasets.remote.or_bench_dataset import ( + _ORBench80KDataset, + _ORBenchHardDataset, + _ORBenchToxicDataset, + ) + from pyrit.datasets.seed_datasets.remote.pku_safe_rlhf_dataset import _PKUSafeRLHFDataset + from pyrit.datasets.seed_datasets.remote.promptintel_dataset import ( + PromptIntelCategory, + PromptIntelSeverity, + _PromptIntelDataset, + ) + from pyrit.datasets.seed_datasets.remote.red_team_social_bias_dataset import _RedTeamSocialBiasDataset + from pyrit.datasets.seed_datasets.remote.remote_dataset_loader import _RemoteDatasetLoader + from pyrit.datasets.seed_datasets.remote.salad_bench_dataset import _SaladBenchDataset + from pyrit.datasets.seed_datasets.remote.sgxstest_dataset import SGXSTestLabel, _SGXSTestDataset + from pyrit.datasets.seed_datasets.remote.simple_safety_tests_dataset import _SimpleSafetyTestsDataset + from pyrit.datasets.seed_datasets.remote.siuo_dataset import SIUOCategory, _SIUODataset + from pyrit.datasets.seed_datasets.remote.sorry_bench_dataset import _SorryBenchDataset + from pyrit.datasets.seed_datasets.remote.sosbench_dataset import _SOSBenchDataset + from pyrit.datasets.seed_datasets.remote.strong_reject_dataset import _StrongRejectDataset + from pyrit.datasets.seed_datasets.remote.tdc23_redteaming_dataset import _TDC23RedteamingDataset + from pyrit.datasets.seed_datasets.remote.toxic_chat_dataset import _ToxicChatDataset + from pyrit.datasets.seed_datasets.remote.transphobia_awareness_dataset import _TransphobiaAwarenessDataset + from pyrit.datasets.seed_datasets.remote.visual_leak_bench_dataset import ( + VisualLeakBenchCategory, + VisualLeakBenchPIIType, + _VisualLeakBenchDataset, + ) + from pyrit.datasets.seed_datasets.remote.vlguard_dataset import ( + VLGuardCategory, + VLGuardSubcategory, + VLGuardSubset, + _VLGuardDataset, + ) + from pyrit.datasets.seed_datasets.remote.vlsu_multimodal_dataset import _VLSUMultimodalDataset + from pyrit.datasets.seed_datasets.remote.wildguardmix_dataset import ( + WildGuardMixAdversarial, + WildGuardMixPromptHarmLabel, + WildGuardMixSplit, + _WildGuardMixDataset, + ) + from pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset import ( + XLSafetyBenchCountry, + XLSafetyBenchCulturalCategory, + XLSafetyBenchJailbreakCategory, + XLSafetyBenchLanguageMode, + _XLSafetyBenchCulturalDataset, + _XLSafetyBenchJailbreakDataset, + _XLSafetyBenchJailbreakObjectivesDataset, + ) + from pyrit.datasets.seed_datasets.remote.xstest_dataset import _XSTestDataset + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AegisHarmCategory": "pyrit.datasets.seed_datasets.remote.aegis_ai_content_safety_dataset", + "CoCoNotCategory": "pyrit.datasets.seed_datasets.remote.coconot_dataset", + "CoCoNotSplit": "pyrit.datasets.seed_datasets.remote.coconot_dataset", + "DecodingTrustToxicitySubset": "pyrit.datasets.seed_datasets.remote.decoding_trust_toxicity_dataset", + "FigStepCategory": "pyrit.datasets.seed_datasets.remote.figstep_dataset", + "FigStepVariant": "pyrit.datasets.seed_datasets.remote.figstep_dataset", + "HiXSTestLanguage": "pyrit.datasets.seed_datasets.remote.hixstest_dataset", + "MMSafetyBenchCategory": "pyrit.datasets.seed_datasets.remote.mm_safetybench_dataset", + "MMSafetyBenchVariant": "pyrit.datasets.seed_datasets.remote.mm_safetybench_dataset", + "MossBenchOversensitivityType": "pyrit.datasets.seed_datasets.remote.mossbench_dataset", + "ODINSecurityBoundary": "pyrit.datasets.seed_datasets.remote.odin_dataset", + "ODINSeverity": "pyrit.datasets.seed_datasets.remote.odin_dataset", + "ODINTaxonomyCategory": "pyrit.datasets.seed_datasets.remote.odin_dataset", + "PromptIntelCategory": "pyrit.datasets.seed_datasets.remote.promptintel_dataset", + "PromptIntelSeverity": "pyrit.datasets.seed_datasets.remote.promptintel_dataset", + "SGXSTestLabel": "pyrit.datasets.seed_datasets.remote.sgxstest_dataset", + "SIUOCategory": "pyrit.datasets.seed_datasets.remote.siuo_dataset", + "VLGuardCategory": "pyrit.datasets.seed_datasets.remote.vlguard_dataset", + "VLGuardSubcategory": "pyrit.datasets.seed_datasets.remote.vlguard_dataset", + "VLGuardSubset": "pyrit.datasets.seed_datasets.remote.vlguard_dataset", + "WildGuardMixAdversarial": "pyrit.datasets.seed_datasets.remote.wildguardmix_dataset", + "WildGuardMixPromptHarmLabel": "pyrit.datasets.seed_datasets.remote.wildguardmix_dataset", + "WildGuardMixSplit": "pyrit.datasets.seed_datasets.remote.wildguardmix_dataset", + "_AegisContentSafetyDataset": "pyrit.datasets.seed_datasets.remote.aegis_ai_content_safety_dataset", + "ATRCategory": "pyrit.datasets.seed_datasets.remote.agent_threat_rules_dataset", + "ATRDetectionField": "pyrit.datasets.seed_datasets.remote.agent_threat_rules_dataset", + "ATRVariationType": "pyrit.datasets.seed_datasets.remote.agent_threat_rules_dataset", + "_AgentThreatRulesDataset": "pyrit.datasets.seed_datasets.remote.agent_threat_rules_dataset", + "_AyaRedteamingDataset": "pyrit.datasets.seed_datasets.remote.aya_redteaming_dataset", + "_BabelscapeAlertDataset": "pyrit.datasets.seed_datasets.remote.babelscape_alert_dataset", + "_BeaverTailsDataset": "pyrit.datasets.seed_datasets.remote.beaver_tails_dataset", + "_CBTBenchDataset": "pyrit.datasets.seed_datasets.remote.cbt_bench_dataset", + "_CCPSensitivePromptsDataset": "pyrit.datasets.seed_datasets.remote.ccp_sensitive_prompts_dataset", + "_CategoricalHarmfulQADataset": "pyrit.datasets.seed_datasets.remote.categorical_harmful_qa_dataset", + "_CoCoNotContrastDataset": "pyrit.datasets.seed_datasets.remote.coconot_dataset", + "_CoCoNotRefusalDataset": "pyrit.datasets.seed_datasets.remote.coconot_dataset", + "_ComicJailbreakDataset": "pyrit.datasets.seed_datasets.remote.comic_jailbreak_dataset", + "COMIC_JAILBREAK_TEMPLATES": "pyrit.datasets.seed_datasets.remote.comic_jailbreak_dataset", + "ComicJailbreakTemplateConfig": "pyrit.datasets.seed_datasets.remote.comic_jailbreak_dataset", + "_DangerousQADataset": "pyrit.datasets.seed_datasets.remote.dangerous_qa_dataset", + "_DarkBenchDataset": "pyrit.datasets.seed_datasets.remote.darkbench_dataset", + "_DecodingTrustToxicityDataset": "pyrit.datasets.seed_datasets.remote.decoding_trust_toxicity_dataset", + "_EquityMedQADataset": "pyrit.datasets.seed_datasets.remote.equitymedqa_dataset", + "_FigStepDataset": "pyrit.datasets.seed_datasets.remote.figstep_dataset", + "_FigStepProDataset": "pyrit.datasets.seed_datasets.remote.figstep_dataset", + "_ForbiddenQuestionsDataset": "pyrit.datasets.seed_datasets.remote.forbidden_questions_dataset", + "_GarakAudioAchillesHeelDataset": "pyrit.datasets.seed_datasets.remote.garak_audio_dataset", + "_GarakCratesDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakDartDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakDrhSystemPromptDataset": "pyrit.datasets.seed_datasets.remote.garak_system_prompt_dataset", + "_GarakNpmDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakPerlDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakPypiDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakRakuDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakRubyGemsDataset": "pyrit.datasets.seed_datasets.remote.garak_package_hallucination_dataset", + "_GarakTmSystemPromptDataset": "pyrit.datasets.seed_datasets.remote.garak_system_prompt_dataset", + "_HarmBenchDataset": "pyrit.datasets.seed_datasets.remote.harmbench_dataset", + "_HarmBenchMultimodalDataset": "pyrit.datasets.seed_datasets.remote.harmbench_multimodal_dataset", + "_HarmfulQADataset": "pyrit.datasets.seed_datasets.remote.harmful_qa_dataset", + "_HiXSTestDataset": "pyrit.datasets.seed_datasets.remote.hixstest_dataset", + "_JailbreakV28KDataset": "pyrit.datasets.seed_datasets.remote.jailbreakv_28k_dataset", + "_JailbreakVRedteam2KDataset": "pyrit.datasets.seed_datasets.remote.jailbreakv_redteam_2k_dataset", + "_JBBBehaviorsDataset": "pyrit.datasets.seed_datasets.remote.jbb_behaviors_dataset", + "_LibrAIDoNotAnswerDataset": "pyrit.datasets.seed_datasets.remote.librai_do_not_answer_dataset", + "_LLMLatentAdversarialTrainingDataset": ( + "pyrit.datasets.seed_datasets.remote.llm_latent_adversarial_training_dataset" + ), + "_MedSafetyBenchDataset": "pyrit.datasets.seed_datasets.remote.medsafetybench_dataset", + "_MICDataset": "pyrit.datasets.seed_datasets.remote.moral_integrity_corpus_dataset", + "_MLCommonsAILuminateDataset": "pyrit.datasets.seed_datasets.remote.mlcommons_ailuminate_dataset", + "_MMSafetyBenchDataset": "pyrit.datasets.seed_datasets.remote.mm_safetybench_dataset", + "_MossBenchDataset": "pyrit.datasets.seed_datasets.remote.mossbench_dataset", + "_MSTSDataset": "pyrit.datasets.seed_datasets.remote.msts_dataset", + "_MultilingualVulnerabilityDataset": "pyrit.datasets.seed_datasets.remote.multilingual_vulnerability_dataset", + "_ODINDataset": "pyrit.datasets.seed_datasets.remote.odin_dataset", + "_ORBench80KDataset": "pyrit.datasets.seed_datasets.remote.or_bench_dataset", + "_ORBenchHardDataset": "pyrit.datasets.seed_datasets.remote.or_bench_dataset", + "_ORBenchToxicDataset": "pyrit.datasets.seed_datasets.remote.or_bench_dataset", + "_PKUSafeRLHFDataset": "pyrit.datasets.seed_datasets.remote.pku_safe_rlhf_dataset", + "_PromptIntelDataset": "pyrit.datasets.seed_datasets.remote.promptintel_dataset", + "_RedTeamSocialBiasDataset": "pyrit.datasets.seed_datasets.remote.red_team_social_bias_dataset", + "_RemoteDatasetLoader": "pyrit.datasets.seed_datasets.remote.remote_dataset_loader", + "_SGXSTestDataset": "pyrit.datasets.seed_datasets.remote.sgxstest_dataset", + "_SaladBenchDataset": "pyrit.datasets.seed_datasets.remote.salad_bench_dataset", + "_SimpleSafetyTestsDataset": "pyrit.datasets.seed_datasets.remote.simple_safety_tests_dataset", + "_SIUODataset": "pyrit.datasets.seed_datasets.remote.siuo_dataset", + "_SOSBenchDataset": "pyrit.datasets.seed_datasets.remote.sosbench_dataset", + "_SorryBenchDataset": "pyrit.datasets.seed_datasets.remote.sorry_bench_dataset", + "_StrongRejectDataset": "pyrit.datasets.seed_datasets.remote.strong_reject_dataset", + "_TDC23RedteamingDataset": "pyrit.datasets.seed_datasets.remote.tdc23_redteaming_dataset", + "_ToxicChatDataset": "pyrit.datasets.seed_datasets.remote.toxic_chat_dataset", + "_TransphobiaAwarenessDataset": "pyrit.datasets.seed_datasets.remote.transphobia_awareness_dataset", + "_VLGuardDataset": "pyrit.datasets.seed_datasets.remote.vlguard_dataset", + "_VLSUMultimodalDataset": "pyrit.datasets.seed_datasets.remote.vlsu_multimodal_dataset", + "_VisualLeakBenchDataset": "pyrit.datasets.seed_datasets.remote.visual_leak_bench_dataset", + "VisualLeakBenchCategory": "pyrit.datasets.seed_datasets.remote.visual_leak_bench_dataset", + "VisualLeakBenchPIIType": "pyrit.datasets.seed_datasets.remote.visual_leak_bench_dataset", + "_WildGuardMixDataset": "pyrit.datasets.seed_datasets.remote.wildguardmix_dataset", + "XLSafetyBenchCountry": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "XLSafetyBenchCulturalCategory": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "XLSafetyBenchJailbreakCategory": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "XLSafetyBenchLanguageMode": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "_XLSafetyBenchCulturalDataset": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "_XLSafetyBenchJailbreakDataset": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "_XLSafetyBenchJailbreakObjectivesDataset": "pyrit.datasets.seed_datasets.remote.xl_safety_bench_dataset", + "_XSTestDataset": "pyrit.datasets.seed_datasets.remote.xstest_dataset", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/datasets/seed_datasets/seed_dataset_provider.py b/pyrit/datasets/seed_datasets/seed_dataset_provider.py index e0ec777802..45fb042ee9 100644 --- a/pyrit/datasets/seed_datasets/seed_dataset_provider.py +++ b/pyrit/datasets/seed_datasets/seed_dataset_provider.py @@ -2,6 +2,7 @@ # Licensed under the MIT license. import asyncio +import importlib import inspect import logging from abc import ABC, abstractmethod @@ -103,6 +104,7 @@ def get_all_providers(cls) -> dict[str, type["SeedDatasetProvider"]]: Returns: dict[str, type[SeedDatasetProvider]]: Dictionary mapping class names to provider classes. """ + cls._materialize_builtin_providers() return cls._registry.copy() @classmethod @@ -123,6 +125,7 @@ async def get_all_dataset_names_async(cls, filters: SeedDatasetFilter | None = N >>> names = await SeedDatasetProvider.get_all_dataset_names_async() >>> print(f"Available datasets: {', '.join(names)}") """ + cls._materialize_builtin_providers() dataset_names = set() for provider_class in cls._registry.values(): try: @@ -268,6 +271,8 @@ async def fetch_datasets_async( ... dataset_names=["harmbench", "DarkBench"] ... ) """ + cls._materialize_builtin_providers() + # Validate dataset names if specified if dataset_names is not None: available_names = await cls.get_all_dataset_names_async() @@ -344,3 +349,14 @@ async def fetch_with_semaphore_async( logger.info(f"Successfully fetched {len(datasets)} unique datasets from {len(cls._registry)} providers") return list(datasets.values()) + + @classmethod + def _materialize_builtin_providers(cls) -> None: + """Import every built-in dataset provider into the provider registry.""" + for package_name in ( + "pyrit.datasets.seed_datasets.local", + "pyrit.datasets.seed_datasets.remote", + ): + package = importlib.import_module(package_name) + for export_name in package.__all__: + getattr(package, export_name) diff --git a/pyrit/embedding/__init__.py b/pyrit/embedding/__init__.py index 4004301254..28f16901b1 100644 --- a/pyrit/embedding/__init__.py +++ b/pyrit/embedding/__init__.py @@ -1,10 +1,31 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -"""Embedding module for PyRIT to provide OpenAI text embedding class.""" - -from pyrit.embedding.openai_text_embedding import OpenAITextEmbedding - -__all__ = [ - "OpenAITextEmbedding", -] +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# ruff: noqa: F401 + +"""Embedding module for PyRIT to provide OpenAI text embedding class.""" + +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.embedding.openai_text_embedding import OpenAITextEmbedding + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "OpenAITextEmbedding": "pyrit.embedding.openai_text_embedding", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/exceptions/__init__.py b/pyrit/exceptions/__init__.py index c4be6078ce..9f3400528c 100644 --- a/pyrit/exceptions/__init__.py +++ b/pyrit/exceptions/__init__.py @@ -1,72 +1,93 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Exception classes, retry helpers, and execution context utilities.""" -from pyrit.exceptions.exception_classes import ( - CONTENT_FILTER_MARKERS, - BadRequestException, - EmptyResponseException, - ExperimentalWarning, - InvalidJsonException, - KeyVaultInitializationException, - MissingPromptPlaceholderException, - PyritException, - RateLimitException, - ScenarioPartialFailureException, - ScorerLLMResponseBlockedException, - get_retry_max_num_attempts, - handle_bad_request_exception, - pyrit_custom_result_retry, - pyrit_json_retry, - pyrit_placeholder_retry, - pyrit_target_retry, -) -from pyrit.exceptions.exception_context import ( - ComponentRole, - ExecutionContext, - ExecutionContextManager, - clear_execution_context, - execution_context, - get_execution_context, - set_execution_context, -) -from pyrit.exceptions.exceptions_helpers import remove_markdown_json -from pyrit.exceptions.retry_collector import ( - RetryCollector, - clear_retry_collector, - get_retry_collector, - set_retry_collector, -) +from typing import TYPE_CHECKING -__all__ = [ - "BadRequestException", - "clear_execution_context", - "clear_retry_collector", - "ComponentRole", - "CONTENT_FILTER_MARKERS", - "EmptyResponseException", - "ExecutionContext", - "ExecutionContextManager", - "ExperimentalWarning", - "get_execution_context", - "get_retry_collector", - "get_retry_max_num_attempts", - "handle_bad_request_exception", - "InvalidJsonException", - "KeyVaultInitializationException", - "MissingPromptPlaceholderException", - "PyritException", - "pyrit_custom_result_retry", - "pyrit_json_retry", - "pyrit_target_retry", - "pyrit_placeholder_retry", - "RateLimitException", - "remove_markdown_json", - "RetryCollector", - "ScenarioPartialFailureException", - "ScorerLLMResponseBlockedException", - "set_execution_context", - "set_retry_collector", - "execution_context", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.exceptions.exception_classes import ( + CONTENT_FILTER_MARKERS, + BadRequestException, + EmptyResponseException, + ExperimentalWarning, + InvalidJsonException, + KeyVaultInitializationException, + MissingPromptPlaceholderException, + PyritException, + RateLimitException, + ScenarioPartialFailureException, + ScorerLLMResponseBlockedException, + get_retry_max_num_attempts, + handle_bad_request_exception, + pyrit_custom_result_retry, + pyrit_json_retry, + pyrit_placeholder_retry, + pyrit_target_retry, + ) + from pyrit.exceptions.exception_context import ( + ComponentRole, + ExecutionContext, + ExecutionContextManager, + clear_execution_context, + execution_context, + get_execution_context, + set_execution_context, + ) + from pyrit.exceptions.exceptions_helpers import remove_markdown_json + from pyrit.exceptions.retry_collector import ( + RetryCollector, + clear_retry_collector, + get_retry_collector, + set_retry_collector, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "BadRequestException": "pyrit.exceptions.exception_classes", + "clear_execution_context": "pyrit.exceptions.exception_context", + "clear_retry_collector": "pyrit.exceptions.retry_collector", + "ComponentRole": "pyrit.exceptions.exception_context", + "CONTENT_FILTER_MARKERS": "pyrit.exceptions.exception_classes", + "EmptyResponseException": "pyrit.exceptions.exception_classes", + "ExecutionContext": "pyrit.exceptions.exception_context", + "ExecutionContextManager": "pyrit.exceptions.exception_context", + "ExperimentalWarning": "pyrit.exceptions.exception_classes", + "get_execution_context": "pyrit.exceptions.exception_context", + "get_retry_collector": "pyrit.exceptions.retry_collector", + "get_retry_max_num_attempts": "pyrit.exceptions.exception_classes", + "handle_bad_request_exception": "pyrit.exceptions.exception_classes", + "InvalidJsonException": "pyrit.exceptions.exception_classes", + "KeyVaultInitializationException": "pyrit.exceptions.exception_classes", + "MissingPromptPlaceholderException": "pyrit.exceptions.exception_classes", + "PyritException": "pyrit.exceptions.exception_classes", + "pyrit_custom_result_retry": "pyrit.exceptions.exception_classes", + "pyrit_json_retry": "pyrit.exceptions.exception_classes", + "pyrit_target_retry": "pyrit.exceptions.exception_classes", + "pyrit_placeholder_retry": "pyrit.exceptions.exception_classes", + "RateLimitException": "pyrit.exceptions.exception_classes", + "remove_markdown_json": "pyrit.exceptions.exceptions_helpers", + "RetryCollector": "pyrit.exceptions.retry_collector", + "ScenarioPartialFailureException": "pyrit.exceptions.exception_classes", + "ScorerLLMResponseBlockedException": "pyrit.exceptions.exception_classes", + "set_execution_context": "pyrit.exceptions.exception_context", + "set_retry_collector": "pyrit.exceptions.retry_collector", + "execution_context": "pyrit.exceptions.exception_context", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/__init__.py b/pyrit/executor/attack/__init__.py index 04b6ff0190..66b6a0e1c1 100644 --- a/pyrit/executor/attack/__init__.py +++ b/pyrit/executor/attack/__init__.py @@ -1,99 +1,116 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Attack executor module.""" -from pyrit.executor.attack.component import ( - ConversationManager, - ConversationState, - PrependedConversationConfig, -) -from pyrit.executor.attack.compound import ( - SequenceCompletionPolicy, - SequentialAttack, - SequentialAttackResult, - SequentialChildAttack, -) -from pyrit.executor.attack.core import ( - AttackAdversarialConfig, - AttackContext, - AttackConverterConfig, - AttackExecutor, - AttackExecutorResult, - AttackParameters, - AttackScoringConfig, - AttackStrategy, -) -from pyrit.executor.attack.multi_turn import ( - ChunkedRequestAttack, - ChunkedRequestAttackContext, - ConversationSession, - CrescendoAttack, - CrescendoAttackContext, - CrescendoAttackResult, - MultiPromptSendingAttack, - MultiPromptSendingAttackParameters, - MultiTurnAttackContext, - MultiTurnAttackStrategy, - PAIRAttack, - RedTeamingAttack, - RTASystemPromptPaths, - TAPAttack, - TAPAttackContext, - TAPAttackResult, - TAPSystemPromptPaths, - TreeOfAttacksWithPruningAttack, - generate_simulated_conversation_async, -) -from pyrit.executor.attack.single_turn import ( - ManyShotJailbreakAttack, - PromptSendingAttack, - SingleTurnAttackContext, - SingleTurnAttackStrategy, - SkeletonKeyAttack, -) -from pyrit.executor.attack.streaming import BargeInAttack, BargeInAttackContext +from typing import TYPE_CHECKING -__all__ = [ - "AttackAdversarialConfig", - "AttackContext", - "AttackConverterConfig", - "AttackExecutor", - "AttackExecutorResult", - "AttackParameters", - "AttackScoringConfig", - "AttackStrategy", - "BargeInAttack", - "BargeInAttackContext", - "ChunkedRequestAttack", - "ChunkedRequestAttackContext", - "ConversationManager", - "ConversationSession", - "ConversationState", - "CrescendoAttack", - "CrescendoAttackContext", - "CrescendoAttackResult", - "ManyShotJailbreakAttack", - "MultiPromptSendingAttack", - "MultiPromptSendingAttackParameters", - "MultiTurnAttackContext", - "MultiTurnAttackStrategy", - "PAIRAttack", - "PrependedConversationConfig", - "PromptSendingAttack", - "RTASystemPromptPaths", - "RedTeamingAttack", - "SequenceCompletionPolicy", - "SequentialAttack", - "SequentialAttackResult", - "SequentialChildAttack", - "SingleTurnAttackContext", - "SingleTurnAttackStrategy", - "SkeletonKeyAttack", - "TAPAttack", - "TAPAttackContext", - "TAPAttackResult", - "TAPSystemPromptPaths", - "TreeOfAttacksWithPruningAttack", - "generate_simulated_conversation_async", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.component import ConversationManager, ConversationState, PrependedConversationConfig + from pyrit.executor.attack.compound import ( + SequenceCompletionPolicy, + SequentialAttack, + SequentialAttackResult, + SequentialChildAttack, + ) + from pyrit.executor.attack.core import ( + AttackAdversarialConfig, + AttackContext, + AttackConverterConfig, + AttackExecutor, + AttackExecutorResult, + AttackParameters, + AttackScoringConfig, + AttackStrategy, + ) + from pyrit.executor.attack.multi_turn import ( + ChunkedRequestAttack, + ChunkedRequestAttackContext, + ConversationSession, + CrescendoAttack, + CrescendoAttackContext, + CrescendoAttackResult, + MultiPromptSendingAttack, + MultiPromptSendingAttackParameters, + MultiTurnAttackContext, + MultiTurnAttackStrategy, + PAIRAttack, + RedTeamingAttack, + RTASystemPromptPaths, + TAPAttack, + TAPAttackContext, + TAPAttackResult, + TAPSystemPromptPaths, + TreeOfAttacksWithPruningAttack, + generate_simulated_conversation_async, + ) + from pyrit.executor.attack.single_turn import ( + ManyShotJailbreakAttack, + PromptSendingAttack, + SingleTurnAttackContext, + SingleTurnAttackStrategy, + SkeletonKeyAttack, + ) + from pyrit.executor.attack.streaming import BargeInAttack, BargeInAttackContext + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AttackAdversarialConfig": "pyrit.executor.attack.core", + "AttackContext": "pyrit.executor.attack.core", + "AttackConverterConfig": "pyrit.executor.attack.core", + "AttackExecutor": "pyrit.executor.attack.core", + "AttackExecutorResult": "pyrit.executor.attack.core", + "AttackParameters": "pyrit.executor.attack.core", + "AttackScoringConfig": "pyrit.executor.attack.core", + "AttackStrategy": "pyrit.executor.attack.core", + "BargeInAttack": "pyrit.executor.attack.streaming", + "BargeInAttackContext": "pyrit.executor.attack.streaming", + "ChunkedRequestAttack": "pyrit.executor.attack.multi_turn", + "ChunkedRequestAttackContext": "pyrit.executor.attack.multi_turn", + "ConversationManager": "pyrit.executor.attack.component", + "ConversationSession": "pyrit.executor.attack.multi_turn", + "ConversationState": "pyrit.executor.attack.component", + "CrescendoAttack": "pyrit.executor.attack.multi_turn", + "CrescendoAttackContext": "pyrit.executor.attack.multi_turn", + "CrescendoAttackResult": "pyrit.executor.attack.multi_turn", + "ManyShotJailbreakAttack": "pyrit.executor.attack.single_turn", + "MultiPromptSendingAttack": "pyrit.executor.attack.multi_turn", + "MultiPromptSendingAttackParameters": "pyrit.executor.attack.multi_turn", + "MultiTurnAttackContext": "pyrit.executor.attack.multi_turn", + "MultiTurnAttackStrategy": "pyrit.executor.attack.multi_turn", + "PAIRAttack": "pyrit.executor.attack.multi_turn", + "PrependedConversationConfig": "pyrit.executor.attack.component", + "PromptSendingAttack": "pyrit.executor.attack.single_turn", + "RTASystemPromptPaths": "pyrit.executor.attack.multi_turn", + "RedTeamingAttack": "pyrit.executor.attack.multi_turn", + "SequenceCompletionPolicy": "pyrit.executor.attack.compound", + "SequentialAttack": "pyrit.executor.attack.compound", + "SequentialAttackResult": "pyrit.executor.attack.compound", + "SequentialChildAttack": "pyrit.executor.attack.compound", + "SingleTurnAttackContext": "pyrit.executor.attack.single_turn", + "SingleTurnAttackStrategy": "pyrit.executor.attack.single_turn", + "SkeletonKeyAttack": "pyrit.executor.attack.single_turn", + "TAPAttack": "pyrit.executor.attack.multi_turn", + "TAPAttackContext": "pyrit.executor.attack.multi_turn", + "TAPAttackResult": "pyrit.executor.attack.multi_turn", + "TAPSystemPromptPaths": "pyrit.executor.attack.multi_turn", + "TreeOfAttacksWithPruningAttack": "pyrit.executor.attack.multi_turn", + "generate_simulated_conversation_async": "pyrit.executor.attack.multi_turn", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/component/__init__.py b/pyrit/executor/attack/component/__init__.py index 62479bd53b..f006ea6718 100644 --- a/pyrit/executor/attack/component/__init__.py +++ b/pyrit/executor/attack/component/__init__.py @@ -1,34 +1,53 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Attack components module.""" -from pyrit.executor.attack.component.adversarial_conversation_manager import ( - AdversarialReply, - AdversarialTurn, - _AdversarialConversationManager, -) -from pyrit.executor.attack.component.conversation_manager import ( - ConversationManager, - ConversationState, - build_conversation_context_string_async, - get_adversarial_chat_messages, - get_prepended_turn_count, - mark_messages_as_simulated, -) -from pyrit.executor.attack.component.prepended_conversation_config import ( - PrependedConversationConfig, -) - -__all__ = [ - "_AdversarialConversationManager", - "AdversarialReply", - "AdversarialTurn", - "build_conversation_context_string_async", - "ConversationManager", - "ConversationState", - "get_adversarial_chat_messages", - "get_prepended_turn_count", - "mark_messages_as_simulated", - "PrependedConversationConfig", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.component.adversarial_conversation_manager import ( + AdversarialReply, + AdversarialTurn, + _AdversarialConversationManager, + ) + from pyrit.executor.attack.component.conversation_manager import ( + ConversationManager, + ConversationState, + build_conversation_context_string_async, + get_adversarial_chat_messages, + get_prepended_turn_count, + mark_messages_as_simulated, + ) + from pyrit.executor.attack.component.prepended_conversation_config import PrependedConversationConfig + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "_AdversarialConversationManager": "pyrit.executor.attack.component.adversarial_conversation_manager", + "AdversarialReply": "pyrit.executor.attack.component.adversarial_conversation_manager", + "AdversarialTurn": "pyrit.executor.attack.component.adversarial_conversation_manager", + "build_conversation_context_string_async": "pyrit.executor.attack.component.conversation_manager", + "ConversationManager": "pyrit.executor.attack.component.conversation_manager", + "ConversationState": "pyrit.executor.attack.component.conversation_manager", + "get_adversarial_chat_messages": "pyrit.executor.attack.component.conversation_manager", + "get_prepended_turn_count": "pyrit.executor.attack.component.conversation_manager", + "mark_messages_as_simulated": "pyrit.executor.attack.component.conversation_manager", + "PrependedConversationConfig": "pyrit.executor.attack.component.prepended_conversation_config", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/compound/__init__.py b/pyrit/executor/attack/compound/__init__.py index 07359072fd..51eae18bb2 100644 --- a/pyrit/executor/attack/compound/__init__.py +++ b/pyrit/executor/attack/compound/__init__.py @@ -1,18 +1,39 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Compound attack strategies that orchestrate multiple inner attack strategies.""" -from pyrit.executor.attack.compound.sequential_attack import ( - SequenceCompletionPolicy, - SequentialAttack, - SequentialAttackResult, - SequentialChildAttack, -) - -__all__ = [ - "SequenceCompletionPolicy", - "SequentialAttack", - "SequentialAttackResult", - "SequentialChildAttack", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.compound.sequential_attack import ( + SequenceCompletionPolicy, + SequentialAttack, + SequentialAttackResult, + SequentialChildAttack, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "SequenceCompletionPolicy": "pyrit.executor.attack.compound.sequential_attack", + "SequentialAttack": "pyrit.executor.attack.compound.sequential_attack", + "SequentialAttackResult": "pyrit.executor.attack.compound.sequential_attack", + "SequentialChildAttack": "pyrit.executor.attack.compound.sequential_attack", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/core/__init__.py b/pyrit/executor/attack/core/__init__.py index f95426eafd..6ed731ea69 100644 --- a/pyrit/executor/attack/core/__init__.py +++ b/pyrit/executor/attack/core/__init__.py @@ -1,39 +1,57 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Core attack strategy module.""" -from pyrit.executor.attack.core.attack_config import ( - AttackAdversarialConfig, - AttackConverterConfig, - AttackScoringConfig, - resolve_adversarial_json_schema, - resolve_adversarial_system_prompt, -) -from pyrit.executor.attack.core.attack_executor import AttackExecutor, AttackExecutorResult -from pyrit.executor.attack.core.attack_parameters import ( - AttackParameters, - AttackParamsT, -) -from pyrit.executor.attack.core.attack_strategy import ( - AttackContext, - AttackStrategy, - AttackStrategyContextT, - AttackStrategyResultT, -) - -__all__ = [ - "AttackParameters", - "AttackParamsT", - "AttackStrategy", - "AttackContext", - "AttackConverterConfig", - "AttackScoringConfig", - "AttackAdversarialConfig", - "AttackStrategyContextT", - "AttackStrategyResultT", - "AttackExecutor", - "AttackExecutorResult", - "resolve_adversarial_json_schema", - "resolve_adversarial_system_prompt", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.core.attack_config import ( + AttackAdversarialConfig, + AttackConverterConfig, + AttackScoringConfig, + resolve_adversarial_json_schema, + resolve_adversarial_system_prompt, + ) + from pyrit.executor.attack.core.attack_executor import AttackExecutor, AttackExecutorResult + from pyrit.executor.attack.core.attack_parameters import AttackParameters, AttackParamsT + from pyrit.executor.attack.core.attack_strategy import ( + AttackContext, + AttackStrategy, + AttackStrategyContextT, + AttackStrategyResultT, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AttackParameters": "pyrit.executor.attack.core.attack_parameters", + "AttackParamsT": "pyrit.executor.attack.core.attack_parameters", + "AttackStrategy": "pyrit.executor.attack.core.attack_strategy", + "AttackContext": "pyrit.executor.attack.core.attack_strategy", + "AttackConverterConfig": "pyrit.executor.attack.core.attack_config", + "AttackScoringConfig": "pyrit.executor.attack.core.attack_config", + "AttackAdversarialConfig": "pyrit.executor.attack.core.attack_config", + "AttackStrategyContextT": "pyrit.executor.attack.core.attack_strategy", + "AttackStrategyResultT": "pyrit.executor.attack.core.attack_strategy", + "AttackExecutor": "pyrit.executor.attack.core.attack_executor", + "AttackExecutorResult": "pyrit.executor.attack.core.attack_executor", + "resolve_adversarial_json_schema": "pyrit.executor.attack.core.attack_config", + "resolve_adversarial_system_prompt": "pyrit.executor.attack.core.attack_config", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/multi_turn/__init__.py b/pyrit/executor/attack/multi_turn/__init__.py index f90f3aaae1..baf90da95a 100644 --- a/pyrit/executor/attack/multi_turn/__init__.py +++ b/pyrit/executor/attack/multi_turn/__init__.py @@ -1,50 +1,73 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Multi-turn attack strategies module.""" -from pyrit.executor.attack.multi_turn.chunked_request import ChunkedRequestAttack, ChunkedRequestAttackContext -from pyrit.executor.attack.multi_turn.crescendo import CrescendoAttack, CrescendoAttackContext, CrescendoAttackResult -from pyrit.executor.attack.multi_turn.multi_prompt_sending import ( - MultiPromptSendingAttack, - MultiPromptSendingAttackParameters, -) -from pyrit.executor.attack.multi_turn.multi_turn_attack_strategy import ( - ConversationSession, - MultiTurnAttackContext, - MultiTurnAttackStrategy, -) -from pyrit.executor.attack.multi_turn.pair import PAIRAttack -from pyrit.executor.attack.multi_turn.red_teaming import RedTeamingAttack, RTASystemPromptPaths -from pyrit.executor.attack.multi_turn.simulated_conversation import ( - generate_simulated_conversation_async, -) -from pyrit.executor.attack.multi_turn.tree_of_attacks import ( - TAPAttack, - TAPAttackContext, - TAPAttackResult, - TAPSystemPromptPaths, - TreeOfAttacksWithPruningAttack, -) - -__all__ = [ - "ChunkedRequestAttack", - "ChunkedRequestAttackContext", - "ConversationSession", - "CrescendoAttack", - "CrescendoAttackContext", - "CrescendoAttackResult", - "MultiPromptSendingAttack", - "MultiPromptSendingAttackParameters", - "MultiTurnAttackContext", - "MultiTurnAttackStrategy", - "PAIRAttack", - "RTASystemPromptPaths", - "RedTeamingAttack", - "TAPAttack", - "TAPAttackContext", - "TAPAttackResult", - "TAPSystemPromptPaths", - "TreeOfAttacksWithPruningAttack", - "generate_simulated_conversation_async", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.multi_turn.chunked_request import ChunkedRequestAttack, ChunkedRequestAttackContext + from pyrit.executor.attack.multi_turn.crescendo import ( + CrescendoAttack, + CrescendoAttackContext, + CrescendoAttackResult, + ) + from pyrit.executor.attack.multi_turn.multi_prompt_sending import ( + MultiPromptSendingAttack, + MultiPromptSendingAttackParameters, + ) + from pyrit.executor.attack.multi_turn.multi_turn_attack_strategy import ( + ConversationSession, + MultiTurnAttackContext, + MultiTurnAttackStrategy, + ) + from pyrit.executor.attack.multi_turn.pair import PAIRAttack + from pyrit.executor.attack.multi_turn.red_teaming import RedTeamingAttack, RTASystemPromptPaths + from pyrit.executor.attack.multi_turn.simulated_conversation import generate_simulated_conversation_async + from pyrit.executor.attack.multi_turn.tree_of_attacks import ( + TAPAttack, + TAPAttackContext, + TAPAttackResult, + TAPSystemPromptPaths, + TreeOfAttacksWithPruningAttack, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "ChunkedRequestAttack": "pyrit.executor.attack.multi_turn.chunked_request", + "ChunkedRequestAttackContext": "pyrit.executor.attack.multi_turn.chunked_request", + "ConversationSession": "pyrit.executor.attack.multi_turn.multi_turn_attack_strategy", + "CrescendoAttack": "pyrit.executor.attack.multi_turn.crescendo", + "CrescendoAttackContext": "pyrit.executor.attack.multi_turn.crescendo", + "CrescendoAttackResult": "pyrit.executor.attack.multi_turn.crescendo", + "MultiPromptSendingAttack": "pyrit.executor.attack.multi_turn.multi_prompt_sending", + "MultiPromptSendingAttackParameters": "pyrit.executor.attack.multi_turn.multi_prompt_sending", + "MultiTurnAttackContext": "pyrit.executor.attack.multi_turn.multi_turn_attack_strategy", + "MultiTurnAttackStrategy": "pyrit.executor.attack.multi_turn.multi_turn_attack_strategy", + "PAIRAttack": "pyrit.executor.attack.multi_turn.pair", + "RTASystemPromptPaths": "pyrit.executor.attack.multi_turn.red_teaming", + "RedTeamingAttack": "pyrit.executor.attack.multi_turn.red_teaming", + "TAPAttack": "pyrit.executor.attack.multi_turn.tree_of_attacks", + "TAPAttackContext": "pyrit.executor.attack.multi_turn.tree_of_attacks", + "TAPAttackResult": "pyrit.executor.attack.multi_turn.tree_of_attacks", + "TAPSystemPromptPaths": "pyrit.executor.attack.multi_turn.tree_of_attacks", + "TreeOfAttacksWithPruningAttack": "pyrit.executor.attack.multi_turn.tree_of_attacks", + "generate_simulated_conversation_async": "pyrit.executor.attack.multi_turn.simulated_conversation", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/single_turn/__init__.py b/pyrit/executor/attack/single_turn/__init__.py index 5a4cdf56a2..f7fcc41c80 100644 --- a/pyrit/executor/attack/single_turn/__init__.py +++ b/pyrit/executor/attack/single_turn/__init__.py @@ -1,20 +1,41 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Singe turn attack strategies module.""" -from pyrit.executor.attack.single_turn.many_shot_jailbreak import ManyShotJailbreakAttack -from pyrit.executor.attack.single_turn.prompt_sending import PromptSendingAttack -from pyrit.executor.attack.single_turn.single_turn_attack_strategy import ( - SingleTurnAttackContext, - SingleTurnAttackStrategy, -) -from pyrit.executor.attack.single_turn.skeleton_key import SkeletonKeyAttack - -__all__ = [ - "SingleTurnAttackStrategy", - "SingleTurnAttackContext", - "PromptSendingAttack", - "ManyShotJailbreakAttack", - "SkeletonKeyAttack", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.single_turn.many_shot_jailbreak import ManyShotJailbreakAttack + from pyrit.executor.attack.single_turn.prompt_sending import PromptSendingAttack + from pyrit.executor.attack.single_turn.single_turn_attack_strategy import ( + SingleTurnAttackContext, + SingleTurnAttackStrategy, + ) + from pyrit.executor.attack.single_turn.skeleton_key import SkeletonKeyAttack + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "SingleTurnAttackStrategy": "pyrit.executor.attack.single_turn.single_turn_attack_strategy", + "SingleTurnAttackContext": "pyrit.executor.attack.single_turn.single_turn_attack_strategy", + "PromptSendingAttack": "pyrit.executor.attack.single_turn.prompt_sending", + "ManyShotJailbreakAttack": "pyrit.executor.attack.single_turn.many_shot_jailbreak", + "SkeletonKeyAttack": "pyrit.executor.attack.single_turn.skeleton_key", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/attack/streaming/__init__.py b/pyrit/executor/attack/streaming/__init__.py index b743ea7961..86a8671cfc 100644 --- a/pyrit/executor/attack/streaming/__init__.py +++ b/pyrit/executor/attack/streaming/__init__.py @@ -1,11 +1,32 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Streaming attack strategies (barge-in over realtime audio targets).""" -from pyrit.executor.attack.streaming.barge_in import BargeInAttack, BargeInAttackContext +from typing import TYPE_CHECKING -__all__ = [ - "BargeInAttack", - "BargeInAttackContext", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.attack.streaming.barge_in import BargeInAttack, BargeInAttackContext + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "BargeInAttack": "pyrit.executor.attack.streaming.barge_in", + "BargeInAttackContext": "pyrit.executor.attack.streaming.barge_in", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/benchmark/__init__.py b/pyrit/executor/benchmark/__init__.py index ee794366d1..78bff233a1 100644 --- a/pyrit/executor/benchmark/__init__.py +++ b/pyrit/executor/benchmark/__init__.py @@ -1,14 +1,38 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Benchmark modules.""" -from pyrit.executor.benchmark.fairness_bias import FairnessBiasBenchmark, FairnessBiasBenchmarkContext -from pyrit.executor.benchmark.question_answering import QuestionAnsweringBenchmark, QuestionAnsweringBenchmarkContext +from typing import TYPE_CHECKING -__all__ = [ - "FairnessBiasBenchmarkContext", - "FairnessBiasBenchmark", - "QuestionAnsweringBenchmarkContext", - "QuestionAnsweringBenchmark", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.benchmark.fairness_bias import FairnessBiasBenchmark, FairnessBiasBenchmarkContext + from pyrit.executor.benchmark.question_answering import ( + QuestionAnsweringBenchmark, + QuestionAnsweringBenchmarkContext, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "FairnessBiasBenchmarkContext": "pyrit.executor.benchmark.fairness_bias", + "FairnessBiasBenchmark": "pyrit.executor.benchmark.fairness_bias", + "QuestionAnsweringBenchmarkContext": "pyrit.executor.benchmark.question_answering", + "QuestionAnsweringBenchmark": "pyrit.executor.benchmark.question_answering", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/core/__init__.py b/pyrit/executor/core/__init__.py index a21eb15771..eeb72b8de6 100644 --- a/pyrit/executor/core/__init__.py +++ b/pyrit/executor/core/__init__.py @@ -1,22 +1,43 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Core executor module.""" -from pyrit.executor.core.config import StrategyConverterConfig -from pyrit.executor.core.strategy import ( - Strategy, - StrategyContext, - StrategyEvent, - StrategyEventData, - StrategyEventHandler, -) - -__all__ = [ - "Strategy", - "StrategyEventHandler", - "StrategyEvent", - "StrategyEventData", - "StrategyContext", - "StrategyConverterConfig", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.core.config import StrategyConverterConfig + from pyrit.executor.core.strategy import ( + Strategy, + StrategyContext, + StrategyEvent, + StrategyEventData, + StrategyEventHandler, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "Strategy": "pyrit.executor.core.strategy", + "StrategyEventHandler": "pyrit.executor.core.strategy", + "StrategyEvent": "pyrit.executor.core.strategy", + "StrategyEventData": "pyrit.executor.core.strategy", + "StrategyContext": "pyrit.executor.core.strategy", + "StrategyConverterConfig": "pyrit.executor.core.config", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/promptgen/__init__.py b/pyrit/executor/promptgen/__init__.py index 305d580f4e..7b8ca6c605 100644 --- a/pyrit/executor/promptgen/__init__.py +++ b/pyrit/executor/promptgen/__init__.py @@ -1,20 +1,41 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Prompt generator strategy imports.""" -from pyrit.executor.promptgen.anecdoctor import AnecdoctorContext, AnecdoctorGenerator, AnecdoctorResult -from pyrit.executor.promptgen.core import ( - PromptGeneratorStrategy, - PromptGeneratorStrategyContext, - PromptGeneratorStrategyResult, -) - -__all__ = [ - "AnecdoctorContext", - "AnecdoctorGenerator", - "AnecdoctorResult", - "PromptGeneratorStrategy", - "PromptGeneratorStrategyContext", - "PromptGeneratorStrategyResult", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.promptgen.anecdoctor import AnecdoctorContext, AnecdoctorGenerator, AnecdoctorResult + from pyrit.executor.promptgen.core import ( + PromptGeneratorStrategy, + PromptGeneratorStrategyContext, + PromptGeneratorStrategyResult, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AnecdoctorContext": "pyrit.executor.promptgen.anecdoctor", + "AnecdoctorGenerator": "pyrit.executor.promptgen.anecdoctor", + "AnecdoctorResult": "pyrit.executor.promptgen.anecdoctor", + "PromptGeneratorStrategy": "pyrit.executor.promptgen.core", + "PromptGeneratorStrategyContext": "pyrit.executor.promptgen.core", + "PromptGeneratorStrategyResult": "pyrit.executor.promptgen.core", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/promptgen/core/__init__.py b/pyrit/executor/promptgen/core/__init__.py index 19576c3b18..b25b76214d 100644 --- a/pyrit/executor/promptgen/core/__init__.py +++ b/pyrit/executor/promptgen/core/__init__.py @@ -1,16 +1,37 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Core prompt generator strategy imports.""" -from pyrit.executor.promptgen.core.prompt_generator_strategy import ( - PromptGeneratorStrategy, - PromptGeneratorStrategyContext, - PromptGeneratorStrategyResult, -) - -__all__ = [ - "PromptGeneratorStrategy", - "PromptGeneratorStrategyContext", - "PromptGeneratorStrategyResult", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.promptgen.core.prompt_generator_strategy import ( + PromptGeneratorStrategy, + PromptGeneratorStrategyContext, + PromptGeneratorStrategyResult, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "PromptGeneratorStrategy": "pyrit.executor.promptgen.core.prompt_generator_strategy", + "PromptGeneratorStrategyContext": "pyrit.executor.promptgen.core.prompt_generator_strategy", + "PromptGeneratorStrategyResult": "pyrit.executor.promptgen.core.prompt_generator_strategy", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/promptgen/fuzzer/__init__.py b/pyrit/executor/promptgen/fuzzer/__init__.py index 17272d6a3d..ea843f2d49 100644 --- a/pyrit/executor/promptgen/fuzzer/__init__.py +++ b/pyrit/executor/promptgen/fuzzer/__init__.py @@ -1,30 +1,46 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Fuzzer module for generating adversarial prompts through mutation and crossover operations.""" -from pyrit.executor.promptgen.fuzzer.fuzzer import ( - FuzzerContext, - FuzzerGenerator, - FuzzerResult, - FuzzerResultPrinter, -) -from pyrit.executor.promptgen.fuzzer.fuzzer_converter_base import FuzzerConverter -from pyrit.executor.promptgen.fuzzer.fuzzer_crossover_converter import FuzzerCrossOverConverter -from pyrit.executor.promptgen.fuzzer.fuzzer_expand_converter import FuzzerExpandConverter -from pyrit.executor.promptgen.fuzzer.fuzzer_rephrase_converter import FuzzerRephraseConverter -from pyrit.executor.promptgen.fuzzer.fuzzer_shorten_converter import FuzzerShortenConverter -from pyrit.executor.promptgen.fuzzer.fuzzer_similar_converter import FuzzerSimilarConverter - -__all__ = [ - "FuzzerContext", - "FuzzerConverter", - "FuzzerCrossOverConverter", - "FuzzerExpandConverter", - "FuzzerGenerator", - "FuzzerRephraseConverter", - "FuzzerResult", - "FuzzerResultPrinter", - "FuzzerShortenConverter", - "FuzzerSimilarConverter", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.promptgen.fuzzer.fuzzer import FuzzerContext, FuzzerGenerator, FuzzerResult, FuzzerResultPrinter + from pyrit.executor.promptgen.fuzzer.fuzzer_converter_base import FuzzerConverter + from pyrit.executor.promptgen.fuzzer.fuzzer_crossover_converter import FuzzerCrossOverConverter + from pyrit.executor.promptgen.fuzzer.fuzzer_expand_converter import FuzzerExpandConverter + from pyrit.executor.promptgen.fuzzer.fuzzer_rephrase_converter import FuzzerRephraseConverter + from pyrit.executor.promptgen.fuzzer.fuzzer_shorten_converter import FuzzerShortenConverter + from pyrit.executor.promptgen.fuzzer.fuzzer_similar_converter import FuzzerSimilarConverter + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "FuzzerContext": "pyrit.executor.promptgen.fuzzer.fuzzer", + "FuzzerConverter": "pyrit.executor.promptgen.fuzzer.fuzzer_converter_base", + "FuzzerCrossOverConverter": "pyrit.executor.promptgen.fuzzer.fuzzer_crossover_converter", + "FuzzerExpandConverter": "pyrit.executor.promptgen.fuzzer.fuzzer_expand_converter", + "FuzzerGenerator": "pyrit.executor.promptgen.fuzzer.fuzzer", + "FuzzerRephraseConverter": "pyrit.executor.promptgen.fuzzer.fuzzer_rephrase_converter", + "FuzzerResult": "pyrit.executor.promptgen.fuzzer.fuzzer", + "FuzzerResultPrinter": "pyrit.executor.promptgen.fuzzer.fuzzer", + "FuzzerShortenConverter": "pyrit.executor.promptgen.fuzzer.fuzzer_shorten_converter", + "FuzzerSimilarConverter": "pyrit.executor.promptgen.fuzzer.fuzzer_similar_converter", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/promptgen/gcg/__init__.py b/pyrit/executor/promptgen/gcg/__init__.py index 9eee714931..9411145330 100644 --- a/pyrit/executor/promptgen/gcg/__init__.py +++ b/pyrit/executor/promptgen/gcg/__init__.py @@ -1,86 +1,29 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401, N814 """ -Public API for the Greedy Coordinate Gradient (GCG) adversarial-suffix generator. +Public API for the Greedy Coordinate Gradient adversarial-suffix generator. -The primary entry point is ``GCG`` (alias for ``GCGGenerator``), a -``pyrit.executor.promptgen.core.PromptGeneratorStrategy`` that produces -adversarial suffixes via the GCG algorithm. - -Example: - from pyrit.executor.promptgen.gcg import ( - GCG, - GCGAlgorithmConfig, - GCGModelConfig, - ) - - generator = GCG( - models=[GCGModelConfig(name="meta-llama/Llama-2-7b-chat-hf")], - algorithm=GCGAlgorithmConfig(n_steps=500, batch_size=512), - ) - result = await generator.execute_async( - goals=["how do I ..."], - targets=["Sure, here is ..."], - ) - -This subpackage is **experimental**: APIs may change in any release without a -deprecation cycle. Pin pyrit to a specific version if you depend on it. To -silence the warning emitted on import:: - - import warnings - from pyrit.exceptions import ExperimentalWarning - warnings.filterwarnings("ignore", category=ExperimentalWarning) +This package is experimental. Its APIs can change in any release without a +deprecation cycle. """ import warnings -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export from pyrit.exceptions import ExperimentalWarning -from pyrit.executor.promptgen.gcg.config import ( - GCGAlgorithmConfig, - GCGConfig, - GCGDataConfig, - GCGModelConfig, - GCGOutputConfig, - GCGStrategyConfig, -) - -warnings.warn( - "pyrit.executor.promptgen.gcg is experimental: APIs may change in any release " - "without a deprecation cycle. Pin pyrit to a specific version if you depend " - "on this module. To silence: " - "warnings.filterwarnings('ignore', category=pyrit.exceptions.ExperimentalWarning).", - ExperimentalWarning, - stacklevel=2, -) - -# Torch-dependent symbols are exposed lazily via PEP 562 __getattr__ so that -# `from pyrit.executor.promptgen.gcg import GCGConfig` works on installs that -# only have the base `dev` extra (no torch). Touching any of these names from -# the package root triggers the underlying module import on first access; if -# torch is missing the user gets a clear ModuleNotFoundError pointing at torch. -# -# The extension Protocols live in ``extension_protocols`` (typing-only — that -# module imports cleanly without torch) but are routed through the same lazy -# mechanism so all GCG public symbols share one re-export pathway. -_LAZY_IMPORTS = { - "CandidateFilter": ("pyrit.executor.promptgen.gcg.extension_protocols", "CandidateFilter"), - "CrossEntropyLoss": ("pyrit.executor.promptgen.gcg.default_implementations", "CrossEntropyLoss"), - "GCG": ("pyrit.executor.promptgen.gcg.generator", "GCGGenerator"), - "GCGContext": ("pyrit.executor.promptgen.gcg.generator", "GCGContext"), - "GCGGenerator": ("pyrit.executor.promptgen.gcg.generator", "GCGGenerator"), - "GCGResult": ("pyrit.executor.promptgen.gcg.generator", "GCGResult"), - "LengthPreservingFilter": ("pyrit.executor.promptgen.gcg.default_implementations", "LengthPreservingFilter"), - "LiteralStringInit": ("pyrit.executor.promptgen.gcg.default_implementations", "LiteralStringInit"), - "LossFunction": ("pyrit.executor.promptgen.gcg.extension_protocols", "LossFunction"), - "SamplingStrategy": ("pyrit.executor.promptgen.gcg.extension_protocols", "SamplingStrategy"), - "StandardGCGSampling": ("pyrit.executor.promptgen.gcg.default_implementations", "StandardGCGSampling"), - "SuffixInitializer": ("pyrit.executor.promptgen.gcg.extension_protocols", "SuffixInitializer"), - "load_goals_and_targets": ("pyrit.executor.promptgen.gcg.data", "load_goals_and_targets"), -} if TYPE_CHECKING: + from pyrit.executor.promptgen.gcg.config import ( + GCGAlgorithmConfig, + GCGConfig, + GCGDataConfig, + GCGModelConfig, + GCGOutputConfig, + GCGStrategyConfig, + ) from pyrit.executor.promptgen.gcg.data import load_goals_and_targets from pyrit.executor.promptgen.gcg.default_implementations import ( CrossEntropyLoss, @@ -99,43 +42,52 @@ GCGGenerator, GCGResult, ) + from pyrit.executor.promptgen.gcg.generator import ( + GCGGenerator as GCG, + ) - GCG = GCGGenerator - +warnings.warn( + "pyrit.executor.promptgen.gcg is experimental: APIs may change in any release " + "without a deprecation cycle. Pin pyrit to a specific version if you depend " + "on this module. To silence: " + "warnings.filterwarnings('ignore', category=pyrit.exceptions.ExperimentalWarning).", + ExperimentalWarning, + stacklevel=2, +) -def __getattr__(name: str) -> Any: - if name in _LAZY_IMPORTS: - import importlib +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "CandidateFilter": "pyrit.executor.promptgen.gcg.extension_protocols", + "CrossEntropyLoss": "pyrit.executor.promptgen.gcg.default_implementations", + "GCG": ("pyrit.executor.promptgen.gcg.generator", "GCGGenerator"), + "GCGAlgorithmConfig": "pyrit.executor.promptgen.gcg.config", + "GCGConfig": "pyrit.executor.promptgen.gcg.config", + "GCGContext": "pyrit.executor.promptgen.gcg.generator", + "GCGDataConfig": "pyrit.executor.promptgen.gcg.config", + "GCGGenerator": "pyrit.executor.promptgen.gcg.generator", + "GCGModelConfig": "pyrit.executor.promptgen.gcg.config", + "GCGOutputConfig": "pyrit.executor.promptgen.gcg.config", + "GCGResult": "pyrit.executor.promptgen.gcg.generator", + "GCGStrategyConfig": "pyrit.executor.promptgen.gcg.config", + "LengthPreservingFilter": "pyrit.executor.promptgen.gcg.default_implementations", + "LiteralStringInit": "pyrit.executor.promptgen.gcg.default_implementations", + "LossFunction": "pyrit.executor.promptgen.gcg.extension_protocols", + "SamplingStrategy": "pyrit.executor.promptgen.gcg.extension_protocols", + "StandardGCGSampling": "pyrit.executor.promptgen.gcg.default_implementations", + "SuffixInitializer": "pyrit.executor.promptgen.gcg.extension_protocols", + "load_goals_and_targets": "pyrit.executor.promptgen.gcg.data", +} - module_name, attr = _LAZY_IMPORTS[name] - value = getattr(importlib.import_module(module_name), attr) - globals()[name] = value - return value - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +__all__ = list(_LAZY_EXPORTS) -def __dir__() -> list[str]: - return sorted(set(list(globals().keys()) + list(_LAZY_IMPORTS.keys()))) +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) -__all__ = [ - "CandidateFilter", - "CrossEntropyLoss", - "GCG", - "GCGAlgorithmConfig", - "GCGConfig", - "GCGContext", - "GCGDataConfig", - "GCGGenerator", - "GCGModelConfig", - "GCGOutputConfig", - "GCGResult", - "GCGStrategyConfig", - "LengthPreservingFilter", - "LiteralStringInit", - "LossFunction", - "SamplingStrategy", - "StandardGCGSampling", - "SuffixInitializer", - "load_goals_and_targets", -] +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/workflow/__init__.py b/pyrit/executor/workflow/__init__.py index 37d32db5c1..991c1f106e 100644 --- a/pyrit/executor/workflow/__init__.py +++ b/pyrit/executor/workflow/__init__.py @@ -1,24 +1,45 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Workflow components and strategies used by the PyRIT executor.""" -from pyrit.executor.workflow.xpia import ( - XPIAContext, - XPIAManualProcessingWorkflow, - XPIAProcessingCallback, - XPIAResult, - XPIAStatus, - XPIATestWorkflow, - XPIAWorkflow, -) - -__all__ = [ - "XPIAContext", - "XPIAResult", - "XPIAWorkflow", - "XPIATestWorkflow", - "XPIAManualProcessingWorkflow", - "XPIAProcessingCallback", - "XPIAStatus", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.workflow.xpia import ( + XPIAContext, + XPIAManualProcessingWorkflow, + XPIAProcessingCallback, + XPIAResult, + XPIAStatus, + XPIATestWorkflow, + XPIAWorkflow, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "XPIAContext": "pyrit.executor.workflow.xpia", + "XPIAResult": "pyrit.executor.workflow.xpia", + "XPIAWorkflow": "pyrit.executor.workflow.xpia", + "XPIATestWorkflow": "pyrit.executor.workflow.xpia", + "XPIAManualProcessingWorkflow": "pyrit.executor.workflow.xpia", + "XPIAProcessingCallback": "pyrit.executor.workflow.xpia", + "XPIAStatus": "pyrit.executor.workflow.xpia", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/executor/workflow/core/__init__.py b/pyrit/executor/workflow/core/__init__.py index 09a172021a..3f4500d4b9 100644 --- a/pyrit/executor/workflow/core/__init__.py +++ b/pyrit/executor/workflow/core/__init__.py @@ -1,16 +1,33 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Workflow components and strategies used by the PyRIT executor.""" -from pyrit.executor.workflow.core.workflow_strategy import ( - WorkflowContext, - WorkflowResult, - WorkflowStrategy, -) - -__all__ = [ - "WorkflowContext", - "WorkflowResult", - "WorkflowStrategy", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.executor.workflow.core.workflow_strategy import WorkflowContext, WorkflowResult, WorkflowStrategy + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "WorkflowContext": "pyrit.executor.workflow.core.workflow_strategy", + "WorkflowResult": "pyrit.executor.workflow.core.workflow_strategy", + "WorkflowStrategy": "pyrit.executor.workflow.core.workflow_strategy", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/memory/__init__.py b/pyrit/memory/__init__.py index 2c78a4ba2d..fd2ee20450 100644 --- a/pyrit/memory/__init__.py +++ b/pyrit/memory/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Provide functionality for storing and retrieving conversation history and embeddings. @@ -7,56 +8,76 @@ This package defines the core `MemoryInterface` and concrete implementations for different storage backends. """ -from pyrit.memory.azure_sql_memory import AzureSQLMemory -from pyrit.memory.central_memory import CentralMemory -from pyrit.memory.memory_embedding import MemoryEmbedding -from pyrit.memory.memory_interface import AttackResultsKeysetCursor, MemoryInterface -from pyrit.memory.memory_models import AttackResultEntry, EmbeddingDataEntry, PromptMemoryEntry, SeedEntry -from pyrit.memory.sqlite_memory import SQLiteMemory -from pyrit.memory.storage import ( - AllowedCategories, - AudioPathDataTypeSerializer, - AzureBlobStorageIO, - BinaryPathDataTypeSerializer, - DataTypeSerializer, - DiskStorageIO, - ErrorDataTypeSerializer, - ImagePathDataTypeSerializer, - StorageIO, - SupportedContentType, - TextDataTypeSerializer, - URLDataTypeSerializer, - VideoPathDataTypeSerializer, - data_serializer_factory, - set_message_piece_sha256_async, - set_seed_sha256_async, -) - -__all__ = [ - "AllowedCategories", - "AttackResultEntry", - "AttackResultsKeysetCursor", - "AudioPathDataTypeSerializer", - "AzureBlobStorageIO", - "AzureSQLMemory", - "BinaryPathDataTypeSerializer", - "CentralMemory", - "DataTypeSerializer", - "data_serializer_factory", - "DiskStorageIO", - "EmbeddingDataEntry", - "ErrorDataTypeSerializer", - "ImagePathDataTypeSerializer", - "MemoryInterface", - "MemoryEmbedding", - "PromptMemoryEntry", - "SeedEntry", - "set_message_piece_sha256_async", - "set_seed_sha256_async", - "SQLiteMemory", - "StorageIO", - "SupportedContentType", - "TextDataTypeSerializer", - "URLDataTypeSerializer", - "VideoPathDataTypeSerializer", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.memory.azure_sql_memory import AzureSQLMemory + from pyrit.memory.central_memory import CentralMemory + from pyrit.memory.memory_embedding import MemoryEmbedding + from pyrit.memory.memory_interface import AttackResultsKeysetCursor, MemoryInterface + from pyrit.memory.memory_models import AttackResultEntry, EmbeddingDataEntry, PromptMemoryEntry, SeedEntry + from pyrit.memory.sqlite_memory import SQLiteMemory + from pyrit.memory.storage import ( + AllowedCategories, + AudioPathDataTypeSerializer, + AzureBlobStorageIO, + BinaryPathDataTypeSerializer, + DataTypeSerializer, + DiskStorageIO, + ErrorDataTypeSerializer, + ImagePathDataTypeSerializer, + StorageIO, + SupportedContentType, + TextDataTypeSerializer, + URLDataTypeSerializer, + VideoPathDataTypeSerializer, + data_serializer_factory, + set_message_piece_sha256_async, + set_seed_sha256_async, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AllowedCategories": "pyrit.memory.storage", + "AttackResultEntry": "pyrit.memory.memory_models", + "AttackResultsKeysetCursor": "pyrit.memory.memory_interface", + "AudioPathDataTypeSerializer": "pyrit.memory.storage", + "AzureBlobStorageIO": "pyrit.memory.storage", + "AzureSQLMemory": "pyrit.memory.azure_sql_memory", + "BinaryPathDataTypeSerializer": "pyrit.memory.storage", + "CentralMemory": "pyrit.memory.central_memory", + "DataTypeSerializer": "pyrit.memory.storage", + "data_serializer_factory": "pyrit.memory.storage", + "DiskStorageIO": "pyrit.memory.storage", + "EmbeddingDataEntry": "pyrit.memory.memory_models", + "ErrorDataTypeSerializer": "pyrit.memory.storage", + "ImagePathDataTypeSerializer": "pyrit.memory.storage", + "MemoryInterface": "pyrit.memory.memory_interface", + "MemoryEmbedding": "pyrit.memory.memory_embedding", + "PromptMemoryEntry": "pyrit.memory.memory_models", + "SeedEntry": "pyrit.memory.memory_models", + "set_message_piece_sha256_async": "pyrit.memory.storage", + "set_seed_sha256_async": "pyrit.memory.storage", + "SQLiteMemory": "pyrit.memory.sqlite_memory", + "StorageIO": "pyrit.memory.storage", + "SupportedContentType": "pyrit.memory.storage", + "TextDataTypeSerializer": "pyrit.memory.storage", + "URLDataTypeSerializer": "pyrit.memory.storage", + "VideoPathDataTypeSerializer": "pyrit.memory.storage", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/memory/storage/__init__.py b/pyrit/memory/storage/__init__.py index 7eca687042..370e5c9009 100644 --- a/pyrit/memory/storage/__init__.py +++ b/pyrit/memory/storage/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Storage layer for PyRIT: storage backends and multi-modal data serializers. @@ -15,46 +16,59 @@ holds the blob payloads those records point to. """ -from pyrit.memory.storage.data_url_converter import ( - convert_local_image_to_data_url_async, -) -from pyrit.memory.storage.serializers import ( - AllowedCategories, - AudioPathDataTypeSerializer, - BinaryPathDataTypeSerializer, - DataTypeSerializer, - ErrorDataTypeSerializer, - ImagePathDataTypeSerializer, - TextDataTypeSerializer, - URLDataTypeSerializer, - VideoPathDataTypeSerializer, - data_serializer_factory, - set_message_piece_sha256_async, - set_seed_sha256_async, -) -from pyrit.memory.storage.storage import ( - AzureBlobStorageIO, - DiskStorageIO, - StorageIO, - SupportedContentType, -) - -__all__ = [ - "AllowedCategories", - "AudioPathDataTypeSerializer", - "AzureBlobStorageIO", - "BinaryPathDataTypeSerializer", - "convert_local_image_to_data_url_async", - "DataTypeSerializer", - "data_serializer_factory", - "DiskStorageIO", - "ErrorDataTypeSerializer", - "ImagePathDataTypeSerializer", - "set_message_piece_sha256_async", - "set_seed_sha256_async", - "StorageIO", - "SupportedContentType", - "TextDataTypeSerializer", - "URLDataTypeSerializer", - "VideoPathDataTypeSerializer", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.memory.storage.data_url_converter import convert_local_image_to_data_url_async + from pyrit.memory.storage.serializers import ( + AllowedCategories, + AudioPathDataTypeSerializer, + BinaryPathDataTypeSerializer, + DataTypeSerializer, + ErrorDataTypeSerializer, + ImagePathDataTypeSerializer, + TextDataTypeSerializer, + URLDataTypeSerializer, + VideoPathDataTypeSerializer, + data_serializer_factory, + set_message_piece_sha256_async, + set_seed_sha256_async, + ) + from pyrit.memory.storage.storage import AzureBlobStorageIO, DiskStorageIO, StorageIO, SupportedContentType + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AllowedCategories": "pyrit.memory.storage.serializers", + "AudioPathDataTypeSerializer": "pyrit.memory.storage.serializers", + "AzureBlobStorageIO": "pyrit.memory.storage.storage", + "BinaryPathDataTypeSerializer": "pyrit.memory.storage.serializers", + "convert_local_image_to_data_url_async": "pyrit.memory.storage.data_url_converter", + "DataTypeSerializer": "pyrit.memory.storage.serializers", + "data_serializer_factory": "pyrit.memory.storage.serializers", + "DiskStorageIO": "pyrit.memory.storage.storage", + "ErrorDataTypeSerializer": "pyrit.memory.storage.serializers", + "ImagePathDataTypeSerializer": "pyrit.memory.storage.serializers", + "set_message_piece_sha256_async": "pyrit.memory.storage.serializers", + "set_seed_sha256_async": "pyrit.memory.storage.serializers", + "StorageIO": "pyrit.memory.storage.storage", + "SupportedContentType": "pyrit.memory.storage.storage", + "TextDataTypeSerializer": "pyrit.memory.storage.serializers", + "URLDataTypeSerializer": "pyrit.memory.storage.serializers", + "VideoPathDataTypeSerializer": "pyrit.memory.storage.serializers", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/message_normalizer/__init__.py b/pyrit/message_normalizer/__init__.py index 79df1cb50d..d7a46f275d 100644 --- a/pyrit/message_normalizer/__init__.py +++ b/pyrit/message_normalizer/__init__.py @@ -1,28 +1,46 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Functionality to normalize messages into compatible formats for targets. """ -from pyrit.message_normalizer.chat_message_normalizer import ChatMessageNormalizer -from pyrit.message_normalizer.conversation_context_normalizer import ConversationContextNormalizer -from pyrit.message_normalizer.generic_system_squash import GenericSystemSquashNormalizer -from pyrit.message_normalizer.history_squash_normalizer import HistorySquashNormalizer -from pyrit.message_normalizer.json_schema_normalizer import JsonSchemaNormalizer -from pyrit.message_normalizer.message_normalizer import ( - MessageListNormalizer, - MessageStringNormalizer, -) -from pyrit.message_normalizer.tokenizer_template_normalizer import TokenizerTemplateNormalizer - -__all__ = [ - "MessageListNormalizer", - "MessageStringNormalizer", - "GenericSystemSquashNormalizer", - "HistorySquashNormalizer", - "JsonSchemaNormalizer", - "TokenizerTemplateNormalizer", - "ConversationContextNormalizer", - "ChatMessageNormalizer", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.message_normalizer.chat_message_normalizer import ChatMessageNormalizer + from pyrit.message_normalizer.conversation_context_normalizer import ConversationContextNormalizer + from pyrit.message_normalizer.generic_system_squash import GenericSystemSquashNormalizer + from pyrit.message_normalizer.history_squash_normalizer import HistorySquashNormalizer + from pyrit.message_normalizer.json_schema_normalizer import JsonSchemaNormalizer + from pyrit.message_normalizer.message_normalizer import MessageListNormalizer, MessageStringNormalizer + from pyrit.message_normalizer.tokenizer_template_normalizer import TokenizerTemplateNormalizer + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "MessageListNormalizer": "pyrit.message_normalizer.message_normalizer", + "MessageStringNormalizer": "pyrit.message_normalizer.message_normalizer", + "GenericSystemSquashNormalizer": "pyrit.message_normalizer.generic_system_squash", + "HistorySquashNormalizer": "pyrit.message_normalizer.history_squash_normalizer", + "JsonSchemaNormalizer": "pyrit.message_normalizer.json_schema_normalizer", + "TokenizerTemplateNormalizer": "pyrit.message_normalizer.tokenizer_template_normalizer", + "ConversationContextNormalizer": "pyrit.message_normalizer.conversation_context_normalizer", + "ChatMessageNormalizer": "pyrit.message_normalizer.chat_message_normalizer", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/output/__init__.py b/pyrit/output/__init__.py index 8885986fa6..1d72304af8 100644 --- a/pyrit/output/__init__.py +++ b/pyrit/output/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Output module for displaying attack, scenario, and scorer results. @@ -14,27 +15,47 @@ Abstract methods inside each printer determine the data source (memory, REST, fixtures). """ -from pyrit.output.base import PrinterBase -from pyrit.output.helpers import ( - output_attack_async, - output_conversation_async, - output_scenario_async, - output_score_async, - output_scorer_async, -) -from pyrit.output.sink import FileSink, IPythonMarkdownSink, OutputFormat, Sink, StdoutSink, get_default_sink - -__all__ = [ - "FileSink", - "get_default_sink", - "IPythonMarkdownSink", - "OutputFormat", - "output_attack_async", - "output_conversation_async", - "output_scenario_async", - "output_score_async", - "output_scorer_async", - "PrinterBase", - "Sink", - "StdoutSink", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.output.base import PrinterBase + from pyrit.output.helpers import ( + output_attack_async, + output_conversation_async, + output_scenario_async, + output_score_async, + output_scorer_async, + ) + from pyrit.output.sink import FileSink, IPythonMarkdownSink, OutputFormat, Sink, StdoutSink, get_default_sink + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "FileSink": "pyrit.output.sink", + "get_default_sink": "pyrit.output.sink", + "IPythonMarkdownSink": "pyrit.output.sink", + "OutputFormat": "pyrit.output.sink", + "output_attack_async": "pyrit.output.helpers", + "output_conversation_async": "pyrit.output.helpers", + "output_scenario_async": "pyrit.output.helpers", + "output_score_async": "pyrit.output.helpers", + "output_scorer_async": "pyrit.output.helpers", + "PrinterBase": "pyrit.output.base", + "Sink": "pyrit.output.sink", + "StdoutSink": "pyrit.output.sink", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/prompt_normalizer/__init__.py b/pyrit/prompt_normalizer/__init__.py index 55351e61ac..06ac952259 100644 --- a/pyrit/prompt_normalizer/__init__.py +++ b/pyrit/prompt_normalizer/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Prompt normalization components for standardizing and converting prompts. @@ -8,14 +9,34 @@ including converter configurations and request handling. """ -from pyrit.prompt_normalizer.converter_configuration import ConverterConfiguration -from pyrit.prompt_normalizer.json_retry import send_json_with_retry_async -from pyrit.prompt_normalizer.normalizer_request import NormalizerRequest -from pyrit.prompt_normalizer.prompt_normalizer import PromptNormalizer - -__all__ = [ - "NormalizerRequest", - "ConverterConfiguration", - "PromptNormalizer", - "send_json_with_retry_async", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.prompt_normalizer.converter_configuration import ConverterConfiguration + from pyrit.prompt_normalizer.json_retry import send_json_with_retry_async + from pyrit.prompt_normalizer.normalizer_request import NormalizerRequest + from pyrit.prompt_normalizer.prompt_normalizer import PromptNormalizer + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "NormalizerRequest": "pyrit.prompt_normalizer.normalizer_request", + "ConverterConfiguration": "pyrit.prompt_normalizer.converter_configuration", + "PromptNormalizer": "pyrit.prompt_normalizer.prompt_normalizer", + "send_json_with_retry_async": "pyrit.prompt_normalizer.json_retry", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/prompt_target/__init__.py b/pyrit/prompt_target/__init__.py index 1d5608d691..999c50834a 100644 --- a/pyrit/prompt_target/__init__.py +++ b/pyrit/prompt_target/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Prompt targets for PyRIT. @@ -8,110 +9,109 @@ for example sending prompts or transferring content (uploads). """ -import importlib from typing import TYPE_CHECKING -from pyrit.prompt_target.azure_blob_storage_target import AzureBlobStorageTarget -from pyrit.prompt_target.azure_ml_chat_target import AzureMLChatTarget -from pyrit.prompt_target.common.conversation_normalization_pipeline import ConversationNormalizationPipeline -from pyrit.prompt_target.common.discover_target_capabilities import ( - discover_target_capabilities_async, -) -from pyrit.prompt_target.common.prompt_target import PromptTarget -from pyrit.prompt_target.common.realtime_audio import ServerVadConfig -from pyrit.prompt_target.common.target_capabilities import ( - CapabilityHandlingPolicy, - CapabilityName, - TargetCapabilities, - UnsupportedCapabilityBehavior, - get_known_capabilities, -) -from pyrit.prompt_target.common.target_configuration import TargetConfiguration -from pyrit.prompt_target.common.target_requirements import CHAT_TARGET_REQUIREMENTS, TargetRequirements -from pyrit.prompt_target.common.utils import limit_requests_per_minute -from pyrit.prompt_target.gandalf_target import GandalfLevel, GandalfTarget -from pyrit.prompt_target.http_target.http_target import HTTPTarget -from pyrit.prompt_target.http_target.http_target_callback_functions import ( - get_http_target_json_response_callback_function, - get_http_target_regex_matching_callback_function, -) -from pyrit.prompt_target.http_target.httpx_api_target import HTTPXAPITarget -from pyrit.prompt_target.litellm_chat_target import LiteLLMChatTarget -from pyrit.prompt_target.openai.openai_chat_audio_config import OpenAIChatAudioConfig -from pyrit.prompt_target.openai.openai_chat_target import OpenAIChatTarget -from pyrit.prompt_target.openai.openai_completion_target import OpenAICompletionTarget -from pyrit.prompt_target.openai.openai_image_target import OpenAIImageTarget -from pyrit.prompt_target.openai.openai_realtime_target import RealtimeTarget -from pyrit.prompt_target.openai.openai_response_target import OpenAIResponseTarget -from pyrit.prompt_target.openai.openai_target import OpenAITarget -from pyrit.prompt_target.openai.openai_tts_target import OpenAITTSTarget -from pyrit.prompt_target.openai.openai_video_target import OpenAIVideoTarget -from pyrit.prompt_target.playwright_copilot_target import CopilotType, PlaywrightCopilotTarget -from pyrit.prompt_target.playwright_target import PlaywrightTarget -from pyrit.prompt_target.prompt_shield_target import PromptShieldTarget -from pyrit.prompt_target.round_robin_target import RoundRobinTarget -from pyrit.prompt_target.text_target import TextTarget -from pyrit.prompt_target.websocket_copilot_target import WebSocketCopilotTarget -from pyrit.prompt_target.websocket_target import WebsocketTarget +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export if TYPE_CHECKING: + from pyrit.prompt_target.azure_blob_storage_target import AzureBlobStorageTarget + from pyrit.prompt_target.azure_ml_chat_target import AzureMLChatTarget + from pyrit.prompt_target.common.conversation_normalization_pipeline import ConversationNormalizationPipeline + from pyrit.prompt_target.common.discover_target_capabilities import discover_target_capabilities_async + from pyrit.prompt_target.common.prompt_target import PromptTarget + from pyrit.prompt_target.common.realtime_audio import ServerVadConfig + from pyrit.prompt_target.common.target_capabilities import ( + CapabilityHandlingPolicy, + CapabilityName, + TargetCapabilities, + UnsupportedCapabilityBehavior, + get_known_capabilities, + ) + from pyrit.prompt_target.common.target_configuration import TargetConfiguration + from pyrit.prompt_target.common.target_requirements import CHAT_TARGET_REQUIREMENTS, TargetRequirements + from pyrit.prompt_target.common.utils import limit_requests_per_minute + from pyrit.prompt_target.gandalf_target import GandalfLevel, GandalfTarget + from pyrit.prompt_target.http_target.http_target import HTTPTarget + from pyrit.prompt_target.http_target.http_target_callback_functions import ( + get_http_target_json_response_callback_function, + get_http_target_regex_matching_callback_function, + ) + from pyrit.prompt_target.http_target.httpx_api_target import HTTPXAPITarget from pyrit.prompt_target.hugging_face.hugging_face_chat_target import HuggingFaceChatTarget + from pyrit.prompt_target.litellm_chat_target import LiteLLMChatTarget + from pyrit.prompt_target.openai.openai_chat_audio_config import OpenAIChatAudioConfig + from pyrit.prompt_target.openai.openai_chat_target import OpenAIChatTarget + from pyrit.prompt_target.openai.openai_completion_target import OpenAICompletionTarget + from pyrit.prompt_target.openai.openai_image_target import OpenAIImageTarget + from pyrit.prompt_target.openai.openai_realtime_target import RealtimeTarget + from pyrit.prompt_target.openai.openai_response_target import OpenAIResponseTarget + from pyrit.prompt_target.openai.openai_target import OpenAITarget + from pyrit.prompt_target.openai.openai_tts_target import OpenAITTSTarget + from pyrit.prompt_target.openai.openai_video_target import OpenAIVideoTarget + from pyrit.prompt_target.playwright_copilot_target import CopilotType, PlaywrightCopilotTarget + from pyrit.prompt_target.playwright_target import PlaywrightTarget + from pyrit.prompt_target.prompt_shield_target import PromptShieldTarget + from pyrit.prompt_target.round_robin_target import RoundRobinTarget + from pyrit.prompt_target.text_target import TextTarget + from pyrit.prompt_target.websocket_copilot_target import WebSocketCopilotTarget + from pyrit.prompt_target.websocket_target import WebsocketTarget -# Keep optional inference targets lazy so package imports do not load their -# target-specific runtime modules. -_LAZY_IMPORTS: dict[str, str] = { +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AzureBlobStorageTarget": "pyrit.prompt_target.azure_blob_storage_target", + "AzureMLChatTarget": "pyrit.prompt_target.azure_ml_chat_target", + "CapabilityName": "pyrit.prompt_target.common.target_capabilities", + "CapabilityHandlingPolicy": "pyrit.prompt_target.common.target_capabilities", + "CHAT_TARGET_REQUIREMENTS": "pyrit.prompt_target.common.target_requirements", + "CopilotType": "pyrit.prompt_target.playwright_copilot_target", + "ConversationNormalizationPipeline": "pyrit.prompt_target.common.conversation_normalization_pipeline", + "GandalfLevel": "pyrit.prompt_target.gandalf_target", + "GandalfTarget": "pyrit.prompt_target.gandalf_target", + "get_http_target_json_response_callback_function": "pyrit.prompt_target.http_target.http_target_callback_functions", + "get_http_target_regex_matching_callback_function": ( + "pyrit.prompt_target.http_target.http_target_callback_functions" + ), + "HTTPTarget": "pyrit.prompt_target.http_target.http_target", + "HTTPXAPITarget": "pyrit.prompt_target.http_target.httpx_api_target", "HuggingFaceChatTarget": "pyrit.prompt_target.hugging_face.hugging_face_chat_target", + "limit_requests_per_minute": "pyrit.prompt_target.common.utils", + "LiteLLMChatTarget": "pyrit.prompt_target.litellm_chat_target", + "OpenAICompletionTarget": "pyrit.prompt_target.openai.openai_completion_target", + "OpenAIChatAudioConfig": "pyrit.prompt_target.openai.openai_chat_audio_config", + "OpenAIChatTarget": "pyrit.prompt_target.openai.openai_chat_target", + "OpenAIImageTarget": "pyrit.prompt_target.openai.openai_image_target", + "OpenAIResponseTarget": "pyrit.prompt_target.openai.openai_response_target", + "OpenAIVideoTarget": "pyrit.prompt_target.openai.openai_video_target", + "OpenAITTSTarget": "pyrit.prompt_target.openai.openai_tts_target", + "OpenAITarget": "pyrit.prompt_target.openai.openai_target", + "PlaywrightTarget": "pyrit.prompt_target.playwright_target", + "PlaywrightCopilotTarget": "pyrit.prompt_target.playwright_copilot_target", + "PromptShieldTarget": "pyrit.prompt_target.prompt_shield_target", + "PromptTarget": "pyrit.prompt_target.common.prompt_target", + "RealtimeTarget": "pyrit.prompt_target.openai.openai_realtime_target", + "ServerVadConfig": "pyrit.prompt_target.common.realtime_audio", + "RoundRobinTarget": "pyrit.prompt_target.round_robin_target", + "TargetCapabilities": "pyrit.prompt_target.common.target_capabilities", + "TargetConfiguration": "pyrit.prompt_target.common.target_configuration", + "TargetRequirements": "pyrit.prompt_target.common.target_requirements", + "UnsupportedCapabilityBehavior": "pyrit.prompt_target.common.target_capabilities", + "TextTarget": "pyrit.prompt_target.text_target", + "WebsocketTarget": "pyrit.prompt_target.websocket_target", + "discover_target_capabilities_async": "pyrit.prompt_target.common.discover_target_capabilities", + "get_known_capabilities": "pyrit.prompt_target.common.target_capabilities", + "WebSocketCopilotTarget": "pyrit.prompt_target.websocket_copilot_target", } +__all__ = list(_LAZY_EXPORTS) + def __getattr__(name: str) -> object: - if name in _LAZY_IMPORTS: - module = importlib.import_module(_LAZY_IMPORTS[name]) - attr = getattr(module, name) - globals()[name] = attr - return attr - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) -__all__ = [ - "AzureBlobStorageTarget", - "AzureMLChatTarget", - "CapabilityName", - "CapabilityHandlingPolicy", - "CHAT_TARGET_REQUIREMENTS", - "CopilotType", - "ConversationNormalizationPipeline", - "GandalfLevel", - "GandalfTarget", - "get_http_target_json_response_callback_function", - "get_http_target_regex_matching_callback_function", - "HTTPTarget", - "HTTPXAPITarget", - "HuggingFaceChatTarget", - "limit_requests_per_minute", - "LiteLLMChatTarget", - "OpenAICompletionTarget", - "OpenAIChatAudioConfig", - "OpenAIChatTarget", - "OpenAIImageTarget", - "OpenAIResponseTarget", - "OpenAIVideoTarget", - "OpenAITTSTarget", - "OpenAITarget", - "PlaywrightTarget", - "PlaywrightCopilotTarget", - "PromptShieldTarget", - "PromptTarget", - "RealtimeTarget", - "ServerVadConfig", - "RoundRobinTarget", - "TargetCapabilities", - "TargetConfiguration", - "TargetRequirements", - "UnsupportedCapabilityBehavior", - "TextTarget", - "WebsocketTarget", - "discover_target_capabilities_async", - "get_known_capabilities", - "WebSocketCopilotTarget", -] +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/registry/__init__.py b/pyrit/registry/__init__.py index 7706953463..9276f3839f 100644 --- a/pyrit/registry/__init__.py +++ b/pyrit/registry/__init__.py @@ -1,53 +1,74 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Registry module for PyRIT class and object registries.""" -from pyrit.registry.components import ( - AttackTechniqueMetadata, - AttackTechniqueRegistry, - ConverterMetadata, - ConverterRegistry, - InitializerMetadata, - InitializerRegistry, - ScenarioMetadata, - ScenarioRegistry, - ScorerMetadata, - ScorerRegistry, - TargetMetadata, - TargetRegistry, -) -from pyrit.registry.discovery import discover_in_directory -from pyrit.registry.instance_registry import ( - DefaultInstanceRegistry, - InstanceRegistry, - RegistryEntry, - SupportsInstances, -) -from pyrit.registry.registry import ParamBagRegistry, Registry -from pyrit.registry.registry_metadata import RegistryMetadata -from pyrit.registry.tag_query import TagQuery - -__all__ = [ - "AttackTechniqueRegistry", - "AttackTechniqueMetadata", - "ConverterRegistry", - "ConverterMetadata", - "DefaultInstanceRegistry", - "InstanceRegistry", - "ParamBagRegistry", - "Registry", - "RegistryMetadata", - "SupportsInstances", - "discover_in_directory", - "InitializerMetadata", - "InitializerRegistry", - "RegistryEntry", - "ScenarioMetadata", - "ScenarioRegistry", - "ScorerRegistry", - "ScorerMetadata", - "TargetRegistry", - "TargetMetadata", - "TagQuery", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.registry.components import ( + AttackTechniqueMetadata, + AttackTechniqueRegistry, + ConverterMetadata, + ConverterRegistry, + InitializerMetadata, + InitializerRegistry, + ScenarioMetadata, + ScenarioRegistry, + ScorerMetadata, + ScorerRegistry, + TargetMetadata, + TargetRegistry, + ) + from pyrit.registry.discovery import discover_in_directory + from pyrit.registry.instance_registry import ( + DefaultInstanceRegistry, + InstanceRegistry, + RegistryEntry, + SupportsInstances, + ) + from pyrit.registry.registry import ParamBagRegistry, Registry + from pyrit.registry.registry_metadata import RegistryMetadata + from pyrit.registry.tag_query import TagQuery + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AttackTechniqueRegistry": "pyrit.registry.components", + "AttackTechniqueMetadata": "pyrit.registry.components", + "ConverterRegistry": "pyrit.registry.components", + "ConverterMetadata": "pyrit.registry.components", + "DefaultInstanceRegistry": "pyrit.registry.instance_registry", + "InstanceRegistry": "pyrit.registry.instance_registry", + "ParamBagRegistry": "pyrit.registry.registry", + "Registry": "pyrit.registry.registry", + "RegistryMetadata": "pyrit.registry.registry_metadata", + "SupportsInstances": "pyrit.registry.instance_registry", + "discover_in_directory": "pyrit.registry.discovery", + "InitializerMetadata": "pyrit.registry.components", + "InitializerRegistry": "pyrit.registry.components", + "RegistryEntry": "pyrit.registry.instance_registry", + "ScenarioMetadata": "pyrit.registry.components", + "ScenarioRegistry": "pyrit.registry.components", + "ScorerRegistry": "pyrit.registry.components", + "ScorerMetadata": "pyrit.registry.components", + "TargetRegistry": "pyrit.registry.components", + "TargetMetadata": "pyrit.registry.components", + "TagQuery": "pyrit.registry.tag_query", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/registry/components/__init__.py b/pyrit/registry/components/__init__.py index b767860053..a3452d40e3 100644 --- a/pyrit/registry/components/__init__.py +++ b/pyrit/registry/components/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Component registries package. @@ -14,42 +15,44 @@ ``DefaultInstanceRegistry``) live at the top level of ``pyrit.registry``. """ -from pyrit.registry.components.attack_technique_registry import ( - AttackTechniqueMetadata, - AttackTechniqueRegistry, -) -from pyrit.registry.components.converter_registry import ( - ConverterMetadata, - ConverterRegistry, -) -from pyrit.registry.components.initializer_registry import ( - InitializerMetadata, - InitializerRegistry, -) -from pyrit.registry.components.scenario_registry import ( - ScenarioMetadata, - ScenarioRegistry, -) -from pyrit.registry.components.scorer_registry import ( - ScorerMetadata, - ScorerRegistry, -) -from pyrit.registry.components.target_registry import ( - TargetMetadata, - TargetRegistry, -) - -__all__ = [ - "AttackTechniqueRegistry", - "AttackTechniqueMetadata", - "ConverterRegistry", - "ConverterMetadata", - "InitializerRegistry", - "InitializerMetadata", - "ScorerRegistry", - "ScorerMetadata", - "ScenarioRegistry", - "ScenarioMetadata", - "TargetRegistry", - "TargetMetadata", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.registry.components.attack_technique_registry import AttackTechniqueMetadata, AttackTechniqueRegistry + from pyrit.registry.components.converter_registry import ConverterMetadata, ConverterRegistry + from pyrit.registry.components.initializer_registry import InitializerMetadata, InitializerRegistry + from pyrit.registry.components.scenario_registry import ScenarioMetadata, ScenarioRegistry + from pyrit.registry.components.scorer_registry import ScorerMetadata, ScorerRegistry + from pyrit.registry.components.target_registry import TargetMetadata, TargetRegistry + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AttackTechniqueRegistry": "pyrit.registry.components.attack_technique_registry", + "AttackTechniqueMetadata": "pyrit.registry.components.attack_technique_registry", + "ConverterRegistry": "pyrit.registry.components.converter_registry", + "ConverterMetadata": "pyrit.registry.components.converter_registry", + "InitializerRegistry": "pyrit.registry.components.initializer_registry", + "InitializerMetadata": "pyrit.registry.components.initializer_registry", + "ScorerRegistry": "pyrit.registry.components.scorer_registry", + "ScorerMetadata": "pyrit.registry.components.scorer_registry", + "ScenarioRegistry": "pyrit.registry.components.scenario_registry", + "ScenarioMetadata": "pyrit.registry.components.scenario_registry", + "TargetRegistry": "pyrit.registry.components.target_registry", + "TargetMetadata": "pyrit.registry.components.target_registry", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/registry/components/scenario_registry.py b/pyrit/registry/components/scenario_registry.py index 1551148085..caf6bba721 100644 --- a/pyrit/registry/components/scenario_registry.py +++ b/pyrit/registry/components/scenario_registry.py @@ -67,6 +67,13 @@ class ScenarioRegistry(ParamBagRegistry["Scenario", ScenarioMetadata]): _DISCOVERY_PACKAGE = "pyrit.scenario.scenarios" + def _discover(self) -> None: + """Materialize every built-in scenario before subclass discovery.""" + from pyrit.scenario.scenarios import _materialize_scenarios + + _materialize_scenarios() + super()._discover() + def _identifier_type(self) -> type[ComponentIdentifier] | None: """Return ``ScenarioIdentifier`` so ``Param.*`` markers drive derivation.""" return ScenarioIdentifier diff --git a/pyrit/scenario/__init__.py b/pyrit/scenario/__init__.py index a405fa93a6..b173b4be76 100644 --- a/pyrit/scenario/__init__.py +++ b/pyrit/scenario/__init__.py @@ -1,100 +1,118 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 -""" -High-level scenario classes for running attack configurations. +"""High-level scenario classes for running attack configurations.""" -Core classes can be imported directly from this module: - from pyrit.scenario import Scenario, AtomicAttack, ScenarioTechnique - -Specific scenarios should be imported from their subpackages: - from pyrit.scenario.airt import RapidResponse, Cyber - from pyrit.scenario.garak import Encoding - from pyrit.scenario.foundry import RedTeamAgent -""" - -import importlib -import pkgutil +import importlib.abc +import importlib.util import sys from types import ModuleType +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.models import ScenarioIdentifier, ScenarioResult + from pyrit.models.parameter import Parameter + from pyrit.scenario.core import ( + AtomicAttack, + AttackTechnique, + AttackTechniqueFactory, + BaselineAttackPolicy, + CompoundDatasetAttackConfiguration, + DatasetAttackConfiguration, + DatasetConfiguration, + DatasetSourceKind, + ResolvedDataset, + Scenario, + ScenarioTechnique, + ) + from pyrit.scenario.scenarios import adaptive, airt, benchmark, foundry, garak + +_SCENARIO_ALIASES = { + "pyrit.scenario.adaptive": "pyrit.scenario.scenarios.adaptive", + "pyrit.scenario.airt": "pyrit.scenario.scenarios.airt", + "pyrit.scenario.benchmark": "pyrit.scenario.scenarios.benchmark", + "pyrit.scenario.foundry": "pyrit.scenario.scenarios.foundry", + "pyrit.scenario.garak": "pyrit.scenario.scenarios.garak", +} + + +class _ScenarioAliasLoader(importlib.abc.Loader): + """Load a short scenario path through its canonical module.""" + + def __init__(self, *, canonical_name: str) -> None: + self._canonical_name = canonical_name + + def exec_module(self, module: ModuleType) -> None: + canonical_module = importlib.import_module(self._canonical_name) + sys.modules[module.__name__] = canonical_module + + +class _ScenarioAliasFinder(importlib.abc.MetaPathFinder): + """Resolve short scenario package paths without importing scenario catalogs.""" + + _pyrit_scenario_alias_finder = True + + def find_spec( + self, + fullname: str, + path: object = None, + target: ModuleType | None = None, + ) -> importlib.machinery.ModuleSpec | None: + for alias_name, canonical_name in _SCENARIO_ALIASES.items(): + if fullname != alias_name and not fullname.startswith(f"{alias_name}."): + continue + + canonical_fullname = canonical_name + fullname[len(alias_name) :] + canonical_spec = importlib.util.find_spec(canonical_fullname) + if canonical_spec is None: + return None + return importlib.util.spec_from_loader( + fullname, + _ScenarioAliasLoader(canonical_name=canonical_fullname), + is_package=canonical_spec.submodule_search_locations is not None, + ) + return None + + +if not any(getattr(finder, "_pyrit_scenario_alias_finder", False) for finder in sys.meta_path): + sys.meta_path.insert(0, _ScenarioAliasFinder()) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AtomicAttack": "pyrit.scenario.core.atomic_attack", + "AttackTechnique": "pyrit.scenario.core.attack_technique", + "AttackTechniqueFactory": "pyrit.scenario.core.attack_technique_factory", + "BaselineAttackPolicy": "pyrit.scenario.core.scenario", + "CompoundDatasetAttackConfiguration": "pyrit.scenario.core.dataset_configuration", + "DatasetAttackConfiguration": "pyrit.scenario.core.dataset_configuration", + "DatasetConfiguration": "pyrit.scenario.core.dataset_configuration", + "DatasetSourceKind": "pyrit.scenario.core.dataset_configuration", + "Parameter": "pyrit.models.parameter", + "ResolvedDataset": "pyrit.scenario.core.dataset_configuration", + "Scenario": "pyrit.scenario.core.scenario", + "ScenarioTechnique": "pyrit.scenario.core.scenario_technique", + "ScenarioIdentifier": "pyrit.models.identifiers.scenario_identifier", + "ScenarioResult": "pyrit.models.results.scenario_result", + "adaptive": ("pyrit.scenario.scenarios.adaptive", None), + "airt": ("pyrit.scenario.scenarios.airt", None), + "benchmark": ("pyrit.scenario.scenarios.benchmark", None), + "garak": ("pyrit.scenario.scenarios.garak", None), + "foundry": ("pyrit.scenario.scenarios.foundry", None), +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + -from pyrit.models import ScenarioIdentifier, ScenarioResult -from pyrit.models.parameter import Parameter -from pyrit.scenario.core import ( - AtomicAttack, - AttackTechnique, - AttackTechniqueFactory, - BaselineAttackPolicy, - CompoundDatasetAttackConfiguration, - DatasetAttackConfiguration, - DatasetConfiguration, - DatasetSourceKind, - ResolvedDataset, - Scenario, - ScenarioTechnique, -) - -# Import scenario submodules directly and register them as virtual subpackages -# This allows: from pyrit.scenario.airt import Jailbreak -# without needing separate pyrit/scenario/airt/ directories -from pyrit.scenario.scenarios import adaptive as _adaptive_module -from pyrit.scenario.scenarios import airt as _airt_module -from pyrit.scenario.scenarios import benchmark as _benchmark_module -from pyrit.scenario.scenarios import foundry as _foundry_module -from pyrit.scenario.scenarios import garak as _garak_module - - -def _register_scenario_alias(short_name: str, canonical_module: ModuleType) -> None: - """ - Alias ``pyrit.scenario.`` (and every submodule) to ``canonical_module``. - - A bare ``sys.modules[short] = canonical`` only fixes ``import - pyrit.scenario.`` itself. Accessing a submodule via the alias path - (``pyrit.scenario..``) re-runs the submodule's file under the - aliased fully-qualified name and produces a duplicate class object — which - silently breaks ``isinstance`` against the canonical class. To prevent that, - we walk the canonical package's submodules eagerly and register every one - under both names so the second import returns the same module object. - """ - sys.modules[f"pyrit.scenario.{short_name}"] = canonical_module - canonical_prefix = canonical_module.__name__ + "." - short_prefix = f"pyrit.scenario.{short_name}." - for module_info in pkgutil.walk_packages(canonical_module.__path__, canonical_prefix): - submodule = importlib.import_module(module_info.name) - sys.modules[short_prefix + module_info.name[len(canonical_prefix) :]] = submodule - - -_register_scenario_alias("adaptive", _adaptive_module) -_register_scenario_alias("airt", _airt_module) -_register_scenario_alias("benchmark", _benchmark_module) -_register_scenario_alias("foundry", _foundry_module) -_register_scenario_alias("garak", _garak_module) - -# Also expose as attributes for IDE support -adaptive = _adaptive_module -airt = _airt_module -benchmark = _benchmark_module -garak = _garak_module -foundry = _foundry_module - -__all__ = [ - "AtomicAttack", - "AttackTechnique", - "AttackTechniqueFactory", - "BaselineAttackPolicy", - "CompoundDatasetAttackConfiguration", - "DatasetAttackConfiguration", - "DatasetConfiguration", - "DatasetSourceKind", - "Parameter", - "ResolvedDataset", - "Scenario", - "ScenarioTechnique", - "ScenarioIdentifier", - "ScenarioResult", - "adaptive", - "airt", - "benchmark", - "garak", - "foundry", -] +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/core/__init__.py b/pyrit/scenario/core/__init__.py index 660b229447..fa57884ad7 100644 --- a/pyrit/scenario/core/__init__.py +++ b/pyrit/scenario/core/__init__.py @@ -1,43 +1,64 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Core scenario classes for running attack configurations.""" -from pyrit.models.parameter import Parameter -from pyrit.scenario.core.atomic_attack import AtomicAttack -from pyrit.scenario.core.attack_technique import AttackTechnique -from pyrit.scenario.core.attack_technique_factory import AttackTechniqueFactory, ScorerOverridePolicy -from pyrit.scenario.core.dataset_configuration import ( - INLINE_DATASET_NAME, - CompoundDatasetAttackConfiguration, - DatasetAttackConfiguration, - DatasetConfiguration, - DatasetConstraintError, - DatasetSourceKind, - ResolvedDataset, - require_nonempty, -) -from pyrit.scenario.core.scenario import BaselineAttackPolicy, Scenario -from pyrit.scenario.core.scenario_target_defaults import get_default_adversarial_target, get_default_scorer_target -from pyrit.scenario.core.scenario_technique import ScenarioTechnique - -__all__ = [ - "AtomicAttack", - "AttackTechnique", - "AttackTechniqueFactory", - "BaselineAttackPolicy", - "CompoundDatasetAttackConfiguration", - "DatasetAttackConfiguration", - "DatasetConfiguration", - "DatasetConstraintError", - "DatasetSourceKind", - "INLINE_DATASET_NAME", - "Parameter", - "ResolvedDataset", - "require_nonempty", - "Scenario", - "ScenarioTechnique", - "ScorerOverridePolicy", - "get_default_scorer_target", - "get_default_adversarial_target", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.models.parameter import Parameter + from pyrit.scenario.core.atomic_attack import AtomicAttack + from pyrit.scenario.core.attack_technique import AttackTechnique + from pyrit.scenario.core.attack_technique_factory import AttackTechniqueFactory, ScorerOverridePolicy + from pyrit.scenario.core.dataset_configuration import ( + INLINE_DATASET_NAME, + CompoundDatasetAttackConfiguration, + DatasetAttackConfiguration, + DatasetConfiguration, + DatasetConstraintError, + DatasetSourceKind, + ResolvedDataset, + require_nonempty, + ) + from pyrit.scenario.core.scenario import BaselineAttackPolicy, Scenario + from pyrit.scenario.core.scenario_target_defaults import get_default_adversarial_target, get_default_scorer_target + from pyrit.scenario.core.scenario_technique import ScenarioTechnique + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AtomicAttack": "pyrit.scenario.core.atomic_attack", + "AttackTechnique": "pyrit.scenario.core.attack_technique", + "AttackTechniqueFactory": "pyrit.scenario.core.attack_technique_factory", + "BaselineAttackPolicy": "pyrit.scenario.core.scenario", + "CompoundDatasetAttackConfiguration": "pyrit.scenario.core.dataset_configuration", + "DatasetAttackConfiguration": "pyrit.scenario.core.dataset_configuration", + "DatasetConfiguration": "pyrit.scenario.core.dataset_configuration", + "DatasetConstraintError": "pyrit.scenario.core.dataset_configuration", + "DatasetSourceKind": "pyrit.scenario.core.dataset_configuration", + "INLINE_DATASET_NAME": "pyrit.scenario.core.dataset_configuration", + "Parameter": "pyrit.models.parameter", + "ResolvedDataset": "pyrit.scenario.core.dataset_configuration", + "require_nonempty": "pyrit.scenario.core.dataset_configuration", + "Scenario": "pyrit.scenario.core.scenario", + "ScenarioTechnique": "pyrit.scenario.core.scenario_technique", + "ScorerOverridePolicy": "pyrit.scenario.core.attack_technique_factory", + "get_default_scorer_target": "pyrit.scenario.core.scenario_target_defaults", + "get_default_adversarial_target": "pyrit.scenario.core.scenario_target_defaults", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/scenarios/__init__.py b/pyrit/scenario/scenarios/__init__.py index c81005c888..da2f40897f 100644 --- a/pyrit/scenario/scenarios/__init__.py +++ b/pyrit/scenario/scenarios/__init__.py @@ -2,3 +2,13 @@ # Licensed under the MIT license. """Scenario implementations package.""" + +import importlib +import pkgutil + + +def _materialize_scenarios() -> None: + """Import every built-in scenario module for complete registry discovery.""" + prefix = f"{__name__}." + for module_info in pkgutil.walk_packages(__path__, prefix): + importlib.import_module(module_info.name) diff --git a/pyrit/scenario/scenarios/_dynamic_techniques.py b/pyrit/scenario/scenarios/_dynamic_techniques.py new file mode 100644 index 0000000000..544a0a754c --- /dev/null +++ b/pyrit/scenario/scenarios/_dynamic_techniques.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +"""Lazy exports for scenario technique classes built from registered catalogs.""" + +from importlib import import_module +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pyrit.scenario.core import ScenarioTechnique + + AdversarialBenchmarkTechnique: type[ScenarioTechnique] + CyberTechnique: type[ScenarioTechnique] + DoctorTechnique: type[ScenarioTechnique] + JailbreakTechnique: type[ScenarioTechnique] + LeakageTechnique: type[ScenarioTechnique] + MultilingualTechnique: type[ScenarioTechnique] + RapidResponseTechnique: type[ScenarioTechnique] + +_TECHNIQUE_BUILDERS = { + "AdversarialBenchmarkTechnique": ( + "pyrit.scenario.scenarios.benchmark.adversarial", + "_build_benchmark_technique", + ), + "CyberTechnique": ("pyrit.scenario.scenarios.airt.cyber", "_build_cyber_technique"), + "DoctorTechnique": ("pyrit.scenario.scenarios.garak.doctor", "_build_doctor_technique"), + "JailbreakTechnique": ("pyrit.scenario.scenarios.airt.jailbreak", "_build_jailbreak_technique"), + "LeakageTechnique": ("pyrit.scenario.scenarios.airt.leakage", "_build_leakage_technique"), + "MultilingualTechnique": ( + "pyrit.scenario.scenarios.airt.multilingual", + "_build_multilingual_technique", + ), + "RapidResponseTechnique": ( + "pyrit.scenario.scenarios.airt.rapid_response", + "_build_rapid_response_technique", + ), +} + + +def __getattr__(name: str) -> object: + try: + module_name, builder_name = _TECHNIQUE_BUILDERS[name] + except KeyError: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None + + builder = getattr(import_module(module_name), builder_name) + value = builder() + globals()[name] = value + return value diff --git a/pyrit/scenario/scenarios/adaptive/__init__.py b/pyrit/scenario/scenarios/adaptive/__init__.py index 4be199024c..b71c7fb445 100644 --- a/pyrit/scenario/scenarios/adaptive/__init__.py +++ b/pyrit/scenario/scenarios/adaptive/__init__.py @@ -1,24 +1,49 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Adaptive scenario classes.""" -from pyrit.scenario.scenarios.adaptive.adaptive_scenario import AdaptiveScenario -from pyrit.scenario.scenarios.adaptive.dispatcher import ( - ADAPTIVE_ATTEMPT_LABEL, - AdaptiveTechniqueDispatcher, - TechniqueBundle, -) -from pyrit.scenario.scenarios.adaptive.selectors import EpsilonGreedyTechniqueSelector, SelectorScope, TechniqueSelector -from pyrit.scenario.scenarios.adaptive.text_adaptive import TextAdaptive - -__all__ = [ - "ADAPTIVE_ATTEMPT_LABEL", - "AdaptiveScenario", - "AdaptiveTechniqueDispatcher", - "EpsilonGreedyTechniqueSelector", - "SelectorScope", - "TechniqueBundle", - "TechniqueSelector", - "TextAdaptive", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.scenario.scenarios.adaptive.adaptive_scenario import AdaptiveScenario + from pyrit.scenario.scenarios.adaptive.dispatcher import ( + ADAPTIVE_ATTEMPT_LABEL, + AdaptiveTechniqueDispatcher, + TechniqueBundle, + ) + from pyrit.scenario.scenarios.adaptive.selectors import ( + EpsilonGreedyTechniqueSelector, + SelectorScope, + TechniqueSelector, + ) + from pyrit.scenario.scenarios.adaptive.text_adaptive import TextAdaptive + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "ADAPTIVE_ATTEMPT_LABEL": "pyrit.scenario.scenarios.adaptive.dispatcher", + "AdaptiveScenario": "pyrit.scenario.scenarios.adaptive.adaptive_scenario", + "AdaptiveTechniqueDispatcher": "pyrit.scenario.scenarios.adaptive.dispatcher", + "EpsilonGreedyTechniqueSelector": "pyrit.scenario.scenarios.adaptive.selectors", + "SelectorScope": "pyrit.scenario.scenarios.adaptive.selectors", + "TechniqueBundle": "pyrit.scenario.scenarios.adaptive.dispatcher", + "TechniqueSelector": "pyrit.scenario.scenarios.adaptive.selectors", + "TextAdaptive": "pyrit.scenario.scenarios.adaptive.text_adaptive", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/scenarios/adaptive/selectors/__init__.py b/pyrit/scenario/scenarios/adaptive/selectors/__init__.py index 50aa4e828a..acd7ab47cd 100644 --- a/pyrit/scenario/scenarios/adaptive/selectors/__init__.py +++ b/pyrit/scenario/scenarios/adaptive/selectors/__init__.py @@ -1,13 +1,34 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Selector protocol and selector implementations.""" -from pyrit.scenario.scenarios.adaptive.selectors.epsilon_greedy import EpsilonGreedyTechniqueSelector -from pyrit.scenario.scenarios.adaptive.selectors.technique_selector import SelectorScope, TechniqueSelector +from typing import TYPE_CHECKING -__all__ = [ - "EpsilonGreedyTechniqueSelector", - "SelectorScope", - "TechniqueSelector", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.scenario.scenarios.adaptive.selectors.epsilon_greedy import EpsilonGreedyTechniqueSelector + from pyrit.scenario.scenarios.adaptive.selectors.technique_selector import SelectorScope, TechniqueSelector + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "EpsilonGreedyTechniqueSelector": "pyrit.scenario.scenarios.adaptive.selectors.epsilon_greedy", + "SelectorScope": "pyrit.scenario.scenarios.adaptive.selectors.technique_selector", + "TechniqueSelector": "pyrit.scenario.scenarios.adaptive.selectors.technique_selector", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/scenarios/airt/__init__.py b/pyrit/scenario/scenarios/airt/__init__.py index df6770c4d2..834b37c89c 100644 --- a/pyrit/scenario/scenarios/airt/__init__.py +++ b/pyrit/scenario/scenarios/airt/__init__.py @@ -1,55 +1,57 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """AIRT scenario classes.""" -from typing import Any - -from pyrit.scenario.scenarios.airt.cyber import Cyber, _build_cyber_technique -from pyrit.scenario.scenarios.airt.jailbreak import Jailbreak, _build_jailbreak_technique -from pyrit.scenario.scenarios.airt.leakage import Leakage, _build_leakage_technique -from pyrit.scenario.scenarios.airt.multilingual import Multilingual, _build_multilingual_technique -from pyrit.scenario.scenarios.airt.psychosocial import Psychosocial, PsychosocialTechnique -from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse, _build_rapid_response_technique -from pyrit.scenario.scenarios.airt.scam import Scam, ScamTechnique - - -def __getattr__(name: str) -> Any: - """ - Lazily resolve dynamic technique classes. - - Returns: - Any: The resolved technique class. - - Raises: - AttributeError: If the attribute name is not recognized. - """ - if name == "RapidResponseTechnique": - return _build_rapid_response_technique() - if name == "LeakageTechnique": - return _build_leakage_technique() - if name == "CyberTechnique": - return _build_cyber_technique() - if name == "JailbreakTechnique": - return _build_jailbreak_technique() - if name == "MultilingualTechnique": - return _build_multilingual_technique() - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - - -__all__ = [ - "Cyber", - "CyberTechnique", - "Jailbreak", - "JailbreakTechnique", - "Leakage", - "LeakageTechnique", - "Multilingual", - "MultilingualTechnique", - "Psychosocial", - "PsychosocialTechnique", - "RapidResponse", - "RapidResponseTechnique", - "Scam", - "ScamTechnique", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.scenario.scenarios._dynamic_techniques import ( + CyberTechnique, + JailbreakTechnique, + LeakageTechnique, + MultilingualTechnique, + RapidResponseTechnique, + ) + from pyrit.scenario.scenarios.airt.cyber import Cyber + from pyrit.scenario.scenarios.airt.jailbreak import Jailbreak + from pyrit.scenario.scenarios.airt.leakage import Leakage + from pyrit.scenario.scenarios.airt.multilingual import Multilingual + from pyrit.scenario.scenarios.airt.psychosocial import Psychosocial, PsychosocialTechnique + from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse + from pyrit.scenario.scenarios.airt.scam import Scam, ScamTechnique + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "Cyber": "pyrit.scenario.scenarios.airt.cyber", + "CyberTechnique": "pyrit.scenario.scenarios._dynamic_techniques", + "Jailbreak": "pyrit.scenario.scenarios.airt.jailbreak", + "JailbreakTechnique": "pyrit.scenario.scenarios._dynamic_techniques", + "Leakage": "pyrit.scenario.scenarios.airt.leakage", + "LeakageTechnique": "pyrit.scenario.scenarios._dynamic_techniques", + "Multilingual": "pyrit.scenario.scenarios.airt.multilingual", + "MultilingualTechnique": "pyrit.scenario.scenarios._dynamic_techniques", + "Psychosocial": "pyrit.scenario.scenarios.airt.psychosocial", + "PsychosocialTechnique": "pyrit.scenario.scenarios.airt.psychosocial", + "RapidResponse": "pyrit.scenario.scenarios.airt.rapid_response", + "RapidResponseTechnique": "pyrit.scenario.scenarios._dynamic_techniques", + "Scam": "pyrit.scenario.scenarios.airt.scam", + "ScamTechnique": "pyrit.scenario.scenarios.airt.scam", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/scenarios/benchmark/__init__.py b/pyrit/scenario/scenarios/benchmark/__init__.py index d5f2f6013e..5da254fc90 100644 --- a/pyrit/scenario/scenarios/benchmark/__init__.py +++ b/pyrit/scenario/scenarios/benchmark/__init__.py @@ -1,26 +1,33 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Benchmark scenario classes.""" -from typing import Any +from typing import TYPE_CHECKING -from pyrit.scenario.scenarios.benchmark.adversarial import AdversarialBenchmark, _build_benchmark_technique +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export +if TYPE_CHECKING: + from pyrit.scenario.scenarios._dynamic_techniques import AdversarialBenchmarkTechnique + from pyrit.scenario.scenarios.benchmark.adversarial import AdversarialBenchmark -def __getattr__(name: str) -> Any: - """ - Lazily resolve the dynamic BenchmarkTechnique class. +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AdversarialBenchmark": "pyrit.scenario.scenarios.benchmark.adversarial", + "AdversarialBenchmarkTechnique": "pyrit.scenario.scenarios._dynamic_techniques", +} - Returns: - Any: The resolved technique class. +__all__ = list(_LAZY_EXPORTS) - Raises: - AttributeError: If the attribute name is not recognized. - """ - if name == "AdversarialBenchmarkTechnique": - return _build_benchmark_technique() - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) -__all__ = ["AdversarialBenchmark", "AdversarialBenchmarkTechnique"] + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/scenarios/foundry/__init__.py b/pyrit/scenario/scenarios/foundry/__init__.py index 6a54357bcf..22f82a4d3f 100644 --- a/pyrit/scenario/scenarios/foundry/__init__.py +++ b/pyrit/scenario/scenarios/foundry/__init__.py @@ -1,12 +1,33 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Foundry scenario classes.""" -from pyrit.scenario.scenarios.foundry.red_team_agent import FoundryComposite, FoundryTechnique, RedTeamAgent +from typing import TYPE_CHECKING -__all__ = [ - "FoundryComposite", - "FoundryTechnique", - "RedTeamAgent", -] +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.scenario.scenarios.foundry.red_team_agent import FoundryComposite, FoundryTechnique, RedTeamAgent + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "FoundryComposite": "pyrit.scenario.scenarios.foundry.red_team_agent", + "FoundryTechnique": "pyrit.scenario.scenarios.foundry.red_team_agent", + "RedTeamAgent": "pyrit.scenario.scenarios.foundry.red_team_agent", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/scenario/scenarios/garak/__init__.py b/pyrit/scenario/scenarios/garak/__init__.py index bbaf31055b..29efef7490 100644 --- a/pyrit/scenario/scenarios/garak/__init__.py +++ b/pyrit/scenario/scenarios/garak/__init__.py @@ -1,53 +1,57 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Garak-based attack scenarios.""" -from typing import Any - -from pyrit.scenario.scenarios.garak.audio_achilles_heel import AudioAchillesHeel, AudioAchillesHeelTechnique -from pyrit.scenario.scenarios.garak.doctor import Doctor, _build_doctor_technique -from pyrit.scenario.scenarios.garak.encoding import Encoding, EncodingTechnique -from pyrit.scenario.scenarios.garak.figstep import FigStep, FigStepTechnique -from pyrit.scenario.scenarios.garak.package_hallucination import ( - PackageHallucination, - PackageHallucinationTechnique, -) -from pyrit.scenario.scenarios.garak.system_prompt_extraction import ( - SystemPromptExtraction, - SystemPromptExtractionTechnique, -) -from pyrit.scenario.scenarios.garak.web_injection import WebInjection, WebInjectionTechnique - - -def __getattr__(name: str) -> Any: - """ - Lazily resolve the dynamically-generated Doctor technique class. - - Returns: - Any: The resolved technique class. - - Raises: - AttributeError: If the attribute name is not recognized. - """ - if name == "DoctorTechnique": - return _build_doctor_technique() - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - - -__all__ = [ - "AudioAchillesHeel", - "AudioAchillesHeelTechnique", - "Doctor", - "DoctorTechnique", - "Encoding", - "EncodingTechnique", - "FigStep", - "FigStepTechnique", - "PackageHallucination", - "PackageHallucinationTechnique", - "SystemPromptExtraction", - "SystemPromptExtractionTechnique", - "WebInjection", - "WebInjectionTechnique", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.scenario.scenarios._dynamic_techniques import DoctorTechnique + from pyrit.scenario.scenarios.garak.audio_achilles_heel import AudioAchillesHeel, AudioAchillesHeelTechnique + from pyrit.scenario.scenarios.garak.doctor import Doctor + from pyrit.scenario.scenarios.garak.encoding import Encoding, EncodingTechnique + from pyrit.scenario.scenarios.garak.figstep import FigStep, FigStepTechnique + from pyrit.scenario.scenarios.garak.package_hallucination import ( + PackageHallucination, + PackageHallucinationTechnique, + ) + from pyrit.scenario.scenarios.garak.system_prompt_extraction import ( + SystemPromptExtraction, + SystemPromptExtractionTechnique, + ) + from pyrit.scenario.scenarios.garak.web_injection import WebInjection, WebInjectionTechnique + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AudioAchillesHeel": "pyrit.scenario.scenarios.garak.audio_achilles_heel", + "AudioAchillesHeelTechnique": "pyrit.scenario.scenarios.garak.audio_achilles_heel", + "Doctor": "pyrit.scenario.scenarios.garak.doctor", + "DoctorTechnique": "pyrit.scenario.scenarios._dynamic_techniques", + "Encoding": "pyrit.scenario.scenarios.garak.encoding", + "EncodingTechnique": "pyrit.scenario.scenarios.garak.encoding", + "FigStep": "pyrit.scenario.scenarios.garak.figstep", + "FigStepTechnique": "pyrit.scenario.scenarios.garak.figstep", + "PackageHallucination": "pyrit.scenario.scenarios.garak.package_hallucination", + "PackageHallucinationTechnique": "pyrit.scenario.scenarios.garak.package_hallucination", + "SystemPromptExtraction": "pyrit.scenario.scenarios.garak.system_prompt_extraction", + "SystemPromptExtractionTechnique": "pyrit.scenario.scenarios.garak.system_prompt_extraction", + "WebInjection": "pyrit.scenario.scenarios.garak.web_injection", + "WebInjectionTechnique": "pyrit.scenario.scenarios.garak.web_injection", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/score/__init__.py b/pyrit/score/__init__.py index 26684946a6..768382dceb 100644 --- a/pyrit/score/__init__.py +++ b/pyrit/score/__init__.py @@ -1,292 +1,264 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Scoring functionality for evaluating AI model responses across various dimensions including harm detection, objective completion, and content classification. """ -import importlib from typing import TYPE_CHECKING -from pyrit.output.scorer.base import ScorerPrinterBase as ScorerPrinter -from pyrit.score.batch_scorer import BatchScorer -from pyrit.score.conversation_scorer import ConversationScorer, create_conversation_scorer -from pyrit.score.float_scale.azure_content_filter_scorer import AzureContentFilterScorer -from pyrit.score.float_scale.float_scale_score_aggregator import ( - FloatScaleScoreAggregator, - FloatScaleScorerAllCategories, - FloatScaleScorerByCategory, -) -from pyrit.score.float_scale.float_scale_scorer import FloatScaleScorer -from pyrit.score.float_scale.insecure_code_scorer import ( - InsecureCodeScorer, - render_insecure_code_system_prompt, -) -from pyrit.score.float_scale.likert_scale import LikertScale, LikertScaleEntry -from pyrit.score.float_scale.numeric_scale import NumericRange, NumericRubric -from pyrit.score.float_scale.plagiarism_scorer import PlagiarismMetric, PlagiarismScorer -from pyrit.score.float_scale.self_ask_general_float_scale_scorer import SelfAskGeneralFloatScaleScorer -from pyrit.score.float_scale.self_ask_likert_scorer import ( - LikertScaleEvalFiles, - LikertScalePaths, - SelfAskLikertScorer, - render_likert_system_prompt, -) -from pyrit.score.float_scale.self_ask_scale_scorer import ( - SelfAskScaleScorer, - render_scale_system_prompt, -) -from pyrit.score.float_scale.system_prompt_extraction_scorer import SystemPromptExtractionScorer -from pyrit.score.message_scorable_resolver import MessageScorableResolver -from pyrit.score.message_scorer import MessageScorer, MessageScoringOptions -from pyrit.score.response_handler import ( - CallableResponseHandler, - JsonSchemaResponseHandler, - ResponseHandler, -) -from pyrit.score.scorable import ContentScorable, MessageScorable, Scorable -from pyrit.score.scorer import Scorer -from pyrit.score.scorer_evaluation.metrics_type import MetricsType, RegistryUpdateBehavior -from pyrit.score.scorer_evaluation.scorer_metrics import ( - HarmScorerMetrics, - ObjectiveScorerMetrics, - ScorerMetrics, - ScorerMetricsWithIdentity, -) -from pyrit.score.scorer_evaluation.scorer_metrics_io import ( - find_objective_metrics_by_eval_hash, - get_all_harm_metrics, - get_all_objective_metrics, -) -from pyrit.score.scorer_info import get_scorer_info -from pyrit.score.scorer_prompt_validator import ScorerPromptValidator -from pyrit.score.true_false.decoding_scorer import DecodingScorer -from pyrit.score.true_false.float_scale_threshold_scorer import FloatScaleThresholdScorer -from pyrit.score.true_false.gandalf_scorer import GandalfScorer -from pyrit.score.true_false.llamaguard_parser import LLAMAGUARD_3_CATEGORY_CODES, parse_llamaguard_response -from pyrit.score.true_false.llamaguard_policy import LlamaGuardCategory, LlamaGuardPolicy -from pyrit.score.true_false.llamaguard_scorer import ( - LlamaGuardMessageRole, - LlamaGuardScorer, - render_llamaguard_prompt, -) -from pyrit.score.true_false.prompt_shield_scorer import PromptShieldScorer -from pyrit.score.true_false.question_answer_scorer import QuestionAnswerScorer -from pyrit.score.true_false.regex.anthrax_keyword_scorer import AnthraxKeywordScorer -from pyrit.score.true_false.regex.credential_leak_scorer import CredentialLeakScorer -from pyrit.score.true_false.regex.fentanyl_keyword_scorer import FentanylKeywordScorer -from pyrit.score.true_false.regex.ldap_injection_output_scorer import LDAPInjectionOutputScorer -from pyrit.score.true_false.regex.markdown_injection import MarkdownInjectionScorer -from pyrit.score.true_false.regex.meth_keyword_scorer import MethKeywordScorer -from pyrit.score.true_false.regex.nerve_agent_keyword_scorer import NerveAgentKeywordScorer -from pyrit.score.true_false.regex.open_redirect_output_scorer import OpenRedirectOutputScorer -from pyrit.score.true_false.regex.package_hallucination_scorer import ( - PackageEcosystem, - PackageHallucinationScorer, -) -from pyrit.score.true_false.regex.path_traversal_output_scorer import PathTraversalOutputScorer -from pyrit.score.true_false.regex.regex_scorer import RegexScorer -from pyrit.score.true_false.regex.shell_command_output_scorer import ShellCommandOutputScorer -from pyrit.score.true_false.regex.sql_injection_output_scorer import SQLInjectionOutputScorer -from pyrit.score.true_false.regex.ssrf_output_scorer import SSRFOutputScorer -from pyrit.score.true_false.regex.ssti_output_scorer import SSTIOutputScorer -from pyrit.score.true_false.regex.static_prompt_injection_scorer import StaticPromptInjectionScorer -from pyrit.score.true_false.regex.xss_output_scorer import XSSOutputScorer -from pyrit.score.true_false.regex.xxe_output_scorer import XXEOutputScorer -from pyrit.score.true_false.self_ask_category_scorer import ( - ContentClassifier, - ContentClassifierCategory, - ContentClassifierPaths, - SelfAskCategoryScorer, - render_category_system_prompt, -) -from pyrit.score.true_false.self_ask_general_true_false_scorer import SelfAskGeneralTrueFalseScorer -from pyrit.score.true_false.self_ask_question_answer_scorer import SelfAskQuestionAnswerScorer -from pyrit.score.true_false.self_ask_refusal_scorer import RefusalScorerPaths, SelfAskRefusalScorer -from pyrit.score.true_false.self_ask_true_false_scorer import ( - SelfAskTrueFalseScorer, - TrueFalseQuestion, - TrueFalseQuestionPaths, - render_true_false_system_prompt, -) -from pyrit.score.true_false.shieldgemma_parser import parse_shieldgemma_response -from pyrit.score.true_false.shieldgemma_policy import ( - SHIELDGEMMA_DEFAULT_POLICY_PATH, - ShieldGemmaGuideline, - ShieldGemmaMessageRole, - ShieldGemmaPolicy, -) -from pyrit.score.true_false.shieldgemma_scorer import ( - ShieldGemmaScorer, - render_shieldgemma_prompt, -) -from pyrit.score.true_false.substring_scorer import SubStringScorer -from pyrit.score.true_false.true_false_composite_scorer import TrueFalseCompositeScorer -from pyrit.score.true_false.true_false_inverter_scorer import TrueFalseInverterScorer -from pyrit.score.true_false.true_false_score_aggregator import TrueFalseAggregatorFunc, TrueFalseScoreAggregator -from pyrit.score.true_false.true_false_scorer import TrueFalseScorer +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export if TYPE_CHECKING: + from pyrit.output.scorer.base import ScorerPrinterBase as ScorerPrinter + from pyrit.score.batch_scorer import BatchScorer + from pyrit.score.conversation_scorer import ConversationScorer, create_conversation_scorer from pyrit.score.float_scale.audio_float_scale_scorer import AudioFloatScaleScorer + from pyrit.score.float_scale.azure_content_filter_scorer import AzureContentFilterScorer + from pyrit.score.float_scale.float_scale_score_aggregator import ( + FloatScaleScoreAggregator, + FloatScaleScorerAllCategories, + FloatScaleScorerByCategory, + ) + from pyrit.score.float_scale.float_scale_scorer import FloatScaleScorer + from pyrit.score.float_scale.insecure_code_scorer import InsecureCodeScorer, render_insecure_code_system_prompt + from pyrit.score.float_scale.likert_scale import LikertScale, LikertScaleEntry + from pyrit.score.float_scale.numeric_scale import NumericRange, NumericRubric + from pyrit.score.float_scale.plagiarism_scorer import PlagiarismMetric, PlagiarismScorer + from pyrit.score.float_scale.self_ask_general_float_scale_scorer import SelfAskGeneralFloatScaleScorer + from pyrit.score.float_scale.self_ask_likert_scorer import ( + LikertScaleEvalFiles, + LikertScalePaths, + SelfAskLikertScorer, + render_likert_system_prompt, + ) + from pyrit.score.float_scale.self_ask_scale_scorer import SelfAskScaleScorer, render_scale_system_prompt + from pyrit.score.float_scale.system_prompt_extraction_scorer import SystemPromptExtractionScorer from pyrit.score.float_scale.video_float_scale_scorer import VideoFloatScaleScorer + from pyrit.score.message_scorable_resolver import MessageScorableResolver + from pyrit.score.message_scorer import MessageScorer, MessageScoringOptions + from pyrit.score.response_handler import CallableResponseHandler, JsonSchemaResponseHandler, ResponseHandler + from pyrit.score.scorable import ContentScorable, MessageScorable, Scorable + from pyrit.score.scorer import Scorer from pyrit.score.scorer_evaluation.human_labeled_dataset import ( HarmHumanLabeledEntry, HumanLabeledDataset, HumanLabeledEntry, ObjectiveHumanLabeledEntry, ) + from pyrit.score.scorer_evaluation.metrics_type import MetricsType, RegistryUpdateBehavior from pyrit.score.scorer_evaluation.scorer_evaluator import ( HarmScorerEvaluator, ObjectiveScorerEvaluator, ScorerEvalDatasetFiles, ScorerEvaluator, ) + from pyrit.score.scorer_evaluation.scorer_metrics import ( + HarmScorerMetrics, + ObjectiveScorerMetrics, + ScorerMetrics, + ScorerMetricsWithIdentity, + ) + from pyrit.score.scorer_evaluation.scorer_metrics_io import ( + find_objective_metrics_by_eval_hash, + get_all_harm_metrics, + get_all_objective_metrics, + ) + from pyrit.score.scorer_info import get_scorer_info + from pyrit.score.scorer_prompt_validator import ScorerPromptValidator from pyrit.score.true_false.audio_true_false_scorer import AudioTrueFalseScorer + from pyrit.score.true_false.decoding_scorer import DecodingScorer + from pyrit.score.true_false.float_scale_threshold_scorer import FloatScaleThresholdScorer + from pyrit.score.true_false.gandalf_scorer import GandalfScorer + from pyrit.score.true_false.llamaguard_parser import LLAMAGUARD_3_CATEGORY_CODES, parse_llamaguard_response + from pyrit.score.true_false.llamaguard_policy import LlamaGuardCategory, LlamaGuardPolicy + from pyrit.score.true_false.llamaguard_scorer import ( + LlamaGuardMessageRole, + LlamaGuardScorer, + render_llamaguard_prompt, + ) + from pyrit.score.true_false.prompt_shield_scorer import PromptShieldScorer + from pyrit.score.true_false.question_answer_scorer import QuestionAnswerScorer + from pyrit.score.true_false.regex.anthrax_keyword_scorer import AnthraxKeywordScorer + from pyrit.score.true_false.regex.credential_leak_scorer import CredentialLeakScorer + from pyrit.score.true_false.regex.fentanyl_keyword_scorer import FentanylKeywordScorer + from pyrit.score.true_false.regex.ldap_injection_output_scorer import LDAPInjectionOutputScorer + from pyrit.score.true_false.regex.markdown_injection import MarkdownInjectionScorer + from pyrit.score.true_false.regex.meth_keyword_scorer import MethKeywordScorer + from pyrit.score.true_false.regex.nerve_agent_keyword_scorer import NerveAgentKeywordScorer + from pyrit.score.true_false.regex.open_redirect_output_scorer import OpenRedirectOutputScorer + from pyrit.score.true_false.regex.package_hallucination_scorer import PackageEcosystem, PackageHallucinationScorer + from pyrit.score.true_false.regex.path_traversal_output_scorer import PathTraversalOutputScorer + from pyrit.score.true_false.regex.regex_scorer import RegexScorer + from pyrit.score.true_false.regex.shell_command_output_scorer import ShellCommandOutputScorer + from pyrit.score.true_false.regex.sql_injection_output_scorer import SQLInjectionOutputScorer + from pyrit.score.true_false.regex.ssrf_output_scorer import SSRFOutputScorer + from pyrit.score.true_false.regex.ssti_output_scorer import SSTIOutputScorer + from pyrit.score.true_false.regex.static_prompt_injection_scorer import StaticPromptInjectionScorer + from pyrit.score.true_false.regex.xss_output_scorer import XSSOutputScorer + from pyrit.score.true_false.regex.xxe_output_scorer import XXEOutputScorer + from pyrit.score.true_false.self_ask_category_scorer import ( + ContentClassifier, + ContentClassifierCategory, + ContentClassifierPaths, + SelfAskCategoryScorer, + render_category_system_prompt, + ) + from pyrit.score.true_false.self_ask_general_true_false_scorer import SelfAskGeneralTrueFalseScorer + from pyrit.score.true_false.self_ask_question_answer_scorer import SelfAskQuestionAnswerScorer + from pyrit.score.true_false.self_ask_refusal_scorer import RefusalScorerPaths, SelfAskRefusalScorer + from pyrit.score.true_false.self_ask_true_false_scorer import ( + SelfAskTrueFalseScorer, + TrueFalseQuestion, + TrueFalseQuestionPaths, + render_true_false_system_prompt, + ) + from pyrit.score.true_false.shieldgemma_parser import parse_shieldgemma_response + from pyrit.score.true_false.shieldgemma_policy import ( + SHIELDGEMMA_DEFAULT_POLICY_PATH, + ShieldGemmaGuideline, + ShieldGemmaMessageRole, + ShieldGemmaPolicy, + ) + from pyrit.score.true_false.shieldgemma_scorer import ShieldGemmaScorer, render_shieldgemma_prompt + from pyrit.score.true_false.substring_scorer import SubStringScorer + from pyrit.score.true_false.true_false_composite_scorer import TrueFalseCompositeScorer + from pyrit.score.true_false.true_false_inverter_scorer import TrueFalseInverterScorer + from pyrit.score.true_false.true_false_score_aggregator import TrueFalseAggregatorFunc, TrueFalseScoreAggregator + from pyrit.score.true_false.true_false_scorer import TrueFalseScorer from pyrit.score.true_false.video_true_false_scorer import VideoTrueFalseScorer -# Lazy imports for modules with heavy third-party dependencies (PEP 562). -# Audio/video scorers import `av` (~1.9s), human_labeled_dataset imports `pandas` (~1.6s), -# scorer_evaluator imports `scipy.stats` (~1s). -_LAZY_IMPORTS: dict[str, str] = { +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AnthraxKeywordScorer": "pyrit.score.true_false.regex.anthrax_keyword_scorer", "AudioFloatScaleScorer": "pyrit.score.float_scale.audio_float_scale_scorer", "AudioTrueFalseScorer": "pyrit.score.true_false.audio_true_false_scorer", - "VideoFloatScaleScorer": "pyrit.score.float_scale.video_float_scale_scorer", - "VideoTrueFalseScorer": "pyrit.score.true_false.video_true_false_scorer", + "AzureContentFilterScorer": "pyrit.score.float_scale.azure_content_filter_scorer", + "BatchScorer": "pyrit.score.batch_scorer", + "CallableResponseHandler": "pyrit.score.response_handler", + "ContentScorable": "pyrit.score.scorable", + "ContentClassifier": "pyrit.score.true_false.self_ask_category_scorer", + "ContentClassifierCategory": "pyrit.score.true_false.self_ask_category_scorer", + "ContentClassifierPaths": "pyrit.score.true_false.self_ask_category_scorer", + "ConversationScorer": "pyrit.score.conversation_scorer", + "CredentialLeakScorer": "pyrit.score.true_false.regex.credential_leak_scorer", + "DecodingScorer": "pyrit.score.true_false.decoding_scorer", + "FentanylKeywordScorer": "pyrit.score.true_false.regex.fentanyl_keyword_scorer", + "create_conversation_scorer": "pyrit.score.conversation_scorer", + "FloatScaleScoreAggregator": "pyrit.score.float_scale.float_scale_score_aggregator", + "FloatScaleScorerAllCategories": "pyrit.score.float_scale.float_scale_score_aggregator", + "FloatScaleScorerByCategory": "pyrit.score.float_scale.float_scale_score_aggregator", + "FloatScaleScorer": "pyrit.score.float_scale.float_scale_scorer", + "FloatScaleThresholdScorer": "pyrit.score.true_false.float_scale_threshold_scorer", + "GandalfScorer": "pyrit.score.true_false.gandalf_scorer", "HarmHumanLabeledEntry": "pyrit.score.scorer_evaluation.human_labeled_dataset", + "HarmScorerEvaluator": "pyrit.score.scorer_evaluation.scorer_evaluator", + "HarmScorerMetrics": "pyrit.score.scorer_evaluation.scorer_metrics", "HumanLabeledDataset": "pyrit.score.scorer_evaluation.human_labeled_dataset", "HumanLabeledEntry": "pyrit.score.scorer_evaluation.human_labeled_dataset", + "InsecureCodeScorer": "pyrit.score.float_scale.insecure_code_scorer", + "JsonSchemaResponseHandler": "pyrit.score.response_handler", + "LDAPInjectionOutputScorer": "pyrit.score.true_false.regex.ldap_injection_output_scorer", + "LikertScaleEvalFiles": "pyrit.score.float_scale.self_ask_likert_scorer", + "LikertScale": "pyrit.score.float_scale.likert_scale", + "LikertScaleEntry": "pyrit.score.float_scale.likert_scale", + "LikertScalePaths": "pyrit.score.float_scale.self_ask_likert_scorer", + "LLAMAGUARD_3_CATEGORY_CODES": "pyrit.score.true_false.llamaguard_parser", + "LlamaGuardCategory": "pyrit.score.true_false.llamaguard_policy", + "LlamaGuardMessageRole": "pyrit.score.true_false.llamaguard_scorer", + "LlamaGuardPolicy": "pyrit.score.true_false.llamaguard_policy", + "LlamaGuardScorer": "pyrit.score.true_false.llamaguard_scorer", + "MarkdownInjectionScorer": "pyrit.score.true_false.regex.markdown_injection", + "MessageScorableResolver": "pyrit.score.message_scorable_resolver", + "MessageScorable": "pyrit.score.scorable", + "MessageScorer": "pyrit.score.message_scorer", + "MessageScoringOptions": "pyrit.score.message_scorer", + "MethKeywordScorer": "pyrit.score.true_false.regex.meth_keyword_scorer", + "MetricsType": "pyrit.score.scorer_evaluation.metrics_type", + "NerveAgentKeywordScorer": "pyrit.score.true_false.regex.nerve_agent_keyword_scorer", + "NumericRange": "pyrit.score.float_scale.numeric_scale", + "NumericRubric": "pyrit.score.float_scale.numeric_scale", "ObjectiveHumanLabeledEntry": "pyrit.score.scorer_evaluation.human_labeled_dataset", - "HarmScorerEvaluator": "pyrit.score.scorer_evaluation.scorer_evaluator", "ObjectiveScorerEvaluator": "pyrit.score.scorer_evaluation.scorer_evaluator", + "ObjectiveScorerMetrics": "pyrit.score.scorer_evaluation.scorer_metrics", + "OpenRedirectOutputScorer": "pyrit.score.true_false.regex.open_redirect_output_scorer", + "PackageEcosystem": "pyrit.score.true_false.regex.package_hallucination_scorer", + "PackageHallucinationScorer": "pyrit.score.true_false.regex.package_hallucination_scorer", + "parse_llamaguard_response": "pyrit.score.true_false.llamaguard_parser", + "parse_shieldgemma_response": "pyrit.score.true_false.shieldgemma_parser", + "PathTraversalOutputScorer": "pyrit.score.true_false.regex.path_traversal_output_scorer", + "PlagiarismMetric": "pyrit.score.float_scale.plagiarism_scorer", + "PlagiarismScorer": "pyrit.score.float_scale.plagiarism_scorer", + "PromptShieldScorer": "pyrit.score.true_false.prompt_shield_scorer", + "QuestionAnswerScorer": "pyrit.score.true_false.question_answer_scorer", + "RegexScorer": "pyrit.score.true_false.regex.regex_scorer", + "RegistryUpdateBehavior": "pyrit.score.scorer_evaluation.metrics_type", + "render_category_system_prompt": "pyrit.score.true_false.self_ask_category_scorer", + "render_insecure_code_system_prompt": "pyrit.score.float_scale.insecure_code_scorer", + "render_llamaguard_prompt": "pyrit.score.true_false.llamaguard_scorer", + "render_likert_system_prompt": "pyrit.score.float_scale.self_ask_likert_scorer", + "render_scale_system_prompt": "pyrit.score.float_scale.self_ask_scale_scorer", + "render_shieldgemma_prompt": "pyrit.score.true_false.shieldgemma_scorer", + "render_true_false_system_prompt": "pyrit.score.true_false.self_ask_true_false_scorer", + "ResponseHandler": "pyrit.score.response_handler", + "Scorer": "pyrit.score.scorer", + "Scorable": "pyrit.score.scorable", "ScorerEvalDatasetFiles": "pyrit.score.scorer_evaluation.scorer_evaluator", "ScorerEvaluator": "pyrit.score.scorer_evaluation.scorer_evaluator", + "ScorerMetrics": "pyrit.score.scorer_evaluation.scorer_metrics", + "ScorerMetricsWithIdentity": "pyrit.score.scorer_evaluation.scorer_metrics", + "get_all_harm_metrics": "pyrit.score.scorer_evaluation.scorer_metrics_io", + "get_all_objective_metrics": "pyrit.score.scorer_evaluation.scorer_metrics_io", + "get_scorer_info": "pyrit.score.scorer_info", + "find_objective_metrics_by_eval_hash": "pyrit.score.scorer_evaluation.scorer_metrics_io", + "ScorerPromptValidator": "pyrit.score.scorer_prompt_validator", + "SelfAskCategoryScorer": "pyrit.score.true_false.self_ask_category_scorer", + "SelfAskGeneralFloatScaleScorer": "pyrit.score.float_scale.self_ask_general_float_scale_scorer", + "SelfAskGeneralTrueFalseScorer": "pyrit.score.true_false.self_ask_general_true_false_scorer", + "SelfAskLikertScorer": "pyrit.score.float_scale.self_ask_likert_scorer", + "SelfAskQuestionAnswerScorer": "pyrit.score.true_false.self_ask_question_answer_scorer", + "RefusalScorerPaths": "pyrit.score.true_false.self_ask_refusal_scorer", + "SelfAskRefusalScorer": "pyrit.score.true_false.self_ask_refusal_scorer", + "SelfAskScaleScorer": "pyrit.score.float_scale.self_ask_scale_scorer", + "SelfAskTrueFalseScorer": "pyrit.score.true_false.self_ask_true_false_scorer", + "ScorerPrinter": ("pyrit.output.scorer.base", "ScorerPrinterBase"), + "SHIELDGEMMA_DEFAULT_POLICY_PATH": "pyrit.score.true_false.shieldgemma_policy", + "ShieldGemmaGuideline": "pyrit.score.true_false.shieldgemma_policy", + "ShieldGemmaMessageRole": "pyrit.score.true_false.shieldgemma_policy", + "ShieldGemmaPolicy": "pyrit.score.true_false.shieldgemma_policy", + "ShieldGemmaScorer": "pyrit.score.true_false.shieldgemma_scorer", + "ShellCommandOutputScorer": "pyrit.score.true_false.regex.shell_command_output_scorer", + "SQLInjectionOutputScorer": "pyrit.score.true_false.regex.sql_injection_output_scorer", + "SSRFOutputScorer": "pyrit.score.true_false.regex.ssrf_output_scorer", + "SSTIOutputScorer": "pyrit.score.true_false.regex.ssti_output_scorer", + "StaticPromptInjectionScorer": "pyrit.score.true_false.regex.static_prompt_injection_scorer", + "SubStringScorer": "pyrit.score.true_false.substring_scorer", + "SystemPromptExtractionScorer": "pyrit.score.float_scale.system_prompt_extraction_scorer", + "TrueFalseCompositeScorer": "pyrit.score.true_false.true_false_composite_scorer", + "TrueFalseInverterScorer": "pyrit.score.true_false.true_false_inverter_scorer", + "TrueFalseQuestion": "pyrit.score.true_false.self_ask_true_false_scorer", + "TrueFalseQuestionPaths": "pyrit.score.true_false.self_ask_true_false_scorer", + "TrueFalseScoreAggregator": "pyrit.score.true_false.true_false_score_aggregator", + "TrueFalseAggregatorFunc": "pyrit.score.true_false.true_false_score_aggregator", + "TrueFalseScorer": "pyrit.score.true_false.true_false_scorer", + "VideoFloatScaleScorer": "pyrit.score.float_scale.video_float_scale_scorer", + "VideoTrueFalseScorer": "pyrit.score.true_false.video_true_false_scorer", + "XSSOutputScorer": "pyrit.score.true_false.regex.xss_output_scorer", + "XXEOutputScorer": "pyrit.score.true_false.regex.xxe_output_scorer", } +__all__ = list(_LAZY_EXPORTS) + def __getattr__(name: str) -> object: - if name in _LAZY_IMPORTS: - module = importlib.import_module(_LAZY_IMPORTS[name]) - attr = getattr(module, name) - globals()[name] = attr - return attr - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) -__all__ = [ - "AnthraxKeywordScorer", - "AudioFloatScaleScorer", - "AudioTrueFalseScorer", - "AzureContentFilterScorer", - "BatchScorer", - "CallableResponseHandler", - "ContentScorable", - "ContentClassifier", - "ContentClassifierCategory", - "ContentClassifierPaths", - "ConversationScorer", - "CredentialLeakScorer", - "DecodingScorer", - "FentanylKeywordScorer", - "create_conversation_scorer", - "FloatScaleScoreAggregator", - "FloatScaleScorerAllCategories", - "FloatScaleScorerByCategory", - "FloatScaleScorer", - "FloatScaleThresholdScorer", - "GandalfScorer", - "HarmHumanLabeledEntry", - "HarmScorerEvaluator", - "HarmScorerMetrics", - "HumanLabeledDataset", - "HumanLabeledEntry", - "InsecureCodeScorer", - "JsonSchemaResponseHandler", - "LDAPInjectionOutputScorer", - "LikertScaleEvalFiles", - "LikertScale", - "LikertScaleEntry", - "LikertScalePaths", - "LLAMAGUARD_3_CATEGORY_CODES", - "LlamaGuardCategory", - "LlamaGuardMessageRole", - "LlamaGuardPolicy", - "LlamaGuardScorer", - "MarkdownInjectionScorer", - "MessageScorableResolver", - "MessageScorable", - "MessageScorer", - "MessageScoringOptions", - "MethKeywordScorer", - "MetricsType", - "NerveAgentKeywordScorer", - "NumericRange", - "NumericRubric", - "ObjectiveHumanLabeledEntry", - "ObjectiveScorerEvaluator", - "ObjectiveScorerMetrics", - "OpenRedirectOutputScorer", - "PackageEcosystem", - "PackageHallucinationScorer", - "parse_llamaguard_response", - "parse_shieldgemma_response", - "PathTraversalOutputScorer", - "PlagiarismMetric", - "PlagiarismScorer", - "PromptShieldScorer", - "QuestionAnswerScorer", - "RegexScorer", - "RegistryUpdateBehavior", - "render_category_system_prompt", - "render_insecure_code_system_prompt", - "render_llamaguard_prompt", - "render_likert_system_prompt", - "render_scale_system_prompt", - "render_shieldgemma_prompt", - "render_true_false_system_prompt", - "ResponseHandler", - "Scorer", - "Scorable", - "ScorerEvalDatasetFiles", - "ScorerEvaluator", - "ScorerMetrics", - "ScorerMetricsWithIdentity", - "get_all_harm_metrics", - "get_all_objective_metrics", - "get_scorer_info", - "find_objective_metrics_by_eval_hash", - "ScorerPromptValidator", - "SelfAskCategoryScorer", - "SelfAskGeneralFloatScaleScorer", - "SelfAskGeneralTrueFalseScorer", - "SelfAskLikertScorer", - "SelfAskQuestionAnswerScorer", - "RefusalScorerPaths", - "SelfAskRefusalScorer", - "SelfAskScaleScorer", - "SelfAskTrueFalseScorer", - "ScorerPrinter", - "SHIELDGEMMA_DEFAULT_POLICY_PATH", - "ShieldGemmaGuideline", - "ShieldGemmaMessageRole", - "ShieldGemmaPolicy", - "ShieldGemmaScorer", - "ShellCommandOutputScorer", - "SQLInjectionOutputScorer", - "SSRFOutputScorer", - "SSTIOutputScorer", - "StaticPromptInjectionScorer", - "SubStringScorer", - "SystemPromptExtractionScorer", - "TrueFalseCompositeScorer", - "TrueFalseInverterScorer", - "TrueFalseQuestion", - "TrueFalseQuestionPaths", - "TrueFalseScoreAggregator", - "TrueFalseAggregatorFunc", - "TrueFalseScorer", - "VideoFloatScaleScorer", - "VideoTrueFalseScorer", - "XSSOutputScorer", - "XXEOutputScorer", -] +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/score/true_false/regex/__init__.py b/pyrit/score/true_false/regex/__init__.py index 76f2b4825e..f72ce3cce8 100644 --- a/pyrit/score/true_false/regex/__init__.py +++ b/pyrit/score/true_false/regex/__init__.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """ Regex-based true/false scorers for detecting credential leaks, OWASP LLM02 @@ -8,46 +9,63 @@ markdown injection, and CBRN/illicit-substance keywords. """ -from pyrit.score.true_false.regex.anthrax_keyword_scorer import AnthraxKeywordScorer -from pyrit.score.true_false.regex.credential_leak_scorer import CredentialLeakScorer -from pyrit.score.true_false.regex.fentanyl_keyword_scorer import FentanylKeywordScorer -from pyrit.score.true_false.regex.ldap_injection_output_scorer import LDAPInjectionOutputScorer -from pyrit.score.true_false.regex.markdown_injection import MarkdownInjectionScorer -from pyrit.score.true_false.regex.meth_keyword_scorer import MethKeywordScorer -from pyrit.score.true_false.regex.nerve_agent_keyword_scorer import NerveAgentKeywordScorer -from pyrit.score.true_false.regex.open_redirect_output_scorer import OpenRedirectOutputScorer -from pyrit.score.true_false.regex.package_hallucination_scorer import ( - PackageEcosystem, - PackageHallucinationScorer, -) -from pyrit.score.true_false.regex.path_traversal_output_scorer import PathTraversalOutputScorer -from pyrit.score.true_false.regex.regex_scorer import RegexScorer -from pyrit.score.true_false.regex.shell_command_output_scorer import ShellCommandOutputScorer -from pyrit.score.true_false.regex.sql_injection_output_scorer import SQLInjectionOutputScorer -from pyrit.score.true_false.regex.ssrf_output_scorer import SSRFOutputScorer -from pyrit.score.true_false.regex.ssti_output_scorer import SSTIOutputScorer -from pyrit.score.true_false.regex.static_prompt_injection_scorer import StaticPromptInjectionScorer -from pyrit.score.true_false.regex.xss_output_scorer import XSSOutputScorer -from pyrit.score.true_false.regex.xxe_output_scorer import XXEOutputScorer - -__all__ = [ - "AnthraxKeywordScorer", - "CredentialLeakScorer", - "FentanylKeywordScorer", - "LDAPInjectionOutputScorer", - "MarkdownInjectionScorer", - "MethKeywordScorer", - "NerveAgentKeywordScorer", - "OpenRedirectOutputScorer", - "PackageEcosystem", - "PackageHallucinationScorer", - "PathTraversalOutputScorer", - "RegexScorer", - "ShellCommandOutputScorer", - "SQLInjectionOutputScorer", - "SSRFOutputScorer", - "SSTIOutputScorer", - "StaticPromptInjectionScorer", - "XSSOutputScorer", - "XXEOutputScorer", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.score.true_false.regex.anthrax_keyword_scorer import AnthraxKeywordScorer + from pyrit.score.true_false.regex.credential_leak_scorer import CredentialLeakScorer + from pyrit.score.true_false.regex.fentanyl_keyword_scorer import FentanylKeywordScorer + from pyrit.score.true_false.regex.ldap_injection_output_scorer import LDAPInjectionOutputScorer + from pyrit.score.true_false.regex.markdown_injection import MarkdownInjectionScorer + from pyrit.score.true_false.regex.meth_keyword_scorer import MethKeywordScorer + from pyrit.score.true_false.regex.nerve_agent_keyword_scorer import NerveAgentKeywordScorer + from pyrit.score.true_false.regex.open_redirect_output_scorer import OpenRedirectOutputScorer + from pyrit.score.true_false.regex.package_hallucination_scorer import PackageEcosystem, PackageHallucinationScorer + from pyrit.score.true_false.regex.path_traversal_output_scorer import PathTraversalOutputScorer + from pyrit.score.true_false.regex.regex_scorer import RegexScorer + from pyrit.score.true_false.regex.shell_command_output_scorer import ShellCommandOutputScorer + from pyrit.score.true_false.regex.sql_injection_output_scorer import SQLInjectionOutputScorer + from pyrit.score.true_false.regex.ssrf_output_scorer import SSRFOutputScorer + from pyrit.score.true_false.regex.ssti_output_scorer import SSTIOutputScorer + from pyrit.score.true_false.regex.static_prompt_injection_scorer import StaticPromptInjectionScorer + from pyrit.score.true_false.regex.xss_output_scorer import XSSOutputScorer + from pyrit.score.true_false.regex.xxe_output_scorer import XXEOutputScorer + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AnthraxKeywordScorer": "pyrit.score.true_false.regex.anthrax_keyword_scorer", + "CredentialLeakScorer": "pyrit.score.true_false.regex.credential_leak_scorer", + "FentanylKeywordScorer": "pyrit.score.true_false.regex.fentanyl_keyword_scorer", + "LDAPInjectionOutputScorer": "pyrit.score.true_false.regex.ldap_injection_output_scorer", + "MarkdownInjectionScorer": "pyrit.score.true_false.regex.markdown_injection", + "MethKeywordScorer": "pyrit.score.true_false.regex.meth_keyword_scorer", + "NerveAgentKeywordScorer": "pyrit.score.true_false.regex.nerve_agent_keyword_scorer", + "OpenRedirectOutputScorer": "pyrit.score.true_false.regex.open_redirect_output_scorer", + "PackageEcosystem": "pyrit.score.true_false.regex.package_hallucination_scorer", + "PackageHallucinationScorer": "pyrit.score.true_false.regex.package_hallucination_scorer", + "PathTraversalOutputScorer": "pyrit.score.true_false.regex.path_traversal_output_scorer", + "RegexScorer": "pyrit.score.true_false.regex.regex_scorer", + "ShellCommandOutputScorer": "pyrit.score.true_false.regex.shell_command_output_scorer", + "SQLInjectionOutputScorer": "pyrit.score.true_false.regex.sql_injection_output_scorer", + "SSRFOutputScorer": "pyrit.score.true_false.regex.ssrf_output_scorer", + "SSTIOutputScorer": "pyrit.score.true_false.regex.ssti_output_scorer", + "StaticPromptInjectionScorer": "pyrit.score.true_false.regex.static_prompt_injection_scorer", + "XSSOutputScorer": "pyrit.score.true_false.regex.xss_output_scorer", + "XXEOutputScorer": "pyrit.score.true_false.regex.xxe_output_scorer", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/setup/__init__.py b/pyrit/setup/__init__.py index 4cac6e1470..7edf3617f2 100644 --- a/pyrit/setup/__init__.py +++ b/pyrit/setup/__init__.py @@ -1,23 +1,38 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Module containing initialization PyRIT.""" -from pyrit.setup.configuration_loader import ConfigurationLoader, initialize_from_config_async -from pyrit.setup.initialization import ( - AZURE_SQL, - IN_MEMORY, - SQLITE, - MemoryDatabaseType, - initialize_pyrit_async, -) - -__all__ = [ - "AZURE_SQL", - "SQLITE", - "IN_MEMORY", - "initialize_pyrit_async", - "initialize_from_config_async", - "MemoryDatabaseType", - "ConfigurationLoader", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.setup.configuration_loader import ConfigurationLoader, initialize_from_config_async + from pyrit.setup.initialization import AZURE_SQL, IN_MEMORY, SQLITE, MemoryDatabaseType, initialize_pyrit_async + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "AZURE_SQL": "pyrit.setup.initialization", + "SQLITE": "pyrit.setup.initialization", + "IN_MEMORY": "pyrit.setup.initialization", + "initialize_pyrit_async": "pyrit.setup.initialization", + "initialize_from_config_async": "pyrit.setup.configuration_loader", + "MemoryDatabaseType": "pyrit.setup.initialization", + "ConfigurationLoader": "pyrit.setup.configuration_loader", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/setup/initializers/__init__.py b/pyrit/setup/initializers/__init__.py index 69cb3d4c11..ceb5c65bd8 100644 --- a/pyrit/setup/initializers/__init__.py +++ b/pyrit/setup/initializers/__init__.py @@ -1,24 +1,45 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """PyRIT initializers package.""" -from pyrit.models.parameter import Parameter -from pyrit.setup.initializers.load_default_datasets import LoadDefaultDatasets -from pyrit.setup.initializers.preload_scenario_metadata import PreloadScenarioMetadata -from pyrit.setup.initializers.refresh_datasets import RefreshDatasets -from pyrit.setup.initializers.scorers import ScorerInitializer -from pyrit.setup.initializers.targets import TargetInitializer -from pyrit.setup.initializers.techniques import TechniqueInitializer -from pyrit.setup.pyrit_initializer import PyRITInitializer - -__all__ = [ - "Parameter", - "PyRITInitializer", - "TechniqueInitializer", - "ScorerInitializer", - "TargetInitializer", - "LoadDefaultDatasets", - "PreloadScenarioMetadata", - "RefreshDatasets", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.models.parameter import Parameter + from pyrit.setup.initializers.load_default_datasets import LoadDefaultDatasets + from pyrit.setup.initializers.preload_scenario_metadata import PreloadScenarioMetadata + from pyrit.setup.initializers.refresh_datasets import RefreshDatasets + from pyrit.setup.initializers.scorers import ScorerInitializer + from pyrit.setup.initializers.targets import TargetInitializer + from pyrit.setup.initializers.techniques import TechniqueInitializer + from pyrit.setup.pyrit_initializer import PyRITInitializer + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "Parameter": "pyrit.models.parameter", + "PyRITInitializer": "pyrit.setup.pyrit_initializer", + "TechniqueInitializer": "pyrit.setup.initializers.techniques", + "ScorerInitializer": "pyrit.setup.initializers.scorers", + "TargetInitializer": "pyrit.setup.initializers.targets", + "LoadDefaultDatasets": "pyrit.setup.initializers.load_default_datasets", + "PreloadScenarioMetadata": "pyrit.setup.initializers.preload_scenario_metadata", + "RefreshDatasets": "pyrit.setup.initializers.refresh_datasets", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/pyrit/setup/initializers/techniques/__init__.py b/pyrit/setup/initializers/techniques/__init__.py index bcadb42944..27ea54483c 100644 --- a/pyrit/setup/initializers/techniques/__init__.py +++ b/pyrit/setup/initializers/techniques/__init__.py @@ -1,16 +1,37 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +# ruff: noqa: F401 """Scenario attack technique groups and the TechniqueInitializer.""" -from pyrit.setup.initializers.techniques.technique_initializer import ( - TechniqueInitializer, - TechniqueInitializerTags, - build_technique_factories, -) - -__all__ = [ - "TechniqueInitializer", - "TechniqueInitializerTags", - "build_technique_factories", -] +from typing import TYPE_CHECKING + +from pyrit.common.lazy_imports import get_lazy_dir, resolve_lazy_export + +if TYPE_CHECKING: + from pyrit.setup.initializers.techniques.technique_initializer import ( + TechniqueInitializer, + TechniqueInitializerTags, + build_technique_factories, + ) + +_LAZY_EXPORTS: dict[str, str | tuple[str, str | None]] = { + "TechniqueInitializer": "pyrit.setup.initializers.techniques.technique_initializer", + "TechniqueInitializerTags": "pyrit.setup.initializers.techniques.technique_initializer", + "build_technique_factories": "pyrit.setup.initializers.techniques.technique_initializer", +} + +__all__ = list(_LAZY_EXPORTS) + + +def __getattr__(name: str) -> object: + return resolve_lazy_export( + name=name, + module_name=__name__, + module_globals=globals(), + exports=_LAZY_EXPORTS, + ) + + +def __dir__() -> list[str]: + return get_lazy_dir(module_globals=globals(), exports=_LAZY_EXPORTS) diff --git a/tests/unit/common/test_lazy_package_imports.py b/tests/unit/common/test_lazy_package_imports.py index 71e65e35c7..a91259c562 100644 --- a/tests/unit/common/test_lazy_package_imports.py +++ b/tests/unit/common/test_lazy_package_imports.py @@ -27,58 +27,7 @@ # The inventory test rejects both unlisted eager packages and stale exceptions. _EAGER_PACKAGE_EXCEPTIONS = frozenset( { - "pyrit", - "pyrit.analytics", - "pyrit.auth", - "pyrit.backend.mappers", - "pyrit.backend.middleware", - "pyrit.backend.models", - "pyrit.backend.routes", - "pyrit.backend.services", - "pyrit.common", - "pyrit.converter", - "pyrit.converter.token_smuggling", - "pyrit.datasets", - "pyrit.datasets.seed_datasets.local", - "pyrit.datasets.seed_datasets.remote", - "pyrit.embedding", - "pyrit.exceptions", - "pyrit.executor.attack", - "pyrit.executor.attack.component", - "pyrit.executor.attack.compound", - "pyrit.executor.attack.core", - "pyrit.executor.attack.multi_turn", - "pyrit.executor.attack.single_turn", - "pyrit.executor.attack.streaming", - "pyrit.executor.benchmark", - "pyrit.executor.core", - "pyrit.executor.promptgen", - "pyrit.executor.promptgen.core", - "pyrit.executor.promptgen.fuzzer", "pyrit.executor.promptgen.gcg", - "pyrit.executor.workflow", - "pyrit.executor.workflow.core", - "pyrit.memory", - "pyrit.memory.storage", - "pyrit.message_normalizer", - "pyrit.output", - "pyrit.prompt_normalizer", - "pyrit.prompt_target", - "pyrit.registry", - "pyrit.registry.components", - "pyrit.scenario", - "pyrit.scenario.core", - "pyrit.scenario.scenarios.adaptive", - "pyrit.scenario.scenarios.adaptive.selectors", - "pyrit.scenario.scenarios.airt", - "pyrit.scenario.scenarios.benchmark", - "pyrit.scenario.scenarios.foundry", - "pyrit.scenario.scenarios.garak", - "pyrit.score", - "pyrit.score.true_false.regex", - "pyrit.setup", - "pyrit.setup.initializers", - "pyrit.setup.initializers.techniques", } ) @@ -131,6 +80,42 @@ "pyrit.models.target.token_usage", "pyrit.models.target.json_schema_definition", ), + ( + "pyrit.converter", + "Base64Converter", + "pyrit.converter.base64_converter", + "pyrit.converter.audio_echo_converter", + ), + ( + "pyrit.datasets", + "SeedDatasetProvider", + "pyrit.datasets.seed_datasets.seed_dataset_provider", + "pyrit.datasets.seed_datasets.remote", + ), + ( + "pyrit.memory", + "CentralMemory", + "pyrit.memory.central_memory", + "pyrit.memory.sqlite_memory", + ), + ( + "pyrit.prompt_target", + "OpenAIChatTarget", + "pyrit.prompt_target.openai.openai_chat_target", + "pyrit.prompt_target.hugging_face.hugging_face_chat_target", + ), + ( + "pyrit.scenario", + "Scenario", + "pyrit.scenario.core.scenario", + "pyrit.scenario.scenarios.airt", + ), + ( + "pyrit.score", + "SubStringScorer", + "pyrit.score.true_false.substring_scorer", + "pyrit.score.true_false.audio_true_false_scorer", + ), ] @@ -277,8 +262,6 @@ def _non_exempt_public_initializers() -> tuple[Path, ...]: """Return public package initializers that must use the lazy contract.""" paths: list[Path] = [] for init_path in _initializer_paths(): - if _package_name(init_path) in _EAGER_PACKAGE_EXCEPTIONS: - continue tree = ast.parse(init_path.read_text(encoding="utf-8")) all_value = _assigned_value(tree=tree, name="__all__") lazy_exports_value = _assigned_value(tree=tree, name="_LAZY_EXPORTS") @@ -353,6 +336,14 @@ def test_lazy_packages_do_not_load_child_modules() -> None: for package_name in sorted(package_names, key=lambda name: name.count(".")): package = importlib.import_module(package_name) descendants = [name for name in sys.modules if name.startswith(f"{{package_name}}.")] + if package_name == "pyrit": + descendants = [ + name + for name in descendants + if name not in {{"pyrit.common", "pyrit.common.lazy_imports"}} + ] + elif package_name == "pyrit.common": + descendants = [name for name in descendants if name != "pyrit.common.lazy_imports"] assert not descendants, (package_name, descendants) assert package.__all__ == list(package._LAZY_EXPORTS) @@ -418,3 +409,80 @@ def test_lazy_import_spot_check( assert {unrelated_module!r} not in sys.modules """ ) + + +def test_dataset_catalog_materializes_only_for_complete_discovery() -> None: + _assert_subprocess_succeeds( + """ + import sys + + from pyrit.datasets import SeedDatasetProvider + + assert "pyrit.datasets.seed_datasets.local" not in sys.modules + assert "pyrit.datasets.seed_datasets.remote" not in sys.modules + + providers = SeedDatasetProvider.get_all_providers() + + assert "_JailbreakTemplatesDataset" in providers + assert "_HarmBenchDataset" in providers + assert "pyrit.datasets.seed_datasets.local" in sys.modules + assert "pyrit.datasets.seed_datasets.remote" in sys.modules + """ + ) + + +def test_scenario_short_imports_preserve_canonical_identity() -> None: + _assert_subprocess_succeeds( + """ + import importlib + import sys + + import pyrit.scenario + + assert "pyrit.scenario.scenarios.airt" not in sys.modules + + alias_package = importlib.import_module("pyrit.scenario.airt") + canonical_package = importlib.import_module("pyrit.scenario.scenarios.airt") + alias_module = importlib.import_module("pyrit.scenario.airt.leakage") + canonical_module = importlib.import_module("pyrit.scenario.scenarios.airt.leakage") + + assert alias_package is canonical_package + assert alias_module is canonical_module + assert alias_package.Leakage is canonical_module.Leakage + """ + ) + + +def test_scenario_registry_materializes_builtin_catalog() -> None: + _assert_subprocess_succeeds( + """ + import sys + + from pyrit.registry import ScenarioRegistry + + assert "pyrit.scenario.scenarios.airt.cyber" not in sys.modules + + names = ScenarioRegistry().get_class_names() + + assert "airt.cyber" in names + assert "garak.encoding" in names + assert "pyrit.scenario.scenarios.airt.cyber" in sys.modules + """ + ) + + +def test_function_exports_override_same_named_child_modules() -> None: + _assert_subprocess_succeeds( + """ + import importlib + + import pyrit + import pyrit.common + + show_versions_module = importlib.import_module("pyrit.show_versions") + apply_defaults_module = importlib.import_module("pyrit.common.apply_defaults") + + assert pyrit.show_versions is show_versions_module.show_versions + assert pyrit.common.apply_defaults is apply_defaults_module.apply_defaults + """ + )