Skip to content

Commit 2f2acfb

Browse files
refactor(application): consistent exception logging and raising
1 parent 7e53a0c commit 2f2acfb

1 file changed

Lines changed: 147 additions & 35 deletions

File tree

src/aignostics/application/_service.py

Lines changed: 147 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,18 @@ def application(self, application_id: str) -> Application:
241241
242242
Raises:
243243
NotFoundException: If the application with the given ID is not found.
244-
Exception: If the application cannot be retrieved.
244+
RuntimeError: If the application cannot be retrieved unexpectedly.
245245
"""
246-
return self._get_platform_client().application(application_id)
246+
try:
247+
return self._get_platform_client().application(application_id)
248+
except NotFoundException as e:
249+
message = f"Application with ID '{application_id}' not found: {e}"
250+
logger.warning(message)
251+
raise NotFoundException(message) from e
252+
except Exception as e:
253+
message = f"Failed to retrieve application with ID '{application_id}': {e}"
254+
logger.exception(message)
255+
raise RuntimeError(message) from e
247256

248257
def application_version(
249258
self, application_version_id: str, use_latest_if_no_version_given: bool = False
@@ -260,7 +269,7 @@ def application_version(
260269
Raises:
261270
ValueError: If the application version ID is invalid.
262271
NotFoundException: If the application with the given ID is not found.
263-
Exception: If the application cannot be retrieved.
272+
RuntimeError: If the application cannot be retrieved unexpectedly.
264273
"""
265274
# Validate format: application_id:vX.Y.Z (where X.Y.Z is a semver)
266275
# This checks for proper format like "he-tme:v0.50.0" where "he-tme" is the application id
@@ -299,9 +308,19 @@ def application_versions(self, application: Application) -> list[ApplicationVers
299308
list[ApplicationVersion]: A list of all application versions.
300309
301310
Raises:
302-
Exception: If version list cannot be retrieved
311+
NotFoundException: If the application with the given ID is not found.
312+
RuntimeError: If version list cannot be retrieved unexpectedly.
303313
"""
304-
return self._get_platform_client().applications.versions.list_sorted(application=application)
314+
try:
315+
return self._get_platform_client().applications.versions.list_sorted(application=application)
316+
except NotFoundException as e:
317+
message = f"Application with ID '{application.application_id}' not found: {e}"
318+
logger.warning(message)
319+
raise NotFoundException(message) from e
320+
except Exception as e:
321+
message = f"Failed to retrieve application versions for application '{application.application_id}': {e}"
322+
logger.exception(message)
323+
raise RuntimeError(message) from e
305324

306325
def application_version_latest(self, application: Application) -> ApplicationVersion | None:
307326
"""Get a latest application version.
@@ -313,7 +332,8 @@ def application_version_latest(self, application: Application) -> ApplicationVer
313332
ApplicationVersion | None: A list of all application versions.
314333
315334
Raises:
316-
Exception: If version list cannot be retrieved
335+
NotFoundException: If the application with the given ID is not found.
336+
RuntimeError: If version list cannot be retrieved unexpectedly.
317337
"""
318338
versions = self.application_versions(application)
319339
return versions[0] if versions else None
@@ -403,12 +423,14 @@ def generate_metadata_from_source_directory(
403423
Exception: If the metadata cannot be generated.
404424
405425
Raises:
426+
NotFoundError: If the application version with the given ID is not found.
406427
ValueError: If the source directory does not exist or is not a directory.
428+
RuntimeError: If the metadata generation fails unexpectedly.
407429
"""
408430
logger.debug("Generating metadata from source directory: %s", source_directory)
409431

410432
# TODO(Helmut): Use it
411-
application_version = Service().application_version(application_version_id, use_latest_if_no_version_given=True) # noqa: F841
433+
_ = Service().application_version(application_version_id, use_latest_if_no_version_given=True)
412434

413435
metadata = []
414436

@@ -453,17 +475,18 @@ def generate_metadata_from_source_directory(
453475
Service._apply_mappings_to_entry(entry, mappings)
454476

455477
metadata.append(entry)
456-
except Exception:
457-
message = f"Failed to process file '{file_path}'"
458-
logger.exception(message)
478+
except Exception as e: # noqa: BLE001
479+
message = f"Failed to process file '{file_path}': {e}"
480+
logger.warning(message)
459481
continue
460482

461483
logger.debug("Generated metadata for %d files", len(metadata))
462484
return metadata
463485

464-
except Exception:
465-
logger.exception("Failed to generate metadata from source directory: %s", source_directory)
466-
raise
486+
except Exception as e:
487+
message = f"Failed to generate metadata from source directory '{source_directory}': {e}"
488+
logger.exception(message)
489+
raise RuntimeError(message) from e
467490

468491
@staticmethod
469492
def application_run_upload(
@@ -485,6 +508,11 @@ def application_run_upload(
485508
486509
Returns:
487510
bool: True if the upload was successful, False otherwise.
511+
512+
Raises:
513+
NotFoundException: If the application version with the given ID is not found.
514+
RuntimeError: If fetching the application version fails unexpectedly.
515+
requests.HTTPError: If the upload fails with an HTTP error.
488516
"""
489517
import psutil # noqa: PLC0415
490518

@@ -555,6 +583,18 @@ def read_in_chunks( # noqa: PLR0913, PLR0917
555583

556584
@staticmethod
557585
def application_runs_static(limit: int | None = None, completed_only: bool = False) -> list[dict[str, Any]]:
586+
"""Get a list of all application runs, static variant.
587+
588+
Args:
589+
limit (int | None): The maximum number of runs to retrieve. If None, all runs are retrieved.
590+
completed_only (bool): If True, only completed runs are retrieved.
591+
592+
Returns:
593+
list[ApplicationRunData]: A list of all application runs.
594+
595+
Raises:
596+
RuntimeError: If the application run list cannot be retrieved.
597+
"""
558598
return [
559599
{
560600
"application_run_id": run.application_run_id,
@@ -580,20 +620,25 @@ def application_runs(
580620
list[ApplicationRunData]: A list of all application runs.
581621
582622
Raises:
583-
Exception: If the application run list cannot be retrieved.
623+
RuntimeError: If the application run list cannot be retrieved.
584624
"""
585625
if limit is not None and limit <= 0:
586626
return []
587627
runs = []
588628
page_size = LIST_APPLICATION_RUNS_MAX_PAGE_SIZE
589-
run_iterator = self._get_platform_client().runs.list_data(sort="-triggered_at", page_size=page_size)
590-
for run in run_iterator:
591-
if status is not None and run.status != status:
592-
continue
593-
runs.append(run)
594-
if limit is not None and len(runs) >= limit:
595-
break
596-
return runs
629+
try:
630+
run_iterator = self._get_platform_client().runs.list_data(sort="-triggered_at", page_size=page_size)
631+
for run in run_iterator:
632+
if status is not None and run.status != status:
633+
continue
634+
runs.append(run)
635+
if limit is not None and len(runs) >= limit:
636+
break
637+
return runs
638+
except Exception as e:
639+
message = f"Failed to retrieve application runs: {e}"
640+
logger.exception(message)
641+
raise RuntimeError(message) from e
597642

598643
def application_run(self, run_id: str) -> ApplicationRun:
599644
"""Find a run by its ID.
@@ -605,9 +650,14 @@ def application_run(self, run_id: str) -> ApplicationRun:
605650
ApplicationRun: The run that can be fetched using the .details() call.
606651
607652
Raises:
608-
Exception: If initializing the client fails.
653+
RuntimeError: If initializing the client fails or the run cannot be retrieved.
609654
"""
610-
return self._get_platform_client().run(run_id)
655+
try:
656+
return self._get_platform_client().run(run_id)
657+
except Exception as e:
658+
message = f"Failed to retrieve application run with ID '{run_id}': {e}"
659+
logger.exception(message)
660+
raise RuntimeError(message) from e
611661

612662
def application_run_submit_from_metadata(
613663
self, application_version_id: str, metadata: list[dict[str, Any]]
@@ -623,8 +673,10 @@ def application_run_submit_from_metadata(
623673
ApplicationRun: The submitted run.
624674
625675
Raises:
626-
ValueError: If platform bucket URL is missing or has unsupported protocol.
627-
Exception: If submitting the run failed unexpectedly.
676+
NotFoundException: If the application version with the given ID is not found.
677+
ValueError: If platform bucket URL is missing or has unsupported protocol,
678+
or if the application version ID is invalid.
679+
RuntimeError: If submitting the run failed unexpectedly.
628680
"""
629681
logger.debug("Submitting application run with metadata: %s", metadata)
630682
items = []
@@ -677,9 +729,14 @@ def application_run_submit_from_metadata(
677729
"Submitted application run with items: %s, application run id %s", items, run.application_run_id
678730
)
679731
return run
680-
except Exception:
681-
logger.exception("Failed to submit application run.")
682-
raise
732+
except ValueError as e:
733+
message = f"Failed to submit application run for version '{application_version_id}': {e}"
734+
logger.warning(message)
735+
raise ValueError(message) from e
736+
except Exception as e:
737+
message = f"Failed to submit application run for version '{application_version_id}': {e}"
738+
logger.exception(message)
739+
raise RuntimeError(message) from e
683740

684741
def application_run_submit(self, application_version_id: str, items: list[InputItem]) -> ApplicationRun:
685742
"""Submit a run for the given application.
@@ -692,9 +749,20 @@ def application_run_submit(self, application_version_id: str, items: list[InputI
692749
ApplicationRun: The submitted run.
693750
694751
Raises:
695-
Exception: If submitting the run failed unexpectedly.
752+
NotFoundException: If the application version with the given ID is not found.
753+
ValueError: If the application version ID is invalid or items invalid.
754+
RuntimeError: If submitting the run failed unexpectedly.
696755
"""
697-
return self._get_platform_client().runs.create(application_version=application_version_id, items=items)
756+
try:
757+
return self._get_platform_client().runs.create(application_version=application_version_id, items=items)
758+
except ValueError as e:
759+
message = f"Failed to submit application run for version '{application_version_id}': {e}"
760+
logger.warning(message)
761+
raise ValueError(message) from e
762+
except Exception as e:
763+
message = f"Failed to submit application run for version '{application_version_id}': {e}"
764+
logger.exception(message)
765+
raise RuntimeError(message) from e
698766

699767
def application_run_cancel(self, run_id: str) -> None:
700768
"""Cancel a run by its ID.
@@ -706,9 +774,24 @@ def application_run_cancel(self, run_id: str) -> None:
706774
Exception: If the client cannot be created.
707775
708776
Raises:
709-
Exception: If canceling the run failed unexpectedly.
777+
NotFoundException: If the application run with the given ID is not found.
778+
ValueError: If the run ID is invalid or the run cannot be canceled given its current status.
779+
RuntimeError: If canceling the run fails unexpectedly.
710780
"""
711-
self.application_run(run_id).cancel()
781+
try:
782+
self.application_run(run_id).cancel()
783+
except ValueError as e:
784+
message = f"Failed to cancel application run with ID '{run_id}': ValueError {e}"
785+
logger.warning(message)
786+
raise ValueError(message) from e
787+
except NotFoundException as e:
788+
message = f"Application run with ID '{run_id}' not found: {e}"
789+
logger.warning(message)
790+
raise NotFoundException(message) from e
791+
except Exception as e:
792+
message = f"Failed to cancel application run with ID '{run_id}': {e}"
793+
logger.exception(message)
794+
raise RuntimeError(message) from e
712795

713796
@staticmethod
714797
def application_run_download_static( # noqa: PLR0913, PLR0917
@@ -720,6 +803,30 @@ def application_run_download_static( # noqa: PLR0913, PLR0917
720803
qupath_project: bool = False,
721804
download_progress_queue: Any | None = None, # noqa: ANN401
722805
) -> Path:
806+
"""Download application run results with progress tracking, static variant.
807+
808+
Args:
809+
run_id (str): The ID of the application run to download.
810+
destination_directory (Path): Directory to save downloaded files.
811+
create_subdirectory_for_run (bool): Whether to create a subdirectory for the run.
812+
create_subdirectory_per_item (bool): Whether to create a subdirectory for each item,
813+
if not set, all items will be downloaded to the same directory but prefixed
814+
with the item reference and underscore.
815+
wait_for_completion (bool): Whether to wait for run completion. Defaults to True.
816+
qupath_project (bool): If True, create QuPath project referencing input slides and results.
817+
This requires QuPath to be installed. The QuPath project will be created in a subfolder
818+
of the destination directory.
819+
download_progress_queue (Queue | None): Queue for GUI progress updates.
820+
821+
Returns:
822+
Path: The directory containing downloaded results.
823+
824+
Raises:
825+
ValueError: If the run ID is invalid or destination directory cannot be created.
826+
NotFoundException: If the application run with the given ID is not found.
827+
RuntimeError: If run details cannot be retrieved or download fails unexpectedly.
828+
requests.HTTPError: If the download fails with an HTTP error.
829+
"""
723830
return Service().application_run_download(
724831
run_id,
725832
destination_directory,
@@ -763,8 +870,9 @@ def application_run_download( # noqa: C901, PLR0912, PLR0913, PLR0914, PLR0915,
763870
764871
Raises:
765872
ValueError: If the run ID is invalid or destination directory cannot be created.
766-
RuntimeError: If run details cannot be retrieved or download fails.
767-
Exception: If run cannot be retrieved, destination directory cannot be created, or download fails.
873+
NotFoundException: If the application run with the given ID is not found.
874+
RuntimeError: If run details cannot be retrieved or download fails unexpectedly.
875+
requests.HTTPError: If the download fails with an HTTP error.
768876
"""
769877
if qupath_project and not has_qupath_extra:
770878
message = "QuPath project creation requested, but 'qupath' extra is not installed."
@@ -778,6 +886,10 @@ def application_run_download( # noqa: C901, PLR0912, PLR0913, PLR0914, PLR0915,
778886
final_destination_directory = destination_directory
779887
try:
780888
details = application_run.details()
889+
except NotFoundException as e:
890+
message = f"Application run with ID '{run_id}' not found: {e}"
891+
logger.warning(message)
892+
raise NotFoundException(message) from e
781893
except ApiException as e:
782894
if e.status == HTTPStatus.UNPROCESSABLE_ENTITY: # Don't use UNPROCESSABLE_CONTENT
783895
message = f"Run ID '{run_id}' invalid: {e!s}."

0 commit comments

Comments
 (0)