uipath 2.0.13__tar.gz → 2.0.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

Files changed (126) hide show
  1. {uipath-2.0.13 → uipath-2.0.15}/PKG-INFO +1 -1
  2. {uipath-2.0.13 → uipath-2.0.15}/pyproject.toml +1 -1
  3. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_run.py +2 -2
  4. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/actions_service.py +30 -9
  5. uipath-2.0.15/src/uipath/tracing/__init__.py +3 -0
  6. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/tracing/_traced.py +116 -38
  7. uipath-2.0.15/tests/tracing/test_tracing_manager.py +165 -0
  8. {uipath-2.0.13 → uipath-2.0.15}/uv.lock +1 -1
  9. uipath-2.0.13/src/uipath/tracing/__init__.py +0 -3
  10. {uipath-2.0.13 → uipath-2.0.15}/.cursorrules +0 -0
  11. {uipath-2.0.13 → uipath-2.0.15}/.editorconfig +0 -0
  12. {uipath-2.0.13 → uipath-2.0.15}/.gitattributes +0 -0
  13. {uipath-2.0.13 → uipath-2.0.15}/.github/workflows/build.yml +0 -0
  14. {uipath-2.0.13 → uipath-2.0.15}/.github/workflows/cd.yml +0 -0
  15. {uipath-2.0.13 → uipath-2.0.15}/.github/workflows/ci.yml +0 -0
  16. {uipath-2.0.13 → uipath-2.0.15}/.github/workflows/commitlint.yml +0 -0
  17. {uipath-2.0.13 → uipath-2.0.15}/.github/workflows/lint.yml +0 -0
  18. {uipath-2.0.13 → uipath-2.0.15}/.github/workflows/test.yml +0 -0
  19. {uipath-2.0.13 → uipath-2.0.15}/.gitignore +0 -0
  20. {uipath-2.0.13 → uipath-2.0.15}/.pre-commit-config.yaml +0 -0
  21. {uipath-2.0.13 → uipath-2.0.15}/.python-version +0 -0
  22. {uipath-2.0.13 → uipath-2.0.15}/.vscode/extensions.json +0 -0
  23. {uipath-2.0.13 → uipath-2.0.15}/.vscode/settings.json +0 -0
  24. {uipath-2.0.13 → uipath-2.0.15}/CONTRIBUTING.md +0 -0
  25. {uipath-2.0.13 → uipath-2.0.15}/LICENSE +0 -0
  26. {uipath-2.0.13 → uipath-2.0.15}/README.md +0 -0
  27. {uipath-2.0.13 → uipath-2.0.15}/docs/CONTRIBUTING.md +0 -0
  28. {uipath-2.0.13 → uipath-2.0.15}/docs/actions.md +0 -0
  29. {uipath-2.0.13 → uipath-2.0.15}/docs/assets/uipath-logo.svg +0 -0
  30. {uipath-2.0.13 → uipath-2.0.15}/docs/assets.md +0 -0
  31. {uipath-2.0.13 → uipath-2.0.15}/docs/buckets.md +0 -0
  32. {uipath-2.0.13 → uipath-2.0.15}/docs/connections.md +0 -0
  33. {uipath-2.0.13 → uipath-2.0.15}/docs/context_grounding.md +0 -0
  34. {uipath-2.0.13 → uipath-2.0.15}/docs/getting_started_agent.md +0 -0
  35. {uipath-2.0.13 → uipath-2.0.15}/docs/getting_started_cli.md +0 -0
  36. {uipath-2.0.13 → uipath-2.0.15}/docs/getting_started_cloud.md +0 -0
  37. {uipath-2.0.13 → uipath-2.0.15}/docs/getting_started_sdk.md +0 -0
  38. {uipath-2.0.13 → uipath-2.0.15}/docs/index.md +0 -0
  39. {uipath-2.0.13 → uipath-2.0.15}/docs/jobs.md +0 -0
  40. {uipath-2.0.13 → uipath-2.0.15}/docs/processes.md +0 -0
  41. {uipath-2.0.13 → uipath-2.0.15}/docs/queues.md +0 -0
  42. {uipath-2.0.13 → uipath-2.0.15}/docs/sdk.md +0 -0
  43. {uipath-2.0.13 → uipath-2.0.15}/justfile +0 -0
  44. {uipath-2.0.13 → uipath-2.0.15}/mkdocs.yml +0 -0
  45. {uipath-2.0.13 → uipath-2.0.15}/py.typed +0 -0
  46. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/__init__.py +0 -0
  47. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/README.md +0 -0
  48. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/__init__.py +0 -0
  49. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/_auth_server.py +0 -0
  50. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/_models.py +0 -0
  51. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
  52. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/_portal_service.py +0 -0
  53. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/_utils.py +0 -0
  54. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/auth_config.json +0 -0
  55. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/index.html +0 -0
  56. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/localhost.crt +0 -0
  57. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_auth/localhost.key +0 -0
  58. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_runtime/_contracts.py +0 -0
  59. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_runtime/_logging.py +0 -0
  60. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_runtime/_runtime.py +0 -0
  61. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
  62. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_templates/.rels.template +0 -0
  63. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
  64. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_templates/main.py.template +0 -0
  65. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
  66. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_utils/_common.py +0 -0
  67. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_utils/_input_args.py +0 -0
  68. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
  69. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_auth.py +0 -0
  70. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_deploy.py +0 -0
  71. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_init.py +0 -0
  72. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_new.py +0 -0
  73. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_pack.py +0 -0
  74. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/cli_publish.py +0 -0
  75. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_cli/middlewares.py +0 -0
  76. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_config.py +0 -0
  77. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_execution_context.py +0 -0
  78. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_folder_context.py +0 -0
  79. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/__init__.py +0 -0
  80. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/_base_service.py +0 -0
  81. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/api_client.py +0 -0
  82. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/assets_service.py +0 -0
  83. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/buckets_service.py +0 -0
  84. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/connections_service.py +0 -0
  85. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/connections_service.pyi +0 -0
  86. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/context_grounding_service.py +0 -0
  87. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/folder_service.py +0 -0
  88. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/jobs_service.py +0 -0
  89. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/llm_gateway_service.py +0 -0
  90. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/processes_service.py +0 -0
  91. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_services/queues_service.py +0 -0
  92. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_uipath.py +0 -0
  93. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/__init__.py +0 -0
  94. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/_endpoint.py +0 -0
  95. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/_infer_bindings.py +0 -0
  96. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/_logs.py +0 -0
  97. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/_request_override.py +0 -0
  98. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/_request_spec.py +0 -0
  99. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/_user_agent.py +0 -0
  100. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/_utils/constants.py +0 -0
  101. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/__init__.py +0 -0
  102. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/action_schema.py +0 -0
  103. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/actions.py +0 -0
  104. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/assets.py +0 -0
  105. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/connections.py +0 -0
  106. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/context_grounding.py +0 -0
  107. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/context_grounding_index.py +0 -0
  108. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/exceptions.py +0 -0
  109. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/interrupt_models.py +0 -0
  110. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/job.py +0 -0
  111. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/llm_gateway.py +0 -0
  112. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/processes.py +0 -0
  113. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/models/queues.py +0 -0
  114. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/py.typed +0 -0
  115. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/tracing/_otel_exporters.py +0 -0
  116. {uipath-2.0.13 → uipath-2.0.15}/src/uipath/tracing/_utils.py +0 -0
  117. {uipath-2.0.13 → uipath-2.0.15}/tests/__init__.py +0 -0
  118. {uipath-2.0.13 → uipath-2.0.15}/tests/cli/test_init.py +0 -0
  119. {uipath-2.0.13 → uipath-2.0.15}/tests/conftest.py +0 -0
  120. {uipath-2.0.13 → uipath-2.0.15}/tests/sdk/services/test_llm_integration.py +0 -0
  121. {uipath-2.0.13 → uipath-2.0.15}/tests/sdk/services/test_llm_service.py +0 -0
  122. {uipath-2.0.13 → uipath-2.0.15}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
  123. {uipath-2.0.13 → uipath-2.0.15}/tests/sdk/test_config.py +0 -0
  124. {uipath-2.0.13 → uipath-2.0.15}/tests/tracing/test_otel_exporters.py +0 -0
  125. {uipath-2.0.13 → uipath-2.0.15}/tests/tracing/test_span_utils.py +0 -0
  126. {uipath-2.0.13 → uipath-2.0.15}/tests/tracing/test_traced.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.0.13
3
+ Version: 2.0.15
4
4
  Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uipath"
3
- version = "2.0.13"
3
+ version = "2.0.15"
4
4
  description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  requires-python = ">=3.10"
@@ -115,9 +115,9 @@ def run(entrypoint: Optional[str], input: Optional[str], resume: bool) -> None:
115
115
 
116
116
  # Handle result from middleware
117
117
  if result.error_message:
118
- click.echo(result.error_message)
118
+ click.echo(result.error_message, err=True)
119
119
  if result.should_include_stacktrace:
120
- click.echo(traceback.format_exc())
120
+ click.echo(traceback.format_exc(), err=True)
121
121
  click.get_current_context().exit(1)
122
122
 
123
123
  if result.info_message:
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import uuid
3
3
  from json import dumps
4
- from typing import Any, Dict, Optional, Tuple
4
+ from typing import Any, Dict, List, Optional, Tuple
5
5
 
6
6
  from .._config import Config
7
7
  from .._execution_context import ExecutionContext
@@ -213,7 +213,7 @@ class ActionsService(FolderContext, BaseService):
213
213
  (key, action_schema) = (
214
214
  (app_key, None)
215
215
  if app_key
216
- else await self.__get_app_key_and_schema_async(app_name)
216
+ else await self._get_app_key_and_schema_async(app_name, app_folder_path)
217
217
  )
218
218
  spec = _create_spec(
219
219
  title=title,
@@ -269,7 +269,9 @@ class ActionsService(FolderContext, BaseService):
269
269
  Exception: If neither app_name nor app_key is provided for app-specific actions
270
270
  """
271
271
  (key, action_schema) = (
272
- (app_key, None) if app_key else self.__get_app_key_and_schema(app_name)
272
+ (app_key, None)
273
+ if app_key
274
+ else self._get_app_key_and_schema(app_name, app_folder_path)
273
275
  )
274
276
  spec = _create_spec(
275
277
  title=title,
@@ -340,8 +342,8 @@ class ActionsService(FolderContext, BaseService):
340
342
 
341
343
  return Action.model_validate(response.json())
342
344
 
343
- async def __get_app_key_and_schema_async(
344
- self, app_name: str
345
+ async def _get_app_key_and_schema_async(
346
+ self, app_name: str, app_folder_path: str
345
347
  ) -> Tuple[str, Optional[ActionSchema]]:
346
348
  """Retrieves an application's key and schema asynchronously.
347
349
 
@@ -362,7 +364,9 @@ class ActionsService(FolderContext, BaseService):
362
364
  spec.method, spec.endpoint, params=spec.params, headers=spec.headers
363
365
  )
364
366
  try:
365
- deployed_app = response.json()["deployed"][0]
367
+ deployed_app = self._extract_deployed_app(
368
+ response.json()["deployed"], app_folder_path
369
+ )
366
370
  action_schema = deployed_app["actionSchema"]
367
371
  deployed_app_key = deployed_app["systemName"]
368
372
  except (KeyError, IndexError):
@@ -381,8 +385,8 @@ class ActionsService(FolderContext, BaseService):
381
385
  except KeyError:
382
386
  raise Exception("Failed to deserialize action schema") from KeyError
383
387
 
384
- def __get_app_key_and_schema(
385
- self, app_name: str
388
+ def _get_app_key_and_schema(
389
+ self, app_name: str, app_folder_path: str
386
390
  ) -> Tuple[str, Optional[ActionSchema]]:
387
391
  if not app_name:
388
392
  raise Exception("appName or appKey is required")
@@ -394,7 +398,9 @@ class ActionsService(FolderContext, BaseService):
394
398
  )
395
399
 
396
400
  try:
397
- deployed_app = response.json()["deployed"][0]
401
+ deployed_app = self._extract_deployed_app(
402
+ response.json()["deployed"], app_folder_path
403
+ )
398
404
  action_schema = deployed_app["actionSchema"]
