diff --git a/src/vocutouts/handlers/external.py b/src/vocutouts/handlers/external.py
index 53db36d..ac2b276 100644
--- a/src/vocutouts/handlers/external.py
+++ b/src/vocutouts/handlers/external.py
@@ -5,15 +5,12 @@
application knows the job parameters.
"""
-from typing import Annotated
-
-from fastapi import APIRouter, Depends, Request, Response
+from fastapi import APIRouter, Request, Response
from safir.metadata import get_metadata
from safir.slack.webhook import SlackRouteErrorHandler
from ..config import config
from ..models.index import Index
-from ..uws.dependencies import UWSFactory, uws_dependency
router = APIRouter(route_class=SlackRouteErrorHandler)
"""FastAPI router for all external handlers."""
@@ -75,22 +72,6 @@ async def get_index() -> Index:
return Index(metadata=metadata)
-@router.get(
- "/availability",
- description="VOSI-availability resource for the image cutout service",
- responses={200: {"content": {"application/xml": {}}}},
- summary="IVOA service availability",
-)
-async def get_availability(
- request: Request,
- uws_factory: Annotated[UWSFactory, Depends(uws_dependency)],
-) -> Response:
- job_service = uws_factory.create_job_service()
- availability = await job_service.availability()
- templates = uws_factory.create_templates()
- return templates.availability(request, availability)
-
-
@router.get(
"/capabilities",
description="VOSI-capabilities resource for the image cutout service",
diff --git a/src/vocutouts/uws/app.py b/src/vocutouts/uws/app.py
index 5d4c224..bce84c3 100644
--- a/src/vocutouts/uws/app.py
+++ b/src/vocutouts/uws/app.py
@@ -22,6 +22,7 @@
from .exceptions import UWSError
from .handlers import (
install_async_post_handler,
+ install_availability_handler,
install_sync_get_handler,
install_sync_post_handler,
uws_router,
@@ -169,6 +170,7 @@ def install_handlers(self, router: APIRouter) -> None:
``/sync`` to create a sync job will be added.
"""
router.include_router(uws_router, prefix="/jobs")
+ install_availability_handler(router)
if route := self._config.sync_get_route:
install_sync_get_handler(router, route)
if route := self._config.sync_post_route:
diff --git a/src/vocutouts/uws/handlers.py b/src/vocutouts/uws/handlers.py
index b5bde99..b5e4c08 100644
--- a/src/vocutouts/uws/handlers.py
+++ b/src/vocutouts/uws/handlers.py
@@ -37,6 +37,7 @@
__all__ = [
"install_async_post_handler",
+ "install_availability_handler",
"install_sync_get_handler",
"install_sync_post_handler",
"uws_router",
@@ -476,6 +477,31 @@ async def get_job_results(
return Response(content=xml, media_type="application/xml")
+def install_availability_handler(router: APIRouter) -> None:
+ """Construct a default handler for the VOSI ``/availability`` interface.
+
+ Parameters
+ ----------
+ router
+ Router into which to install the handler.
+ """
+
+ @router.get(
+ "/availability",
+ description="VOSI-availability resource for the service",
+ responses={200: {"content": {"application/xml": {}}}},
+ summary="IVOA service availability",
+ )
+ async def get_availability(
+ request: Request,
+ uws_factory: Annotated[UWSFactory, Depends(uws_dependency)],
+ ) -> Response:
+ job_service = uws_factory.create_job_service()
+ availability = await job_service.availability()
+ xml = availability.to_xml(skip_empty=True)
+ return Response(content=xml, media_type="application/xml")
+
+
def install_async_post_handler(router: APIRouter, route: UWSRoute) -> None:
"""Construct the POST handler for creating an async job.
diff --git a/src/vocutouts/uws/models.py b/src/vocutouts/uws/models.py
index dcf8773..6c82108 100644
--- a/src/vocutouts/uws/models.py
+++ b/src/vocutouts/uws/models.py
@@ -23,7 +23,6 @@
__all__ = [
"ACTIVE_PHASES",
- "Availability",
"ErrorCode",
"UWSJob",
"UWSJobDescription",
@@ -34,17 +33,6 @@
]
-@dataclass
-class Availability:
- """Availability information (from VOSI)."""
-
- available: bool
- """Whether the service appears to be available."""
-
- note: str | None = None
- """Supplemental information, usually when the service is not available."""
-
-
ACTIVE_PHASES = {
ExecutionPhase.PENDING,
ExecutionPhase.QUEUED,
diff --git a/src/vocutouts/uws/responses.py b/src/vocutouts/uws/responses.py
index b7621a1..6e95b77 100644
--- a/src/vocutouts/uws/responses.py
+++ b/src/vocutouts/uws/responses.py
@@ -7,7 +7,7 @@
from fastapi.templating import Jinja2Templates
from safir.datetime import isodatetime
-from .models import Availability, UWSJob, UWSJobError
+from .models import UWSJob, UWSJobError
from .results import ResultStore
__all__ = ["UWSTemplates"]
@@ -30,17 +30,6 @@ class UWSTemplates:
def __init__(self, result_store: ResultStore) -> None:
self._result_store = result_store
- def availability(
- self, request: Request, availability: Availability
- ) -> Response:
- """Return the availability of a service as an XML response."""
- return _templates.TemplateResponse(
- request,
- "availability.xml",
- {"availability": availability},
- media_type="application/xml",
- )
-
def error(self, request: Request, error: UWSJobError) -> Response:
"""Return the error of a job as an XML response."""
return _templates.TemplateResponse(
diff --git a/src/vocutouts/uws/service.py b/src/vocutouts/uws/service.py
index 4e13f59..9e6ee8a 100644
--- a/src/vocutouts/uws/service.py
+++ b/src/vocutouts/uws/service.py
@@ -9,6 +9,7 @@
from safir.datetime import current_datetime, isodatetime
from structlog.stdlib import BoundLogger
from vo_models.uws.types import ExecutionPhase
+from vo_models.vosi.availability import Availability
from .config import ParametersModel, UWSConfig
from .constants import JOB_STOP_TIMEOUT
@@ -21,7 +22,6 @@
)
from .models import (
ACTIVE_PHASES,
- Availability,
UWSJob,
UWSJobDescription,
UWSJobParameter,
@@ -103,13 +103,12 @@ async def abort(self, user: str, job_id: str) -> None:
async def availability(self) -> Availability:
"""Check whether the service is up.
- Used for ``/availability`` endpoints. Currently this only checks the
- database. Eventually it should push an end-to-end test through the
- job execution system.
+ Used for ``/availability`` endpoints. Currently this only checks the
+ database.
Returns
-------
- vocutouts.uws.models.Availability
+ Availability
Service availability information.
"""
return await self._storage.availability()
diff --git a/src/vocutouts/uws/storage.py b/src/vocutouts/uws/storage.py
index d4ebcf3..2e601f6 100644
--- a/src/vocutouts/uws/storage.py
+++ b/src/vocutouts/uws/storage.py
@@ -15,10 +15,10 @@
from sqlalchemy.ext.asyncio import async_scoped_session
from sqlalchemy.future import select
from vo_models.uws.types import ErrorType, ExecutionPhase
+from vo_models.vosi.availability import Availability
from .exceptions import TaskError, UnknownJobError
from .models import (
- Availability,
ErrorCode,
UWSJob,
UWSJobDescription,
diff --git a/src/vocutouts/uws/templates/availability.xml b/src/vocutouts/uws/templates/availability.xml
deleted file mode 100644
index c63116f..0000000
--- a/src/vocutouts/uws/templates/availability.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
- {% if availability.available %}true{% else %}false{% endif %}
- {%- if availability.note %}
- {availability.note}
- {%- endif %}
-
diff --git a/tests/handlers/external_test.py b/tests/handlers/external_test.py
index 6702fe5..1120ff9 100644
--- a/tests/handlers/external_test.py
+++ b/tests/handlers/external_test.py
@@ -8,12 +8,6 @@
from vocutouts.config import config
-AVAILABILITY = """
-
- true
-
-"""
-
CAPABILITIES = """
None:
assert isinstance(metadata["documentation_url"], str)
-@pytest.mark.asyncio
-async def test_availability(client: AsyncClient) -> None:
- r = await client.get("/api/cutout/availability")
- assert r.status_code == 200
- assert r.text == AVAILABILITY.strip()
-
-
@pytest.mark.asyncio
async def test_capabilities(client: AsyncClient) -> None:
r = await client.get("/api/cutout/capabilities")
diff --git a/tests/uws/vosi_test.py b/tests/uws/vosi_test.py
new file mode 100644
index 0000000..c998897
--- /dev/null
+++ b/tests/uws/vosi_test.py
@@ -0,0 +1,20 @@
+"""Tests for VOSI functionality provided by the UWS library."""
+
+from __future__ import annotations
+
+import pytest
+from httpx import AsyncClient
+from vo_models.vosi.availability import Availability
+
+AVAILABILITY = """
+
+ true
+
+"""
+
+
+@pytest.mark.asyncio
+async def test_availability(client: AsyncClient) -> None:
+ r = await client.get("/test/availability")
+ assert r.status_code == 200
+ assert Availability.from_xml(r.text) == Availability.from_xml(AVAILABILITY)