Skip to content

Commit

Permalink
Add self-description functionality and setup obscore config loading a…
Browse files Browse the repository at this point in the history
…s a singleton
  • Loading branch information
stvoutsin committed Oct 18, 2024
1 parent 86450e6 commit d796583
Show file tree
Hide file tree
Showing 9 changed files with 573 additions and 1 deletion.
9 changes: 9 additions & 0 deletions changelog.d/20241018_183148_steliosvoutsinas_DM_46975.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- Delete the sections that don't apply -->

### New features

- Setup self-description when maxrec is set to 0

### Other changes

- Move obscore config loading to a dependency run at startime
3 changes: 3 additions & 0 deletions src/sia/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
"responseformat",
}
"""Parameters that should be treated as single values."""

BASE_RESOURCE_IDENTIFIER = "ivo://rubin/"
"""The base resource identifier for any rubin SIA service."""
2 changes: 2 additions & 0 deletions src/sia/dependencies/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ..config import Config
from ..factory import Factory
from .labeled_butler_factory import labeled_butler_factory_dependency
from .obscore_configs import obscore_config_dependency

__all__ = [
"ContextDependency",
Expand Down Expand Up @@ -96,6 +97,7 @@ async def create_factory(self, logger: BoundLogger) -> Factory:
logger=logger,
config=self._config,
labeled_butler_factory=await labeled_butler_factory_dependency(),
obscore_configs=await obscore_config_dependency(),
)

async def aclose(self) -> None:
Expand Down
36 changes: 36 additions & 0 deletions src/sia/dependencies/obscore_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Dependency class for loading the Obscore configs."""

from lsst.dax.obscore import ExporterConfig

from ..config import Config


class ObscoreConfigDependency:
"""Provides a mapping of label names to Obscore (Exporter) Configs as a
dependency.
"""

def __init__(self) -> None:
self._config_mapping: dict[str, ExporterConfig] | None = None

async def initialize(self, config: Config) -> None:
"""Initialize the dependency by processing the Butler Collections."""
self._config_mapping = {}
for collection in config.butler_data_collections:
exporter_config = collection.get_exporter_config()
self._config_mapping[collection.label] = exporter_config

async def __call__(self) -> dict[str, ExporterConfig]:
"""Return the mapping of label names to ExporterConfigs."""
if self._config_mapping is None:
raise RuntimeError("ExporterConfigDependency is not initialized")
return self._config_mapping

async def aclose(self) -> None:
"""Clear the config mapping."""
self._config_mapping = None


obscore_config_dependency = ObscoreConfigDependency()
"""The dependency that will return the mapping of label names to
Obscore Configs."""
20 changes: 20 additions & 0 deletions src/sia/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import structlog
from lsst.daf.butler import Butler, LabeledButlerFactory
from lsst.daf.butler.registry import RegistryDefaults
from lsst.dax.obscore import ExporterConfig
from structlog.stdlib import BoundLogger

from .config import Config
Expand All @@ -26,6 +27,8 @@ class Factory:
The configuration instance
labeled_butler_factory
The LabeledButlerFactory singleton
obscore_configs
The Obscore configurations
logger
The logger instance
"""
Expand All @@ -34,10 +37,12 @@ def __init__(
self,
config: Config,
labeled_butler_factory: LabeledButlerFactory,
obscore_configs: dict[str, ExporterConfig],
logger: BoundLogger | None = None,
) -> None:
self._config = config
self._labeled_butler_factory = labeled_butler_factory
self._obscore_configs = obscore_configs
self._logger = (
logger if logger else structlog.get_logger(self._config.name)
)
Expand Down Expand Up @@ -71,6 +76,21 @@ def create_butler(
)
return butler

def create_obscore_config(self, label: str) -> ExporterConfig:
"""Create an Obscore config object for a given label.
Parameters
----------
label
The label for the Obscore config.
Returns
-------
ExporterConfig
The Obscore config.
"""
return self._obscore_configs[label]

def create_data_collection_service(self) -> DataCollectionService:
"""Create a data collection service.
Expand Down
71 changes: 70 additions & 1 deletion src/sia/services/response_handler.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""Module for the Query Processor service."""

from collections.abc import Callable
from pathlib import Path

import astropy
import structlog
from fastapi import Request
from fastapi.templating import Jinja2Templates
from lsst.daf.butler import Butler
from lsst.dax.obscore import ExporterConfig
from lsst.dax.obscore.siav2 import SIAv2Parameters
from starlette.responses import Response

from ..constants import BASE_RESOURCE_IDENTIFIER
from ..constants import RESULT_NAME as RESULT
from ..factory import Factory
from ..models.data_collections import ButlerDataCollection
Expand All @@ -22,10 +25,67 @@
astropy.io.votable.tree.VOTableFile,
]

BASE_DIR = Path(__file__).resolve().parent.parent
_TEMPLATES = Jinja2Templates(directory=str(Path(BASE_DIR, "templates")))


class ResponseHandlerService:
"""Service for handling the SIAv2 query response."""

@staticmethod
def self_description_response(
request: Request,
butler: Butler,
obscore_config: ExporterConfig,
butler_collection: ButlerDataCollection,
) -> Response:
"""Return a self-description response for the SIAv2 service.
This should provide metadata about the expected parameters and return
values for the service.
Parameters
----------
request
The request object.
butler
The Butler instance.
obscore_config
The ObsCore configuration.
butler_collection
The Butler data collection.
Returns
-------
Response
The response containing the self-description.
"""
return _TEMPLATES.TemplateResponse(
request,
"self_description.xml",
{
"request": request,
"instruments": [
rec.name
for rec in butler.query_dimension_records("instrument")
],
"collections": [obscore_config.obs_collection],
# This may need to be updated if we decide to change the
# dax_obscore config to hold multiple collections
"resource_identifier": f"{BASE_RESOURCE_IDENTIFIER}/"
f"{butler_collection.label}"
f"{butler_collection.name}",
"access_url": request.url_for(
"query", collection_name=butler_collection.name
),
"facility_name": obscore_config.facility_name.strip(),
},
headers={
"content-disposition": f"attachment; filename={RESULT}.xml",
"Content-Type": "application/x-votable+xml",
},
media_type="application/x-votable+xml",
)

@staticmethod
def process_query(
*,
Expand Down Expand Up @@ -68,11 +128,20 @@ def process_query(
butler_collection=collection,
token=token,
)
obscore_config = factory.create_obscore_config(collection.label)

if params.maxrec == 0:
return ResponseHandlerService.self_description_response(
request=request,
butler=butler,
obscore_config=obscore_config,
butler_collection=collection,
)

# Execute the query
table_as_votable = sia_query(
butler,
collection.get_exporter_config(),
obscore_config,
params,
)

Expand Down
Loading

0 comments on commit d796583

Please sign in to comment.