From 74e4d2255026704d0a10944b79da10ba306069db Mon Sep 17 00:00:00 2001 From: avichalsri24 Date: Tue, 1 Sep 2026 14:20:02 +0530 Subject: [PATCH 1/3] fix(platform)!: drop inert OData params from list_records The Data Fabric read endpoint (GET .../EntityService/entity/{key}/read) implements only start, limit and expansionLevel. It accepts $filter, $orderby, $select and $expand, then silently ignores them and returns the full unfiltered set. Verified against a live tenant: a filter matching nothing still returned all 32 records, as did deliberately malformed filter syntax, and $select left every column in place. There is no v2 read endpoint that honours them. The four params were added in #1616 alongside the structured query API. Their test asserted only that httpx received them under a mock, so it could not catch that the server drops them. The docstring advertised them as OData support, making the failure mode a silent wrong answer: a filtered query returning every record. Remove them, restoring the pre-#1616 surface plus expansion_level, which does work. Filtering, sorting and projection belong to retrieve_records (POST .../query), which genuinely filters. BREAKING CHANGE: list_records() and list_records_async() no longer accept filter, orderby, select or expand. Passing them now raises TypeError instead of silently returning unfiltered records. Use retrieve_records() with an EntityQueryFilterGroup instead. Generated with Claude Code Co-Authored-By: Claude --- packages/uipath-platform/pyproject.toml | 2 +- .../platform/entities/_entities_service.py | 68 ++++++------------- .../platform/entities/_entity_data_service.py | 36 ++-------- .../tests/services/test_entities_service.py | 14 ++-- 4 files changed, 36 insertions(+), 84 deletions(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 6307ad554..fe8dc8a9c 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.22" +version = "0.2.23" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py index fa2845e63..2c3c84a51 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entities_service.py @@ -593,10 +593,6 @@ def list_records( start: Optional[int] = None, limit: Optional[int] = None, expansion_level: Optional[int] = None, - filter: Optional[str] = None, - orderby: Optional[str] = None, - select: Optional[List[str]] = None, - expand: Optional[List[str]] = None, ) -> EntityRecordsListResponse: """List records from an entity with optional pagination and schema validation. @@ -637,14 +633,6 @@ class CustomerRecord: expansion_level (Optional[int]): Depth of foreign-key expansion in the response (``0`` means no expansion). Higher values inline related records up to that many hops. - filter (Optional[str]): OData ``$filter`` expression - (e.g. ``"status eq 'active'"``). - orderby (Optional[str]): OData ``$orderby`` expression - (e.g. ``"created_at desc"``). - select (Optional[List[str]]): Column projection — field names to - include (rendered as ``$select``). - expand (Optional[List[str]]): Relationship names to expand inline - (rendered as ``$expand``). Returns: EntityRecordsListResponse: A list-compatible response with @@ -674,15 +662,25 @@ class CustomerRecord: "Customers", start=50, limit=50 ) - With OData filter, sorting, projection, and expansion:: + With foreign-key expansion:: - records = entities_service.list_records( + records = entities_service.list_records("Customers", expansion_level=1) + + To filter, sort, or project, use :meth:`retrieve_records` — this + endpoint only pages:: + + result = entities_service.retrieve_records( "Customers", - filter="status eq 'active'", - orderby="created_at desc", - select=["name", "email", "status"], - expand=["company"], - expansion_level=1, + filter_group=EntityQueryFilterGroup( + logical_operator=LogicalOperator.And, + query_filters=[ + EntityQueryFilter( + field_name="status", + operator=QueryFilterOperator.Equals, + value="active", + ) + ], + ), ) With schema validation:: @@ -709,10 +707,6 @@ class CustomerRecord: start=start, limit=limit, expansion_level=expansion_level, - filter=filter, - orderby=orderby, - select=select, - expand=expand, ) @traced(name="entity_list_records", run_type="uipath") @@ -723,10 +717,6 @@ async def list_records_async( start: Optional[int] = None, limit: Optional[int] = None, expansion_level: Optional[int] = None, - filter: Optional[str] = None, - orderby: Optional[str] = None, - select: Optional[List[str]] = None, - expand: Optional[List[str]] = None, ) -> EntityRecordsListResponse: """Asynchronously list records from an entity with optional pagination and schema validation. @@ -767,14 +757,6 @@ class CustomerRecord: expansion_level (Optional[int]): Depth of foreign-key expansion in the response (``0`` means no expansion). Higher values inline related records up to that many hops. - filter (Optional[str]): OData ``$filter`` expression - (e.g. ``"status eq 'active'"``). - orderby (Optional[str]): OData ``$orderby`` expression - (e.g. ``"created_at desc"``). - select (Optional[List[str]]): Column projection — field names to - include (rendered as ``$select``). - expand (Optional[List[str]]): Relationship names to expand inline - (rendered as ``$expand``). Returns: EntityRecordsListResponse: A list-compatible response with @@ -804,17 +786,15 @@ class CustomerRecord: "Customers", start=50, limit=50 ) - With OData filter, sorting, projection, and expansion:: + With foreign-key expansion:: records = await entities_service.list_records_async( - "Customers", - filter="status eq 'active'", - orderby="created_at desc", - select=["name", "email", "status"], - expand=["company"], - expansion_level=1, + "Customers", expansion_level=1 ) + To filter, sort, or project, use :meth:`retrieve_records_async` — + this endpoint only pages. + With schema validation:: class CustomerRecord: @@ -839,10 +819,6 @@ class CustomerRecord: start=start, limit=limit, expansion_level=expansion_level, - filter=filter, - orderby=orderby, - select=select, - expand=expand, ) @traced(name="entity_insert_record", run_type="uipath") diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py index 4dc09855d..1162694b7 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py @@ -144,10 +144,6 @@ def list_records( start: Optional[int] = None, limit: Optional[int] = None, expansion_level: Optional[int] = None, - filter: Optional[str] = None, - orderby: Optional[str] = None, - select: Optional[List[str]] = None, - expand: Optional[List[str]] = None, ) -> EntityRecordsListResponse: """Internal implementation; see :meth:`EntitiesService.list_records`.""" spec = self._list_records_spec( @@ -155,10 +151,6 @@ def list_records( start=start, limit=limit, expansion_level=expansion_level, - filter=filter, - orderby=orderby, - select=select, - expand=expand, ) response = self.request(spec.method, spec.endpoint, params=spec.params) return self._build_records_list_response(response, schema, start, limit) @@ -170,10 +162,6 @@ async def list_records_async( start: Optional[int] = None, limit: Optional[int] = None, expansion_level: Optional[int] = None, - filter: Optional[str] = None, - orderby: Optional[str] = None, - select: Optional[List[str]] = None, - expand: Optional[List[str]] = None, ) -> EntityRecordsListResponse: """Async variant of :meth:`list_records`.""" spec = self._list_records_spec( @@ -181,10 +169,6 @@ async def list_records_async( start=start, limit=limit, expansion_level=expansion_level, - filter=filter, - orderby=orderby, - select=select, - expand=expand, ) response = await self.request_async( spec.method, spec.endpoint, params=spec.params @@ -714,12 +698,14 @@ def _list_records_spec( start: Optional[int] = None, limit: Optional[int] = None, expansion_level: Optional[int] = None, - filter: Optional[str] = None, - orderby: Optional[str] = None, - select: Optional[List[str]] = None, - expand: Optional[List[str]] = None, ) -> RequestSpec: - """Build the GET spec for the multi-record read endpoint.""" + """Build the GET spec for the multi-record read endpoint. + + The endpoint implements only ``start``, ``limit`` and ``expansionLevel``. + OData-style ``$filter`` / ``$orderby`` / ``$select`` / ``$expand`` params + are accepted and silently ignored by the backend, so they are not sent — + use :meth:`retrieve_records` (``POST .../query``) to filter or sort. + """ params: Dict[str, Any] = {} if start is not None: params["start"] = start @@ -727,14 +713,6 @@ def _list_records_spec( params["limit"] = limit if expansion_level is not None: params["expansionLevel"] = expansion_level - if filter is not None: - params["$filter"] = filter - if orderby is not None: - params["$orderby"] = orderby - if select: - params["$select"] = ",".join(select) - if expand: - params["$expand"] = ",".join(expand) return RequestSpec( method="GET", endpoint=Endpoint( diff --git a/packages/uipath-platform/tests/services/test_entities_service.py b/packages/uipath-platform/tests/services/test_entities_service.py index 9a63bbc46..b540c3775 100644 --- a/packages/uipath-platform/tests/services/test_entities_service.py +++ b/packages/uipath-platform/tests/services/test_entities_service.py @@ -1709,10 +1709,6 @@ def test_list_records_returns_paginated_metadata( start=0, limit=3, expansion_level=2, - filter="status eq 'active'", - orderby="name asc", - select=["Id", "name"], - expand=["Company"], ) # New pagination metadata: backend totalCount surfaced verbatim. @@ -1729,11 +1725,13 @@ def test_list_records_returns_paginated_metadata( sent = httpx_mock.get_request() assert sent is not None params = sent.url.params + assert params.get("start") == "0" + assert params.get("limit") == "3" assert params.get("expansionLevel") == "2" - assert params.get("$filter") == "status eq 'active'" - assert params.get("$orderby") == "name asc" - assert params.get("$select") == "Id,name" - assert params.get("$expand") == "Company" + # The /read endpoint implements paging and expansion only — it accepts + # OData params and silently ignores them, returning the unfiltered set. + # Sending them would promise filtering this endpoint cannot do. + assert [key for key in params if key.startswith("$")] == [] def test_insert_records_passes_expansion_level_and_fail_on_first( self, From 2aa09245e878d6a493bc7ab9a539f9f9ebfcdbf5 Mon Sep 17 00:00:00 2001 From: avichalsri24 Date: Tue, 1 Sep 2026 14:30:46 +0530 Subject: [PATCH 2/3] chore: sync uv.lock files with the uipath-platform 0.2.23 bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lint jobs run `uv sync --locked`, which fails while the lockfiles still pin uipath-platform 0.2.22. Only the version line is touched in each lock — `uv lock --check` passes on both. Generated with Claude Code Co-Authored-By: Claude --- packages/uipath-platform/uv.lock | 2 +- packages/uipath/uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index e53d23e88..2172f797c 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.22" +version = "0.2.23" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index cf3a541b4..513633a63 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.22" +version = "0.2.23" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" }, From f3db8d9d919c50b215d66f175c256f0b0bb876ff Mon Sep 17 00:00:00 2001 From: avichalsri24 Date: Tue, 1 Sep 2026 14:35:46 +0530 Subject: [PATCH 3/3] test(platform): assert list_records rejects the removed OData kwargs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback: the wire-level assertion showed no $-params are sent, but nothing pinned the caller-facing contract. Parametrized test asserts filter/orderby/select/expand each raise TypeError, so the params cannot quietly return — including via a future **kwargs. Also corrects the section header above list_records, which still described the method as a "multi-record read with OData filters". Generated with Claude Code Co-Authored-By: Claude --- .../platform/entities/_entity_data_service.py | 2 +- .../tests/services/test_entities_service.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py b/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py index 1162694b7..5be4541bb 100644 --- a/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py +++ b/packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py @@ -134,7 +134,7 @@ async def get_choiceset_values_async( return self._parse_choiceset_values(response) # ------------------------------------------------------------------ - # List records (multi-record read with OData filters) + # List records (multi-record read: paging and expansion only) # ------------------------------------------------------------------ def list_records( diff --git a/packages/uipath-platform/tests/services/test_entities_service.py b/packages/uipath-platform/tests/services/test_entities_service.py index b540c3775..dc683fb66 100644 --- a/packages/uipath-platform/tests/services/test_entities_service.py +++ b/packages/uipath-platform/tests/services/test_entities_service.py @@ -1733,6 +1733,30 @@ def test_list_records_returns_paginated_metadata( # Sending them would promise filtering this endpoint cannot do. assert [key for key in params if key.startswith("$")] == [] + @pytest.mark.parametrize( + "kwarg,value", + [ + ("filter", "status eq 'active'"), + ("orderby", "name asc"), + ("select", ["Id"]), + ("expand", ["Company"]), + ], + ) + def test_list_records_rejects_odata_kwargs( + self, + service: EntitiesService, + kwarg: str, + value: object, + ) -> None: + """The /read endpoint cannot filter, sort or project. + + Accepting these kwargs again — directly or via ``**kwargs`` — would + return the full unfiltered set while looking like it filtered. Failing + loudly points callers at ``retrieve_records`` instead. + """ + with pytest.raises(TypeError, match=kwarg): + service.list_records(entity_key=str(uuid.uuid4()), **{kwarg: value}) + def test_insert_records_passes_expansion_level_and_fail_on_first( self, httpx_mock: HTTPXMock,