diff --git a/api/controllers/console/wraps.py b/api/controllers/console/wraps.py index 8e353c6c980..7850f4d206a 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -661,7 +661,8 @@ def model_validate[T, M: BaseModel, **P, R]( """Validate request data and inject the model instance as the first arg after self. Source is determined by HTTP method: - GET/DELETE -> request.args + GET -> request.args + DELETE -> request.args, falling back to JSON body when the query string is empty POST/PUT/PATCH -> JSON body """ @@ -670,8 +671,10 @@ def model_validate[T, M: BaseModel, **P, R]( ) -> Callable[Concatenate[T, P], R]: @wraps(view) def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> R: - if request.method in ("GET", "DELETE"): + if request.method == "GET": raw = request.args.to_dict(flat=True) + elif request.method == "DELETE": + raw = request.args.to_dict(flat=True) or (request.get_json(silent=True) or {}) else: raw = request.get_json(silent=True) or {} diff --git a/api/tests/unit_tests/controllers/console/test_wraps.py b/api/tests/unit_tests/controllers/console/test_wraps.py index a9688112153..ee9a54a21cd 100644 --- a/api/tests/unit_tests/controllers/console/test_wraps.py +++ b/api/tests/unit_tests/controllers/console/test_wraps.py @@ -670,6 +670,49 @@ class TestModelValidationInjection: assert payload == self.Payload(name="alpha", count=2) + def test_should_inject_delete_payload_from_query_params(self): + app = Flask(__name__) + + class Handler: + @model_validate(TestModelValidationInjection.Payload) + def delete(self, payload: TestModelValidationInjection.Payload): + return payload + + with app.test_request_context("/items?name=alpha&count=2", method="DELETE"): + payload = Handler().delete() + + assert payload == self.Payload(name="alpha", count=2) + + def test_should_inject_delete_payload_from_json_body(self): + app = Flask(__name__) + + class Handler: + @model_validate(TestModelValidationInjection.Payload) + def delete(self, payload: TestModelValidationInjection.Payload): + return payload + + with app.test_request_context("/items", method="DELETE", json={"name": "alpha", "count": 2}): + payload = Handler().delete() + + assert payload == self.Payload(name="alpha", count=2) + + def test_should_prefer_delete_query_params_over_json_body(self): + app = Flask(__name__) + + class Handler: + @model_validate(TestModelValidationInjection.Payload) + def delete(self, payload: TestModelValidationInjection.Payload): + return payload + + with app.test_request_context( + "/items?name=alpha&count=2", + method="DELETE", + json={"name": "beta", "count": 9}, + ): + payload = Handler().delete() + + assert payload == self.Payload(name="alpha", count=2) + def test_should_raise_unprocessable_entity_for_invalid_payload(self): app = Flask(__name__)