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
5 changes: 5 additions & 0 deletions packages/uipath-platform/src/uipath/platform/_uipath.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .chat import ConversationsService, UiPathLlmChatService, UiPathOpenAIService
from .common import (
ApiClient,
BindingsService,
ExternalApplicationService,
UiPathApiConfig,
UiPathExecutionContext,
Expand Down Expand Up @@ -76,6 +77,10 @@ def __init__(
raise SecretMissingError() from e
self._execution_context = UiPathExecutionContext()

@cached_property
def bindings(self) -> BindingsService:
return BindingsService()

@property
def api_client(self) -> ApiClient:
return ApiClient(self._config, self._execution_context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
from ._bindings import (
ConnectionResourceOverwrite,
GenericResourceOverwrite,
PropertyResourceOverwrite,
ResourceOverwrite,
ResourceOverwriteParser,
ResourceOverwritesContext,
resource_override,
)
from ._bindings_service import BindingsService
from ._config import UiPathApiConfig, UiPathConfig
from ._endpoints_manager import EndpointManager
from ._execution_context import UiPathExecutionContext
Expand Down Expand Up @@ -99,8 +101,10 @@
"validate_pagination_params",
"EndpointManager",
"jsonschema_to_pydantic",
"BindingsService",
"ConnectionResourceOverwrite",
"GenericResourceOverwrite",
"PropertyResourceOverwrite",
"ResourceOverwrite",
"ResourceOverwriteParser",
"ResourceOverwritesContext",
Expand Down
17 changes: 16 additions & 1 deletion packages/uipath-platform/src/uipath/platform/common/_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,23 @@ def folder_identifier(self) -> str:
return self.folder_key


class PropertyResourceOverwrite(ResourceOverwrite):
resource_type: Literal["property"]
values: dict[str, str] = Field(alias="values")

model_config = ConfigDict(populate_by_name=True, extra="ignore")

@property
def resource_identifier(self) -> str:
return ""

@property
def folder_identifier(self) -> str:
return ""


ResourceOverwriteUnion = Annotated[
Union[GenericResourceOverwrite, ConnectionResourceOverwrite],
Union[GenericResourceOverwrite, ConnectionResourceOverwrite, PropertyResourceOverwrite],
Field(discriminator="resource_type"),
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import json
import logging
from pathlib import Path
from typing import Optional, Union, overload

from ._bindings import PropertyResourceOverwrite, _resource_overwrites
from ._config import UiPathConfig

logger = logging.getLogger(__name__)


class BindingsService:
"""Service for reading Property binding resources from bindings.json.

Provides access to connector-defined property values (e.g. SharePoint folder IDs)
that are configured at design time and resolved at runtime.
"""

def __init__(self, bindings_file_path: Optional[Path] = None) -> None:
self._bindings_file_path = bindings_file_path or UiPathConfig.bindings_file_path

def _load_bindings(self) -> list[dict]:
try:
with open(self._bindings_file_path, "r") as f:
data = json.load(f)
return data.get("resources", [])
except FileNotFoundError:
logger.debug("Bindings file not found: %s", self._bindings_file_path)
return []
except (json.JSONDecodeError, OSError) as e:
logger.warning("Failed to load bindings file %s: %s", self._bindings_file_path, e)
return []

def _find_property_resource(self, key: str) -> Optional[dict]:
"""Find a Property binding resource by exact or suffix key match."""
resources = self._load_bindings()
for resource in resources:
if resource.get("resource", "").lower() != "property":
continue
resource_key = resource.get("key", "")
if resource_key == key or resource_key.endswith(f".{key}") or resource_key.endswith(key):
return resource
return None

def _get_overwrite(self, key: str) -> Optional[PropertyResourceOverwrite]:
"""Check context var for a runtime overwrite for the given key."""
context_overwrites = _resource_overwrites.get()
if context_overwrites is None:
return None
# Try both "property.<key>" and the raw key
for lookup_key in (f"property.{key}", key):
overwrite = context_overwrites.get(lookup_key)
if isinstance(overwrite, PropertyResourceOverwrite):
return overwrite
return None

@overload
def get_property(self, key: str) -> dict[str, str]: ...

@overload
def get_property(self, key: str, sub_property: str) -> str: ...

def get_property(
self, key: str, sub_property: Optional[str] = None
) -> Union[str, dict[str, str]]:
"""Get the value(s) of a Property binding resource.

Args:
key: The binding key, e.g. ``"sharepoint-connection.SharePoint Invoices folder"``.
Accepts the full key or a suffix that uniquely identifies the binding.
sub_property: The name of a specific sub-property to retrieve (e.g. ``"ID"``).
If omitted, returns all sub-properties as a ``{name: value}`` dict.

Returns:
The ``defaultValue`` of the requested sub-property when ``sub_property`` is
given, or a dict of all sub-property names mapped to their ``defaultValue``
when ``sub_property`` is omitted.

Raises:
KeyError: When the binding key is not found, or when ``sub_property`` is given
but does not exist on the binding.
"""
# Check for runtime overwrite first
overwrite = self._get_overwrite(key)
if overwrite is not None:
if sub_property is not None:
if sub_property not in overwrite.values:
raise KeyError(
f"Sub-property '{sub_property}' not found in Property binding '{key}'. "
f"Available: {list(overwrite.values.keys())}"
)
return overwrite.values[sub_property]
return dict(overwrite.values)

# Fall back to bindings.json
resource = self._find_property_resource(key)
if resource is None:
raise KeyError(
f"Property binding '{key}' not found in {self._bindings_file_path}."
)

value: dict = resource.get("value", {})
all_values = {name: props.get("defaultValue", "") for name, props in value.items()}

if sub_property is not None:
if sub_property not in all_values:
raise KeyError(
f"Sub-property '{sub_property}' not found in Property binding '{key}'. "
f"Available: {list(all_values.keys())}"
)
return all_values[sub_property]

return all_values
149 changes: 143 additions & 6 deletions packages/uipath/specs/bindings.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The configuration supports multiple resource types:
4. **index** - Search indexes
5. **apps** - Action center apps
6. **connection** - External connections
7. **Property** - Connector-defined resource properties (e.g. SharePoint folder IDs selected at design time)


---
Expand All @@ -53,7 +54,7 @@ Each resource in the `resources` array has the following structure:

```json
{
"resource": "asset|process|bucket|index|connection",
"resource": "asset|process|bucket|index|connection|Property",
"key": "unique_key",
"value": { ... },
"metadata": { ... }
Expand All @@ -64,7 +65,7 @@ Each resource in the `resources` array has the following structure:

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `resource` | `string` | Yes | Resource type (one of the five types) |
| `resource` | `string` | Yes | Resource type (one of the seven types) |
| `key` | `string` | Yes | Unique identifier for this resource |
| `value` | `object` | Yes | Resource-specific configuration |
| `metadata` | `object` | No | Additional metadata for the binding |
Expand Down Expand Up @@ -303,6 +304,88 @@ Connections define external system integrations.

---

### 7. Property

Property bindings represent connector-defined resources that a user browses and selects at design time (e.g. a SharePoint folder, an OneDrive file). They are child resources of a parent Connection binding and contain **arbitrary sub-properties** with resolved values.

**Key Format:** `<parent-connection-uuid>.<label>`

**Example:**

```json
{
"resource": "Property",
"key": "775694d9-4c5b-430f-bf47-6079b0ce8623.SharePoint Invoices folder",
"value": {
"FullName": {
"defaultValue": "Invoices",
"isExpression": false,
"displayName": "File or folder",
"description": "Select a file or folder",
"propertyName": "BrowserItemFriendlyName"
},
"ID": {
"defaultValue": "017NI543GXSYR5TZEZOBHJQNL6I2H4VA3M",
"isExpression": false,
"displayName": "File or folder",
"description": "The file or folder of interest",
"propertyName": "BrowserItemId"
},
"ParentDriveID": {
"defaultValue": "b!fFiPzsQBgk2xGTJUTRo5jryva9eCrqNPowK3pN2kXWKF90cVuHqnS4RUsG9j1cRt",
"isExpression": false,
"displayName": "Drive",
"description": "The drive (OneDrive/SharePoint) of file or folder",
"propertyName": "BrowserDriveId"
}
},
"metadata": {
"ActivityName": "SharePoint Invoices folder",
"BindingsVersion": "2.1",
"ObjectName": "CuratedFile",
"DisplayLabel": "FullName",
"ParentResourceKey": "Connection.775694d9-4c5b-430f-bf47-6079b0ce8623"
}
}
```

**Property-Specific Metadata:**
- `ParentResourceKey`: The key of the parent Connection resource (`"Connection.<uuid>"`)
- `ObjectName`: The connector-defined object type (e.g. `"CuratedFile"`)
- `DisplayLabel`: The sub-property used as the primary display value

**Reading Property bindings at runtime:**

Use `sdk.bindings.get_property()` to read resolved Property values:

```python
from uipath import UiPath

sdk = UiPath()

# Get a single sub-property value
folder_id = sdk.bindings.get_property(
"775694d9-4c5b-430f-bf47-6079b0ce8623.SharePoint Invoices folder",
"ID"
)
# → "017NI543GXSYR5TZEZOBHJQNL6I2H4VA3M"

# Get all sub-properties as a dict
props = sdk.bindings.get_property(
"775694d9-4c5b-430f-bf47-6079b0ce8623.SharePoint Invoices folder"
)
# → {"FullName": "Invoices", "ID": "017NI543...", "ParentDriveID": "b!fFiPz..."}
```

The key argument also accepts a suffix — any trailing portion of the full key that uniquely identifies the binding:

```python
# Equivalent to the full key above
folder_id = sdk.bindings.get_property("SharePoint Invoices folder", "ID")
```

---

## Value Object Structure

### For Assets, Processes, Buckets, Apps and Indexes
Expand Down Expand Up @@ -334,13 +417,31 @@ Connections define external system integrations.
}
```

### Property Definition Fields
### For Property bindings

The keys and number of sub-properties are connector-defined and vary by connector activity. Each sub-property follows this structure:

```json
{
"<SubPropertyName>": {
"defaultValue": "resolved_value",
"isExpression": false,
"displayName": "Human-readable label",
"description": "Optional longer description",
"propertyName": "ConnectorInternalPropertyName"
}
}
```

### Sub-property Definition Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `defaultValue` | `string` | Yes | The default value for this property |
| `defaultValue` | `string` | Yes | The resolved value for this sub-property |
| `isExpression` | `boolean` | Yes | Whether the value is a dynamic expression (usually `false`) |
| `displayName` | `string` | Yes | Human-readable name shown in UI |
| `description` | `string` | No | Optional longer description of the sub-property |
| `propertyName` | `string` | No | Internal connector property name |

---

Expand All @@ -352,11 +453,13 @@ Metadata provides additional context about the resource binding.

| Field | Type | Description | Applicable To |
|-------|------|-------------|---------------|
| `ActivityName` | `string` | Activity used to access the resource | asset, process, bucket, index |
| `ActivityName` | `string` | Activity used to access the resource | asset, process, bucket, index, Property |
| `BindingsVersion` | `string` | Version of the bindings schema | All resources |
| `DisplayLabel` | `string` | Label format for display | asset, process, bucket, index |
| `DisplayLabel` | `string` | Label format for display | asset, process, bucket, index, Property |
| `Connector` | `string` | Type of connector | connection |
| `UseConnectionService` | `string` | Whether to use connection service | connection |
| `ObjectName` | `string` | Connector-defined object type | Property |
| `ParentResourceKey` | `string` | Key of the parent Connection resource (`"Connection.<uuid>"`) | Property |

---

Expand Down Expand Up @@ -487,6 +590,40 @@ Metadata provides additional context about the resource binding.
"Connector": "Salesforce",
"UseConnectionService": "True"
}
},
{
"resource": "Property",
"key": "775694d9-4c5b-430f-bf47-6079b0ce8623.SharePoint Invoices folder",
"value": {
"FullName": {
"defaultValue": "Invoices",
"isExpression": false,
"displayName": "File or folder",
"description": "Select a file or folder",
"propertyName": "BrowserItemFriendlyName"
},
"ID": {
"defaultValue": "017NI543GXSYR5TZEZOBHJQNL6I2H4VA3M",
"isExpression": false,
"displayName": "File or folder",
"description": "The file or folder of interest",
"propertyName": "BrowserItemId"
},
"ParentDriveID": {
"defaultValue": "b!fFiPzsQBgk2xGTJUTRo5jryva9eCrqNPowK3pN2kXWKF90cVuHqnS4RUsG9j1cRt",
"isExpression": false,
"displayName": "Drive",
"description": "The drive (OneDrive/SharePoint) of file or folder",
"propertyName": "BrowserDriveId"
}
},
"metadata": {
"ActivityName": "SharePoint Invoices folder",
"BindingsVersion": "2.1",
"ObjectName": "CuratedFile",
"DisplayLabel": "FullName",
"ParentResourceKey": "Connection.775694d9-4c5b-430f-bf47-6079b0ce8623"
}
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions packages/uipath/src/uipath/_cli/models/runtime_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class BindingResourceValue(BaseModelWithDefaultConfig):
default_value: str = Field(..., alias="defaultValue")
is_expression: bool = Field(..., alias="isExpression")
display_name: str = Field(..., alias="displayName")
description: str | None = Field(default=None, alias="description")
property_name: str | None = Field(default=None, alias="propertyName")


# TODO: create stronger binding resource definition with discriminator based on resource enum.
Expand Down
Loading
Loading