From f9828d051b059c9d19ab3aeeaa307f151a930c2e Mon Sep 17 00:00:00 2001 From: Sena Date: Sun, 27 Sep 2026 20:50:18 +0300 Subject: [PATCH] service: accept 200 OK as a successful update_entity response update_entity() treated anything but 204 No Content as a failure. OData V2 answers a successful update with 204, but some services reply 200 OK instead; SAP SuccessFactors does so for PUT, so updates that had succeeded on the server raised HttpError on the client. Accept both 200 and 204, as the other request handlers already accept 200. Any other status still raises HttpError. Refs #136 (the 200/204 part only; UPSERT and batch are separate) --- CHANGELOG.md | 4 ++++ pyodata/v2/service.py | 5 ++++- tests/test_service_v2.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc46495..0597a77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Fixed + +- service: `update_entity` accepts `200 OK` as well as `204 No Content`, so updates against services such as SAP SuccessFactors no longer raise `HttpError` (#136) - Sena Köse + ## [1.12.1] ### Fixed diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 10b5d7a..05449d4 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -23,6 +23,7 @@ HTTP_CODE_OK = 200 HTTP_CODE_CREATED = 201 +HTTP_CODE_NO_CONTENT = 204 def urljoin(*path): @@ -1594,7 +1595,9 @@ def update_entity(self, key=None, method=None, encode_path=True, **kwargs): def update_entity_handler(response): """Gets modified entity encoded in HTTP Response""" - if response.status_code != 204: + # OData V2 answers a successful update with 204 No Content, but some services + # (e.g. SAP SuccessFactors) reply 200 OK instead. + if response.status_code not in (HTTP_CODE_OK, HTTP_CODE_NO_CONTENT): raise HttpError('HTTP modify request for Entity Set {} failed with status code {}' .format(self._name, response.status_code), response) diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index 24793bf..276c9d9 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -756,6 +756,36 @@ def test_update_entity(service): request.execute() +@responses.activate +@pytest.mark.parametrize('status', [200, 204]) +def test_update_entity_accepts_ok_and_no_content(service, status): + """Update succeeds whether the service answers 204 No Content or 200 OK (e.g. SuccessFactors, #136)""" + + # pylint: disable=redefined-outer-name + responses.add(responses.PATCH, f"{service.url}/{quote('Employees(23)')}", status=status) + + request = service.entity_sets.Employees.update_entity(23) + request.set(NameFirst='Jane') + + assert request.execute() is None + + +@responses.activate +def test_update_entity_rejects_unexpected_status(service): + """Update still raises HttpError for a status that does not mean success""" + + # pylint: disable=redefined-outer-name + responses.add(responses.PATCH, f"{service.url}/{quote('Employees(23)')}", status=400) + + request = service.entity_sets.Employees.update_entity(23) + request.set(NameFirst='Jane') + + with pytest.raises(HttpError) as e_info: + request.execute() + + assert str(e_info.value) == 'HTTP modify request for Entity Set Employees failed with status code 400' + + @responses.activate def test_delete_entity(service): """Check deleting of entity"""