From d2aa2d7fbb412d4fda5b67bc1a76df0e1aa075e4 Mon Sep 17 00:00:00 2001 From: Vitor Coelho <6161176+VitorCoelho@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:10:01 -0300 Subject: [PATCH 1/3] Refactor 'in' case to use list comprehension --- pyodata/v2/service.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 5b526a8..c566c44 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -1298,7 +1298,11 @@ def _build_expression(self, field_name, operator, value): high_bound = target_field.to_literal(value[1]) return f'{field_name} gte {low_bound} and {field_name} lte {high_bound}' case 'in': - literal_values = (f'{field_name} eq {target_field.to_literal(item)}' for item in value) + literal_values = [f'{field_name} eq {target_field.to_literal(item)}' for item in value] + if len(literal_values) > 1: + # 'and' binds tighter than 'or', so the alternatives must be + # grouped to stay correct when combined with other lookups + return f"({' or '.join(literal_values)})" return ' or '.join(literal_values) case 'eq': return f'{field_name} eq {value}' From f08e4097461d0d0ce995ac3d2d90dbdb7395f42d Mon Sep 17 00:00:00 2001 From: Vitor Coelho <6161176+VitorCoelho@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:10:17 -0300 Subject: [PATCH 2/3] Enhance tests for employee count with filters Added tests for counting employees with chainable filters. --- tests/test_service_v2.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index 276c9d9..d0d1aad 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -2601,7 +2601,7 @@ def test_count_with_chainable_filter_in_operator(service): responses.add( responses.GET, - f"{service.url}/Employees/$count?$filter=ID%20eq%201%20or%20ID%20eq%202%20or%20ID%20eq%203", + f"{service.url}/Employees/$count?$filter=%28ID%20eq%201%20or%20ID%20eq%202%20or%20ID%20eq%203%29", json=3, status=200) @@ -2613,6 +2613,37 @@ def test_count_with_chainable_filter_in_operator(service): assert request.execute() == 3 +@responses.activate +def test_count_with_chainable_filter_in_operator_and_other_lookup(service): + """Check that the alternatives of $filter in are grouped when combined with another lookup""" + + # pylint: disable=redefined-outer-name + + responses.add( + responses.GET, + f"{service.url}/Employees/$count?$filter=%28ID%20eq%201%20or%20ID%20eq%202%29%20and%20NickName%20eq%20%27Tim%27", + json=1, + status=200) + + employees = service.entity_sets.Employees.get_entities() + request = employees.filter(ID__in=[1, 2], NickName="Tim").count() + + assert request.get_query_params()['$filter'] == "(ID eq 1 or ID eq 2) and NickName eq 'Tim'" + assert request.execute() == 1 + + +@responses.activate +def test_count_with_chainable_filter_in_operator_single_value(service): + """Check that $filter in with a single value is a plain comparison""" + + # pylint: disable=redefined-outer-name + + employees = service.entity_sets.Employees.get_entities() + request = employees.filter(ID__in=[1]).count() + + assert request.get_query_params()['$filter'] == "ID eq 1" + + @responses.activate def test_count_with_chainable_filter_startswith_operator(service): """Check getting $count with $filter in""" @@ -3204,4 +3235,4 @@ def hook(response): def test_service_without_response_hook_works(service): """response_hook defaults to None and does not affect normal operation""" - assert service.response_hook is None \ No newline at end of file + assert service.response_hook is None From 54f9cd015b2a5d36241f144989496df46ca34d58 Mon Sep 17 00:00:00 2001 From: Vitor Coelho <6161176+VitorCoelho@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:10:29 -0300 Subject: [PATCH 3/3] Update CHANGELOG with recent fixes Document fixes related to filter lookup and entity updates. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0597a77..3af033d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- service: the `__in` filter lookup wraps its `or` alternatives in parentheses, so combining it with another lookup (e.g. `filter(ID__in=[1, 2], NickName='Tim')`) no longer changes the meaning of the filter - Vitor Coelho - 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]