Skip to content

Commit

Permalink
Merge pull request #206 from lsst-sqre/tickets/DM-45138
Browse files Browse the repository at this point in the history
DM-45138: Add summary and description for added routes
  • Loading branch information
rra authored Jul 12, 2024
2 parents de2b36c + ab64381 commit 31611b3
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 47 deletions.
30 changes: 26 additions & 4 deletions src/vocutouts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from .dependencies import get_params_dependency, post_params_dependency
from .models.cutout import CutoutParameters
from .uws.app import UWSApplication
from .uws.config import UWSConfig
from .uws.config import UWSConfig, UWSRoute

_postgres_dsn_adapter = TypeAdapter(PostgresDsn)

Expand Down Expand Up @@ -236,9 +236,31 @@ def uws_config(self) -> UWSConfig:
database_password=self.database_password,
slack_webhook=self.slack_webhook,
sync_timeout=self.sync_timeout,
async_post_dependency=post_params_dependency,
sync_get_dependency=get_params_dependency,
sync_post_dependency=post_params_dependency,
async_post_route=UWSRoute(
dependency=post_params_dependency,
summary="Create async cutout job",
description="Create a new UWS job to perform an image cutout",
),
sync_get_route=UWSRoute(
dependency=get_params_dependency,
summary="Synchronous cutout",
description=(
"Synchronously request a cutout. This will wait for the"
" cutout to be completed and return the resulting image"
" as a FITS file. The image will be returned via a"
" redirect to a URL at the underlying object store."
),
),
sync_post_route=UWSRoute(
dependency=post_params_dependency,
summary="Synchronous cutout",
description=(
"Synchronously request a cutout. This will wait for the"
" cutout to be completed and return the resulting image"
" as a FITS file. The image will be returned via a"
" redirect to a URL at the underlying object store."
),
),
)


Expand Down
15 changes: 7 additions & 8 deletions src/vocutouts/uws/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,17 @@ def install_handlers(self, router: APIRouter) -> None:
This method will always install a POST handler at the root of the
router that creates an async job, and handlers under ``/jobs`` that
implement the UWS protocol for managing those jobs. If
``sync_post_dependency`` is set in the
``sync_post_route`` is set in the
`~vocutouts.uws.interface.UWSInterface` that this application was
configured with, a POST handler for ``/sync`` to create a sync job
will be added. If ``sync_get_dependency`` is set, a GET handler for
will be added. If ``sync_get_route`` is set, a GET handler for
``/sync`` to create a sync job will be added.
"""
router.include_router(uws_router, prefix="/jobs")
if dependency := self._config.sync_get_dependency:
install_sync_get_handler(router, dependency)
if dependency := self._config.sync_post_dependency:
install_sync_post_handler(router, dependency)
if route := self._config.sync_get_route:
install_sync_get_handler(router, route)
if route := self._config.sync_post_route:
install_sync_post_handler(router, route)

# This handler must be installed directly on the provided router. Do
# not install it on the UWS router before include_router. The process
Expand All @@ -183,5 +183,4 @@ def install_handlers(self, router: APIRouter) -> None:
# This is probably because the dependency is a dynamic function not
# known statically, which may confuse the handler copying code in
# FastAPI. This problem was last verified in FastAPI 0.111.0.
dependency = self._config.async_post_dependency
install_async_post_handler(router, dependency)
install_async_post_handler(router, self._config.async_post_route)
27 changes: 18 additions & 9 deletions src/vocutouts/uws/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,33 @@
]
"""Type for a validator for a new execution duration."""

ParametersDependency: TypeAlias = Callable[
..., Coroutine[None, None, list[UWSJobParameter]]
]
"""Type for a dependency that gathers parameters for a job."""

T = TypeVar("T", bound=BaseModel)
"""Generic type for the worker parameters."""

__all__ = [
"DestructionValidator",
"ExecutionDurationValidator",
"ParametersDependency",
"ParametersModel",
"T",
"UWSConfig",
"UWSRoute",
]


@dataclass
class UWSRoute:
"""Defines a FastAPI dependency to get the UWS job parameters."""

dependency: Callable[..., Coroutine[None, None, list[UWSJobParameter]]]
"""Type for a dependency that gathers parameters for a job."""

summary: str
"""Summary string for API documentation."""

description: str | None = None
"""Description string for API documentation."""


class ParametersModel(BaseModel, ABC, Generic[T]):
"""Defines the interface for a model suitable for job parameters."""

Expand Down Expand Up @@ -88,7 +97,7 @@ class encapsulates the configuration of the UWS component that may vary by
arq_redis_settings: RedisSettings
"""Settings for Redis for the arq queue."""

async_post_dependency: ParametersDependency
async_post_route: UWSRoute
"""Dependency for job parameters for an async job via POST.
This FastAPI should expect POST parameters and return a list of
Expand Down Expand Up @@ -138,7 +147,7 @@ class encapsulates the configuration of the UWS component that may vary by
slack_webhook: SecretStr | None = None
"""Slack incoming webhook for reporting errors."""

sync_get_dependency: ParametersDependency | None = None
sync_get_route: UWSRoute | None = None
"""Dependency for job parameters for a sync job via GET.
This FastAPI should expect GET parameters and return a list of
Expand All @@ -147,7 +156,7 @@ class encapsulates the configuration of the UWS component that may vary by
created.
"""