399
405
  deployed_app_key = deployed_app["systemName"]
400
406
  except (KeyError, IndexError):
@@ -413,6 +419,21 @@ class ActionsService(FolderContext, BaseService):
413
419
  except KeyError:
414
420
  raise Exception("Failed to deserialize action schema") from KeyError
415
421
 
422
+ # should be removed after folder filtering support is added on apps API
423
+ def _extract_deployed_app(
424
+ self, deployed_apps: List[Dict[str, Any]], app_folder_path: Optional[str]
425
+ ) -> Dict[str, Any]:
426
+ if len(deployed_apps) > 1 and not app_folder_path:
427
+ raise Exception("Multiple app schemas found")
428
+ try:
429
+ return next(
430
+ app
431
+ for app in deployed_apps
432
+ if app["deploymentFolder"]["fullyQualifiedName"] == app_folder_path
433
+ )
434
+ except StopIteration:
435
+ raise KeyError from StopIteration
436
+
416
437
  @property
417
438
  def custom_headers(self) -> Dict[str, str]:
418
439
  return self.folder_headers
@@ -0,0 +1,3 @@
1
+ from ._traced import TracingManager, traced, wait_for_tracers # noqa: D104
2
+
3
+ __all__ = ["TracingManager", "traced", "wait_for_tracers"]
@@ -1,8 +1,9 @@
1
+ import importlib
1
2
  import inspect
2
3
  import json
3
4
  import logging
4
5
  from functools import wraps
5
- from typing import Any, Callable, Optional
6
+ from typing import Any, Callable, List, Optional, Tuple
6
7
 
7
8
  from opentelemetry import trace
8
9
  from opentelemetry.sdk.trace import TracerProvider
