Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/uipath-platform/src/uipath/platform/_uipath.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .connections import ConnectionsService
from .context_grounding import ContextGroundingService
from .documents import DocumentsService
from .entities import EntitiesService
from .entities import EntitiesService, EntitiesServiceV2, EntitiesServiceV3
from .errors import BaseUrlMissingError, SecretMissingError
from .external_applications import ExternalApplicationService
from .governance import GovernanceService
Expand Down Expand Up @@ -152,6 +152,20 @@ def entities(self) -> EntitiesService:
self._config, self._execution_context, folders_service=self.folders
)

@property
def entities_v2(self) -> EntitiesServiceV2:
"""Entities service targeting the v2 API (preview, limited surface)."""
return EntitiesServiceV2(
self._config, self._execution_context, folders_service=self.folders
)

@property
def entities_v3(self) -> EntitiesServiceV3:
"""Entities service targeting the v3 API (preview)."""
return EntitiesServiceV3(
self._config, self._execution_context, folders_service=self.folders
)

@cached_property
def resource_catalog(self) -> ResourceCatalogService:
return ResourceCatalogService(
Expand Down
28 changes: 28 additions & 0 deletions packages/uipath-platform/src/uipath/platform/entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"""

from ._entities_service import EntitiesService
from ._entities_service_v2 import EntitiesServiceV2
from ._entities_service_v3 import EntitiesServiceV3
from .entities import (
AggregateRow,
ChoiceSetValue,
Expand Down Expand Up @@ -41,12 +43,34 @@
RetrieveEntityRecordsResponse,
SourceJoinCriteria,
)
from .entities_v3 import (
BatchOperationFailureRecord,
BatchOperationResponse,
ChildArrayBlock,
ChildArrayPaginationRef,
CompositeEntityMetadataResponse,
CompositeInfo,
CompositeMemberMetadata,
EntityRecordV3,
EntityWriteResponseV3,
GetAllResponseV3,
QueryResponseV3,
)

__all__ = [
"AggregateRow",
"BatchOperationFailureRecord",
"BatchOperationResponse",
"ChildArrayBlock",
"ChildArrayPaginationRef",
"ChoiceSetValue",
"CompositeEntityMetadataResponse",
"CompositeInfo",
"CompositeMemberMetadata",
"DataFabricEntityItem",
"EntitiesService",
"EntitiesServiceV2",
"EntitiesServiceV3",
"Entity",
"EntityAggregate",
"EntityAggregateFunction",
Expand All @@ -63,8 +87,10 @@
"EntityQueryFilterGroup",
"EntityQuerySortOption",
"EntityRecord",
"EntityRecordV3",
"EntityRecordsBatchResponse",
"EntityRecordsListResponse",
"EntityWriteResponseV3",
"EntityRouting",
"EntitySetResolution",
"ExternalField",
Expand All @@ -73,8 +99,10 @@
"FailureRecord",
"FieldDataType",
"FieldMetadata",
"GetAllResponseV3",
"LogicalOperator",
"QueryFilterOperator",
"QueryResponseV3",
"QueryRoutingOverrideContext",
"ReferenceType",
"RetrieveEntityRecordsResponse",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
"""Public facade for the v2 Data Fabric entities API (preview).

The v2 backend is a **partial** surface: it re-implements only structured
``query`` and single-record ``read`` for records, plus entity listing. Its
response shapes are identical to v1, so :class:`EntitiesServiceV2` reuses the v1
models (:class:`RetrieveEntityRecordsResponse`, :class:`EntityRecord`,
:class:`Entity`) and parsing, overriding only the endpoints to target
``datafabric_/api/v2/...``.

Reach it via ``sdk.entities_v2``. Operations the v2 backend does not implement
(insert/update/delete, batch, attachments, schema writes, choice-set values)
are intentionally absent — use ``sdk.entities`` (v1) or ``sdk.entities_v3`` for
those.
"""

import logging
from typing import Any, List, Optional

from uipath.core.tracing import traced

from ..common._base_service import BaseService
from ..common._config import UiPathApiConfig
from ..common._execution_context import UiPathExecutionContext
from ..common._models import Endpoint, RequestSpec
from ..orchestrator._folder_service import FolderService
from ._entity_data_service import EntityDataService, build_query_body
from ._entity_resolution import create_routing_strategy
from ._entity_schema_service import EntitySchemaService
from .entities import (
Entity,
EntityAggregate,
EntityBinning,
EntityJoin,
EntityQueryFilterGroup,
EntityQuerySortOption,
EntityRecord,
QueryRoutingOverrideContext,
RetrieveEntityRecordsResponse,
)

logger = logging.getLogger(__name__)


class EntityDataServiceV2(EntityDataService):
"""v1 data service with the two record endpoints v2 implements re-pointed to v2."""

@staticmethod
def _retrieve_records_spec(
entity_key: str,
filter_group: Optional[EntityQueryFilterGroup] = None,
sort_options: Optional[List[EntityQuerySortOption]] = None,
selected_fields: Optional[List[str]] = None,
expansions: Optional[List[Any]] = None,
expansion_level: Optional[int] = None,
aggregates: Optional[List[Any]] = None,
group_by: Optional[List[str]] = None,
joins: Optional[List[EntityJoin]] = None,
binnings: Optional[List[EntityBinning]] = None,
start: Optional[int] = None,
limit: Optional[int] = None,
) -> RequestSpec:
"""Build the v2 structured-query spec (always the ``v2`` endpoint)."""
body = build_query_body(
filter_group=filter_group,
sort_options=sort_options,
selected_fields=selected_fields,
expansions=expansions,
aggregates=aggregates,
group_by=group_by,
joins=joins,
binnings=binnings,
start=start,
limit=limit,
)
params: dict[str, Any] = {}
if expansion_level is not None:
params["expansionLevel"] = expansion_level
return RequestSpec(
method="POST",
endpoint=Endpoint(
f"datafabric_/api/v2/EntityService/entity/{entity_key}/query"
),
params=params,
json=body,
)

@staticmethod
def _get_record_spec(
entity_key: str,
record_id: str,
expansion_level: Optional[int] = None,
) -> RequestSpec:
"""Build the v2 single-record read spec."""
params: dict[str, Any] = {}
if expansion_level is not None:
params["expansionLevel"] = expansion_level
return RequestSpec(
method="GET",
endpoint=Endpoint(
f"datafabric_/api/v2/EntityService/entity/{entity_key}/read/{record_id}"
),
params=params,
)


class EntitySchemaServiceV2(EntitySchemaService):
"""v1 schema service with entity listing re-pointed to the v2 endpoint."""

@staticmethod
def _list_entities_spec() -> RequestSpec:
"""Build the v2 list-entities spec."""
return RequestSpec(method="GET", endpoint=Endpoint("datafabric_/api/v2/Entity"))


class EntitiesServiceV2(BaseService):
"""Service for UiPath Data Service entities using the **v2** API (preview).

Exposes only the operations the v2 backend implements: structured queries
(:meth:`retrieve_records`), single-record reads (:meth:`get_record`), and
entity listing (:meth:`list_entities`). Responses use the v1 models.

!!! warning "Preview Feature"
This service is experimental and intentionally limited. Behavior and
parameters are subject to change in future versions.
"""

def __init__(
self,
config: UiPathApiConfig,
execution_context: UiPathExecutionContext,
folders_service: Optional[FolderService] = None,
folders_map: Optional[dict[str, str]] = None,
entity_name_overrides: Optional[dict[str, str]] = None,
routing_context: Optional[QueryRoutingOverrideContext] = None,
) -> None:
"""Initialise the v2 facade and its underlying schema and data services."""
super().__init__(config=config, execution_context=execution_context)
self._folders_service = folders_service
self._routing_strategy = create_routing_strategy(
folders_map=folders_map,
effective_entity_names=entity_name_overrides,
routing_context=routing_context,
folders_service=folders_service,
)
self._schema = EntitySchemaServiceV2(
config=config,
execution_context=execution_context,
folders_service=folders_service,
)
self._data = EntityDataServiceV2(
config=config,
execution_context=execution_context,
folders_service=folders_service,
routing_strategy=self._routing_strategy,
)

@traced(name="list_entities", run_type="uipath")
def list_entities(self) -> List[Entity]:
"""List all entities in Data Service (v2)."""
return self._schema.list_entities()

@traced(name="list_entities", run_type="uipath")
async def list_entities_async(self) -> List[Entity]:
"""Async variant of :meth:`list_entities`."""
return await self._schema.list_entities_async()

@traced(name="entity_get_record", run_type="uipath")
def get_record(
self, entity_key: str, record_id: str, expansion_level: Optional[int] = None
) -> EntityRecord:
"""Fetch a single entity record by its id (v2)."""
return self._data.get_record(
entity_key, record_id, expansion_level=expansion_level
)

@traced(name="entity_get_record", run_type="uipath")
async def get_record_async(
self, entity_key: str, record_id: str, expansion_level: Optional[int] = None
) -> EntityRecord:
"""Async variant of :meth:`get_record`."""
return await self._data.get_record_async(
entity_key, record_id, expansion_level=expansion_level
)

@traced(name="entity_retrieve_records", run_type="uipath")
def retrieve_records(
self,
entity_key: str,
filter_group: Optional[EntityQueryFilterGroup] = None,
sort_options: Optional[List[EntityQuerySortOption]] = None,
selected_fields: Optional[List[str]] = None,
expansions: Optional[List[Any]] = None,
expansion_level: Optional[int] = None,
aggregates: Optional[List[EntityAggregate]] = None,
group_by: Optional[List[str]] = None,
joins: Optional[List[EntityJoin]] = None,
binnings: Optional[List[EntityBinning]] = None,
start: Optional[int] = None,
limit: Optional[int] = None,
) -> RetrieveEntityRecordsResponse:
"""Retrieve records with structured filters, sorting, and aggregates (v2)."""
return self._data.retrieve_records(
entity_key,
filter_group=filter_group,
sort_options=sort_options,
selected_fields=selected_fields,
expansions=expansions,
expansion_level=expansion_level,
aggregates=aggregates,
group_by=group_by,
joins=joins,
binnings=binnings,
start=start,
limit=limit,
)

@traced(name="entity_retrieve_records", run_type="uipath")
async def retrieve_records_async(
self,
entity_key: str,
filter_group: Optional[EntityQueryFilterGroup] = None,
sort_options: Optional[List[EntityQuerySortOption]] = None,
selected_fields: Optional[List[str]] = None,
expansions: Optional[List[Any]] = None,
expansion_level: Optional[int] = None,
aggregates: Optional[List[EntityAggregate]] = None,
group_by: Optional[List[str]] = None,
joins: Optional[List[EntityJoin]] = None,
binnings: Optional[List[EntityBinning]] = None,
start: Optional[int] = None,
limit: Optional[int] = None,
) -> RetrieveEntityRecordsResponse:
"""Async variant of :meth:`retrieve_records`."""
return await self._data.retrieve_records_async(
entity_key,
filter_group=filter_group,
sort_options=sort_options,
selected_fields=selected_fields,
expansions=expansions,
expansion_level=expansion_level,
aggregates=aggregates,
group_by=group_by,
joins=joins,
binnings=binnings,
start=start,
limit=limit,
)
Loading