The primary accessor for ORM record(s) Args: db_session: the database session to use when retrieving the record identifiers: a list of identifiers of the records to read, can be the id string or the UUID object for backwards compatibility actor: if specifi
(
cls,
db_session: "Session",
identifiers: List[str] = [],
actor: Optional["User"] = None,
access: Optional[List[Literal["read", "write", "admin"]]] = ["read"],
access_type: AccessType = AccessType.ORGANIZATION,
**kwargs,
)
| 303 | @classmethod |
| 304 | @handle_db_timeout |
| 305 | def read_multiple( |
| 306 | cls, |
| 307 | db_session: "Session", |
| 308 | identifiers: List[str] = [], |
| 309 | actor: Optional["User"] = None, |
| 310 | access: Optional[List[Literal["read", "write", "admin"]]] = ["read"], |
| 311 | access_type: AccessType = AccessType.ORGANIZATION, |
| 312 | **kwargs, |
| 313 | ) -> List["SqlalchemyBase"]: |
| 314 | """The primary accessor for ORM record(s) |
| 315 | Args: |
| 316 | db_session: the database session to use when retrieving the record |
| 317 | identifiers: a list of identifiers of the records to read, can be the id string or the UUID object for backwards compatibility |
| 318 | actor: if specified, results will be scoped only to records the user is able to access |
| 319 | access: if actor is specified, records will be filtered to the minimum permission level for the actor |
| 320 | kwargs: additional arguments to pass to the read, used for more complex objects |
| 321 | Returns: |
| 322 | The matching object |
| 323 | Raises: |
| 324 | NoResultFound: if the object is not found |
| 325 | """ |
| 326 | logger.debug(f"Reading {cls.__name__} with ID(s): {identifiers} with actor={actor}") |
| 327 | |
| 328 | # Start the query |
| 329 | query = select(cls) |
| 330 | # Collect query conditions for better error reporting |
| 331 | query_conditions = [] |
| 332 | |
| 333 | # If an identifier is provided, add it to the query conditions |
| 334 | if len(identifiers) > 0: |
| 335 | query = query.where(cls.id.in_(identifiers)) |
| 336 | query_conditions.append(f"id='{identifiers}'") |
| 337 | elif not kwargs: |
| 338 | logger.debug(f"No identifiers provided for {cls.__name__}, returning empty list") |
| 339 | return [] |
| 340 | |
| 341 | if kwargs: |
| 342 | query = query.filter_by(**kwargs) |
| 343 | query_conditions.append(", ".join(f"{key}='{value}'" for key, value in kwargs.items())) |
| 344 | |
| 345 | if actor: |
| 346 | query = cls.apply_access_predicate(query, actor, access, access_type) |
| 347 | query_conditions.append(f"access level in {access} for actor='{actor}'") |
| 348 | |
| 349 | if hasattr(cls, "is_deleted"): |
| 350 | query = query.where(cls.is_deleted == False) |
| 351 | query_conditions.append("is_deleted=False") |
| 352 | |
| 353 | results = db_session.execute(query).scalars().all() |
| 354 | if results: # if empty list a.k.a. no results |
| 355 | if len(identifiers) > 0: |
| 356 | # find which identifiers were not found |
| 357 | # only when identifier length is greater than 0 (so it was used in the actual query) |
| 358 | identifier_set = set(identifiers) |
| 359 | results_set = set(map(lambda obj: obj.id, results)) |
| 360 | |
| 361 | # we log a warning message if any of the queried IDs were not found. |
| 362 | # TODO: should we error out instead? |