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.
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/lint.yml +3 -0
- {uipath-2.0.76 → uipath-2.0.78}/PKG-INFO +1 -1
- {uipath-2.0.76 → uipath-2.0.78}/justfile +5 -0
- {uipath-2.0.76 → uipath-2.0.78}/pyproject.toml +1 -1
- uipath-2.0.78/scripts/debug_test.py +28 -0
- uipath-2.0.78/scripts/lint_httpx_client.py +237 -0
- uipath-2.0.78/src/uipath/_cli/_utils/_constants.py +58 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_pack.py +2 -3
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/attachments_service.py +1 -1
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/_otel_exporters.py +8 -4
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/utils/_endpoints_manager.py +3 -1
- {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_otel_exporters.py +7 -7
- {uipath-2.0.76 → uipath-2.0.78}/uv.lock +2 -1
- uipath-2.0.76/src/uipath/_cli/_utils/_constants.py +0 -1
- {uipath-2.0.76 → uipath-2.0.78}/.cursorrules +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.editorconfig +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.gitattributes +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/cd.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/ci.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/commitlint.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/publish-dev.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/publish-docs.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/slack.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.github/workflows/test.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.gitignore +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.pre-commit-config.yaml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.python-version +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.vscode/extensions.json +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.vscode/launch.json +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/.vscode/settings.json +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/CONTRIBUTING.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/LICENSE +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/README.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/CONTRIBUTING.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/FAQ.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/assets/env-preparation-failed-dark.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/assets/env-preparation-failed-light.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/assets/favicon.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/assets/logo-dark.svg +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/assets/logo-light.svg +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/cli/index.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/actions.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_dark.gif +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_light.gif +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_secret_dark.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/cloud_env_var_secret_light.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/copy_path_dark.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets/copy_path_light.png +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/assets.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/attachments.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/buckets.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/connections.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/context_grounding.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/environment_variables.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/getting_started.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/jobs.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/processes.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/queues.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/core/traced.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/hooks.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/index.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/javascripts/extra.js +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/overrides/main.html +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/overrides/partials/actions.html +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/overrides/partials/logo.html +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/release_policy.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/docs/stylesheets/extra.css +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/mkdocs.yml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/py.typed +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/README.md +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_auth_server.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_client_credentials.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_models.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_portal_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/_utils.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/auth_config.json +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/index.html +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/localhost.crt +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_auth/localhost.key +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_contracts.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_escalation.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_hitl.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_logging.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_runtime/_runtime.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/.rels.template +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/main.py.template +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_common.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_console.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_debug.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_folders.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_input_args.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_processes.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/_utils/_tracing.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_auth.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_deploy.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_init.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_invoke.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_new.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_publish.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/cli_run.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/middlewares.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_cli/spinner.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_config.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_execution_context.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_folder_context.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/_base_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/actions_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/api_client.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/assets_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/buckets_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/connections_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/context_grounding_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/folder_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/jobs_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/llm_gateway_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/processes_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_services/queues_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_uipath.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_endpoint.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_infer_bindings.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_logs.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_read_overwrites.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_request_override.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_request_spec.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_ssl_context.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_url.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/_user_agent.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/_utils/constants.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/action_schema.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/actions.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/assets.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/attachment.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/buckets.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/connections.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/context_grounding.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/context_grounding_index.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/errors.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/exceptions.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/interrupt_models.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/job.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/llm_gateway.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/processes.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/models/queues.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/py.typed +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/telemetry/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/telemetry/_constants.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/telemetry/_track.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/_traced.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/tracing/_utils.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/src/uipath/utils/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/__init__.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/conftest.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/bindings_script.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/pyproject.toml +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/simple_script.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/uipath-mock.json +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/mocks/uipath-simple-script-mock.json +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_hitl.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_init.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_invoke.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_new.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_pack.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_publish.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_run.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/test_utils.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/utils/project_details.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/cli/utils/uipath_json.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/conftest.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/conftest.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_actions_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_api_client.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_assets_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_attachments_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_base_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_buckets_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_connections_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_context_grounding_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_folder_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_jobs_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_llm_integration.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_llm_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_processes_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_queues_service.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/test_bindings_inference.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/test_config.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/sdk/test_overwrites.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_span_utils.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_traced.py +0 -0
- {uipath-2.0.76 → uipath-2.0.78}/tests/tracing/test_tracing_manager.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: uipath
|
3
|
-
Version: 2.0.
|
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.
|
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
|
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
|
-
|
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, **
|
24
|
+
def __init__(self, **client_kwargs):
|
23
25
|
"""Initialize the exporter with the base URL and authentication token."""
|
24
|
-
super().__init__(**
|
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
|
-
|
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.
|
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
|
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
|