33
44import logging
55import struct
6- from collections .abc import MutableSequence , Sequence
6+ from collections .abc import Sequence
77from contextlib import closing
88from 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
1111from sqlalchemy import and_ , create_engine , event , exists , text
1212from sqlalchemy .engine .base import Engine
1313from sqlalchemy .exc import SQLAlchemyError
14- from sqlalchemy .orm import InstrumentedAttribute , joinedload , sessionmaker
14+ from sqlalchemy .orm import InstrumentedAttribute , sessionmaker
1515from sqlalchemy .orm .session import Session
1616from sqlalchemy .sql .expression import ColumnElement , TextClause
1717
2121from pyrit .memory .memory_interface import MemoryInterface
2222from pyrit .memory .memory_models import (
2323 AttackResultEntry ,
24- Base ,
25- EmbeddingDataEntry ,
2624 PromptMemoryEntry ,
2725)
2826from pyrit .memory .storage import AzureBlobStorageIO
3331
3432logger = logging .getLogger (__name__ )
3533
36- Model = TypeVar ("Model" )
37-
3834
3935class 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