From 17bc74eb5fa735d853b7aac9f91f05f2c0a534e9 Mon Sep 17 00:00:00 2001 From: Tim Jenness Date: Thu, 2 Nov 2023 14:54:28 -0700 Subject: [PATCH] Add experimental exception handling in client/server Demonstrate that MissingDatasetTypeError can be caught in the server and raised again in the client. --- .../daf/butler/remote_butler/_remote_butler.py | 6 +++++- .../daf/butler/remote_butler/server/_server.py | 15 ++++++++++++++- tests/test_server.py | 5 ++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/python/lsst/daf/butler/remote_butler/_remote_butler.py b/python/lsst/daf/butler/remote_butler/_remote_butler.py index f66b52e1cb..20cff12322 100644 --- a/python/lsst/daf/butler/remote_butler/_remote_butler.py +++ b/python/lsst/daf/butler/remote_butler/_remote_butler.py @@ -49,7 +49,7 @@ from .._timespan import Timespan from ..datastore import DatasetRefURIs from ..dimensions import DataCoordinate, DataId, DimensionConfig, DimensionUniverse, SerializedDataCoordinate -from ..registry import NoDefaultCollectionError, Registry, RegistryDefaults +from ..registry import MissingDatasetTypeError, NoDefaultCollectionError, Registry, RegistryDefaults from ..registry.wildcards import CollectionWildcard from ..transfers import RepoExportContext from ._config import RemoteButlerConfigModel @@ -216,6 +216,10 @@ def get_dataset_type(self, name: str) -> DatasetType: # and only go to the server if the dataset type is not known. path = f"dataset_type/{name}" response = self._client.get(self._get_url(path)) + if response.status_code != httpx.codes.OK: + content = response.json() + if content["exception"] == "MissingDatasetTypeError": + raise MissingDatasetTypeError(content["detail"]) response.raise_for_status() return DatasetType.from_simple(SerializedDatasetType(**response.json()), universe=self.dimensions) diff --git a/python/lsst/daf/butler/remote_butler/server/_server.py b/python/lsst/daf/butler/remote_butler/server/_server.py index 18f2c5e997..a0c84555bd 100644 --- a/python/lsst/daf/butler/remote_butler/server/_server.py +++ b/python/lsst/daf/butler/remote_butler/server/_server.py @@ -34,11 +34,13 @@ from functools import cache from typing import Any -from fastapi import Depends, FastAPI +from fastapi import Depends, FastAPI, Request from fastapi.middleware.gzip import GZipMiddleware +from fastapi.responses import JSONResponse from lsst.daf.butler import ( Butler, DataCoordinate, + MissingDatasetTypeError, SerializedDataCoordinate, SerializedDatasetRef, SerializedDatasetType, @@ -55,6 +57,17 @@ app.add_middleware(GZipMiddleware, minimum_size=1000) +@app.exception_handler(MissingDatasetTypeError) +def missing_dataset_type_exception_handler(request: Request, exc: MissingDatasetTypeError) -> JSONResponse: + # Remove the double quotes around the string form. These confuse + # the JSON serialization when single quotes are in the message. + message = str(exc).strip('"') + return JSONResponse( + status_code=404, + content={"detail": message, "exception": "MissingDatasetTypeError"}, + ) + + @cache def _make_global_butler() -> Butler: return Butler.from_config(BUTLER_ROOT) diff --git a/tests/test_server.py b/tests/test_server.py index 3adb794726..a7661023fd 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -38,7 +38,7 @@ TestClient = None app = None -from lsst.daf.butler import Butler, DataCoordinate, DatasetRef, StorageClassFactory +from lsst.daf.butler import Butler, DataCoordinate, DatasetRef, MissingDatasetTypeError, StorageClassFactory from lsst.daf.butler.tests import DatastoreMock from lsst.daf.butler.tests.utils import MetricTestRepo, makeTestTempDir, removeTestTempDir @@ -107,6 +107,9 @@ def test_get_dataset_type(self): bias_type = self.butler.get_dataset_type("bias") self.assertEqual(bias_type.name, "bias") + with self.assertRaises(MissingDatasetTypeError): + self.butler.get_dataset_type("not_bias") + def test_find_dataset(self): storage_class = self.storageClassFactory.getStorageClass("Exposure")