diff --git a/api/.env.example b/api/.env.example index 1e32fdd..1dd9ff3 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,2 +1,3 @@ +ENVIRONMENT=development AZURE_STORAGE_BLOB_URL=https://ACCOUNT_NAME.blob.core.windows.net AZURE_ACCESS_KEY=SECRET_KEY diff --git a/api/data/human_left_gradient_10k_fs_lr.nii.gz b/api/data/human_left_gradient_10k_fs_lr.nii.gz new file mode 100644 index 0000000..e39105d --- /dev/null +++ b/api/data/human_left_gradient_10k_fs_lr.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:147c2df4f3441c1f5aebd87272ebd02136964564e3afd34fd46e65c17fc9fc2f +size 527190 diff --git a/api/data/human_left_inflated_10k_fs_lr.surf.gii b/api/data/human_left_inflated_10k_fs_lr.surf.gii new file mode 100644 index 0000000..c761e1a --- /dev/null +++ b/api/data/human_left_inflated_10k_fs_lr.surf.gii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d4e55f3c9dfb22ecb7818c4f367c6ed85bb8e69328e00c93e0333d5e468bc56 +size 243132 diff --git a/api/data/human_right_gradient_10k_fs_lr.nii.gz b/api/data/human_right_gradient_10k_fs_lr.nii.gz new file mode 100644 index 0000000..8d53864 --- /dev/null +++ b/api/data/human_right_gradient_10k_fs_lr.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18abf0b91b8617ea006c336fd0195b83430bad4a38e4220844181aa8504acd78 +size 527735 diff --git a/api/data/human_right_inflated_10k_fs_lr.surf.gii b/api/data/human_right_inflated_10k_fs_lr.surf.gii new file mode 100644 index 0000000..56f6cb8 --- /dev/null +++ b/api/data/human_right_inflated_10k_fs_lr.surf.gii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92bda4e92c8697ab63ae1e0542393a52701b19da368e4e7719bfe59ccc48911e +size 230497 diff --git a/api/data/macaque_left_gradient_10k_fs_lr.nii.gz b/api/data/macaque_left_gradient_10k_fs_lr.nii.gz new file mode 100644 index 0000000..5db5202 --- /dev/null +++ b/api/data/macaque_left_gradient_10k_fs_lr.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f23ad5a5e7cd96a2d7d9b95553ce5802d7886f849a3996fbd821cdebd77b840f +size 502425 diff --git a/api/data/macaque_left_inflated_10k_fs_lr.surf.gii b/api/data/macaque_left_inflated_10k_fs_lr.surf.gii new file mode 100644 index 0000000..cccc164 --- /dev/null +++ b/api/data/macaque_left_inflated_10k_fs_lr.surf.gii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a6a262c613bef931d8cb14782f76892294f7d4a43c759a79726235cca9facb2 +size 243124 diff --git a/api/data/macaque_right_gradient_10k_fs_lr.nii.gz b/api/data/macaque_right_gradient_10k_fs_lr.nii.gz new file mode 100644 index 0000000..c50d1a6 --- /dev/null +++ b/api/data/macaque_right_gradient_10k_fs_lr.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e40c717c59c2ccc2a3c0d0a87374d0c3b79e7c0ab57279e5fc12ca54496036cd +size 502798 diff --git a/api/data/macaque_right_inflated_10k_fs_lr.surf.gii b/api/data/macaque_right_inflated_10k_fs_lr.surf.gii new file mode 100644 index 0000000..5838ac6 --- /dev/null +++ b/api/data/macaque_right_inflated_10k_fs_lr.surf.gii @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fef665c1c7d7cd64ce6c38cbc6e713921f3f0513e0cdd7c80251361e8730c74 +size 230497 diff --git a/api/src/core/data.py b/api/src/core/data.py index d573d88..3b7c687 100644 --- a/api/src/core/data.py +++ b/api/src/core/data.py @@ -9,6 +9,8 @@ from src.core import settings config = settings.get_settings() +ENVIRONMENT = config.ENVIRONMENT +DATA_DIR = config.DATA_DIR AZURE_STORAGE_BLOB_URL = config.AZURE_STORAGE_BLOB_URL AZURE_ACCESS_KEY = config.AZURE_ACCESS_KEY @@ -63,6 +65,10 @@ def get_feature_data(species: str, side: str) -> np.ndarray: """ logger.info("Getting feature file.") filename = f"{species}_{side}_gradient_10k_fs_lr.nii.gz" + + if ENVIRONMENT == "development": + return nibabel.load(DATA_DIR / filename).get_fdata() # type: ignore[attr-defined] + with tempfile.NamedTemporaryFile(suffix=".nii.gz") as temp_file: download_file_from_blob(filename, temp_file.name) return nibabel.load(temp_file.name).get_fdata() # type: ignore[attr-defined] @@ -81,6 +87,9 @@ def get_surface_file(species: str, side: str) -> nibabel.GiftiImage: """ logger.info("Getting surface file.") filename = f"{species}_{side}_inflated_10k_fs_lr.surf.gii" + if ENVIRONMENT == "development": + return nibabel.load(DATA_DIR / filename) # type: ignore[return-value] + with tempfile.NamedTemporaryFile(suffix=".surf.gii") as temp_file: download_file_from_blob(filename, temp_file.name) return nibabel.load(temp_file.name) # type: ignore[return-value] diff --git a/api/src/core/settings.py b/api/src/core/settings.py index e414f44..2d5e149 100644 --- a/api/src/core/settings.py +++ b/api/src/core/settings.py @@ -1,6 +1,7 @@ """Settings for the API.""" import functools import logging +import pathlib import pydantic @@ -10,6 +11,11 @@ class Settings(pydantic.BaseSettings): # type: ignore[valid-type, misc] LOGGER_NAME: str = pydantic.Field("Cross Species Mapper API", env="LOGGER_NAME") + ENVIRONMENT: str = pydantic.Field("development", env="ENVIRONMENT") + DATA_DIR: pathlib.Path = pydantic.Field( + pathlib.Path(__file__).parent.parent.parent / "data", env="DATA_DIR" + ) + AZURE_STORAGE_BLOB_URL: str = pydantic.Field(..., env="AZURE_STORAGE_BLOB_URL") AZURE_ACCESS_KEY: pydantic.SecretStr = pydantic.Field(..., env="AZURE_ACCESS_KEY") diff --git a/api/src/core/utils.py b/api/src/core/utils.py index 3376942..289b3f1 100644 --- a/api/src/core/utils.py +++ b/api/src/core/utils.py @@ -16,6 +16,25 @@ logger = logging.getLogger(LOGGER_NAME) +def add_cache_control( + response: fastapi.Response, expiry_in_minutes: int = 525600 +) -> fastapi.Response: + """Adds cache control headers to a response. + + Args: + response: The response to add the headers to. + expiry_in_minutes: The number of minutes to set the expiry to. + Defaults to 525600 (1 year). + + Returns: + The response with the headers added. + + """ + logger.info("Adding cache control headers.") + response.headers["Cache-Control"] = f"max-age={expiry_in_minutes * 60}" + return response + + class Surface: """Convenience class for surface handling.""" diff --git a/api/src/routers/features/views.py b/api/src/routers/features/views.py index 49862eb..dcbb346 100644 --- a/api/src/routers/features/views.py +++ b/api/src/routers/features/views.py @@ -6,7 +6,8 @@ import fastapi from fastapi import status -from src.core import settings + +from src.core import settings, utils from src.routers.features import controller, schemas router = fastapi.APIRouter(prefix="/features", tags=["features"]) @@ -21,6 +22,7 @@ responses={status.HTTP_200_OK: {"model": List[schemas.FeatureSimilarity]}}, ) def get_feature_similarity( + response: fastapi.Response, seed_species: str = fastapi.Query( ..., example="human", description="The species to fetch the hemispheres for." ), @@ -47,4 +49,5 @@ def get_feature_similarity( the seed vertex. """ logger.info("Calling GET /surfaces/similarity endpoint.") + response = utils.add_cache_control(response) return controller.get_cross_species_features(seed_species, seed_side, seed_vertex) diff --git a/api/src/routers/surfaces/views.py b/api/src/routers/surfaces/views.py index 098e541..45fe23f 100644 --- a/api/src/routers/surfaces/views.py +++ b/api/src/routers/surfaces/views.py @@ -3,7 +3,8 @@ import fastapi from fastapi import status -from src.core import settings + +from src.core import settings, utils from src.routers.surfaces import controller, schemas config = settings.get_settings() @@ -16,6 +17,7 @@ @router.get("/hemispheres", responses={status.HTTP_200_OK: {"model": schemas.Surface}}) def get_hemispheres( + response: fastapi.Response, species: str = fastapi.Query( ..., example="human", description="The species to fetch the hemispheres for." ), @@ -35,4 +37,5 @@ def get_hemispheres( A Pydantic BaseClass containing the surface. """ logger.info("Calling GET /surfaces/hemispheres endpoint.") + response = utils.add_cache_control(response) return controller.get_hemispheres(species, side)