sync_post_dependency: ParametersDependency | None = None
sync_post_route: UWSRoute | None = None
"""Dependency for job parameters for a sync job via POST.
This FastAPI should expect POST parameters and return a list of
Expand Down
43 changes: 21 additions & 22 deletions src/vocutouts/uws/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from safir.slack.webhook import SlackRouteErrorHandler
from structlog.stdlib import BoundLogger

from .config import ParametersDependency
from .config import UWSRoute
from .dependencies import (
UWSFactory,
runid_post_dependency,
Expand Down Expand Up @@ -470,32 +470,33 @@ async def get_job_results(
return await templates.results(request, job)


def install_async_post_handler(
router: APIRouter, dependency: ParametersDependency
) -> None:
def install_async_post_handler(router: APIRouter, route: UWSRoute) -> None:
"""Construct the POST handler for creating an async job.
Parameters
----------
router
Router into which to install the handler.
dependency
Dependency that returns the job parameters.
route
Configuration for this route.
"""

@router.post(
"/jobs",
response_class=RedirectResponse,
status_code=303,
summary="Create async job",
summary=route.summary,
description=route.description,
)
async def create_job(
*,
request: Request,
phase: Annotated[
Literal["RUN"] | None, Query(title="Immediately start job")
] = None,
parameters: Annotated[list[UWSJobParameter], Depends(dependency)],
parameters: Annotated[
list[UWSJobParameter], Depends(route.dependency)
],
runid: Annotated[str | None, Depends(runid_post_dependency)],
user: Annotated[str, Depends(auth_dependency)],
token: Annotated[str, Depends(auth_delegated_token_dependency)],
Expand All @@ -508,29 +509,28 @@ async def create_job(
return str(request.url_for("get_job", job_id=job.job_id))


def install_sync_post_handler(
router: APIRouter, dependency: ParametersDependency
) -> None:
def install_sync_post_handler(router: APIRouter, route: UWSRoute) -> None:
"""Construct the POST handler for creating a sync job.
Parameters
----------
router
Router into which to install the handler.
dependency
Dependency that returns the job parameters.
route
Configuration for this route.
"""

@router.post(
"/sync",
response_class=RedirectResponse,
status_code=303,
summary="Create sync job",
summary=route.summary,
description=route.description,
)
async def post_sync(
*,
runid: Annotated[str | None, Depends(runid_post_dependency)],
params: Annotated[list[UWSJobParameter], Depends(dependency)],
params: Annotated[list[UWSJobParameter], Depends(route.dependency)],
user: Annotated[str, Depends(auth_dependency)],
token: Annotated[str, Depends(auth_delegated_token_dependency)],
uws_factory: Annotated[UWSFactory, Depends(uws_dependency)],
Expand All @@ -543,24 +543,23 @@ async def post_sync(
return result_store.sign_url(result).url


def install_sync_get_handler(
router: APIRouter, dependency: ParametersDependency
) -> None:
def install_sync_get_handler(router: APIRouter, route: UWSRoute) -> None:
"""Construct the GET handler for creating a sync job.
Parameters
----------
router
Router into which to install the handler.
dependency
Dependency that returns the job parameters.
route
Configuration for this route.
"""

@router.get(
"/sync",
response_class=RedirectResponse,
status_code=303,
summary="Create sync job",
summary=route.summary,
description=route.description,
)
async def get_sync(
*,
Expand All @@ -575,7 +574,7 @@ async def get_sync(
),
),
] = None,
params: Annotated[list[UWSJobParameter], Depends(dependency)],
params: Annotated[list[UWSJobParameter], Depends(route.dependency)],
user: Annotated[str, Depends(auth_dependency)],
token: Annotated[str, Depends(auth_delegated_token_dependency)],
uws_factory: Annotated[UWSFactory, Depends(uws_dependency)],
Expand Down
14 changes: 10 additions & 4 deletions tests/support/uws.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from pydantic import BaseModel, SecretStr
from safir.arq import ArqMode, JobMetadata, MockArqQueue

from vocutouts.uws.config import ParametersModel, UWSConfig
from vocutouts.uws.config import ParametersModel, UWSConfig, UWSRoute
from vocutouts.uws.dependencies import UWSFactory
from vocutouts.uws.models import UWSJob, UWSJobParameter, UWSJobResult

Expand Down Expand Up @@ -71,16 +71,22 @@ def build_uws_config() -> UWSConfig:
host=os.environ["REDIS_HOST"],
port=int(os.environ["REDIS_6379_TCP_PORT"]),
),
async_post_dependency=_post_dependency,
async_post_route=UWSRoute(
dependency=_post_dependency, summary="Create async job"
),
database_url=database_url,
database_password=SecretStr(os.environ["POSTGRES_PASSWORD"]),
execution_duration=timedelta(minutes=10),
lifetime=timedelta(days=1),
parameters_type=SimpleParameters,
signing_service_account="[email protected]",
slack_webhook=SecretStr("https://example.com/fake-webhook"),
sync_get_dependency=_get_dependency,
sync_post_dependency=_post_dependency,
sync_get_route=UWSRoute(
dependency=_get_dependency, summary="Sync request"
),
sync_post_route=UWSRoute(
dependency=_post_dependency, summary="Sync request"
),
worker="hello",
)

Expand Down

0 comments on commit 31611b3

Please sign in to comment.