Defining service inputs¶
Your UWS service will take one more more input parameters. The UWS library cannot know what those parameters are, so you will need to define them and pass that configuration into the UWS library configuration. This is done by writing a FastAPI dependency that returns a Pydantic model for your job parameters. See Define job parameter models for details on how to define that model.
Ways to create jobs¶
There are three possible ways to create a job in UWS: POST
to create an async job, POST
to create a sync job, and GET
to create a sync job.
An async job creates a job record and starts the operation in the background. The client then needs to wait or poll for the job to complete and can retrieve the results from the job record. Multiple results are supported, and each will have its own identifier.
A sync job creates the job, waits for it to complete, and returns the result. Sync jobs can only be used for operations that complete relatively quickly, because many HTTP clients will not wait for long for a response. Browsers will normally not wait more than a minute at most, and sync jobs are not suitable for any operation that takes longer than five minutes. Sync jobs are not supported by default, but can be easily enabled.
Sync jobs can be created via either POST
or GET
.
You can pick whether your application will support sync POST
, sync GET
, both, or neither.
Supporting GET
makes it easier for people to assemble ad hoc jobs by writing the URL directly in their web browser.
However, due to unfixable web security limitations in the HTTP protocol, GET
jobs can be created by any malicious site on the Internet, and therefore should not be supported if the operation of your service is destructive, expensive, or dangerous if performed by unauthorized people.
For each supported way to create a job, your application must provide a FastAPI dependency that reads input parameters via that method and returns the Pydantic model for parameters that you defined in Define job parameter models.
Async POST dependency¶
Supporting async POST
is required.
First, writing a FastAPI dependency that accepts the input parameters for your job as form parameters.
Conventionally, this dependency goes into dependencies.py
.
Here is an example for a SODA service that performs circular cutouts:
from typing import Annotated
from fastapi import Depends, Form
from .models import CircleStencil, CutoutParameters
async def post_params_dependency(
*,
id: Annotated[
list[str] | None,
Form(
title="Source ID",
description=(
"Identifiers of images from which to make a cutout. This"
" parameter is mandatory."
),
),
] = None,
circle: Annotated[
list[str] | None,
Form(
title="Cutout circle positions",
description=(
"Circles to cut out. The value must be the ra and dec of the"
" center of the circle and then the radius, as"
" double-precision floating point numbers expressed as"
" strings and separated by spaces."
),
),
] = None,
) -> CutoutParameters:
return CutoutParameters(
ids=id,
stencils=[CircleStencil.from_string(c) for c in circle],
)
This first declares the input parameters, with full documentation, as FastAPI Form
parameters.
Note that the type is list[str]
, which allows the parameter to be specified multiple times.
If the parameters for your service cannot be repeated, change this to str
(or another appropriate basic type, such as int
).
Then, it converts the form parameters into the Pydantic model for your job parameters.
Here, most of the work is done by the from_string
static method on CircleStencil
, defined in Input parameter model.
This conversion should also perform any necessary input validation.
Async POST configuration¶
Finally, you need to tell the UWS library about this configuration, and also provide some additional metadata for the route.
This is done in the async_post_route
argument to UWSAppSettings.build_uws_config
as mentioned in Add UWS configuration options.
Now you can replace the ...
in that example with a full UWSRoute
object.
Here is an example for the same cutout service:
from safir.uws import UWSRoute
from .dependencies import 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",
)
This would then be passed as the async_post_route
argument.
The summary
and description
attributes are only used to generate the API documentation.
They contain a brief summary and a longer description of the async POST
route and will be copied into the generated OpenAPI specification for the service.
Sync POST¶
Supporting sync POST
is very similar: define a FastAPI dependency that accepts POST
parameters and returns your Pydantic parameter model, and then define a UWSRoute
object including that dependency and pass it as the sync_post_route
argument to UWSAppSettings.build_uws_config
.
By default, sync POST
is not supported.
Normally, the input parameters for sync POST
will be the same as the input parameters for async POST
, so you can reuse the same FastAPI dependency.
Here is an example for the same cutout service:
from safir.uws import UWSRoute
from .dependencies import post_params_dependency
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."
),
)
This would then be passed as the sync_post_route
argument.
Sync GET¶
Supporting sync GET
follows the same pattern, but here you will need to define a separate dependency that takes query parameters rather than form parameters.
Here is an example dependency for a cutout service:
from typing import Annotated
from fastapi import Depends, Query
from .models import CircleStencil, CutoutParameters
async def get_params_dependency(
*,
id: Annotated[
list[str],
Query(
title="Source ID",
description=(
"Identifiers of images from which to make a cutout. This"
" parameter is mandatory."
),
),
],
circle: Annotated[
list[str],
Query(
title="Cutout circle positions",
description=(
"Circles to cut out. The value must be the ra and dec of"
" the center of the circle and then the radius, as"
" double-precision floating point numbers expressed as"
" strings and separated by spaces."
),
),
],
) -> CutoutParameters:
return CutoutParameters(
ids=id,
stencils=[CircleStencil.from_string(c) for c in circle],
)
The body here is identical to the body of the dependency for POST
.
The difference is in how the parameters are defined (Query
vs. Form
).
As in the other cases, you will then need to pass a UWSRoute
object as the sync_get_route
argument to UWSAppSettings.build_uws_config
.
Here is an example:
from safir.uws import UWSRoute
from .dependencies import get_params_dependency
sync_post_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."
),
)
This would then be passed as the sync_post_route
argument.
Next steps¶
Write the backend worker Write the backend worker