Skip to content

return clean 4xx from v1 API without reporting a 500 - #3207

Open
Alc-Alc wants to merge 2 commits into
python:mainfrom
Alc-Alc:api-v1-unsupported-media-type-main
Open

Alc-Alc wants to merge 2 commits into
python:mainfrom
Alc-Alc:api-v1-unsupported-media-type-main

Conversation

@Alc-Alc

@Alc-Alc Alc-Alc commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Description

  • Tastypie's catch-all handler fires got_request_exception even when it returns a 4xx
  • Sentry and structlog log these as 500s even though clients get the correct 4xx (with a generic "try again later" body)

Fix

  • Catch the two exceptions on GenericResource and return 4xx.
  • Status codes are unchanged, the body now carries the real error message under error

Closes

  • Fixes PYDOTORG-PROD-36E (issue)
  • Fixes PYDOTORG-PROD-1MG (issue)
Before / After (DEBUG=False), traceback trimmed for brevity

PYDOTORG-PROD-36E

curl -i -X POST http://localhost:8000/api/v1/pages/page/ -H 'Content-Type: application/yaml' --data 'title: x'

Before (415 status code; server log has a 500 with a traceback, then the 415 warning)

HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json

{"error_message": "Sorry, this request could not be processed. Please try again later."}
[error] request_failed [django_structlog.middlewares.request] code=500 request='POST /api/v1/pages/page/' user_id=None
Traceback (most recent call last):
  ...
  File "<site-packages>/tastypie/serializers.py", line 278, in deserialize
    raise UnsupportedDeserializationFormat(format)
tastypie.exceptions.UnsupportedDeserializationFormat: The format indicated 'application/yaml' had no available deserialization method. Please check your ``formats`` and ``content_types`` on your Serializer.
[warning] "POST /api/v1/pages/page/ HTTP/1.1" 415 88 [django.server] status_code=415

After (415 status code; server log has only the 415 warning)

HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json

{"error": "The format indicated 'application/yaml' had no available deserialization method. Please check your ``formats`` and ``content_types`` on your Serializer."}
[warning] "POST /api/v1/pages/page/ HTTP/1.1" 415 165 [django.server] status_code=415

PYDOTORG-PROD-1MG

curl -i http://localhost:8000/api/v1/downloads/release/latest/

Before (404 status code; server log has a 500 with a traceback, then the 404 warning)

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error_message": "Sorry, this request could not be processed. Please try again later."}
[error] request_failed [django_structlog.middlewares.request] code=500 request='GET /api/v1/downloads/release/latest/' user_id=None
Traceback (most recent call last):
  ...
ValueError: Field 'id' expected a number but got 'latest'.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  ...
  File "<site-packages>/tastypie/resources.py", line 2248, in obj_get
    raise NotFound("Invalid resource lookup data provided (mismatched type).")
tastypie.exceptions.NotFound: Invalid resource lookup data provided (mismatched type).
[warning] "GET /api/v1/downloads/release/latest/ HTTP/1.1" 404 88 [django.server] status_code=404

After (404 status code; server log has only the 404 warning)

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": "Invalid resource lookup data provided (mismatched type)."}
[warning] "GET /api/v1/downloads/release/latest/ HTTP/1.1" 404 69 [django.server] status_code=404

PS: Both issues are fixed here as they share the root cause and the base class. 🙂

@Alc-Alc
Alc-Alc requested a review from JacobCoffee as a code owner November 4, 2026 08:40
@Alc-Alc
Alc-Alc force-pushed the api-v1-unsupported-media-type-main branch from 5bc2783 to e8129f7 Compare November 4, 2026 08:43

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant