uipath 2.0.76__tar.gz → 2.0.78__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.
Files changed (201) hide show
  1. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/lint.yml +3 -0
  2. {uipath-2.0.76 → uipath-2.0.78}/PKG-INFO +1 -1
  3. {uipath-2.0.76 → uipath-2.0.78}/justfile +5 -0
  4. {uipath-2.0.76 → uipath-2.0.78}/pyproject.toml +1 -1
  5. uipath-2.0.78/scripts/debug_test.py +28 -0
  6. uipath-2.0.78/scripts/lint_httpx_client.py +237 -0
  7. uipath-2.0.78/src/uipath/_cli/_utils/_constants.py +58 -0
  8. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_pack.py +2 -3
  9. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/attachments_service.py +1 -1
  10. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/_otel_exporters.py +8 -4
  11. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/utils/_endpoints_manager.py +3 -1
  12. {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_otel_exporters.py +7 -7
  13. {uipath-2.0.76 → uipath-2.0.78}/uv.lock +2 -1
  14. uipath-2.0.76/src/uipath/_cli/_utils/_constants.py +0 -1
  15. {uipath-2.0.76 → uipath-2.0.78}/.cursorrules +0 -0
  16. {uipath-2.0.76 → uipath-2.0.78}/.editorconfig +0 -0
  17. {uipath-2.0.76 → uipath-2.0.78}/.gitattributes +0 -0
  18. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/cd.yml +0 -0
  19. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/ci.yml +0 -0
  20. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/commitlint.yml +0 -0
  21. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/publish-dev.yml +0 -0
  22. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/publish-docs.yml +0 -0
  23. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/slack.yml +0 -0
  24. {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/test.yml +0 -0
  25. {uipath-2.0.76 → uipath-2.0.78}/.gitignore +0 -0
  26. {uipath-2.0.76 → uipath-2.0.78}/.pre-commit-config.yaml +0 -0
  27. {uipath-2.0.76 → uipath-2.0.78}/.python-version +0 -0
  28. {uipath-2.0.76 → uipath-2.0.78}/.vscode/extensions.json +0 -0
  29. {uipath-2.0.76 → uipath-2.0.78}/.vscode/launch.json +0 -0
  30. {uipath-2.0.76 → uipath-2.0.78}/.vscode/settings.json +0 -0
  31. {uipath-2.0.76 → uipath-2.0.78}/CONTRIBUTING.md +0 -0
  32. {uipath-2.0.76 → uipath-2.0.78}/LICENSE +0 -0
  33. {uipath-2.0.76 → uipath-2.0.78}/README.md +0 -0
  34. {uipath-2.0.76 → uipath-2.0.78}/docs/CONTRIBUTING.md +0 -0
  35. {uipath-2.0.76 → uipath-2.0.78}/docs/FAQ.md +0 -0
  36. {uipath-2.0.76 → uipath-2.0.78}/docs/assets/env-preparation-failed-dark.png +0 -0
  37. {uipath-2.0.76 → uipath-2.0.78}/docs/assets/env-preparation-failed-light.png +0 -0
  38. {uipath-2.0.76 → uipath-2.0.78}/docs/assets/favicon.png +0 -0
  39. {uipath-2.0.76 → uipath-2.0.78}/docs/assets/logo-dark.svg +0 -0
  40. {uipath-2.0.76 → uipath-2.0.78}/docs/assets/logo-light.svg +0 -0
  41. {uipath-2.0.76 → uipath-2.0.78}/docs/cli/index.md +0 -0
  42. {uipath-2.0.76 → uipath-2.0.78}/docs/core/actions.md +0 -0
  43. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_dark.gif +0 -0
  44. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_light.gif +0 -0
  45. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_secret_dark.png +0 -0
  46. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_secret_light.png +0 -0
  47. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/copy_path_dark.png +0 -0
  48. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/copy_path_light.png +0 -0
  49. {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets.md +0 -0
  50. {uipath-2.0.76 → uipath-2.0.78}/docs/core/attachments.md +0 -0
  51. {uipath-2.0.76 → uipath-2.0.78}/docs/core/buckets.md +0 -0
  52. {uipath-2.0.76 → uipath-2.0.78}/docs/core/connections.md +0 -0
  53. {uipath-2.0.76 → uipath-2.0.78}/docs/core/context_grounding.md +0 -0
  54. {uipath-2.0.76 → uipath-2.0.78}/docs/core/environment_variables.md +0 -0
  55. {uipath-2.0.76 → uipath-2.0.78}/docs/core/getting_started.md +0 -0
  56. {uipath-2.0.76 → uipath-2.0.78}/docs/core/jobs.md +0 -0
  57. {uipath-2.0.76 → uipath-2.0.78}/docs/core/processes.md +0 -0
  58. {uipath-2.0.76 → uipath-2.0.78}/docs/core/queues.md +0 -0
  59. {uipath-2.0.76 → uipath-2.0.78}/docs/core/traced.md +0 -0
  60. {uipath-2.0.76 → uipath-2.0.78}/docs/hooks.py +0 -0
  61. {uipath-2.0.76 → uipath-2.0.78}/docs/index.md +0 -0
  62. {uipath-2.0.76 → uipath-2.0.78}/docs/javascripts/extra.js +0 -0
  63. {uipath-2.0.76 → uipath-2.0.78}/docs/overrides/main.html +0 -0
  64. {uipath-2.0.76 → uipath-2.0.78}/docs/overrides/partials/actions.html +0 -0
  65. {uipath-2.0.76 → uipath-2.0.78}/docs/overrides/partials/logo.html +0 -0
  66. {uipath-2.0.76 → uipath-2.0.78}/docs/release_policy.md +0 -0
  67. {uipath-2.0.76 → uipath-2.0.78}/docs/stylesheets/extra.css +0 -0
  68. {uipath-2.0.76 → uipath-2.0.78}/mkdocs.yml +0 -0
  69. {uipath-2.0.76 → uipath-2.0.78}/py.typed +0 -0
  70. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/__init__.py +0 -0
  71. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/README.md +0 -0
  72. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/__init__.py +0 -0
  73. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_auth_server.py +0 -0
  74. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_client_credentials.py +0 -0
  75. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_models.py +0 -0
  76. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
  77. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_portal_service.py +0 -0
  78. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_utils.py +0 -0
  79. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/auth_config.json +0 -0
  80. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/index.html +0 -0
  81. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/localhost.crt +0 -0
  82. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/localhost.key +0 -0
  83. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_contracts.py +0 -0
  84. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_escalation.py +0 -0
  85. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_hitl.py +0 -0
  86. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_logging.py +0 -0
  87. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_runtime.py +0 -0
  88. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
  89. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/.rels.template +0 -0
  90. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
  91. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/main.py.template +0 -0
  92. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
  93. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_common.py +0 -0
  94. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_console.py +0 -0
  95. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_debug.py +0 -0
  96. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_folders.py +0 -0
  97. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_input_args.py +0 -0
  98. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
  99. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_processes.py +0 -0
  100. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_tracing.py +0 -0
  101. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_auth.py +0 -0
  102. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_deploy.py +0 -0
  103. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_init.py +0 -0
  104. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_invoke.py +0 -0
  105. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_new.py +0 -0
  106. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_publish.py +0 -0
  107. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_run.py +0 -0
  108. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/middlewares.py +0 -0
  109. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/spinner.py +0 -0
  110. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_config.py +0 -0
  111. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_execution_context.py +0 -0
  112. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_folder_context.py +0 -0
  113. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/__init__.py +0 -0
  114. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/_base_service.py +0 -0
  115. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/actions_service.py +0 -0
  116. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/api_client.py +0 -0
  117. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/assets_service.py +0 -0
  118. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/buckets_service.py +0 -0
  119. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/connections_service.py +0 -0
  120. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/context_grounding_service.py +0 -0
  121. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/folder_service.py +0 -0
  122. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/jobs_service.py +0 -0
  123. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/llm_gateway_service.py +0 -0
  124. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/processes_service.py +0 -0
  125. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/queues_service.py +0 -0
  126. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_uipath.py +0 -0
  127. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/__init__.py +0 -0
  128. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_endpoint.py +0 -0
  129. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_infer_bindings.py +0 -0
  130. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_logs.py +0 -0
  131. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_read_overwrites.py +0 -0
  132. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_request_override.py +0 -0
  133. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_request_spec.py +0 -0
  134. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_ssl_context.py +0 -0
  135. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_url.py +0 -0
  136. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_user_agent.py +0 -0
  137. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/constants.py +0 -0
  138. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/__init__.py +0 -0
  139. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/action_schema.py +0 -0
  140. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/actions.py +0 -0
  141. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/assets.py +0 -0
  142. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/attachment.py +0 -0
  143. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/buckets.py +0 -0
  144. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/connections.py +0 -0
  145. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/context_grounding.py +0 -0
  146. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/context_grounding_index.py +0 -0
  147. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/errors.py +0 -0
  148. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/exceptions.py +0 -0
  149. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/interrupt_models.py +0 -0
  150. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/job.py +0 -0
  151. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/llm_gateway.py +0 -0
  152. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/processes.py +0 -0
  153. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/queues.py +0 -0
  154. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/py.typed +0 -0
  155. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/telemetry/__init__.py +0 -0
  156. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/telemetry/_constants.py +0 -0
  157. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/telemetry/_track.py +0 -0
  158. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/__init__.py +0 -0
  159. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/_traced.py +0 -0
  160. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/_utils.py +0 -0
  161. {uipath-2.0.76 → uipath-2.0.78}/src/uipath/utils/__init__.py +0 -0
  162. {uipath-2.0.76 → uipath-2.0.78}/tests/__init__.py +0 -0
  163. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/conftest.py +0 -0
  164. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/bindings_script.py +0 -0
  165. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/pyproject.toml +0 -0
  166. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/simple_script.py +0 -0
  167. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/uipath-mock.json +0 -0
  168. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/uipath-simple-script-mock.json +0 -0
  169. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_hitl.py +0 -0
  170. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_init.py +0 -0
  171. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_invoke.py +0 -0
  172. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_new.py +0 -0
  173. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_pack.py +0 -0
  174. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_publish.py +0 -0
  175. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_run.py +0 -0
  176. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_utils.py +0 -0
  177. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/utils/project_details.py +0 -0
  178. {uipath-2.0.76 → uipath-2.0.78}/tests/cli/utils/uipath_json.py +0 -0
  179. {uipath-2.0.76 → uipath-2.0.78}/tests/conftest.py +0 -0
  180. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/conftest.py +0 -0
  181. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_actions_service.py +0 -0
  182. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_api_client.py +0 -0
  183. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_assets_service.py +0 -0
  184. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_attachments_service.py +0 -0
  185. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_base_service.py +0 -0
  186. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_buckets_service.py +0 -0
  187. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_connections_service.py +0 -0
  188. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_context_grounding_service.py +0 -0
  189. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_folder_service.py +0 -0
  190. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_jobs_service.py +0 -0
  191. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_llm_integration.py +0 -0
  192. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_llm_service.py +0 -0
  193. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_processes_service.py +0 -0
  194. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_queues_service.py +0 -0
  195. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
  196. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/test_bindings_inference.py +0 -0
  197. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/test_config.py +0 -0
  198. {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/test_overwrites.py +0 -0
  199. {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_span_utils.py +0 -0
  200. {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_traced.py +0 -0
  201. {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_tracing_manager.py +0 -0
@@ -35,4 +35,7 @@ jobs:
35
35
 
36
36
  - name: Check formatting
37
37
  run: uv run ruff format --check .
38
+
39
+ - name: Check httpx.Client() usage
40
+ run: uv run python scripts/lint_httpx_client.py
38
41
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.0.76
3
+ Version: 2.0.78
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
@@ -4,6 +4,7 @@ default: lint format
4
4
 
5
5
  lint:
6
6
  ruff check .
7
+ python scripts/lint_httpx_client.py
7
8
 
8
9
  format:
9
10
  ruff format --check .
@@ -13,3 +14,7 @@ build:
13
14
 
14
15
  install:
15
16
  uv sync --all-extras
17
+
18
+ # Test the custom linter
19
+ test-lint-httpx:
20
+ python scripts/test_httpx_linter.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uipath"
3
- version = "2.0.76"
3
+ version = "2.0.78"
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"
@@ -0,0 +1,28 @@
1
+ import subprocess
2
+ import sys
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ # Simple test
7
+ test_code = """
8
+ import httpx
9
+
10
+ def test():
11
+ client = httpx.Client()
12
+ return client
13
+ """
14
+
15
+ linter_path = Path(__file__).parent / "lint_httpx_client.py"
16
+
17
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
18
+ f.write(test_code)
19
+ f.flush()
20
+
21
+ print(f"Testing file: {f.name}")
22
+ result = subprocess.run(
23
+ [sys.executable, str(linter_path), f.name], capture_output=True, text=True
24
+ )
25
+
26
+ print(f"Return code: {result.returncode}")
27
+ print(f"Stdout: {result.stdout}")
28
+ print(f"Stderr: {result.stderr}")
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ """Custom linter to check for httpx.Client() usage.
3
+
4
+ This script checks for direct usage of httpx.Client() without using the
5
+ get_httpx_client_kwargs() function, which is required for proper SSL
6
+ and proxy configuration in the UiPath Python SDK.
7
+ """
8
+
9
+ import ast
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import List, NamedTuple
13
+
14
+
15
+ class LintViolation(NamedTuple):
16
+ """Represents a linting violation."""
17
+
18
+ filename: str
19
+ line: int
20
+ column: int
21
+ message: str
22
+ rule_code: str
23
+
24
+
25
+ class HttpxClientChecker(ast.NodeVisitor):
26
+ """AST visitor to check for httpx.Client() usage violations."""
27
+
28
+ def __init__(self, filename: str):
29
+ """Initialize the checker with a filename.
30
+
31
+ Args:
32
+ filename: The path to the file being checked.
33
+ """
34
+ self.filename = filename
35
+ self.violations: List[LintViolation] = []
36
+ self.has_httpx_import = False
37
+ self.has_get_httpx_client_kwargs_import = False
38
+ # Track variables that contain get_httpx_client_kwargs
39
+ self.variables_with_httpx_kwargs: set[str] = set()
40
+
41
+ def visit_Import(self, node: ast.Import) -> None:
42
+ """Check for httpx imports."""
43
+ for alias in node.names:
44
+ if alias.name == "httpx":
45
+ self.has_httpx_import = True
46
+ self.generic_visit(node)
47
+
48
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
49
+ """Check for imports from httpx or get_httpx_client_kwargs."""
50
+ if node.module == "httpx":
51
+ self.has_httpx_import = True
52
+ elif node.module and "get_httpx_client_kwargs" in [
53
+ alias.name for alias in (node.names or [])
54
+ ]:
55
+ self.has_get_httpx_client_kwargs_import = True
56
+ self.generic_visit(node)
57
+
58
+ def visit_Assign(self, node: ast.Assign) -> None:
59
+ """Track variable assignments that use get_httpx_client_kwargs."""
60
+ if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
61
+ var_name = node.targets[0].id
62
+ if self._assignment_uses_get_httpx_client_kwargs(node.value):
63
+ self.variables_with_httpx_kwargs.add(var_name)
64
+ self.generic_visit(node)
65
+
66
+ def _assignment_uses_get_httpx_client_kwargs(self, value_node: ast.AST) -> bool:
67
+ """Check if an assignment value uses get_httpx_client_kwargs."""
68
+ if isinstance(value_node, ast.Call):
69
+ # Direct call: var = get_httpx_client_kwargs()
70
+ if isinstance(value_node.func, ast.Name):
71
+ if value_node.func.id == "get_httpx_client_kwargs":
72
+ return True
73
+ elif isinstance(value_node.func, ast.Attribute):
74
+ if value_node.func.attr == "get_httpx_client_kwargs":
75
+ return True
76
+ elif isinstance(value_node, ast.Dict):
77
+ # Dictionary that spreads get_httpx_client_kwargs: {..., **get_httpx_client_kwargs()}
78
+ for key in value_node.keys:
79
+ if key is None: # This is a **kwargs expansion
80
+ # Find corresponding value
81
+ idx = value_node.keys.index(key)
82
+ if idx < len(value_node.values):
83
+ spread_value = value_node.values[idx]
84
+ if isinstance(spread_value, ast.Call):
85
+ if isinstance(spread_value.func, ast.Name):
86
+ if spread_value.func.id == "get_httpx_client_kwargs":
87
+ return True
88
+ elif isinstance(spread_value.func, ast.Attribute):
89
+ if spread_value.func.attr == "get_httpx_client_kwargs":
90
+ return True
91
+ elif isinstance(spread_value, ast.Name):
92
+ # Spreading another variable that might contain httpx kwargs
93
+ if spread_value.id in self.variables_with_httpx_kwargs:
94
+ return True
95
+ return False
96
+
97
+ def visit_Call(self, node: ast.Call) -> None:
98
+ """Check for httpx.Client() and httpx.AsyncClient() calls."""
99
+ if self._is_httpx_client_call(node):
100
+ # Check if this is a proper usage with get_httpx_client_kwargs
101
+ if not self._is_using_get_httpx_client_kwargs(node):
102
+ client_type = self._get_client_type(node)
103
+ violation = LintViolation(
104
+ filename=self.filename,
105
+ line=node.lineno,
106
+ column=node.col_offset,
107
+ message=f"Use **get_httpx_client_kwargs() with {client_type}() - should be: {client_type}(**get_httpx_client_kwargs())",
108
+ rule_code="UIPATH001",
109
+ )
110
+ self.violations.append(violation)
111
+
112
+ self.generic_visit(node)
113
+
114
+ def _is_httpx_client_call(self, node: ast.Call) -> bool:
115
+ """Check if the call is httpx.Client() or httpx.AsyncClient()."""
116
+ if isinstance(node.func, ast.Attribute):
117
+ if (
118
+ isinstance(node.func.value, ast.Name)
119
+ and node.func.value.id == "httpx"
120
+ and node.func.attr in ("Client", "AsyncClient")
121
+ ):
122
+ return True
123
+ elif isinstance(node.func, ast.Name) and node.func.id in (
124
+ "Client",
125
+ "AsyncClient",
126
+ ):
127
+ # This could be a direct Client/AsyncClient import, check if httpx is imported
128
+ return self.has_httpx_import
129
+ return False
130
+
131
+ def _get_client_type(self, node: ast.Call) -> str:
132
+ """Get the client type name (Client or AsyncClient)."""
133
+ if isinstance(node.func, ast.Attribute):
134
+ return f"httpx.{node.func.attr}"
135
+ elif isinstance(node.func, ast.Name):
136
+ return node.func.id
137
+ return "httpx.Client"
138
+
139
+ def _is_using_get_httpx_client_kwargs(self, node: ast.Call) -> bool:
140
+ """Check if the httpx.Client() call is using **get_httpx_client_kwargs()."""
141
+ # Check if there are any **kwargs that use get_httpx_client_kwargs directly
142
+ for keyword in node.keywords:
143
+ if keyword.arg is None: # This is a **kwargs expansion
144
+ if isinstance(keyword.value, ast.Call):
145
+ if isinstance(keyword.value.func, ast.Name):
146
+ if keyword.value.func.id == "get_httpx_client_kwargs":
147
+ return True
148
+ elif isinstance(keyword.value.func, ast.Attribute):
149
+ if keyword.value.func.attr == "get_httpx_client_kwargs":
150
+ return True
151
+ elif isinstance(keyword.value, ast.Name):
152
+ # Check if this variable might contain get_httpx_client_kwargs
153
+ # This handles cases like: **client_kwargs where client_kwargs was built from get_httpx_client_kwargs
154
+ var_name = keyword.value.id
155
+ if self._variable_contains_get_httpx_client_kwargs(var_name):
156
+ return True
157
+
158
+ # Also check if it's the ONLY argument and it's **get_httpx_client_kwargs()
159
+ # This handles cases like: httpx.Client(**get_httpx_client_kwargs())
160
+ if len(node.args) == 0 and len(node.keywords) == 1:
161
+ keyword = node.keywords[0]
162
+ if keyword.arg is None and isinstance(keyword.value, ast.Call):
163
+ if isinstance(keyword.value.func, ast.Name):
164
+ if keyword.value.func.id == "get_httpx_client_kwargs":
165
+ return True
166
+ elif isinstance(keyword.value.func, ast.Attribute):
167
+ if keyword.value.func.attr == "get_httpx_client_kwargs":
168
+ return True
169
+
170
+ return False
171
+
172
+ def _variable_contains_get_httpx_client_kwargs(self, var_name: str) -> bool:
173
+ """Check if a variable was built using get_httpx_client_kwargs()."""
174
+ # Check if we've tracked this variable as containing httpx kwargs
175
+ if var_name in self.variables_with_httpx_kwargs:
176
+ return True
177
+
178
+ # Fallback: Simple heuristic based on naming patterns
179
+ # This handles cases where the variable assignment might be complex
180
+ if "client_kwargs" in var_name.lower() or "httpx_kwargs" in var_name.lower():
181
+ return True
182
+
183
+ return False
184
+
185
+
186
+ def check_file(filepath: Path) -> List[LintViolation]:
187
+ """Check a single Python file for httpx.Client() violations."""
188
+ try:
189
+ with open(filepath, "r", encoding="utf-8") as f:
190
+ content = f.read()
191
+
192
+ tree = ast.parse(content, filename=str(filepath))
193
+ checker = HttpxClientChecker(str(filepath))
194
+ checker.visit(tree)
195
+ return checker.violations
196
+
197
+ except SyntaxError:
198
+ # Skip files with syntax errors
199
+ return []
200
+ except Exception as e:
201
+ print(f"Error checking {filepath}: {e}", file=sys.stderr)
202
+ return []
203
+
204
+
205
+ def main():
206
+ """Main function to run the linter."""
207
+ if len(sys.argv) > 1:
208
+ paths = [Path(p) for p in sys.argv[1:]]
209
+ else:
210
+ # Default to checking src and tests directories
211
+ paths = [Path("src"), Path("tests")]
212
+
213
+ all_violations = []
214
+
215
+ for path in paths:
216
+ if path.is_file() and path.suffix == ".py":
217
+ violations = check_file(path)
218
+ all_violations.extend(violations)
219
+ elif path.is_dir():
220
+ for py_file in path.rglob("*.py"):
221
+ violations = check_file(py_file)
222
+ all_violations.extend(violations)
223
+
224
+ # Report violations
225
+ if all_violations:
226
+ for violation in all_violations:
227
+ print(
228
+ f"{violation.filename}:{violation.line}:{violation.column}: {violation.rule_code} {violation.message}"
229
+ )
230
+ sys.exit(1)
231
+ else:
232
+ print("No httpx.Client() violations found.")
233
+ sys.exit(0)
234
+
235
+
236
+ if __name__ == "__main__":
237
+ main()
@@ -0,0 +1,58 @@
1
+ BINDINGS_VERSION = "2.2"
2
+
3
+ # Binary file extension categories
4
+ IMAGE_EXTENSIONS = {
5
+ ".png",
6
+ ".jpg",
7
+ ".jpeg",
8
+ ".gif",
9
+ ".bmp",
10
+ ".ico",
11
+ ".tiff",
12
+ ".webp",
13
+ ".svg",
14
+ }
15
+
16
+ DOCUMENT_EXTENSIONS = {".pdf", ".docx", ".pptx", ".xlsx", ".xls"}
17
+
18
+ ARCHIVE_EXTENSIONS = {".zip", ".tar", ".gz", ".rar", ".7z", ".bz2", ".xz"}
19
+
20
+ MEDIA_EXTENSIONS = {
21
+ ".mp3",
22
+ ".wav",
23
+ ".flac",
24
+ ".aac",
25
+ ".mp4",
26
+ ".avi",
27
+ ".mov",
28
+ ".mkv",
29
+ ".wmv",
30
+ }
31
+
32
+ FONT_EXTENSIONS = {".woff", ".woff2", ".ttf", ".otf", ".eot"}
33
+
34
+ EXECUTABLE_EXTENSIONS = {".exe", ".dll", ".so", ".dylib", ".bin"}
35
+
36
+ DATABASE_EXTENSIONS = {".db", ".sqlite", ".sqlite3"}
37
+
38
+ PYTHON_BINARY_EXTENSIONS = {".pickle", ".pkl"}
39
+
40
+ SPECIAL_EXTENSIONS = {""} # Extensionless binary files
41
+
42
+ # Pre-compute the union for optimal performance
43
+ BINARY_EXTENSIONS = (
44
+ IMAGE_EXTENSIONS
45
+ | DOCUMENT_EXTENSIONS
46
+ | ARCHIVE_EXTENSIONS
47
+ | MEDIA_EXTENSIONS
48
+ | FONT_EXTENSIONS
49
+ | EXECUTABLE_EXTENSIONS
50
+ | DATABASE_EXTENSIONS
51
+ | PYTHON_BINARY_EXTENSIONS
52
+ | SPECIAL_EXTENSIONS
53
+ )
54
+
55
+
56
+ def is_binary_file(file_extension: str) -> bool:
57
+ """Determine if a file should be treated as binary."""
58
+ return file_extension.lower() in BINARY_EXTENSIONS
@@ -17,6 +17,7 @@ except ImportError:
17
17
 
18
18
  from ..telemetry import track
19
19
  from ._utils._console import ConsoleLogger
20
+ from ._utils._constants import is_binary_file
20
21
 
21
22
  console = ConsoleLogger()
22
23
 
@@ -265,8 +266,6 @@ def pack_fn(
265
266
  # Define the allowlist of file extensions to include
266
267
  file_extensions_included = [".py", ".mermaid", ".json", ".yaml", ".yml"]
267
268
  files_included = []
268
- # Binary files that should be read in binary mode
269
- binary_extensions = [".exe", "", ".xlsx", ".xls"]
270
269
 
271
270
  with open(config_path, "r") as f:
272
271
  config_data = json.load(f)
@@ -328,7 +327,7 @@ def pack_fn(
328
327
  if file_extension in file_extensions_included or file in files_included:
329
328
  file_path = os.path.join(root, file)
330
329
  rel_path = os.path.relpath(file_path, directory)
331
- if file_extension in binary_extensions:
330
+ if is_binary_file(file_extension):
332
331
  # Read binary files in binary mode
333
332
  with open(file_path, "rb") as f:
334
333
  z.writestr(f"content/{rel_path}", f.read())
@@ -240,7 +240,7 @@ class AttachmentsService(FolderContext, BaseService):
240
240
  async for chunk in response.aiter_bytes(chunk_size=8192):
241
241
  file.write(chunk)
242
242
  else:
243
- async with httpx.AsyncClient() as client:
243
+ async with httpx.AsyncClient(**get_httpx_client_kwargs()) as client:
244
244
  async with client.stream(
245
245
  "GET", download_uri, headers=headers
246
246
  ) as response:
@@ -4,13 +4,15 @@ import os
4
4
  import time
5
5
  from typing import Any, Dict, Sequence
6
6
 
7
- from httpx import Client
7
+ import httpx
8
8
  from opentelemetry.sdk.trace import ReadableSpan
9
9
  from opentelemetry.sdk.trace.export import (
10
10
  SpanExporter,
11
11
  SpanExportResult,
12
12
  )
13
13
 
14
+ from uipath._utils._ssl_context import get_httpx_client_kwargs
15
+
14
16
  from ._utils import _SpanUtils
15
17
 
16
18
  logger = logging.getLogger(__name__)
@@ -19,9 +21,9 @@ logger = logging.getLogger(__name__)
19
21
  class LlmOpsHttpExporter(SpanExporter):
20
22
  """An OpenTelemetry span exporter that sends spans to UiPath LLM Ops."""
21
23
 
22
- def __init__(self, **kwargs):
24
+ def __init__(self, **client_kwargs):
23
25
  """Initialize the exporter with the base URL and authentication token."""
24
- super().__init__(**kwargs)
26
+ super().__init__(**client_kwargs)
25
27
  self.base_url = self._get_base_url()
26
28
  self.auth_token = os.environ.get("UIPATH_ACCESS_TOKEN")
27
29
  self.headers = {
@@ -29,7 +31,9 @@ class LlmOpsHttpExporter(SpanExporter):
29
31
  "Authorization": f"Bearer {self.auth_token}",
30
32
  }
31
33
 
32
- self.http_client = Client(headers=self.headers)
34
+ client_kwargs = get_httpx_client_kwargs()
35
+
36
+ self.http_client = httpx.Client(**client_kwargs, headers=self.headers)
33
37
 
34
38
  def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
35
39
  """Export spans to UiPath LLM Ops."""
@@ -5,6 +5,8 @@ from typing import Optional
5
5
 
6
6
  import httpx
7
7
 
8
+ from uipath._utils._ssl_context import get_httpx_client_kwargs
9
+
8
10
  loggger = logging.getLogger(__name__)
9
11
 
10
12
 
@@ -56,7 +58,7 @@ class EndpointManager:
56
58
  def _check_agenthub(cls) -> bool:
57
59
  """Perform the actual check for AgentHub capabilities."""
58
60
  try:
59
- with httpx.Client() as http_client:
61
+ with httpx.Client(**get_httpx_client_kwargs()) as http_client:
60
62
  base_url = os.getenv("UIPATH_URL", "")
61
63
  capabilities_url = f"{base_url.rstrip('/')}/{UiPathEndpoints.AH_CAPABILITIES_ENDPOINT.value}"
62
64
  loggger.debug(f"Checking AgentHub capabilities at {capabilities_url}")
@@ -42,7 +42,7 @@ def mock_span():
42
42
  @pytest.fixture
43
43
  def exporter(mock_env_vars):
44
44
  """Create an exporter instance for testing."""
45
- with patch("uipath.tracing._otel_exporters.Client"):
45
+ with patch("uipath.tracing._otel_exporters.httpx.Client"):
46
46
  exporter = LlmOpsHttpExporter()
47
47
  # Mock _build_url to include query parameters as in the actual implementation
48
48
  exporter._build_url = MagicMock( # type: ignore
@@ -53,7 +53,7 @@ def exporter(mock_env_vars):
53
53
 
54
54
  def test_init_with_env_vars(mock_env_vars):
55
55
  """Test initialization with environment variables."""
56
- with patch("uipath.tracing._otel_exporters.Client"):
56
+ with patch("uipath.tracing._otel_exporters.httpx.Client"):
57
57
  exporter = LlmOpsHttpExporter()
58
58
 
59
59
  assert exporter.base_url == "https://test.uipath.com/org/tenant"
@@ -67,7 +67,7 @@ def test_init_with_env_vars(mock_env_vars):
67
67
  def test_init_with_default_url():
68
68
  """Test initialization with default URL when environment variable is not set."""
69
69
  with (
70
- patch("uipath.tracing._otel_exporters.Client"),
70
+ patch("uipath.tracing._otel_exporters.httpx.Client"),
71
71
  patch.dict(os.environ, {"UIPATH_ACCESS_TOKEN": "test-token"}, clear=True),
72
72
  ):
73
73
  exporter = LlmOpsHttpExporter()
@@ -154,7 +154,7 @@ def test_get_base_url():
154
154
  with patch.dict(
155
155
  os.environ, {"UIPATH_URL": "https://custom.uipath.com/org/tenant/"}, clear=True
156
156
  ):
157
- with patch("uipath.tracing._otel_exporters.Client"):
157
+ with patch("uipath.tracing._otel_exporters.httpx.Client"):
158
158
  exporter = LlmOpsHttpExporter()
159
159
  assert exporter.base_url == "https://custom.uipath.com/org/tenant"
160
160
 
@@ -162,20 +162,20 @@ def test_get_base_url():
162
162
  with patch.dict(
163
163
  os.environ, {"UIPATH_URL": "https://custom.uipath.com/org/tenant"}, clear=True
164
164
  ):
165
- with patch("uipath.tracing._otel_exporters.Client"):
165
+ with patch("uipath.tracing._otel_exporters.httpx.Client"):
166
166
  exporter = LlmOpsHttpExporter()
167
167
  assert exporter.base_url == "https://custom.uipath.com/org/tenant"
168
168
 
169
169
  # Test with no environment variable
170
170
  with patch.dict(os.environ, {}, clear=True):
171
- with patch("uipath.tracing._otel_exporters.Client"):
171
+ with patch("uipath.tracing._otel_exporters.httpx.Client"):
172
172
  exporter = LlmOpsHttpExporter()
173
173
  assert exporter.base_url == "https://cloud.uipath.com/dummyOrg/dummyTennant"
174
174
 
175
175
 
176
176
  def test_send_with_retries_success():
177
177
  """Test _send_with_retries method with successful response."""
178
- with patch("uipath.tracing._otel_exporters.Client"):
178
+ with patch("uipath.tracing._otel_exporters.httpx.Client"):
179
179
  exporter = LlmOpsHttpExporter()
180
180
 
181
181
  mock_response = MagicMock()
@@ -1,4 +1,5 @@
1
1
  version = 1
2
+ revision = 1
2
3
  requires-python = ">=3.10"
3
4
  resolution-markers = [
4
5
  "python_full_version >= '3.12.4'",
@@ -3120,7 +3121,7 @@ wheels = [
3120
3121
 
3121
3122
  [[package]]
3122
3123
  name = "uipath"
3123
- version = "2.0.76"
3124
+ version = "2.0.78"
3124
3125
  source = { editable = "." }
3125
3126
  dependencies = [
3126
3127
  { name = "azure-monitor-opentelemetry" },
@@ -1 +0,0 @@
1
- BINDINGS_VERSION = "2.2"
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