Skip to content

Commit 31118f0

Browse files
authored
Merge branch 'main' into copilot/standardize-deprecation-calls
2 parents ce47dea + 520d689 commit 31118f0

27 files changed

Lines changed: 430 additions & 134 deletions

doc/code/memory/embeddings.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
}
7878
],
7979
"source": [
80-
"embedding_response.to_json()"
80+
"embedding_response.model_dump_json()"
8181
]
8282
},
8383
{

doc/code/memory/embeddings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
# To view the json of an embedding
4141

4242
# %%
43-
embedding_response.to_json()
43+
embedding_response.model_dump_json()
4444

4545
# %% [markdown]
4646
# To save an embedding to disk

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/backend/mappers/attack_mappers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
import logging
1515
import mimetypes
16-
import os
1716
import time
1817
import uuid
1918
from datetime import datetime, timedelta, timezone
19+
from pathlib import Path
2020
from typing import TYPE_CHECKING, Optional, cast
2121
from urllib.parse import quote, urlparse
2222

@@ -178,7 +178,7 @@ def _resolve_media_url(*, value: Optional[str], data_type: str) -> Optional[str]
178178
if value.startswith(("http://", "https://", "data:")):
179179
return value
180180
# Local file path — construct a media endpoint URL
181-
if os.path.isfile(value):
181+
if Path(value).is_file():
182182
return f"/api/media?path={quote(str(value))}"
183183
return value
184184

@@ -373,7 +373,7 @@ def _build_filename(
373373
source = value
374374
if source.startswith("http"):
375375
source = urlparse(source).path
376-
ext = os.path.splitext(source)[1] # e.g. ".png"
376+
ext = Path(source).suffix # e.g. ".png"
377377

378378
if not ext:
379379
# Fallback: guess from mime type based on data type prefix

pyrit/backend/routes/media.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import logging
1414
import mimetypes
15-
import os
1615
from pathlib import Path
1716

1817
from fastapi import APIRouter, HTTPException, Query
@@ -61,39 +60,40 @@
6160
}
6261

6362

64-
def _validate_media_path(*, path: str, allowed_root: str) -> str:
63+
def _validate_media_path(*, path: str, allowed_root: Path) -> Path:
6564
"""
6665
Validate and sanitize a user-provided file path against an allowed root directory.
6766
68-
Uses ``os.path.realpath`` to resolve symlinks and ``..`` components, then
69-
verifies the canonical path starts with the allowed root prefix. This is
70-
the standard sanitization pattern recognized by static analysis tools
71-
(e.g. CodeQL ``py/path-injection``).
67+
Uses ``Path.resolve()`` to resolve symlinks and ``..`` components, then
68+
verifies the canonical path is under the allowed root. This is the standard
69+
sanitization pattern recognized by static analysis tools (e.g. CodeQL
70+
``py/path-injection``).
7271
7372
Args:
7473
path: The user-provided file path to validate.
75-
allowed_root: The canonical (``realpath``-resolved) allowed root directory.
74+
allowed_root: The canonical (``resolve``-d) allowed root directory.
7675
7776
Returns:
7877
The canonical, validated file path.
7978
8079
Raises:
8180
HTTPException 403: If the path fails any validation check.
8281
"""
83-
real_path = os.path.realpath(path)
84-
allowed_prefix = allowed_root + os.sep
82+
real_path = Path(path).resolve(strict=False)
8583

86-
if not real_path.startswith(allowed_prefix):
87-
raise HTTPException(status_code=403, detail="Access denied: path is outside the allowed results directory.")
84+
try:
85+
relative_parts = real_path.relative_to(allowed_root).parts
86+
except ValueError as exc:
87+
raise HTTPException(
88+
status_code=403, detail="Access denied: path is outside the allowed results directory."
89+
) from exc
8890

8991
# Restrict to known media subdirectories (e.g. prompt-memory-entries/)
90-
relative_parts = Path(os.path.relpath(real_path, allowed_root)).parts
9192
if not relative_parts or relative_parts[0] not in _ALLOWED_SUBDIRECTORIES:
9293
raise HTTPException(status_code=403, detail="Access denied: path is not in a media subdirectory.")
9394

9495
# Only allow known media file extensions
95-
_, ext = os.path.splitext(real_path)
96-
if ext.lower() not in _ALLOWED_EXTENSIONS:
96+
if real_path.suffix.lower() not in _ALLOWED_EXTENSIONS:
9797
raise HTTPException(status_code=403, detail="Access denied: file type is not allowed.")
9898

9999
return real_path
@@ -125,13 +125,13 @@ async def serve_media_async(
125125
memory = CentralMemory.get_memory_instance()
126126
if not memory.results_path:
127127
raise HTTPException(status_code=500, detail="Memory results_path is not configured.")
128-
allowed_root = os.path.realpath(memory.results_path)
128+
allowed_root = Path(memory.results_path).resolve(strict=False)
129129
except Exception as exc:
130130
raise HTTPException(status_code=500, detail="Memory not initialized; cannot determine results path.") from exc
131131

132132
validated_path = _validate_media_path(path=path, allowed_root=allowed_root)
133133

134-
if not os.path.isfile(validated_path):
134+
if not validated_path.is_file():
135135
raise HTTPException(status_code=404, detail="File not found.")
136136

137137
mime_type, _ = mimetypes.guess_type(validated_path)

pyrit/memory/azure_sql_memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def _get_condition_json_property_match(
354354
json_column (InstrumentedAttribute[Any]): The JSON-backed model field to query.
355355
property_path (str): The JSON path for the property to match.
356356
value (str): The string value that must match the extracted JSON property value.
357-
partial_match (bool): Whether to perform a substring match.
357+
partial_match (bool): Whether to perform a substring match. Defaults to False.
358358
case_sensitive (bool): Whether the match should be case-sensitive. Defaults to False.
359359
360360
Returns:

pyrit/memory/memory_interface.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def _get_condition_json_match(
197197
and the condition should resolve if any element in that array matches the value.
198198
Cannot be used with partial_match.
199199
value (str): The string value that must match the extracted JSON property value.
200-
partial_match (bool): Whether to perform a substring match.
200+
partial_match (bool): Whether to perform a substring match. Defaults to False.
201201
case_sensitive (bool): Whether the match should be case-sensitive. Defaults to False.
202202
203203
Returns:
@@ -238,11 +238,16 @@ def _get_condition_json_property_match(
238238
"""
239239
Return a database-specific condition for matching a value at a given path within a JSON object.
240240
241+
Concrete subclasses translate this contract into their SQL dialect (e.g. SQLite's
242+
``json_extract``, Azure SQL's ``JSON_VALUE`` + ``ISJSON``). Implementations must honor
243+
``partial_match`` and ``case_sensitive`` identically so callers can rely on consistent
244+
matching semantics across backends.
245+
241246
Args:
242247
json_column (InstrumentedAttribute[Any]): The JSON-backed model field to query.
243248
property_path (str): The JSON path for the property to match.
244249
value (str): The string value that must match the extracted JSON property value.
245-
partial_match (bool): Whether to perform a substring match.
250+
partial_match (bool): Whether to perform a substring match. Defaults to False.
246251
case_sensitive (bool): Whether the match should be case-sensitive. Defaults to False.
247252
248253
Returns:
@@ -262,6 +267,11 @@ def _get_condition_json_array_match(
262267
"""
263268
Return a database-specific condition for matching an array at a given path within a JSON object.
264269
270+
Concrete subclasses translate this contract into their SQL dialect (e.g. SQLite's
271+
``json_each`` + ``json_extract``, Azure SQL's ``OPENJSON`` + ``JSON_QUERY``).
272+
Implementations must honor ``match_mode`` and the empty-``array_to_match`` "absence"
273+
semantics identically so callers can rely on consistent matching across backends.
274+
265275
Args:
266276
json_column (InstrumentedAttribute[Any]): The JSON-backed SQLAlchemy field to query.
267277
property_path (str): The JSON path for the target array.

pyrit/memory/sqlite_memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def _get_condition_json_property_match(
220220
json_column (InstrumentedAttribute[Any]): The JSON-backed model field to query.
221221
property_path (str): The JSON path for the property to match.
222222
value (str): The string value that must match the extracted JSON property value.
223-
partial_match (bool): Whether to perform a substring match.
223+
partial_match (bool): Whether to perform a substring match. Defaults to False.
224224
case_sensitive (bool): Whether the match should be case-sensitive. Defaults to False.
225225
226226
Returns:

pyrit/models/chat_message.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,37 @@ class ChatMessage(BaseModel):
3535
tool_calls: Optional[list[ToolCall]] = None
3636
tool_call_id: Optional[str] = None
3737

38-
def to_json(self) -> str:
38+
def to_dict(self) -> dict[str, Any]:
3939
"""
40-
Serialize the ChatMessage to a JSON string.
40+
Convert the ChatMessage to a dictionary.
4141
4242
Returns:
43-
A JSON string representation of the message.
43+
A dictionary representation of the message, excluding None values.
4444
4545
"""
46-
return self.model_dump_json()
46+
return self.model_dump(exclude_none=True)
4747

48-
def to_dict(self) -> dict[str, Any]:
48+
def to_json(self) -> str:
4949
"""
50-
Convert the ChatMessage to a dictionary.
50+
Serialize the ChatMessage to a JSON string (deprecated, use ``model_dump_json`` instead).
5151
5252
Returns:
53-
A dictionary representation of the message, excluding None values.
53+
A JSON string representation of the message.
5454
5555
"""
56-
return self.model_dump(exclude_none=True)
56+
from pyrit.common.deprecation import print_deprecation_message
57+
58+
print_deprecation_message(
59+
old_item="ChatMessage.to_json",
60+
new_item="ChatMessage.model_dump_json",
61+
removed_in="0.15.0",
62+
)
63+
return self.model_dump_json()
5764

5865
@classmethod
5966
def from_json(cls, json_str: str) -> "ChatMessage":
6067
"""
61-
Deserialize a ChatMessage from a JSON string.
68+
Deserialize a ChatMessage from a JSON string (deprecated, use ``model_validate_json`` instead).
6269
6370
Args:
6471
json_str: A JSON string representation of a ChatMessage.
@@ -67,6 +74,13 @@ def from_json(cls, json_str: str) -> "ChatMessage":
6774
A ChatMessage instance.
6875
6976
"""
77+
from pyrit.common.deprecation import print_deprecation_message
78+
79+
print_deprecation_message(
80+
old_item="ChatMessage.from_json",
81+
new_item="ChatMessage.model_validate_json",
82+
removed_in="0.15.0",
83+
)
7084
return cls.model_validate_json(json_str)
7185

7286

pyrit/models/embeddings.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,19 @@ def load_from_file(file_path: Path) -> EmbeddingResponse:
7070

7171
def to_json(self) -> str:
7272
"""
73-
Serialize this embedding response to JSON.
73+
Serialize this embedding response to JSON (deprecated, use ``model_dump_json`` instead).
7474
7575
Returns:
7676
str: JSON-encoded embedding response.
7777
7878
"""
79+
from pyrit.common.deprecation import print_deprecation_message
80+
81+
print_deprecation_message(
82+
old_item="EmbeddingResponse.to_json",
83+
new_item="EmbeddingResponse.model_dump_json",
84+
removed_in="0.15.0",
85+
)
7986
return self.model_dump_json()
8087

8188

0 commit comments

Comments
 (0)