Skip to content

Commit d08e1bd

Browse files
fix(platform): expose SecretValue on assets
1 parent 1c469bc commit d08e1bd

8 files changed

Lines changed: 399 additions & 71 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.1.64"
3+
version = "0.1.65"
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/orchestrator/_assets_service.py

Lines changed: 88 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,14 @@ async def retrieve_async(
283283
else:
284284
return Asset.model_validate(response.json()["value"][0])
285285

286+
def _ensure_robot_context(self) -> None:
287+
try:
288+
is_user = self._execution_context.robot_key is not None
289+
except ValueError:
290+
is_user = False
291+
if not is_user:
292+
raise ValueError("This method can only be used for robot assets.")
293+
286294
@resource_override(resource_type="asset")
287295
@traced(
288296
name="assets_credential", run_type="uipath", hide_input=True, hide_output=True
@@ -294,11 +302,9 @@ def retrieve_credential(
294302
folder_key: Optional[str] = None,
295303
folder_path: Optional[str] = None,
296304
) -> Optional[str]:
297-
"""Gets a specified Orchestrator credential.
298-
299-
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable)
305+
"""Get the decrypted password of a Credential asset.
300306
301-
Related Activity: [Get Credential](https://docs.uipath.com/activities/other/latest/workflow/get-robot-credential)
307+
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
302308
303309
Args:
304310
name (str): The name of the credential asset.
@@ -309,22 +315,10 @@ def retrieve_credential(
309315
Optional[str]: The decrypted credential password.
310316
311317
Raises:
312-
ValueError: If the method is called for a user asset.
318+
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
313319
"""
314-
try:
315-
is_user = self._execution_context.robot_key is not None
316-
except ValueError:
317-
is_user = False
318-
319-
if not is_user:
320-
raise ValueError("This method can only be used for robot assets.")
321-
322-
spec = self._retrieve_spec(
323-
name,
324-
folder_key=folder_key,
325-
folder_path=folder_path,
326-
)
327-
320+
self._ensure_robot_context()
321+
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
328322
response = self.request(
329323
spec.method,
330324
url=spec.endpoint,
@@ -333,10 +327,7 @@ def retrieve_credential(
333327
content=spec.content,
334328
headers=spec.headers,
335329
)
336-
337-
user_asset = UserAsset.model_validate(response.json())
338-
339-
return user_asset.credential_password
330+
return UserAsset.model_validate(response.json()).credential_password
340331

341332
@resource_override(resource_type="asset")
342333
@traced(
@@ -349,11 +340,9 @@ async def retrieve_credential_async(
349340
folder_key: Optional[str] = None,
350341
folder_path: Optional[str] = None,
351342
) -> Optional[str]:
352-
"""Asynchronously gets a specified Orchestrator credential.
343+
"""Asynchronously get the decrypted password of a Credential asset.
353344
354-
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable)
355-
356-
Related Activity: [Get Credential](https://docs.uipath.com/activities/other/latest/workflow/get-robot-credential)
345+
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
357346
358347
Args:
359348
name (str): The name of the credential asset.
@@ -364,34 +353,91 @@ async def retrieve_credential_async(
364353
Optional[str]: The decrypted credential password.
365354
366355
Raises:
367-
ValueError: If the method is called for a user asset.
356+
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
368357
"""
369-
try:
370-
is_user = self._execution_context.robot_key is not None
371-
except ValueError:
372-
is_user = False
358+
self._ensure_robot_context()
359+
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
360+
response = await self.request_async(
361+
spec.method,
362+
url=spec.endpoint,
363+
params=spec.params,
364+
json=spec.json,
365+
content=spec.content,
366+
headers=spec.headers,
367+
)
368+
return UserAsset.model_validate(response.json()).credential_password
373369

374-
if not is_user:
375-
raise ValueError("This method can only be used for robot assets.")
370+
@resource_override(resource_type="asset")
371+
@traced(name="assets_secret", run_type="uipath", hide_input=True, hide_output=True)
372+
def retrieve_secret(
373+
self,
374+
name: str,
375+
*,
376+
folder_key: Optional[str] = None,
377+
folder_path: Optional[str] = None,
378+
) -> Optional[str]:
379+
"""Get the decrypted value of a Secret asset.
376380
377-
spec = self._retrieve_spec(
378-
name,
379-
folder_key=folder_key,
380-
folder_path=folder_path,
381-
)
381+
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
382382
383-
response = await self.request_async(
383+
Args:
384+
name (str): The name of the secret asset.
385+
folder_key (Optional[str]): The key of the folder to execute the process in. Override the default one set in the SDK config.
386+
folder_path (Optional[str]): The path of the folder to execute the process in. Override the default one set in the SDK config.
387+
388+
Returns:
389+
Optional[str]: The decrypted secret value.
390+
391+
Raises:
392+
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
393+
"""
394+
self._ensure_robot_context()
395+
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
396+
response = self.request(
384397
spec.method,
385398
url=spec.endpoint,
386399
params=spec.params,
387400
json=spec.json,
388401
content=spec.content,
389402
headers=spec.headers,
390403
)
404+
return UserAsset.model_validate(response.json()).secret_value
391405

392-
user_asset = UserAsset.model_validate(response.json())
406+
@resource_override(resource_type="asset")
407+
@traced(name="assets_secret", run_type="uipath", hide_input=True, hide_output=True)
408+
async def retrieve_secret_async(
409+
self,
410+
name: str,
411+
*,
412+
folder_key: Optional[str] = None,
413+
folder_path: Optional[str] = None,
414+
) -> Optional[str]:
415+
"""Asynchronously get the decrypted value of a Secret asset.
416+
417+
The robot id is retrieved from the execution context (`UIPATH_ROBOT_KEY` environment variable).
418+
419+
Args:
420+
name (str): The name of the secret asset.
421+
folder_key (Optional[str]): The key of the folder to execute the process in. Override the default one set in the SDK config.
422+
folder_path (Optional[str]): The path of the folder to execute the process in. Override the default one set in the SDK config.
423+
424+
Returns:
425+
Optional[str]: The decrypted secret value.
393426
394-
return user_asset.credential_password
427+
Raises:
428+
ValueError: If called outside a robot context (no `UIPATH_ROBOT_KEY`).
429+
"""
430+
self._ensure_robot_context()
431+
spec = self._retrieve_spec(name, folder_key=folder_key, folder_path=folder_path)
432+
response = await self.request_async(
433+
spec.method,
434+
url=spec.endpoint,
435+
params=spec.params,
436+
json=spec.json,
437+
content=spec.content,
438+
headers=spec.headers,
439+
)
440+
return UserAsset.model_validate(response.json()).secret_value
395441

396442
@traced(name="assets_update", run_type="uipath", hide_input=True, hide_output=True)
397443
def update(

packages/uipath-platform/src/uipath/platform/orchestrator/assets.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class UserAsset(BaseModel):
3838
int_value: Optional[int] = Field(default=None, alias="IntValue")
3939
credential_username: Optional[str] = Field(default=None, alias="CredentialUsername")
4040
credential_password: Optional[str] = Field(default=None, alias="CredentialPassword")
41+
secret_value: Optional[str] = Field(default=None, alias="SecretValue")
4142
external_name: Optional[str] = Field(default=None, alias="ExternalName")
4243
credential_store_id: Optional[int] = Field(default=None, alias="CredentialStoreId")
4344
key_value_list: Optional[List[Dict[str, str]]] = Field(
@@ -69,5 +70,6 @@ class Asset(BaseModel):
6970
int_value: Optional[int] = Field(default=None, alias="IntValue")
7071
credential_username: Optional[str] = Field(default=None, alias="CredentialUsername")
7172
credential_password: Optional[str] = Field(default=None, alias="CredentialPassword")
73+
secret_value: Optional[str] = Field(default=None, alias="SecretValue")
7274
external_name: Optional[str] = Field(default=None, alias="ExternalName")
7375
credential_store_id: Optional[int] = Field(default=None, alias="CredentialStoreId")

packages/uipath-platform/tests/services/test_assets_service.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,106 @@ async def test_retrieve_credential_async(
417417
== f"UiPath.Python.Sdk/UiPath.Python.Sdk.Activities.AssetsService.retrieve_credential_async/{version}"
418418
)
419419

420+
def test_retrieve_secret(
421+
self,
422+
httpx_mock: HTTPXMock,
423+
service: AssetsService,
424+
base_url: str,
425+
org: str,
426+
tenant: str,
427+
) -> None:
428+
"""retrieve_secret returns SecretValue for Secret-type assets."""
429+
httpx_mock.add_response(
430+
url=f"{base_url}{org}{tenant}/orchestrator_/odata/Assets/UiPath.Server.Configuration.OData.GetRobotAssetByNameForRobotKey",
431+
status_code=200,
432+
json={
433+
"Id": 1,
434+
"Name": "Test Secret",
435+
"ValueType": "Secret",
436+
"SecretValue": "super-secret-value",
437+
},
438+
)
439+
440+
secret = service.retrieve_secret(name="Test Secret")
441+
442+
assert secret == "super-secret-value"
443+
444+
async def test_retrieve_secret_async(
445+
self,
446+
httpx_mock: HTTPXMock,
447+
service: AssetsService,
448+
base_url: str,
449+
org: str,
450+
tenant: str,
451+
) -> None:
452+
"""retrieve_secret_async returns SecretValue for Secret-type assets."""
453+
httpx_mock.add_response(
454+
url=f"{base_url}{org}{tenant}/orchestrator_/odata/Assets/UiPath.Server.Configuration.OData.GetRobotAssetByNameForRobotKey",
455+
status_code=200,
456+
json={
457+
"Id": 1,
458+
"Name": "Test Secret",
459+
"ValueType": "Secret",
460+
"SecretValue": "super-secret-value",
461+
},
462+
)
463+
464+
secret = await service.retrieve_secret_async(name="Test Secret")
465+
466+
assert secret == "super-secret-value"
467+
468+
def test_retrieve_robot_asset_exposes_secret_value(
469+
self,
470+
httpx_mock: HTTPXMock,
471+
service: AssetsService,
472+
base_url: str,
473+
org: str,
474+
tenant: str,
475+
) -> None:
476+
"""`retrieve` must expose SecretValue on UserAsset for Secret-type assets."""
477+
httpx_mock.add_response(
478+
url=f"{base_url}{org}{tenant}/orchestrator_/odata/Assets/UiPath.Server.Configuration.OData.GetRobotAssetByNameForRobotKey",
479+
status_code=200,
480+
json={
481+
"Id": 1,
482+
"Name": "Test Secret",
483+
"ValueType": "Secret",
484+
"SecretValue": "super-secret-value",
485+
},
486+
)
487+
488+
asset = service.retrieve(name="Test Secret")
489+
490+
assert isinstance(asset, UserAsset)
491+
assert asset.value_type == "Secret"
492+
assert asset.secret_value == "super-secret-value"
493+
494+
async def test_retrieve_async_robot_asset_exposes_secret_value(
495+
self,
496+
httpx_mock: HTTPXMock,
497+
service: AssetsService,
498+
base_url: str,
499+
org: str,
500+
tenant: str,
501+
) -> None:
502+
"""`retrieve_async` must expose SecretValue on UserAsset for Secret-type assets."""
503+
httpx_mock.add_response(
504+
url=f"{base_url}{org}{tenant}/orchestrator_/odata/Assets/UiPath.Server.Configuration.OData.GetRobotAssetByNameForRobotKey",
505+
status_code=200,
506+
json={
507+
"Id": 1,
508+
"Name": "Test Secret",
509+
"ValueType": "Secret",
510+
"SecretValue": "super-secret-value",
511+
},
512+
)
513+
514+
asset = await service.retrieve_async(name="Test Secret")
515+
516+
assert isinstance(asset, UserAsset)
517+
assert asset.value_type == "Secret"
518+
assert asset.secret_value == "super-secret-value"
519+
420520
def test_update(
421521
self,
422522
httpx_mock: HTTPXMock,

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/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"
3-
version = "2.10.82"
3+
version = "2.10.83"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

0 commit comments

Comments
 (0)