Skip to content

Commit

Permalink
Add Slack reporting of uncaught exceptions
Browse files Browse the repository at this point in the history
Add support for reporting uncaught exceptions in route handlers to
Slack if a Slack incoming webhook is configured for that purpose.
Add tests to verify that normal user errors don't trigger Slack
exceptions.

Tidy up development dependencies and add pytest-sugar.
  • Loading branch information
rra committed Jun 6, 2024
1 parent 7df1328 commit a2501d0
Show file tree
Hide file tree
Showing 21 changed files with 221 additions and 146 deletions.
3 changes: 3 additions & 0 deletions changelog.d/20240606_141501_rra_DM_44606.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### New features

- Add support for sending Slack notifications for uncaught exceptions in route handlers.
9 changes: 3 additions & 6 deletions requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,17 @@

-c main.txt

# Development
pre-commit
tox
tox-uv

# Testing and linting
# Testing
asgi-lifespan
coverage[toml]
httpx
mypy
pytest
pytest-asyncio
pytest-cov
pytest-sugar
pytest-timeout
respx
sqlalchemy[mypy]

# Documentation
Expand Down
60 changes: 10 additions & 50 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,12 @@ asgi-lifespan==2.1.0
# via -r requirements/dev.in
attrs==23.2.0
# via scriv
cachetools==5.3.3
# via
# -c requirements/main.txt
# tox
certifi==2024.6.2
# via
# -c requirements/main.txt
# httpcore
# httpx
# requests
cfgv==3.4.0
# via pre-commit
chardet==5.2.0
# via tox
charset-normalizer==3.3.2
# via
# -c requirements/main.txt
Expand All @@ -33,18 +25,10 @@ click==8.1.7
# scriv
click-log==0.4.0
# via scriv
colorama==0.4.6
# via tox
coverage==7.5.3
# via
# -r requirements/dev.in
# pytest-cov
distlib==0.3.8
# via virtualenv
filelock==3.14.0
# via
# tox
# virtualenv
greenlet==3.0.3
# via
# -c requirements/main.txt
Expand All @@ -61,8 +45,7 @@ httpx==0.27.0
# via
# -c requirements/main.txt
# -r requirements/dev.in
identify==2.5.36
# via pre-commit
# respx
idna==3.7
# via
# -c requirements/main.txt
Expand Down Expand Up @@ -93,47 +76,34 @@ mypy==1.10.0
# sqlalchemy
mypy-extensions==1.0.0
# via mypy
nodeenv==1.9.1
# via pre-commit
packaging==24.0
# via
# -c requirements/main.txt
# pyproject-api
# pytest
# tox
# tox-uv
platformdirs==4.2.2
# via
# tox
# virtualenv
# pytest-sugar
pluggy==1.5.0
# via
# pytest
# tox
pre-commit==3.7.1
# via -r requirements/dev.in
pyproject-api==1.6.1
# via tox
# via pytest
pytest==8.2.2
# via
# -r requirements/dev.in
# pytest-asyncio
# pytest-cov
# pytest-sugar
# pytest-timeout
pytest-asyncio==0.23.7
# via -r requirements/dev.in
pytest-cov==5.0.0
# via -r requirements/dev.in
pytest-sugar==1.0.0
# via -r requirements/dev.in
pytest-timeout==2.3.1
# via -r requirements/dev.in
pyyaml==6.0.1
# via
# -c requirements/main.txt
# pre-commit
requests==2.32.3
# via
# -c requirements/main.txt
# scriv
respx==0.21.1
# via -r requirements/dev.in
scriv==1.5.1
# via -r requirements/dev.in
sniffio==1.3.1
Expand All @@ -146,12 +116,8 @@ sqlalchemy==2.0.30
# via
# -c requirements/main.txt
# -r requirements/dev.in
tox==4.15.1
# via
# -r requirements/dev.in
# tox-uv
tox-uv==1.9.0
# via -r requirements/dev.in
termcolor==2.4.0
# via pytest-sugar
typing-extensions==4.12.1
# via
# -c requirements/main.txt
Expand All @@ -161,9 +127,3 @@ urllib3==2.2.1
# via
# -c requirements/main.txt
# requests
uv==0.2.8
# via tox-uv
virtualenv==20.26.2
# via
# pre-commit
# tox
2 changes: 1 addition & 1 deletion requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ rich==13.7.1
# via typer
rsa==4.9
# via google-auth
safir @ git+https://github.com/lsst-sqre/safir@9ce8b39d810df41cf028afe1c8befea5613951a8
safir @ git+https://github.com/lsst-sqre/safir@53062ff688230856f89ce9a82b9ec3036dcb4beb
# via -r requirements/main.in
shellingham==1.5.4
# via typer
Expand Down
34 changes: 8 additions & 26 deletions requirements/tox.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# uv pip compile --output-file requirements/tox.txt requirements/tox.in
cachetools==5.3.3
# via
# -c requirements/dev.txt
# -c requirements/main.txt
# tox
certifi==2024.6.2
Expand All @@ -11,27 +10,20 @@ certifi==2024.6.2
# -c requirements/main.txt
# requests
chardet==5.2.0
# via
# -c requirements/dev.txt
# tox
# via tox
charset-normalizer==3.3.2
# via
# -c requirements/dev.txt
# -c requirements/main.txt
# requests
colorama==0.4.6
# via
# -c requirements/dev.txt
# tox
# via tox
distlib==0.3.8
# via
# -c requirements/dev.txt
# virtualenv
# via virtualenv
docker==7.1.0
# via tox-docker
filelock==3.14.0
# via
# -c requirements/dev.txt
# tox
# virtualenv
idna==3.7
Expand All @@ -48,45 +40,35 @@ packaging==24.0
# tox-uv
platformdirs==4.2.2
# via
# -c requirements/dev.txt
# tox
# virtualenv
pluggy==1.5.0
# via
# -c requirements/dev.txt
# tox
pyproject-api==1.6.1
# via
# -c requirements/dev.txt
# tox
# via tox
requests==2.32.3
# via
# -c requirements/dev.txt
# -c requirements/main.txt
# docker
tox==4.15.1
# via
# -c requirements/dev.txt
# -r requirements/tox.in
# tox-docker
# tox-uv
tox-docker==5.0.0
# via -r requirements/tox.in
tox-uv==1.9.0
# via
# -c requirements/dev.txt
# -r requirements/tox.in
# via -r requirements/tox.in
urllib3==2.2.1
# via
# -c requirements/dev.txt
# -c requirements/main.txt
# docker
# requests
uv==0.2.8
# via
# -c requirements/dev.txt
# tox-uv
uv==0.2.9
# via tox-uv
virtualenv==20.26.2
# via
# -c requirements/dev.txt
# tox
# via tox
16 changes: 12 additions & 4 deletions src/vocutouts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,19 @@ class Config(BaseSettings):
),
)

