Skip to content

Commit 03a6dec

Browse files
feat(entities): optional source header on datafabric query/execute (#1808)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8124543 commit 03a6dec

7 files changed

Lines changed: 66 additions & 15 deletions

File tree

‎packages/uipath-platform/pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.2.7"
3+
version = "0.2.8"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

‎packages/uipath-platform/src/uipath/platform/constants/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
HEADER_FOLDER_PATH = "x-uipath-folderpath"
4242
HEADER_FOLDER_PATH_ENCODED = "x-uipath-folderpath-encoded"
4343
HEADER_USER_AGENT = "x-uipath-user-agent"
44+
HEADER_SOURCE = "x-uipath-source"
4445
HEADER_TENANT_ID = "x-uipath-tenantid"
4546
HEADER_INTERNAL_TENANT_ID = "x-uipath-internal-tenantid"
4647
HEADER_INTERNAL_ACCOUNT_ID = "x-uipath-internal-accountid"

‎packages/uipath-platform/src/uipath/platform/entities/_entities_service.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,7 +1820,9 @@ async def retrieve_records_async(
18201820

18211821
@attach_datafabric_error_mapping("query_entity_records")
18221822
@traced(name="entity_query_records", run_type="uipath")
1823-
def query_entity_records(self, sql_query: str) -> List[Dict[str, Any]]:
1823+
def query_entity_records(
1824+
self, sql_query: str, source: Optional[str] = None
1825+
) -> List[Dict[str, Any]]:
18241826
"""Query entity records using a validated SQL query.
18251827
18261828
PREVIEW: This method is in preview and may change in future releases.
@@ -1829,6 +1831,9 @@ def query_entity_records(self, sql_query: str) -> List[Dict[str, Any]]:
18291831
sql_query (str): A SQL SELECT query to execute against Data Service entities.
18301832
Only SELECT statements are allowed. Queries without WHERE must include
18311833
a LIMIT clause. Subqueries and multi-statement queries are not permitted.
1834+
source (str, optional): Value for the ``x-uipath-source`` header on the
1835+
query/execute request, identifying the calling surface (e.g.
1836+
``"LOW_CODE_AGENT"``). Omitted from the request when not set.
18321837
18331838
Notes:
18341839
A routing context is always derived from the configured ``folders_map``
@@ -1841,11 +1846,13 @@ def query_entity_records(self, sql_query: str) -> List[Dict[str, Any]]:
18411846
ValueError: If the SQL query fails validation (e.g., non-SELECT, missing
18421847
WHERE/LIMIT, forbidden keywords, subqueries).
18431848
"""
1844-
return self._data.query_entity_records(sql_query)
1849+
return self._data.query_entity_records(sql_query, source)
18451850

18461851
@attach_datafabric_error_mapping("query_entity_records_async")
18471852
@traced(name="entity_query_records", run_type="uipath")
1848-
async def query_entity_records_async(self, sql_query: str) -> List[Dict[str, Any]]:
1853+
async def query_entity_records_async(
1854+
self, sql_query: str, source: Optional[str] = None
1855+
) -> List[Dict[str, Any]]:
18491856
"""Asynchronously query entity records using a validated SQL query.
18501857
18511858
PREVIEW: This method is in preview and may change in future releases.
@@ -1854,6 +1861,9 @@ async def query_entity_records_async(self, sql_query: str) -> List[Dict[str, Any
18541861
sql_query (str): A SQL SELECT query to execute against Data Service entities.
18551862
Only SELECT statements are allowed. Queries without WHERE must include
18561863
a LIMIT clause. Subqueries and multi-statement queries are not permitted.
1864+
source (str, optional): Value for the ``x-uipath-source`` header on the
1865+
query/execute request, identifying the calling surface (e.g.
1866+
``"LOW_CODE_AGENT"``). Omitted from the request when not set.
18571867
18581868
Notes:
18591869
A routing context is always derived from the configured ``folders_map``
@@ -1866,7 +1876,7 @@ async def query_entity_records_async(self, sql_query: str) -> List[Dict[str, Any
18661876
ValueError: If the SQL query fails validation (e.g., non-SELECT, missing
18671877
WHERE/LIMIT, forbidden keywords, subqueries).
18681878
"""
1869-
return await self._data.query_entity_records_async(sql_query)
1879+
return await self._data.query_entity_records_async(sql_query, source)
18701880

18711881
@traced(name="entity_upload_attachment", run_type="uipath")
18721882
def upload_attachment(

‎packages/uipath-platform/src/uipath/platform/entities/_entity_data_service.py‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from ..common._config import UiPathApiConfig
2323
from ..common._execution_context import UiPathExecutionContext
2424
from ..common._models import Endpoint, RequestSpec
25+
from ..constants import HEADER_SOURCE
2526
from ..errors._enriched_exception import EnrichedException
2627
from ..orchestrator._folder_service import FolderService
2728
from ._entity_resolution import RoutingStrategy, create_routing_strategy
@@ -518,16 +519,18 @@ async def retrieve_records_async(
518519
def query_entity_records(
519520
self,
520521
sql_query: str,
522+
source: Optional[str] = None,
521523
) -> List[Dict[str, Any]]:
522524
"""Internal implementation; see :meth:`EntitiesService.query_entity_records`."""
523-
return self._query_entities_for_records(sql_query)
525+
return self._query_entities_for_records(sql_query, source)
524526

525527
async def query_entity_records_async(
526528
self,
527529
sql_query: str,
530+
source: Optional[str] = None,
528531
) -> List[Dict[str, Any]]:
529532
"""Async variant of :meth:`query_entity_records`."""
530-
return await self._query_entities_for_records_async(sql_query)
533+
return await self._query_entities_for_records_async(sql_query, source)
531534

532535
# ------------------------------------------------------------------
533536
# Attachments
@@ -680,22 +683,28 @@ def validate_entity_batch(
680683
# Internal helpers — request specs
681684
# ------------------------------------------------------------------
682685

683-
def _query_entities_for_records(self, sql_query: str) -> List[Dict[str, Any]]:
686+
def _query_entities_for_records(
687+
self, sql_query: str, source: Optional[str] = None
688+
) -> List[Dict[str, Any]]:
684689
"""Synchronously run a validated SQL query through the federated query engine."""
685690
self._validate_sql_query(sql_query)
686691
routing_context = self._routing_strategy.resolve()
687-
spec = self._query_entity_records_spec(sql_query, routing_context)
688-
response = self.request(spec.method, spec.endpoint, json=spec.json)
692+
spec = self._query_entity_records_spec(sql_query, routing_context, source)
693+
response = self.request(
694+
spec.method, spec.endpoint, json=spec.json, headers=spec.headers
695+
)
689696
return response.json().get("results", [])
690697

691698
async def _query_entities_for_records_async(
692-
self, sql_query: str
699+
self, sql_query: str, source: Optional[str] = None
693700
) -> List[Dict[str, Any]]:
694701
"""Asynchronously run a validated SQL query through the federated query engine."""
695702
self._validate_sql_query(sql_query)
696703
routing_context = await self._routing_strategy.resolve_async()
697-
spec = self._query_entity_records_spec(sql_query, routing_context)
698-
response = await self.request_async(spec.method, spec.endpoint, json=spec.json)
704+
spec = self._query_entity_records_spec(sql_query, routing_context, source)
705+
response = await self.request_async(
706+
spec.method, spec.endpoint, json=spec.json, headers=spec.headers
707+
)
699708
return response.json().get("results", [])
700709

701710
@staticmethod
@@ -956,17 +965,20 @@ def _retrieve_records_spec(
956965
def _query_entity_records_spec(
957966
sql_query: str,
958967
routing_context: Optional[QueryRoutingOverrideContext] = None,
968+
source: Optional[str] = None,
959969
) -> RequestSpec:
960970
"""Build the POST spec for the federated SQL query endpoint."""
961971
body: Dict[str, Any] = {"query": sql_query}
962972
if routing_context:
963973
body["routingContext"] = routing_context.model_dump(
964974
by_alias=True, exclude_none=True
965975
)
976+
headers = {HEADER_SOURCE: source} if source else {}
966977
return RequestSpec(
967978
method="POST",
968979
endpoint=Endpoint("datafabric_/api/v1/query/execute"),
969980
json=body,
981+
headers=headers,
970982
)
971983

972984
@staticmethod

‎packages/uipath-platform/tests/services/test_entities_service.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,34 @@ def test_query_entity_records_calls_request_for_valid_sql(
459459
assert result == [{"id": 1}, {"id": 2}]
460460
service._data.request.assert_called_once()
461461

462+
def test_query_entity_records_sets_source_header_when_provided(
463+
self,
464+
service: EntitiesService,
465+
) -> None:
466+
response = MagicMock()
467+
response.json.return_value = {"results": []}
468+
service._data.request = MagicMock(return_value=response) # type: ignore[method-assign]
469+
470+
service.query_entity_records(
471+
"SELECT id FROM Customers WHERE id > 0", source="LOW_CODE_AGENT"
472+
)
473+
474+
headers = service._data.request.call_args.kwargs.get("headers") or {}
475+
assert headers.get("x-uipath-source") == "LOW_CODE_AGENT"
476+
477+
def test_query_entity_records_omits_source_header_by_default(
478+
self,
479+
service: EntitiesService,
480+
) -> None:
481+
response = MagicMock()
482+
response.json.return_value = {"results": []}
483+
service._data.request = MagicMock(return_value=response) # type: ignore[method-assign]
484+
485+
service.query_entity_records("SELECT id FROM Customers WHERE id > 0")
486+
487+
headers = service._data.request.call_args.kwargs.get("headers") or {}
488+
assert "x-uipath-source" not in headers
489+
462490
@pytest.mark.anyio
463491
async def test_query_entity_records_async_rejects_invalid_sql_before_network_call(
464492
self,

‎packages/uipath-platform/uv.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/uipath/uv.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)