Skip to content

Commit 820f243

Browse files
romanlutzCopilot
andauthored
MAINT: Emit deprecation warnings for ContentHarms and Originator aliases (#1816)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2c7c3e4 commit 820f243

6 files changed

Lines changed: 102 additions & 21 deletions

File tree

doc/scanner/0_scanner.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ PyRIT ships with scenarios organized into the following families:
3232

3333
| Family | Scenarios | Documentation |
3434
|--------|-----------|---------------|
35-
| **AIRT** | ContentHarms, Psychosocial, Cyber, Jailbreak, Leakage, Scam | [AIRT Scenarios](airt.ipynb) |
35+
| **AIRT** | RapidResponse, Psychosocial, Cyber, Jailbreak, Leakage, Scam | [AIRT Scenarios](airt.ipynb) |
3636
| **Benchmark** | AdversarialBenchmark | [Benchmark Scenarios](benchmark.ipynb) |
3737
| **Foundry** | RedTeamAgent | [Foundry Scenarios](foundry.ipynb) |
3838
| **Garak** | Encoding | [Garak Scenarios](garak.ipynb) |

pyrit/models/message_piece.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,31 @@
1717
if TYPE_CHECKING:
1818
from pyrit.models.message import Message
1919

20-
Originator = Literal["attack", "converter", "undefined", "scorer"]
21-
"""Deprecated: The Originator type alias will be removed in a future release."""
20+
Originator = Literal["attack", "converter", "undefined", "scorer"]
21+
22+
23+
def __getattr__(name: str) -> Any:
24+
"""
25+
Lazily resolve deprecated module-level aliases.
26+
27+
Returns:
28+
Any: The resolved deprecated alias.
29+
30+
Raises:
31+
AttributeError: If the attribute name is not recognized.
32+
"""
33+
if name == "Originator":
34+
print_deprecation_message(
35+
old_item="pyrit.models.message_piece.Originator",
36+
new_item=(
37+
"inline Literal['attack', 'converter', 'undefined', 'scorer'] "
38+
"(the type alias is being removed; the originator field itself is "
39+
"deprecated and will be removed in 0.15.0)"
40+
),
41+
removed_in="0.15.0",
42+
)
43+
return Literal["attack", "converter", "undefined", "scorer"]
44+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
2245

2346

2447
class MessagePiece:
@@ -50,7 +73,7 @@ def __init__(
5073
original_value_data_type: PromptDataType = "text",
5174
converted_value_data_type: Optional[PromptDataType] = None,
5275
response_error: PromptResponseError = "none",
53-
originator: Originator = "undefined",
76+
originator: Literal["attack", "converter", "undefined", "scorer"] = "undefined",
5477
original_prompt_id: Optional[uuid.UUID] = None,
5578
timestamp: Optional[datetime] = None,
5679
scores: Optional[list[Score]] = None,

pyrit/scenario/scenarios/airt/__init__.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@
33

44
"""AIRT scenario classes."""
55

6-
from typing import Any
6+
import importlib
7+
from typing import TYPE_CHECKING, Any
78

8-
from pyrit.scenario.scenarios.airt.content_harms import ContentHarms
99
from pyrit.scenario.scenarios.airt.cyber import Cyber
1010
from pyrit.scenario.scenarios.airt.jailbreak import Jailbreak, JailbreakStrategy
1111
from pyrit.scenario.scenarios.airt.leakage import Leakage
1212
from pyrit.scenario.scenarios.airt.psychosocial import Psychosocial, PsychosocialStrategy
1313
from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse
1414
from pyrit.scenario.scenarios.airt.scam import Scam, ScamStrategy
1515

16+
if TYPE_CHECKING:
17+
from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse as ContentHarms # noqa: F401
18+
19+
ContentHarmsStrategy = Any
20+
1621

1722
def __getattr__(name: str) -> Any:
1823
"""
19-
Lazily resolve dynamic strategy classes.
24+
Lazily resolve dynamic strategy classes and deprecated aliases.
2025
2126
Returns:
22-
Any: The resolved strategy class.
27+
Any: The resolved strategy class or deprecated alias.
2328
2429
Raises:
2530
AttributeError: If the attribute name is not recognized.
@@ -28,8 +33,12 @@ def __getattr__(name: str) -> Any:
2833
return RapidResponse.get_strategy_class()
2934
if name == "LeakageStrategy":
3035
return Leakage.get_strategy_class()
31-
if name == "ContentHarmsStrategy":
32-
return ContentHarms.get_strategy_class()
36+
if name in ("ContentHarms", "ContentHarmsStrategy"):
37+
# Delegate to the content_harms module so it can emit the deprecation
38+
# warning. We import lazily here to avoid triggering the warning on
39+
# every `import pyrit.scenario.scenarios.airt`.
40+
content_harms = importlib.import_module("pyrit.scenario.scenarios.airt.content_harms")
41+
return getattr(content_harms, name)
3342
if name == "CyberStrategy":
3443
return Cyber.get_strategy_class()
3544
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

pyrit/scenario/scenarios/airt/content_harms.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,47 @@
55
Deprecated — use ``rapid_response`` instead.
66
77
``ContentHarms`` and ``ContentHarmsStrategy`` are thin aliases kept for
8-
backward compatibility. They will be removed in v0.15.0.
8+
backward compatibility. They will be removed in v0.15.0.
99
"""
1010

11-
from typing import Any
11+
from typing import TYPE_CHECKING, Any
1212

13-
from pyrit.scenario.scenarios.airt.rapid_response import (
14-
RapidResponse as ContentHarms,
15-
)
13+
from pyrit.common.deprecation import print_deprecation_message
14+
15+
if TYPE_CHECKING:
16+
from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse as ContentHarms # noqa: F401
17+
18+
ContentHarmsStrategy = Any
1619

1720

1821
def __getattr__(name: str) -> Any:
1922
"""
20-
Lazily resolve deprecated strategy class.
23+
Lazily resolve deprecated aliases and emit a deprecation warning.
2124
2225
Returns:
23-
Any: The resolved strategy class.
26+
Any: The resolved alias (``RapidResponse`` or its strategy class).
2427
2528
Raises:
2629
AttributeError: If the attribute name is not recognized.
2730
"""
31+
if name == "ContentHarms":
32+
print_deprecation_message(
33+
old_item="pyrit.scenario.scenarios.airt.content_harms.ContentHarms",
34+
new_item="pyrit.scenario.scenarios.airt.rapid_response.RapidResponse",
35+
removed_in="0.15.0",
36+
)
37+
from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse
38+
39+
return RapidResponse
2840
if name == "ContentHarmsStrategy":
29-
return ContentHarms.get_strategy_class()
41+
print_deprecation_message(
42+
old_item="pyrit.scenario.scenarios.airt.content_harms.ContentHarmsStrategy",
43+
new_item="pyrit.scenario.scenarios.airt.rapid_response.RapidResponse.get_strategy_class()",
44+
removed_in="0.15.0",
45+
)
46+
from pyrit.scenario.scenarios.airt.rapid_response import RapidResponse
47+
48+
return RapidResponse.get_strategy_class()
3049
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
3150

3251

tests/unit/models/test_message_piece.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,19 @@ def test_labels_emits_deprecation_warning(self):
11041104
assert any("labels" in str(m.message) for m in deprecation_msgs)
11051105

11061106

1107+
class TestOriginatorTypeAliasDeprecation:
1108+
"""Tests for the deprecated ``Originator`` module-level type alias."""
1109+
1110+
def test_originator_alias_emits_deprecation_warning(self):
1111+
from typing import Literal, get_args
1112+
1113+
with pytest.warns(DeprecationWarning, match="Originator"):
1114+
from pyrit.models.message_piece import Originator
1115+
1116+
assert get_args(Originator) == ("attack", "converter", "undefined", "scorer")
1117+
assert Originator is Literal["attack", "converter", "undefined", "scorer"]
1118+
1119+
11071120
class TestCopyLineageFrom:
11081121
"""Tests for MessagePiece.copy_lineage_from."""
11091122

tests/unit/scenario/test_rapid_response.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -479,25 +479,42 @@ class TestDeprecatedAliases:
479479
"""Tests for backward-compatible ContentHarms aliases."""
480480

481481
def test_content_harms_is_rapid_response(self):
482-
from pyrit.scenario.scenarios.airt.content_harms import ContentHarms
482+
with pytest.warns(DeprecationWarning, match="ContentHarms"):
483+
from pyrit.scenario.scenarios.airt.content_harms import ContentHarms
483484

484485
assert ContentHarms is RapidResponse
485486

486487
def test_content_harms_strategy_is_rapid_response_strategy(self):
487-
from pyrit.scenario.scenarios.airt.content_harms import ContentHarmsStrategy
488+
with pytest.warns(DeprecationWarning, match="ContentHarmsStrategy"):
489+
from pyrit.scenario.scenarios.airt.content_harms import ContentHarmsStrategy
488490

489491
assert ContentHarmsStrategy is _strategy_class()
490492

491493
def test_content_harms_instance_name_is_rapid_response(self, mock_objective_scorer):
492494
"""ContentHarms() creates a RapidResponse with name 'RapidResponse'."""
493-
from pyrit.scenario.scenarios.airt.content_harms import ContentHarms
495+
with pytest.warns(DeprecationWarning, match="ContentHarms"):
496+
from pyrit.scenario.scenarios.airt.content_harms import ContentHarms
494497

495498
scenario = ContentHarms(
496499
objective_scorer=mock_objective_scorer,
497500
)
498501
assert scenario.name == "RapidResponse"
499502
assert isinstance(scenario, RapidResponse)
500503

504+
def test_content_harms_via_airt_package_emits_deprecation_warning(self):
505+
"""Importing ``ContentHarms`` from the parent ``airt`` package emits the warning."""
506+
with pytest.warns(DeprecationWarning, match="ContentHarms"):
507+
from pyrit.scenario.scenarios.airt import ContentHarms
508+
509+
assert ContentHarms is RapidResponse
510+
511+
def test_content_harms_strategy_via_airt_package_emits_deprecation_warning(self):
512+
"""Importing ``ContentHarmsStrategy`` from the parent ``airt`` package emits the warning."""
513+
with pytest.warns(DeprecationWarning, match="ContentHarmsStrategy"):
514+
from pyrit.scenario.scenarios.airt import ContentHarmsStrategy
515+
516+
assert ContentHarmsStrategy is _strategy_class()
517+
501518

502519
# ===========================================================================
503520
# Registry integration tests

0 commit comments

Comments
 (0)