Skip to content

Commit

Permalink
Merge pull request #70 from lsst-sqre/tickets/DM-43264
Browse files Browse the repository at this point in the history
DM-43264: Adopt Safir ClientRequestError and Slack messaging for internal errors
  • Loading branch information
jonathansick authored Mar 13, 2024
2 parents 1d88a1e + 9d91b28 commit a623beb
Show file tree
Hide file tree
Showing 20 changed files with 594 additions and 270 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repos:
- id: check-toml

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.3.0
rev: v0.3.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ Collect fragments into this file with: scriv collect --version X.Y.Z

<!-- scriv-insert-here -->

<a id='changelog-0.10.0'></a>

## 0.10.0 (2024-03-13)

### New features

- We've adopted Safir's `safir.fastapi.ClientRequestError` so that errors like 404 and 422 (input validation) now use the same error format as FastAPI uses for its built-in model validation errors. For parameter errors from endpoints like `GET /v1/pages:page/html` the parameter name is now part of the `loc` field in the error message.
- Times Square and its worker now send uncaught exceptions to a Slack webhook for better error reporting. The webhook URL is set in the `TS_SLACK_WEBHOOK_URL` environment variable.

### Other changes

- Updated to Python 3.12
- Updated to Pydantic 2
- Adopted Ruff for linting and formatting, replacing black, flake8, and isort.
- Switch to using Annotated for Pydantic models and FastAPI path functions.

<a id='changelog-0.9.2'></a>

## 0.9.2 (2023-09-21)
Expand Down
6 changes: 0 additions & 6 deletions changelog.d/20240306_111612_jsick_DM_43173.md

This file was deleted.

98 changes: 49 additions & 49 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -492,34 +492,34 @@ mdurl==0.1.2 \
# via
# -c requirements/main.txt
# markdown-it-py
mypy==1.8.0 \
--hash=sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6 \
--hash=sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d \
--hash=sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02 \
--hash=sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d \
--hash=sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3 \
--hash=sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3 \
--hash=sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3 \
--hash=sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66 \
--hash=sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259 \
--hash=sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835 \
--hash=sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd \
--hash=sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d \
--hash=sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8 \
--hash=sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07 \
--hash=sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b \
--hash=sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e \
--hash=sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6 \
--hash=sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae \
--hash=sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9 \
--hash=sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d \
--hash=sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a \
--hash=sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592 \
--hash=sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218 \
--hash=sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817 \
--hash=sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4 \
--hash=sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410 \
--hash=sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55
mypy==1.9.0 \
--hash=sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6 \
--hash=sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913 \
--hash=sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129 \
--hash=sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc \
--hash=sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974 \
--hash=sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374 \
--hash=sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150 \
--hash=sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03 \
--hash=sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9 \
--hash=sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02 \
--hash=sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89 \
--hash=sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2 \
--hash=sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d \
--hash=sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3 \
--hash=sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612 \
--hash=sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e \
--hash=sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3 \
--hash=sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e \
--hash=sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd \
--hash=sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04 \
--hash=sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed \
--hash=sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185 \
--hash=sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf \
--hash=sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b \
--hash=sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4 \
--hash=sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f \
--hash=sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6
# via
# -r requirements/dev.in
# sqlalchemy
Expand All @@ -535,9 +535,9 @@ nodeenv==1.8.0 \
--hash=sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2 \
--hash=sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec
# via pre-commit
packaging==23.2 \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
# via
# -c requirements/main.txt
# pydata-sphinx-theme
Expand Down Expand Up @@ -677,17 +677,17 @@ pygments==2.17.2 \
pylatexenc==2.10 \
--hash=sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3
# via documenteer
pytest==8.0.2 \
--hash=sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd \
--hash=sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096
pytest==8.1.1 \
--hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \
--hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044
# via
# -r requirements/dev.in
# pytest-asyncio
# pytest-cov
# pytest-mock
pytest-asyncio==0.23.5 \
--hash=sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675 \
--hash=sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac
pytest-asyncio==0.23.5.post1 \
--hash=sha256:30f54d27774e79ac409778889880242b0403d09cabd65b727ce90fe92dd5d80e \
--hash=sha256:b9a8806bea78c21276bc34321bbf234ba1b2ea5b30d9f0ce0f2dea45e4685813
# via -r requirements/dev.in
pytest-cov==4.1.0 \
--hash=sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6 \
Expand Down Expand Up @@ -1052,17 +1052,17 @@ tomlkit==0.12.4 \
--hash=sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b \
--hash=sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3
# via documenteer
types-pyopenssl==24.0.0.20240228 \
--hash=sha256:a472cf877a873549175e81972f153f44e975302a3cf17381eb5f3d41ccfb75a4 \
--hash=sha256:cd990717d8aa3743ef0e73e0f462e64b54d90c304249232d48fece4f0f7c3c6a
types-pyopenssl==24.0.0.20240311 \
--hash=sha256:6e8e8bfad34924067333232c93f7fc4b369856d8bea0d5c9d1808cb290ab1972 \
--hash=sha256:7bca00cfc4e7ef9c5d2663c6a1c068c35798e59670595439f6296e7ba3d58083
# via types-redis
types-pyyaml==6.0.12.12 \
--hash=sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062 \
--hash=sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24
types-pyyaml==6.0.12.20240311 \
--hash=sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342 \
--hash=sha256:b845b06a1c7e54b8e5b4c683043de0d9caf205e7434b3edc678ff2411979b8f6
# via -r requirements/dev.in
types-redis==4.6.0.20240218 \
--hash=sha256:5103d7e690e5c74c974a161317b2d59ac2303cf8bef24175b04c2a4c3486cb39 \
--hash=sha256:dc9c45a068240e33a04302aec5655cf41e80f91eecffccbb2df215b2f6fc375d
types-redis==4.6.0.20240311 \
--hash=sha256:6b9d68a29aba1ee400c823d8e5fe88675282eb69d7211e72fe65dbe54b33daca \
--hash=sha256:e049bbdff0e0a1f8e701b64636811291d21bff79bf1e7850850a44055224a85f
# via -r requirements/dev.in
typing-extensions==4.10.0 \
--hash=sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475 \
Expand All @@ -1085,9 +1085,9 @@ urllib3==2.2.1 \
# via
# documenteer
# requests
uvicorn==0.27.1 \
--hash=sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a \
--hash=sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4
uvicorn==0.28.0 \
--hash=sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1 \
--hash=sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067
# via
# -c requirements/main.txt
# -r requirements/dev.in
Expand Down
22 changes: 9 additions & 13 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ arq==0.25.0 \
--hash=sha256:d176ebadfba920c039dc578814d19b7814d67fa15f82fdccccaedb4330d65dae \
--hash=sha256:db072d0f39c0bc06b436db67ae1f315c81abc1527563b828955670531815290b
# via safir
async-timeout==4.0.3 \
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
# via redis
asyncpg==0.29.0 \
--hash=sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9 \
--hash=sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7 \
Expand Down Expand Up @@ -598,9 +594,9 @@ nbformat==5.9.2 \
# -r requirements/main.in
# nbclient
# nbconvert
packaging==23.2 \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
# via
# gunicorn
# nbconvert
Expand Down Expand Up @@ -879,9 +875,9 @@ pyzmq==25.1.2 \
--hash=sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872 \
--hash=sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30
# via jupyter-client
redis[hiredis]==5.0.2 \
--hash=sha256:3f82cc80d350e93042c8e6e7a5d0596e4dd68715babffba79492733e1f367037 \
--hash=sha256:4caa8e1fcb6f3c0ef28dba99535101d80934b7d4cd541bbb47f4a3826ee472d1
redis[hiredis]==5.0.3 \
--hash=sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580 \
--hash=sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d
# via
# arq
# safir
Expand Down Expand Up @@ -1119,9 +1115,9 @@ uritemplate==4.1.1 \
--hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \
--hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e
# via gidgethub
uvicorn[standard]==0.27.1 \
--hash=sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a \
--hash=sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4
uvicorn[standard]==0.28.0 \
--hash=sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1 \
--hash=sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067
# via -r requirements/main.in
uvloop==0.19.0 \
--hash=sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd \
Expand Down
2 changes: 1 addition & 1 deletion src/timessquare/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""The application version string (PEP 440 / SemVer compatible)."""

try:
__version__ = version(__name__)
__version__ = version("times-square")
except PackageNotFoundError:
# package is not installed
__version__ = "0.0.0"
10 changes: 10 additions & 0 deletions src/timessquare/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ class Config(BaseSettings):
),
]

slack_webhook_url: Annotated[
HttpUrl | None,
Field(
alias="TS_SLACK_WEBHOOK_URL",
description=(
"Webhook URL for sending error messages to a Slack channel."
),
),
] = None

@field_validator("path_prefix")
@classmethod
def validate_path_prefix(cls, v: str) -> str:
Expand Down
32 changes: 21 additions & 11 deletions src/timessquare/domain/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@ def read_ipynb(source: str) -> nbformat.NotebookNode:
try:
return nbformat.reads(source, as_version=NB_VERSION)
except Exception as e:
raise PageNotebookFormatError(str(e)) from e
message = f"The notebook is not a valid ipynb file.\n\n{e}"
raise PageNotebookFormatError(message) from e

@staticmethod
def write_ipynb(notebook: nbformat.NotebookNode) -> str:
Expand Down Expand Up @@ -399,9 +400,9 @@ def validate_parameter_name(name: str) -> None:
They also cannot be Python keywords.
"""
if parameter_name_pattern.match(name) is None:
raise ParameterNameValidationError(name)
raise ParameterNameValidationError.for_param(name)
if keyword.iskeyword(name):
raise ParameterNameValidationError(name)
raise ParameterNameValidationError.for_param(name)

def resolve_and_validate_values(
self, requested_values: Mapping[str, Any]
Expand Down Expand Up @@ -429,14 +430,16 @@ def resolve_and_validate_values(
try:
cast_values[name] = self.parameters[name].cast_value(value)
except PageParameterValueCastingError as e:
raise PageParameterError(
raise PageParameterError.for_param(
name, value, self.parameters[name]
) from e

# Ensure each parameter's value is valid
for name, value in cast_values.items():
if not self.parameters[name].validate(value):
raise PageParameterError(name, value, self.parameters[name])
raise PageParameterError.for_param(
name, value, self.parameters[name]
)

return cast_values

Expand Down Expand Up @@ -568,14 +571,17 @@ def create_and_validate(
try:
Draft202012Validator.check_schema(json_schema)
except jsonschema.exceptions.SchemaError as e:
raise ParameterSchemaError(name, str(e)) from e
message = f"The schema for the {name} parameter is invalid.\n\n{e}"
raise ParameterSchemaError.for_param(name, message) from e

if "default" not in json_schema:
raise ParameterDefaultMissingError(name)
raise ParameterDefaultMissingError.for_param(name)

instance = cls.create(json_schema)
if not instance.validate(json_schema["default"]):
raise ParameterDefaultInvalidError(name, json_schema["default"])
raise ParameterDefaultInvalidError.for_param(
name, json_schema["default"]
)

return instance

Expand Down Expand Up @@ -629,13 +635,17 @@ def cast_value(self, v: Any) -> Any: # noqa: C901 PLR0912
elif v.lower() == "false":
return False
else:
raise PageParameterValueCastingError(v, schema_type)
raise PageParameterValueCastingError.for_value(
v, schema_type
)
else:
return v
else:
raise PageParameterValueCastingError(v, schema_type)
raise PageParameterValueCastingError.for_value(v, schema_type)
except ValueError as e:
raise PageParameterValueCastingError(v, schema_type) from e
raise PageParameterValueCastingError.for_value(
v, schema_type
) from e


@dataclass
Expand Down
Loading

0 comments on commit a623beb

Please sign in to comment.