66import logging
77import typing
88import uuid
9+ import warnings
910from typing import TYPE_CHECKING , Annotated , Literal
1011
1112import pydantic
@@ -822,19 +823,23 @@ def entities_with_identifiers(
822823
823824 return list (entities_dict .values ())
824825
825- def entities_in_operation (self , operation_id : str ) -> list [Entity ]:
826- """Get entities directly from a single operation in one query.
826+ def entities_in_operations (self , operation_ids : str | set [ str ] ) -> list [Entity ]:
827+ """Get entities directly from one or more operations in one query.
827828
828- This method is optimized for the common case of fetching entities from
829- a single operation. It performs the entire operation in a single database
830- query, avoiding the need to first fetch entity IDs and then fetch entities.
829+ This method fetches entities from one or more operations in a single
830+ database query, avoiding the need to first fetch entity IDs and then
831+ fetch entities.
831832
832833 Args:
833- operation_id: The operation identifier to fetch entities for
834+ operation_ids: A single operation identifier or a set of operation
835+ identifiers to fetch entities for.
834836
835837 Returns:
836- List of Entity objects that were sampled in the specified operation
838+ List of Entity objects that were sampled in the specified operation(s)
837839 """
840+ if isinstance (operation_ids , str ):
841+ operation_ids = {operation_ids }
842+
838843 query = sqlalchemy .text (f"""
839844 SELECT
840845 ent.identifier,
@@ -844,16 +849,18 @@ def entities_in_operation(self, operation_id: str) -> list[Entity]:
844849 JOIN { self ._tablename } _measurement_results res ON res.entity_id = ent.identifier
845850 JOIN { self ._tablename } _measurement_requests_results reqres ON reqres.result_uid = res.uid
846851 JOIN { self ._tablename } _measurement_requests req ON reqres.request_uid = req.uid
847- WHERE req.operation_id = :operation_id
852+ WHERE req.operation_id IN :operation_ids
848853 """ ).bindparams ( # noqa: S608 - self._tablename is not untrusted
849- operation_id = operation_id
854+ sqlalchemy .bindparam (
855+ "operation_ids" , value = list (operation_ids ), expanding = True
856+ )
850857 )
851858
852859 try :
853860 with self .engine .begin () as connectable :
854861 cur = connectable .execute (query )
855862 except SQLAlchemyError as error :
856- msg = f"Unable to fetch entities for operation { operation_id } from sample store { self ._tablename } "
863+ msg = f"Unable to fetch entities for operations { operation_ids } from sample store { self ._tablename } "
857864 self .log .critical (f"{ msg } . Error: { error } " )
858865 raise SystemError (f"{ msg } . Error: { error } " ) from error
859866
@@ -903,6 +910,15 @@ def entities_in_operation(self, operation_id: str) -> list[Entity]:
903910
904911 return list (entities_dict .values ())
905912
913+ def entities_in_operation (self , operation_ids : str | set [str ]) -> list [Entity ]:
914+ """Deprecated: use entities_in_operations instead."""
915+ warnings .warn (
916+ "entities_in_operation is deprecated, use entities_in_operations instead." ,
917+ DeprecationWarning ,
918+ stacklevel = 2 ,
919+ )
920+ return self .entities_in_operations (operation_ids )
921+
906922 @property
907923 def numberOfEntities (self ) -> int :
908924
@@ -2122,29 +2138,56 @@ def experiments_in_operation(self, operation_id: str) -> list[Experiment]:
21222138 for e in cur
21232139 ]
21242140
2125- def entity_identifiers_in_operation (self , operation_id : str ) -> set [str ]:
2141+ def entity_identifiers_in_operations (
2142+ self , operation_ids : str | set [str ]
2143+ ) -> set [str ]:
2144+ """Get the set of entity identifiers sampled in one or more operations.
2145+
2146+ Args:
2147+ operation_ids: A single operation identifier or a set of operation
2148+ identifiers to look up entity identifiers for.
2149+
2150+ Returns:
2151+ Set of entity identifier strings across all specified operations.
2152+ """
2153+ if isinstance (operation_ids , str ):
2154+ operation_ids = {operation_ids }
2155+
21262156 try :
21272157 with self .engine .begin () as connectable :
21282158 query = sqlalchemy .text (f"""
21292159 SELECT DISTINCT(res.entity_id)
21302160 FROM (
21312161 SELECT *
21322162 FROM { self ._tablename } _measurement_requests
2133- WHERE operation_id = :operation_id
2163+ WHERE operation_id IN :operation_ids
21342164 ) req
21352165 JOIN { self ._tablename } _measurement_requests_results reqres ON reqres.request_uid = req.uid
21362166 JOIN { self ._tablename } _measurement_results res ON reqres.result_uid = res.uid
21372167 """ ).bindparams ( # noqa: S608 - self._tablename is not untrusted
2138- operation_id = operation_id
2168+ sqlalchemy .bindparam (
2169+ "operation_ids" , value = list (operation_ids ), expanding = True
2170+ )
21392171 )
21402172 cur = connectable .execute (query )
21412173 except SQLAlchemyError as error :
2142- msg = f"Unable to get the entity ids for operation { operation_id } "
2174+ msg = f"Unable to get the entity ids for operations { operation_ids } "
21432175 self .log .critical (f"{ msg } . Error: { error } " )
21442176 raise SystemError (f"{ msg } . Error: { error } " ) from error
21452177
21462178 return {ident [0 ] for ident in cur }
21472179
2180+ def entity_identifiers_in_operation (
2181+ self , operation_ids : str | set [str ]
2182+ ) -> set [str ]:
2183+ """Deprecated: use entity_identifiers_in_operations instead."""
2184+ warnings .warn (
2185+ "entity_identifiers_in_operation is deprecated, use entity_identifiers_in_operations instead." ,
2186+ DeprecationWarning ,
2187+ stacklevel = 2 ,
2188+ )
2189+ return self .entity_identifiers_in_operations (operation_ids )
2190+
21482191 def complete_measurement_request_with_results_timeseries (
21492192 self ,
21502193 operation_id : str ,
0 commit comments