Skip to content

Commit 0bf64b5

Browse files
behnam-oBehnam Ousat
andauthored
MAINT Consolidate memory operations (microsoft#2207)
Co-authored-by: Behnam Ousat <behnamousat@microsoft.com>
1 parent c58f663 commit 0bf64b5

4 files changed

Lines changed: 190 additions & 354 deletions

File tree

pyrit/memory/azure_sql_memory.py

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

44
import logging
55
import struct
6-
from collections.abc import MutableSequence, Sequence
6+
from collections.abc import Sequence
77
from contextlib import closing
88
from datetime import datetime, timedelta, timezone
9-
from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast
9+
from typing import TYPE_CHECKING, Any, Literal, cast
1010

1111
from sqlalchemy import and_, create_engine, event, exists, text
1212
from sqlalchemy.engine.base import Engine
1313
from sqlalchemy.exc import SQLAlchemyError
14-
from sqlalchemy.orm import InstrumentedAttribute, joinedload, sessionmaker
14+
from sqlalchemy.orm import InstrumentedAttribute, sessionmaker
1515
from sqlalchemy.orm.session import Session
1616
from sqlalchemy.sql.expression import ColumnElement, TextClause
1717

@@ -21,8 +21,6 @@
2121
from pyrit.memory.memory_interface import MemoryInterface
2222
from pyrit.memory.memory_models import (
2323
AttackResultEntry,
24-
Base,
25-
EmbeddingDataEntry,
2624
PromptMemoryEntry,
2725
)
2826
from pyrit.memory.storage import AzureBlobStorageIO
@@ -33,8 +31,6 @@
3331

3432
logger = logging.getLogger(__name__)
3533

36-
Model = TypeVar("Model")
37-
3834

3935
class AzureSQLMemory(MemoryInterface, metaclass=Singleton):
4036
"""
@@ -246,12 +242,6 @@ def provide_token(_dialect: Any, _conn_rec: Any, cargs: list[Any], cparams: dict
246242
# add the encoded token
247243
cparams["attrs_before"] = {self.SQL_COPT_SS_ACCESS_TOKEN: packed_azure_token}
248244

249-
def _add_embeddings_to_memory(self, *, embedding_data: Sequence[EmbeddingDataEntry]) -> None:
250-
"""
251-
Insert embedding data into memory storage.
252-
"""
253-
self._insert_entries(entries=embedding_data)
254-
255245
def _get_message_pieces_memory_label_conditions(self, *, memory_labels: dict[str, str]) -> list[Any]:
256246
"""
257247
Generate SQL conditions for filtering message pieces by memory labels.
@@ -620,73 +610,6 @@ def _get_scenario_result_label_condition(self, *, labels: dict[str, str]) -> Any
620610
conditions.append(condition)
621611
return and_(*conditions)
622612

623-
def dispose_engine(self) -> None:
624-
"""
625-
Dispose the engine and clean up resources.
626-
"""
627-
if self.engine:
628-
self.engine.dispose()
629-
# During interpreter shutdown, logging handler streams may already be closed,
630-
# causing the framework to print "Logging error" to stderr (GH-1520).
631-
# Temporarily suppress logging errors for this teardown message.
632-
previous_raise = logging.raiseExceptions
633-
logging.raiseExceptions = False
634-
try:
635-
logger.info("Engine disposed successfully.")
636-
finally:
637-
logging.raiseExceptions = previous_raise
638-
639-
def get_all_embeddings(self) -> Sequence[EmbeddingDataEntry]:
640-
"""
641-
Fetch all entries from the specified table and returns them as model instances.
642-
643-
Returns:
644-
Sequence[EmbeddingDataEntry]: A sequence of EmbeddingDataEntry instances representing all stored embeddings.
645-
"""
646-
result: Sequence[EmbeddingDataEntry] = self._query_entries(EmbeddingDataEntry)
647-
return result
648-
649-
def _insert_entry(self, entry: Base) -> None:
650-
"""
651-
Insert an entry into the Table.
652-
653-
Args:
654-
entry: An instance of a SQLAlchemy model to be added to the Table.
655-
656-
Raises:
657-
SQLAlchemyError: If the insertion fails.
658-
"""
659-
with closing(self.get_session()) as session:
660-
try:
661-
session.add(entry)
662-
session.commit()
663-
except SQLAlchemyError as e:
664-
session.rollback()
665-
logger.exception(f"Error inserting entry into the table: {e}")
666-
raise
667-
668-
# The following methods are not part of MemoryInterface, but seem
669-
# common between SQLAlchemy-based implementations, regardless of engine.
670-
# Perhaps we should find a way to refactor
671-
def _insert_entries(self, *, entries: Sequence[Base]) -> None:
672-
"""
673-
Insert multiple entries into the database.
674-
675-
Args:
676-
entries (Sequence[Base]): A sequence of SQLAlchemy model instances to insert.
677-
678-
Raises:
679-
SQLAlchemyError: If the insertion fails.
680-
"""
681-
with closing(self.get_session()) as session:
682-
try:
683-
session.add_all(entries)
684-
session.commit()
685-
except SQLAlchemyError as e:
686-
session.rollback()
687-
logger.exception(f"Error inserting multiple entries into the table: {e}")
688-
raise
689-
690613
def get_session(self) -> Session:
691614
"""
692615
Provide a session for database operations.
@@ -695,99 +618,3 @@ def get_session(self) -> Session:
695618
Session: A new SQLAlchemy session bound to the configured engine.
696619
"""
697620
return self.SessionFactory()
698-
699-
def _query_entries(
700-
self,
701-
model_class: type[Model],
702-
*,
703-
conditions: Any | None = None,
704-
distinct: bool = False,
705-
join_scores: bool = False,
706-
order_by: Any | None = None,
707-
limit: int | None = None,
708-
) -> MutableSequence[Model]:
709-
"""
710-
Fetch data from the specified table model with optional conditions.
711-
712-
Args:
713-
model_class: The SQLAlchemy model class to query.
714-
conditions: SQLAlchemy filter conditions (Optional).
715-
distinct: Flag to return distinct rows (defaults to False).
716-
join_scores: Flag to join the scores table with entries (defaults to False).
717-
order_by: SQLAlchemy order_by clause (Optional).
718-
limit (int | None): Maximum number of rows to return. Defaults to None (no limit).
719-
720-
Returns:
721-
List of model instances representing the rows fetched from the table.
722-
723-
Raises:
724-
SQLAlchemyError: If the query fails.
725-
"""
726-
with closing(self.get_session()) as session:
727-
try:
728-
query = session.query(model_class)
729-
if join_scores and model_class == PromptMemoryEntry:
730-
query = query.options(
731-
joinedload(PromptMemoryEntry.scores),
732-
)
733-
elif model_class == AttackResultEntry:
734-
query = query.options(
735-
joinedload(AttackResultEntry.last_response).joinedload(PromptMemoryEntry.scores),
736-
joinedload(AttackResultEntry.last_score),
737-
)
738-
if conditions is not None:
739-
query = query.filter(conditions)
740-
if order_by is not None:
741-
query = query.order_by(order_by)
742-
if distinct:
743-
query = query.distinct()
744-
if limit is not None:
745-
query = query.limit(limit)
746-
return query.all()
747-
except SQLAlchemyError as e:
748-
logger.exception(f"Error fetching data from table {model_class.__tablename__}: {e}") # type: ignore[ty:unresolved-attribute]
749-
raise
750-
751-
def _update_entries(self, *, entries: MutableSequence[Base], update_fields: dict[str, Any]) -> bool:
752-
"""
753-
Update the given entries with the specified field values.
754-
755-
Args:
756-
entries (Sequence[Base]): A list of SQLAlchemy model instances to be updated.
757-
update_fields (dict): A dictionary of field names and their new values.
758-
759-
Returns:
760-
bool: True if the update was successful, False otherwise.
761-
762-
Raises:
763-
ValueError: If 'update_fields' is empty.
764-
SQLAlchemyError: If the update fails.
765-
"""
766-
if not update_fields:
767-
raise ValueError("update_fields must be provided to update prompt entries.")
768-
with closing(self.get_session()) as session:
769-
try:
770-
for entry in entries:
771-
# Load a fresh copy by primary key so we only touch the
772-
# requested fields. Using merge() would copy ALL
773-
# attributes from the (potentially stale) detached object
774-
# and silently overwrite concurrent updates to columns
775-
# that are NOT in update_fields.
776-
entry_in_session = session.get(type(entry), entry.id) # type: ignore[ty:unresolved-attribute]
777-
if entry_in_session is None:
778-
entry_in_session = session.merge(entry)
779-
for field, value in update_fields.items():
780-
if field in vars(entry_in_session):
781-
setattr(entry_in_session, field, value)
782-
else:
783-
session.rollback()
784-
raise ValueError(
785-
f"Field '{field}' does not exist in the table \
786-
'{entry_in_session.__tablename__}'. Rolling back changes..."
787-
)
788-
session.commit()
789-
return True
790-
except SQLAlchemyError as e:
791-
session.rollback()
792-
logger.exception(f"Error updating entries: {e}")
793-
raise

0 commit comments

Comments
 (0)