feat: expand Data Fabric entities service [DS-8360]#1616
Conversation
There was a problem hiding this comment.
Pull request overview
This PR expands the Data Fabric EntitiesService surface area by adding missing Data Fabric entity operations (single-record CRUD, structured query, attachments, schema management, bulk import) and by exposing more backend parameters/metadata on existing batch and list endpoints.
Changes:
- Added new Data Fabric APIs to
EntitiesService: single-record CRUD, structured query (V1/V2 routing), attachments, entity schema create/update/delete, and CSV bulk import (sync + async). - Extended existing record list/batch operations to support additional backend parameters and richer pagination metadata (
EntityRecordsListResponse), plus improved batch error recovery for per-record failures on HTTP 400. - Added/updated entity/query/schema models and comprehensive tests for the new behaviors.
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath/uv.lock | Bumps editable uipath-platform version reference. |
| packages/uipath-platform/uv.lock | Bumps uipath-platform version to 0.1.47. |
| packages/uipath-platform/pyproject.toml | Version bump to 0.1.47. |
| packages/uipath-platform/src/uipath/platform/entities/entities.py | Adds new response/query/schema models (batch failure shape, list response w/ metadata, structured query types). |
| packages/uipath-platform/src/uipath/platform/entities/_entities_service.py | Implements the new/extended EntitiesService methods and request/response helpers. |
| packages/uipath-platform/src/uipath/platform/entities/init.py | Exposes the newly added entities/query/schema symbols via package exports. |
| packages/uipath-platform/tests/services/test_entities_service.py | Adds test coverage for new methods, query routing, attachments, schema creation defaults, and validation/error recovery. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if isinstance(metadata, EntityMetadataUpdateOptions): | ||
| body = metadata.model_dump(by_alias=True, exclude_none=True) | ||
| else: | ||
| body = dict(metadata) | ||
| return RequestSpec( |
There was a problem hiding this comment.
dict inputs now route through EntityMetadataUpdateOptions.model_validate(...) then model_dump(by_alias=True, exclude_none=True), so display_name/is_rbac_enabled get serialized as displayName/isRbacEnabled on the wire. New test test_update_entity_metadata_normalizes_snake_case_dict_keys locks this in.
| "fields": [self._build_schema_field_payload(f) for f in fields], | ||
| "folderId": opts.folder_key or DATA_FABRIC_TENANT_FOLDER_ID, | ||
| "isRbacEnabled": bool(opts.is_rbac_enabled or False), | ||
| "isInsightsEnabled": bool(opts.is_analytics_enabled or False), |
There was a problem hiding this comment.
is_analytics_enabled → isInsightsEnabled — added a one-line comment at the payload site explaining the legacy wire-name divergence.
| def __getitem__(self, index: int) -> EntityRecord: | ||
| """Index records by position (delegates to ``self.items``).""" | ||
| return self.items[index] |
There was a problem hiding this comment.
added @overload for int and slice on EntityQueryRecordsResponse, so slicing returns List[EntityRecord] and indexing returns EntityRecord with proper type narrowing.
| ) -> EntityRecordsListResponse: | ||
| """Asynchronously list records from an entity with optional pagination and schema validation. | ||
|
|
||
| The schema parameter enables type-safe access to entity records by validating the |
There was a problem hiding this comment.
docstring now mirrors the sync version: Args list includes expansion_level, filter, orderby, select, expand; Returns describes EntityRecordsListResponse with pagination metadata; Examples include an OData filter/orderby/select/expand sample.
| FileContent = Union[bytes, bytearray, memoryview] | ||
| """Acceptable raw bytes types for attachment/CSV uploads.""" | ||
|
|
||
| _NAME_RE = re.compile(r"^[a-zA-Z]\w*$") |
There was a problem hiding this comment.
Is this consistent with backend?
There was a problem hiding this comment.
Verified against the UI's create-form validators. Tightened _NAME_RE to ^[a-zA-Z][a-zA-Z0-9]*$ (no underscores) and split lengths by context: entity 1–30, field 3–100. Anything accepted by the SDK can now round-trip through the UI.
| ) | ||
|
|
||
| return self.validate_entity_batch(response, schema) | ||
| async def _do() -> Response: |
| ), | ||
| ) | ||
|
|
||
| def _query_records_spec( |
There was a problem hiding this comment.
!!! You are swapping out the query_records_spec which is catering to FQS query endpoint from DataService. This breaks multiple APIs for agents. Right strategy to avoid this is:
- Isolate the changes for schema into EntitySchemaService
- Isolate the changes for data into EntityDataService.
- EntityService is a facade that doesnt change the external contract.
- Carefully understand the APIs for DS and FQS and then make these changes.
There was a problem hiding this comment.
As discussed, this is a new method for odata query and not impacting sql endpoint consumed by agents (query_entity_records_spec vs query_records_spec).
Regarding refactor, updated the code, split into EntitySchemaService (entity/choiceset CRUD) and EntityDataService (records, attachments, queries — both FQS query_entity_records and DS query). EntitiesService is now a thin facade that preserves the existing flat sdk.entities.* contract via explicit delegation. No external API change. The new DS structured-query method is named query (not query_records) so it can't be confused with the existing FQS query_entity_records, which is fully intact. Sub-services are kept internal (not exported in init.py).
🚨 Heads up:
|
Adds the missing Data Fabric methods on EntitiesService and exposes the
full backend parameter set on existing batch methods.
Internally splits the service into a facade pattern for clearer
separation between schema- and data-side operations while keeping the
public sdk.entities.* surface unchanged:
- EntitySchemaService (internal): entity / choiceset CRUD against
datafabric_/api/Entity.
- EntityDataService (internal): record CRUD (single + batch), structured
query, federated SQL query, attachments, bulk import, choiceset
values, against datafabric_/api/EntityService/... +
datafabric_/api/Attachment + datafabric_/api/v1/query/execute.
- EntitiesService (public): thin facade that delegates to the two
sub-services and owns cross-cutting concerns (agent entity-set
resolution).
New methods (sync + async on the facade):
- Single-record ops: insert_record, get_record, update_record,
delete_record — fire trigger events on each mutation.
- query — structured query with filter_group, sort_options,
selected_fields, expansions, expansion_level, aggregates, group_by,
joins, binnings, start, limit. Routes to V2 endpoint when binnings
is supplied.
- Attachments: upload_attachment (bytes or path), download_attachment,
delete_attachment.
- Schema: create_entity (with SQL-type mapping and per-type constraint
defaults), delete_entity, update_entity_metadata.
- import_records for CSV bulk upload.
Existing methods extended (ticket §B):
- insert_records / update_records / delete_records accept
expansion_level and fail_on_first.
- list_records accepts OData filter / orderby / select / expand /
expansion_level and returns EntityRecordsListResponse (a list
subclass with total_count / has_next_page / next_cursor).
Bug fixes (ticket §C):
- Batch operations recover per-record failures from HTTP 400 responses
that carry successRecords / failureRecords lists; other non-2xx
statuses propagate.
- Record input is normalized — accepts dicts, Pydantic models,
EntityRecord, or any object with __dict__.
Client-side validation in create_entity uses the UI's create-form rules:
^[a-zA-Z][a-zA-Z0-9]*$ (no underscores), entity 1-30 chars, field 3-100
chars, plus reserved-name and per-field constraint range checks. Anything
accepted by the SDK round-trips cleanly through the Data Service UI.
Backward compatibility: public method signatures only gained optional
kwargs. EntityRecord.id stays required. list_records return type
subclasses list, so iteration / indexing / len() / isinstance(records,
list) continue to work. Existing method docstrings preserved verbatim
from main; only ticket-mandated additions appear in their docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ff1d809 to
bcabb27
Compare
- Add 13 async-variant tests covering retrieve_async, retrieve_by_name (sync + async), list_entities_async, list_records_async, update_record_async, batch async (insert / update / delete), import_records_async, plus validate_entity_batch and 5xx-shape edge cases. - Convert remaining ``Union[A, B]`` annotations to ``A | B`` (PEP 604) across entities source files; drop now-unused ``Union`` imports. Coverage on new code: 87.6% -> ~95% across the four new files, clearing the SonarCloud 90% quality gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
|



Summary
Adds the missing Data Fabric methods on
EntitiesServiceand exposes the full backend parameter set on existing batch methods. Internally splits the service for clearer separation between schema- and data-side operations while keeping the publicsdk.entities.*surface unchanged.Jira: https://uipath.atlassian.net/browse/DS-8360
Internal structure
EntitiesServiceis now a thin facade that delegates to two internal sub-services:EntitySchemaService(in_entity_schema_service.py, not exported)datafabric_/api/EntityEntityDataService(in_entity_data_service.py, not exported)datafabric_/api/EntityService/...+Attachment+v1/query/executeSub-services are deliberately not exported via
__init__.py—sdk.entities.*remains the only sanctioned entry point.EntityDataService.queryis the new structured-query method (namedqueryso it can't be confused with the existing FQSquery_entity_records, which is fully intact).New methods (sync + async on the facade)
insert_record,get_record,update_record,delete_record— fire trigger events on each mutation.query— structured query withfilter_group,sort_options,selected_fields,expansions,expansion_level,aggregates,group_by,joins,binnings,start,limit. Routes to V2 endpoint whenbinningsis supplied.upload_attachment(bytes or path),download_attachment,delete_attachment.create_entity(with full SQL-type mapping and per-type constraint defaults),delete_entity,update_entity_metadata.import_recordsfor CSV bulk upload.Existing methods extended
insert_records/update_records/delete_recordsacceptexpansion_levelandfail_on_first.list_recordsaccepts ODatafilter/orderby/select/expand/expansion_leveland returnsEntityRecordsListResponse(alistsubclass withtotal_count/has_next_page/next_cursor).Bug fixes
successRecords/failureRecordslists; other non-2xx statuses propagate.EntityRecord, or any object with__dict__.Client-side validation
create_entityvalidates entity / field names client-side using the UI's create-form rules:^[a-zA-Z][a-zA-Z0-9]*$(no underscores), entity 1–30 chars, field 3–100 chars, plus reserved-name and per-field constraint range checks. Anything accepted by the SDK round-trips cleanly through the Data Service UI.Backward compatibility
Public method signatures only gained optional kwargs.
EntityRecord.idstays required.list_recordsreturn type subclasseslist, so iteration / indexing /len()/isinstance(records, list)continue to work. Existing docstrings are preserved verbatim frommain; only ticket-mandated additions (new param entries, new Examples, new return types) appear in their docs.Test plan
pytest packages/uipath-platform/tests/services/test_entities_service.py— 112 passpytest packages/uipath-platform/tests/— 1158 passpytest packages/uipath/tests/— 1840 passruff check,ruff format --check,mypy src tests, customlint_httpx_client.py— clean across both packages🤖 Generated with Claude Code