From 3d5ea27347ea52e7e5d90abd9a5f0a9822c25361 Mon Sep 17 00:00:00 2001 From: Emil B Date: Fri, 17 Apr 2026 11:45:49 +0200 Subject: [PATCH 1/2] Return a list of EntityProxies when FunctionImport return type is a Collection --- CHANGELOG.md | 2 ++ pyodata/v2/service.py | 9 +++++++++ tests/metadata.xml | 2 +- tests/test_service_v2.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f0b7df..f952c045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- service: let FunctionRequests return a list of EntityProxies instead of the raw json, when the `ReturnType` is a Collection. - Emil B. + ## [1.11.2] ### Fixed diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 6c1aa3f2..9b660754 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -1712,6 +1712,15 @@ def function_import_handler(fimport, response): entity_set = self._service.schema.entity_set(fimport.entity_set_name) return EntityProxy(self._service, entity_set, fimport.return_type, response_data) + # 1.b alternatively, if return type is a Collection, return a list of appropriate entity proxy + if isinstance(fimport.return_type, model.Collection): + entity_set = self._service.schema.entity_set(fimport.entity_set_name) + collection_item_type = fimport.return_type.item_type + collection = [] + for entity in response_data['results']: + collection.append(EntityProxy(self._service, entity_set, collection_item_type, entity)) + return collection + # 2. return raw data for all other return types (primitives, complex types encoded in dicts, etc.) return response_data diff --git a/tests/metadata.xml b/tests/metadata.xml index a9814699..fd03d4f1 100644 --- a/tests/metadata.xml +++ b/tests/metadata.xml @@ -384,7 +384,7 @@ m:HttpMethod="GET"/> + EntitySet="TemperatureMeasurements" m:HttpMethod="GET"/> diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index f0d5bc54..5d9bd329 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -587,6 +587,40 @@ def test_function_import_entity(service): assert result.Sensor == 'Sensor-address' assert result.Value == 456.8 +@responses.activate +def test_function_import_collection(service): + """Function call with collection return type""" + + # pylint: disable=redefined-outer-name + + responses.add( + responses.GET, + f'{service.url}/get_best_measurements', + headers={'Content-type': 'application/json'}, + json={'d': {'results' : [{ + 'Sensor': 'sensor2', + 'Date': "/Date(1776417069000)/", + 'Value': '29.8d' + }, { + 'Sensor': 'sensor4', + 'Date': "/Date(1776417578000)/", + 'Value': '30.2d' + }]}}, + status=200) + + result = service.functions.get_best_measurements.execute() + assert isinstance(result, list) + assert len(result) == 2 + + assert isinstance(result[0], pyodata.v2.service.EntityProxy) + assert result[0].Sensor == 'sensor2' + assert result[0].Date == datetime.datetime(2026, 4, 17, 9, 11, 9, tzinfo=datetime.timezone.utc) + assert result[0].Value == 29.8 + + assert isinstance(result[1], pyodata.v2.service.EntityProxy) + assert result[1].Sensor == 'sensor4' + assert result[1].Date == datetime.datetime(2026, 4, 17, 9, 19, 38, tzinfo=datetime.timezone.utc) + assert result[1].Value == 30.2 @responses.activate def test_update_entity(service): From 6206c247401bfee7553e43c572c7f4b743ff92e4 Mon Sep 17 00:00:00 2001 From: Emil B <44379110+Ixaruz@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:31:43 +0200 Subject: [PATCH 2/2] Don't use a plain list of EntityProxies but a ListWithTotalCount --- pyodata/v2/service.py | 9 +++++++- tests/test_service_v2.py | 50 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 9b660754..f1f73e51 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -1714,9 +1714,16 @@ def function_import_handler(fimport, response): # 1.b alternatively, if return type is a Collection, return a list of appropriate entity proxy if isinstance(fimport.return_type, model.Collection): + total_count = None + next_url = None + if '__count' in response_data: + total_count = int(response_data['__count']) + if '__next' in response_data: + next_url = response_data['__next'] + collection = ListWithTotalCount(total_count, next_url) + entity_set = self._service.schema.entity_set(fimport.entity_set_name) collection_item_type = fimport.return_type.item_type - collection = [] for entity in response_data['results']: collection.append(EntityProxy(self._service, entity_set, collection_item_type, entity)) return collection diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index 5d9bd329..1ca22d62 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -605,7 +605,8 @@ def test_function_import_collection(service): 'Sensor': 'sensor4', 'Date': "/Date(1776417578000)/", 'Value': '30.2d' - }]}}, + }], + }}, status=200) result = service.functions.get_best_measurements.execute() @@ -622,6 +623,53 @@ def test_function_import_collection(service): assert result[1].Date == datetime.datetime(2026, 4, 17, 9, 19, 38, tzinfo=datetime.timezone.utc) assert result[1].Value == 30.2 + +@responses.activate +def test_function_import_collection_with_pagination_metadata(service): + """Function call with collection return type including pagination metadata + + This test demonstrates that both __next and __count metadata should be preserved + when a FunctionImport returns a partial Collection with inline count. + Per OData v2 spec, this metadata is part of the Collection response format. + """ + + # pylint: disable=redefined-outer-name + + + responses.add( + responses.GET, + f'{service.url}/get_best_measurements?$inlinecount=allpages', + headers={'Content-type': 'application/json'}, + json={'d': {'results' : [{ + 'Sensor': 'sensor2', + 'Date': "/Date(1776417069000)/", + 'Value': '29.8d' + }, { + 'Sensor': 'sensor4', + 'Date': "/Date(1776417578000)/", + 'Value': '30.2d' + }], + '__count': '50', + '__next': f"{service.url}/get_best_measurements?$skiptoken=2" + }}, + status=200) + + + result = service.functions.get_best_measurements.count(inline=True).execute() + + assert isinstance(result, list) + assert len(result) == 2 + assert isinstance(result[0], pyodata.v2.service.EntityProxy) + assert isinstance(result[1], pyodata.v2.service.EntityProxy) + + assert hasattr(result, 'total_count'), \ + "FunctionImport Collection should preserve __count like get_entities()" + assert result.total_count == 50 + + assert hasattr(result, 'next_url'), \ + "FunctionImport Collection should preserve __next like get_entities()" + assert result.next_url == f"{service.url}/get_best_measurements?$skiptoken=2" + @responses.activate def test_update_entity(service): """Check updating of entity properties"""