slack_webhook: SecretStr | None = Field(
None,
title="Slack webhook for alerts",
description="If set, alerts will be posted to this Slack webhook",
)

storage_url: str = Field(
...,
title="Root URL for cutout results",
description=(
"Must be an ``s3`` URL pointing to a Google Cloud Storage bucket"
" that is writable by the backend and readable by the frontend."
"Must be a ``gs`` or ``s3`` URL pointing to a Google Cloud Storage"
" bucket that is writable by the backend and readable by the"
" frontend."
),
)

Expand Down Expand Up @@ -200,13 +207,14 @@ def arq_redis_settings(self) -> RedisSettings:
def uws_config(self) -> UWSConfig:
"""Corresponding configuration for the UWS subsystem."""
return UWSConfig(
arq_mode=self.arq_mode,
arq_redis_settings=self.arq_redis_settings,
execution_duration=self.timeout,
lifetime=self.lifetime,
database_url=self.database_url,
database_password=self.database_password,
arq_mode=self.arq_mode,
arq_redis_settings=self.arq_redis_settings,
signing_service_account=self.service_account,
slack_webhook=self.slack_webhook,
)


Expand Down
21 changes: 11 additions & 10 deletions src/vocutouts/handlers/external.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
auth_logger_dependency,
)
from safir.metadata import get_metadata
from safir.slack.webhook import SlackRouteErrorHandler
from structlog.stdlib import BoundLogger

from ..config import config
Expand All @@ -27,9 +28,7 @@
from ..uws.handlers import uws_router
from ..uws.models import ExecutionPhase, UWSJobParameter

__all__ = ["external_router"]

external_router = APIRouter()
router = APIRouter(route_class=SlackRouteErrorHandler)
"""FastAPI router for all external handlers."""

_CAPABILITIES_TEMPLATE = """
Expand Down Expand Up @@ -61,8 +60,10 @@
</capabilities>
"""

__all__ = ["router"]


@external_router.get(
@router.get(
"/",
response_model=Index,
response_model_exclude_none=True,
Expand All @@ -87,7 +88,7 @@ async def get_index() -> Index:
return Index(metadata=metadata)


@external_router.get(
@router.get(
"/availability",
description="VOSI-availability resource for the image cutout service",
responses={200: {"content": {"application/xml": {}}}},
Expand All @@ -103,7 +104,7 @@ async def get_availability(
return templates.availability(request, availability)


@external_router.get(
@router.get(
"/capabilities",
description="VOSI-capabilities resource for the image cutout service",
responses={200: {"content": {"application/xml": {}}}},
Expand Down Expand Up @@ -177,7 +178,7 @@ async def _sync_request(
return RedirectResponse(result.url, status_code=303)


@external_router.get(
@router.get(
"/sync",
description=(
"Synchronously request a cutout. This will wait for the cutout to be"
Expand Down Expand Up @@ -272,7 +273,7 @@ async def get_sync(
)


@external_router.post(
@router.post(
"/sync",
description=(
"Synchronously request a cutout. This will wait for the cutout to be"
Expand Down Expand Up @@ -473,7 +474,7 @@ async def create_job(
return str(request.url_for("get_job", job_id=job.job_id))


# Add the UWS routes to our external routes. This must be done after defining
# Add the UWS routes to our external routes. This must be done after defining
# the POST handler for /jobs because of oddities in the implementation details
# of include_router.
external_router.include_router(uws_router, prefix="/jobs")
router.include_router(uws_router, prefix="/jobs")
Loading

0 comments on commit a2501d0

Please sign in to comment.