@@ -18,9 +19,94 @@ trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(LlmOpsHttpExpo
18
19
  tracer = trace.get_tracer(__name__)
19
20
 
20
21
 
21
- def wait_for_tracers():
22
- """Wait for all tracers to finish."""
23
- trace.get_tracer_provider().shutdown() # type: ignore
22
+ class TracingManager:
23
+ """Static utility class to manage tracing implementations and decorated functions."""
24
+
25
+ # Registry to track original functions, decorated functions, and their parameters
26
+ # Each entry is (original_func, decorated_func, params)
27
+ _traced_registry: List[Tuple[Callable[..., Any], Callable[..., Any], Any]] = []
28
+
29
+ # Custom tracer implementation
30
+ _custom_tracer_implementation = None
31
+
32
+ @classmethod
33
+ def get_custom_tracer_implementation(cls):
34
+ """Get the currently set custom tracer implementation."""
35
+ return cls._custom_tracer_implementation
36
+
37
+ @classmethod
38
+ def register_traced_function(cls, original_func, decorated_func, params):
39
+ """Register a function decorated with @traced and its parameters.
40
+
41
+ Args:
42
+ original_func: The original function before decoration
43
+ decorated_func: The function after decoration
44
+ params: The parameters used for tracing
45
+ """
46
+ cls._traced_registry.append((original_func, decorated_func, params))
47
+
48
+ @classmethod
49
+ def reapply_traced_decorator(cls, tracer_implementation):
50
+ """Reapply a different tracer implementation to all functions previously decorated with @traced.
51
+
52
+ Args:
53
+ tracer_implementation: A function that takes the same parameters as _opentelemetry_traced
54
+ and returns a decorator
55
+ """
56
+ cls._custom_tracer_implementation = tracer_implementation
57
+
58
+ # Work with a copy of the registry to avoid modifying it during iteration
59
+ registry_copy = cls._traced_registry.copy()
60
+
61
+ for original_func, decorated_func, params in registry_copy:
62
+ # Apply the new decorator with the same parameters
63
+ new_decorated_func = tracer_implementation(**params)(original_func)
64
+
65
+ logger.debug(
66
+ f"Reapplying decorator to {original_func.__name__}, from {decorated_func.__name__}"
67
+ )
68
+
69
+ # If this is a method on a class, we need to update the class
70
+ if hasattr(original_func, "__self__") and hasattr(
71
+ original_func, "__func__"
72
+ ):
73
+ setattr(
74
+ original_func.__self__.__class__,
75
+ original_func.__name__,
76
+ new_decorated_func.__get__(
77
+ original_func.__self__, original_func.__self__.__class__
78
+ ),
79
+ )
80
+ else:
81
+ # Replace the function in its module
82
+ if hasattr(original_func, "__module__") and hasattr(
83
+ original_func, "__qualname__"
84
+ ):
85
+ try:
86
+ module = importlib.import_module(original_func.__module__)
87
+ parts = original_func.__qualname__.split(".")
88
+
89
+ # Handle nested objects
90
+ obj = module
91
+ for part in parts[:-1]:
92
+ obj = getattr(obj, part)
93
+
94
+ setattr(obj, parts[-1], new_decorated_func)
95
+
96
+ # Update the registry entry for this function
97
+ # Find the index and replace with updated entry
98
+ for i, (orig, _dec, _p) in enumerate(cls._traced_registry):
99
+ if orig is original_func:
100
+ cls._traced_registry[i] = (
101
+ original_func,
102
+ new_decorated_func,
103
+ params,
104
+ )
105
+ break
106
+ except (ImportError, AttributeError) as e:
107
+ # Log the error but continue processing other functions
108
+ logger.warning(f"Error reapplying decorator: {e}")
109
+ continue
24
110
 
25
111
 
26
112
  def _default_input_processor(inputs):
@@ -33,23 +119,9 @@ def _default_output_processor(outputs):
33
119
  return {"redacted": "Output data not logged for privacy/security"}
34
120
 
35
121
 
36
- class TracedDecoratorRegistry:
37
- """Registry for tracing decorators."""
38
-
39
- _decorators: dict[str, Any] = {}
40
- _active_decorator = "opentelemetry"
41
-
42
- @classmethod
43
- def register_decorator(cls, name, decorator_factory):
44
- """Register a decorator factory function with a name."""
45
- cls._decorators[name] = decorator_factory
46
- cls._active_decorator = name
47
- return cls
48
-
49
- @classmethod
50
- def get_decorator(cls):
51
- """Get the currently active decorator factory."""
52
- return cls._decorators.get(cls._active_decorator)
122
+ def wait_for_tracers():
123
+ """Wait for all tracers to finish."""
124
+ trace.get_tracer_provider().shutdown() # type: ignore
53
125
 
54
126
 
55
127
  def _opentelemetry_traced(
@@ -58,6 +130,8 @@ def _opentelemetry_traced(
58
130
  input_processor: Optional[Callable[..., Any]] = None,
59
131
  output_processor: Optional[Callable[..., Any]] = None,
60
132
  ):
133
+ """Default tracer implementation using OpenTelemetry."""
134
+
61
135
  def decorator(func):
62
136
  @wraps(func)
63
137
  def sync_wrapper(*args, **kwargs):
@@ -78,9 +152,7 @@ def _opentelemetry_traced(
78
152
  if input_processor is not None:
79
153
  processed_inputs = input_processor(json.loads(inputs))
80
154
  inputs = json.dumps(processed_inputs, default=str)
81
-
82
155
  span.set_attribute("inputs", inputs)
83
-
84
156
  try:
85
157
  result = func(*args, **kwargs)
86
158
  # Process output if processor is provided
@@ -115,9 +187,7 @@ def _opentelemetry_traced(
115
187
  if input_processor is not None:
116
188
  processed_inputs = input_processor(json.loads(inputs))
117
189
  inputs = json.dumps(processed_inputs, default=str)
118
-
119
190
  span.set_attribute("inputs", inputs)
120
-
121
191
  try:
122
192
  result = await func(*args, **kwargs)
123
193
  # Process output if processor is provided
@@ -152,9 +222,7 @@ def _opentelemetry_traced(
152
222
  if input_processor is not None:
153
223
  processed_inputs = input_processor(json.loads(inputs))
154
224
  inputs = json.dumps(processed_inputs, default=str)
155
-
156
225
  span.set_attribute("inputs", inputs)
157
-
158
226
  outputs = []
159
227
  try:
160
228
  for item in func(*args, **kwargs):
@@ -195,9 +263,7 @@ def _opentelemetry_traced(
195
263
  if input_processor is not None:
196
264
  processed_inputs = input_processor(json.loads(inputs))
197
265
  inputs = json.dumps(processed_inputs, default=str)
198
-
199
266
  span.set_attribute("inputs", inputs)
200
-
201
267
  outputs = []
202
268
  try:
203
269
  async for item in func(*args, **kwargs):
@@ -254,16 +320,28 @@ def traced(
254
320
  # Apply default processors selectively based on hide flags
255
321
  if hide_input:
256
322
  input_processor = _default_input_processor
257
-
258
323
  if hide_output:
259
324
  output_processor = _default_output_processor
260
325
 
261
- decorator_factory = TracedDecoratorRegistry.get_decorator()
326
+ # Store the parameters for later reapplication
327
+ params = {
328
+ "run_type": run_type,
329
+ "span_type": span_type,
330
+ "input_processor": input_processor,
331
+ "output_processor": output_processor,
332
+ }
262
333
 
263
- if decorator_factory:
264
- return decorator_factory(run_type, span_type, input_processor, output_processor)
265
- else:
266
- # Fallback to original implementation if no active decorator
267
- return _opentelemetry_traced(
268
- run_type, span_type, input_processor, output_processor
269
- )
334
+ # Check for custom implementation first
335
+ custom_implementation = TracingManager.get_custom_tracer_implementation()
336
+ tracer_impl: Any = (
337
+ custom_implementation if custom_implementation else _opentelemetry_traced
338
+ )
339
+
340
+ def decorator(func):
341
+ # Decorate the function
342
+ decorated_func = tracer_impl(**params)(func)
343
+ # Register both original and decorated function with parameters
344
+ TracingManager.register_traced_function(func, decorated_func, params)
345
+ return decorated_func
346
+
347
+ return decorator
@@ -0,0 +1,165 @@
1
+ from functools import wraps
2
+
3
+ from uipath.tracing._traced import TracingManager, traced
4
+
5
+
6
+ # Custom wrapper that does nothing
7
+ def donothing_custom_tracer(**kwargs):
8
+ def decorator(func):
9
+ @wraps(func)
10
+ def wrapper(*args, **kwargs):
11
+ # Simple implementation that just adds a marker to the result
12
+ result = func(*args, **kwargs)
13
+ return result
14
+
15
+ return wrapper
16
+
17
+ return decorator
18
+
19
+
20
+ # Helper function for testing custom tracer
21
+ def simple_custom_tracer(**kwargs):
22
+ def decorator(func):
23
+ @wraps(func)
24
+ def wrapper(*args, **kwargs):
25
+ # Simple implementation that just adds a marker to the result
26
+ result = func(*args, **kwargs)
27
+ if isinstance(result, dict):
28
+ result["custom_tracer_used"] = True
29
+ return result
30
+
31
+ return wrapper
32
+
33
+ return decorator
34
+
35
+
36
+ # Helper function for testing custom tracer with method
37
+ def custom_method_tracer(**kwargs):
38
+ def decorator(func):
39
+ @wraps(func)
40
+ def wrapper(*args, **kwargs):
41
+ result = func(*args, **kwargs)
42
+ if isinstance(result, dict):
43
+ result["custom_method_tracer_used"] = True
44
+ return result
45
+
46
+ return wrapper
47
+
48
+ return decorator
49
+
50
+
51
+ # Helper function for testing with counter
52
+ def custom_tracer_with_counter(call_counter, **kwargs):
53
+ def decorator(func):
54
+ @wraps(func)
55
+ def wrapper(*args, **kwargs):
56
+ call_counter["count"] += 1
57
+ result = func(*args, **kwargs)
58
+ if isinstance(result, dict):
59
+ result["custom_tracer_id"] = call_counter["count"]
60
+ return result
61
+
62
+ return wrapper
63
+
64
+ return decorator
65
+
66
+
67
+ # Define the test classes
68
+ class TestClassForMethodTest:
69
+ @traced()
70
+ def sample_method(self, x, y):
71
+ return {"product": x * y}
72
+
73
+
74
+ # Module level functions for function test
75
+ @traced()
76
+ def func1_for_test(x):
77
+ return {"result": x * 2}
78
+
79
+
80
+ @traced()
81
+ def func2_for_test(x):
82
+ return {"result": x * 3}
83
+
84
+
85
+ # Define a function with @traced
86
+ @traced()
87
+ def sample_function():
88
+ return {"status": "success"}
89
+
90
+
91
+ def test_tracing_manager_custom_implementation():
92
+ """Test setting and getting a custom tracer implementation."""
93
+ # Set the custom implementation
94
+ TracingManager.reapply_traced_decorator(simple_custom_tracer)
95
+
96
+ # Get the implementation and verify it's the same one
97
+ impl = TracingManager.get_custom_tracer_implementation()
98
+ assert impl is simple_custom_tracer
99
+
100
+
101
+ def test_traced_with_custom_implementation():
102
+ """Test that @traced uses a custom implementation when provided."""
103
+ # Set the custom implementation
104
+ TracingManager.reapply_traced_decorator(simple_custom_tracer)
105
+
106
+ # Call the function and verify the custom implementation was used
107
+ result = sample_function()
108
+ assert "custom_tracer_used" in result
109
+ assert result["custom_tracer_used"] is True
110
+
111
+
112
+ def test_reapply_traced_decorator_to_class_method():
113
+ """Test reapply_traced_decorator with class methods."""
114
+ TracingManager.reapply_traced_decorator(donothing_custom_tracer)
115
+
116
+ # Create instance and call with default implementation
117
+ instance = TestClassForMethodTest()
118
+ result1 = instance.sample_method(2, 3)
119
+ assert result1 == {"product": 6}
120
+
121
+ # Apply our custom implementation
122
+ TracingManager.reapply_traced_decorator(custom_method_tracer)
123
+
124
+ # Create a NEW instance which will use the updated class method
125
+ new_instance = TestClassForMethodTest()
126
+ result2 = new_instance.sample_method(2, 3)
127
+
128
+ # Verify the result
129
+ assert "product" in result2
130
+ assert result2["product"] == 6
131
+ assert "custom_method_tracer_used" in result2
132
+
133
+
134
+ def test_reapply_to_module_level_functions():
135
+ """Test reapply_traced_decorator with module level functions."""
136
+
137
+ TracingManager.reapply_traced_decorator(donothing_custom_tracer)
138
+
139
+ # First call with default implementation
140
+ assert func1_for_test(5) == {"result": 10}
141
+ assert func2_for_test(5) == {"result": 15}
142
+
143
+ # Counter to track custom tracer calls
144
+ call_counter = {"count": 0}
145
+
146
+ # Reapply with the custom implementation
147
+ TracingManager.reapply_traced_decorator(
148
+ lambda **kwargs: custom_tracer_with_counter(call_counter, **kwargs)
149
+ )
150
+
151
+ # Call the functions directly - they should now use the updated implementation
152
+ result1 = func1_for_test(5)
153
+ result2 = func2_for_test(5)
154
+
155
+ # Verify the custom implementation was applied
156
+ assert "result" in result1
157
+ assert result1["result"] == 10
158
+ assert "custom_tracer_id" in result1
159
+
160
+ assert "result" in result2
161
+ assert result2["result"] == 15
162
+ assert "custom_tracer_id" in result2
163
+
164
+ # Verify both functions were processed
165
+ assert call_counter["count"] == 2
@@ -2393,7 +2393,7 @@ wheels = [
2393
2393
 
2394
2394
  [[package]]
2395
2395
  name = "uipath"
2396
- version = "2.0.13"
2396
+ version = "2.0.15"
2397
2397
  source = { editable = "." }
2398
2398
  dependencies = [
2399
2399
  { name = "click" },
@@ -1,3 +0,0 @@
1
- from ._traced import TracedDecoratorRegistry, traced, wait_for_tracers # noqa: D104
2
-
3
- __all__ = ["TracedDecoratorRegistry", "traced", "wait_for_tracers"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes