"""A request the app refuses, said in a way the UI can put in the reader's language. Every refusal the API answers carries a structured `detail` — `{code, reason, …params}` — and the client translates it (`lib/api/refusalText.ts` `localizeDetail`). A bare English string reached the Hungarian UI verbatim: the blocking error dialog, and every inline surface that printed `HttpError.detail`, showed whatever sentence the route happened to hand-write. `refused` is the generic family (`errors.refused.`, the params interpolated into the sentence); the older specific codes (`not_found`, `youtube`, `playlist`, the admin refusals, …) stay, since each is already translated. A test (`test_http_details.py`) reads every `HTTPException` in the app and fails on a string detail, and the frontend's contract test (`i18n/backendIds.contract.test.ts`) requires every `code` a detail carries to be in one record of `lib/api/refusalCodes.ts`, and every reason it can be sent with — a literal, or a `Literal` type when it travels in a variable — to have its sentence in both languages. A code or reason that scan cannot read fails it until it is listed with what holds it. So the rule is enforced, not described. """ from fastapi import HTTPException def refused(status: int, reason: str, **params: object) -> HTTPException: """The refusal to raise: `raise refused(409, "view_name_taken", name=name)`. `params` travel in the detail for the sentence to interpolate (`{{name}}`); keep them to values the reader may see.""" return HTTPException(status_code=status, detail={"code": "refused", "reason": reason, **params}) # `sysconfig.InvalidSetting.reason` → the refusal it answers as. A dict of literals, so the frontend's # contract test can read every reason this can produce. _SETTING_REASONS = { "not_a_number": "setting_not_a_number", "too_low": "setting_too_low", "too_high": "setting_too_high", "not_a_choice": "setting_not_a_choice", "no_encryption_key": "no_encryption_key", } def setting_refused(exc) -> HTTPException: """A value the admin entered that `sysconfig.set_value` refused (`exc`: its `InvalidSetting`).""" return refused(400, _SETTING_REASONS[exc.reason], key=exc.key, **exc.params)