diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f0b7d..f952c04 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 6c1aa3f..f1f73e5 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -1712,6 +1712,22 @@ 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): + 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 + 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 a981469..fd03d4f 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 f0d5bc5..1ca22d6 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -587,6 +587,88 @@ 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_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):