alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__py3-none-any.whl
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.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +156 -0
- alita_sdk/cli/agent_loader.py +245 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3113 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +91 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +388 -46
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +157 -39
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
- alita_sdk/runtime/langchain/langraph_agent.py +405 -84
- alita_sdk/runtime/langchain/utils.py +106 -7
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +31 -0
- alita_sdk/runtime/toolkits/application.py +29 -10
- alita_sdk/runtime/toolkits/artifact.py +20 -11
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +783 -0
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +356 -69
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +10 -3
- alita_sdk/runtime/tools/application.py +27 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +67 -35
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +148 -46
- alita_sdk/runtime/tools/llm.py +1003 -128
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +65 -48
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +9 -3
- alita_sdk/runtime/tools/vectorstore_base.py +70 -14
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +40 -13
- alita_sdk/runtime/utils/toolkit_utils.py +30 -9
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +134 -35
- alita_sdk/tools/ado/repos/__init__.py +51 -32
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -13
- alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +271 -84
- alita_sdk/tools/bitbucket/__init__.py +17 -11
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +11 -8
- alita_sdk/tools/code_indexer_toolkit.py +82 -22
- alita_sdk/tools/confluence/__init__.py +22 -16
- alita_sdk/tools/confluence/api_wrapper.py +107 -30
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +14 -15
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +16 -11
- alita_sdk/tools/gitlab/api_wrapper.py +218 -48
- alita_sdk/tools/gitlab_org/__init__.py +10 -9
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +11 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -10
- alita_sdk/tools/jira/api_wrapper.py +92 -41
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +12 -4
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -9
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +31 -11
- alita_sdk/tools/qtest/api_wrapper.py +2135 -86
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -8
- alita_sdk/tools/salesforce/__init__.py +10 -8
- alita_sdk/tools/servicenow/__init__.py +17 -15
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -7
- alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +10 -7
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +12 -9
- alita_sdk/tools/testio/__init__.py +10 -7
- alita_sdk/tools/testrail/__init__.py +11 -10
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +103 -18
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
- alita_sdk/tools/xray/__init__.py +13 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +10 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
- alita_sdk/tools/zephyr_essential/__init__.py +10 -7
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -7
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.379.dist-info/RECORD +0 -360
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from office365.onedrive.sharepoint_settings import SharepointSettings
|
|
1
3
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
2
4
|
|
|
3
5
|
|
|
@@ -17,3 +19,149 @@ class SharepointConfiguration(BaseModel):
|
|
|
17
19
|
client_id: str = Field(description="SharePoint Client ID")
|
|
18
20
|
client_secret: SecretStr = Field(description="SharePoint Client Secret")
|
|
19
21
|
site_url: str = Field(description="SharePoint Site URL")
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def check_connection(settings: dict) -> str | None:
|
|
25
|
+
"""
|
|
26
|
+
Test the connection to SharePoint API using OAuth2 client credentials.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
settings: Dictionary containing 'client_id', 'client_secret', and 'site_url' (all required)
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
None if connection is successful, error message string otherwise
|
|
33
|
+
"""
|
|
34
|
+
# Validate client_id
|
|
35
|
+
client_id = settings.get("client_id")
|
|
36
|
+
if client_id is None or client_id == "":
|
|
37
|
+
if client_id == "":
|
|
38
|
+
return "Client ID cannot be empty"
|
|
39
|
+
return "Client ID is required"
|
|
40
|
+
|
|
41
|
+
if not isinstance(client_id, str):
|
|
42
|
+
return "Client ID must be a string"
|
|
43
|
+
|
|
44
|
+
client_id = client_id.strip()
|
|
45
|
+
if not client_id:
|
|
46
|
+
return "Client ID cannot be empty"
|
|
47
|
+
|
|
48
|
+
# Validate client_secret
|
|
49
|
+
client_secret = settings.get("client_secret")
|
|
50
|
+
if client_secret is None:
|
|
51
|
+
return "Client secret is required"
|
|
52
|
+
|
|
53
|
+
# Extract secret value if it's a SecretStr
|
|
54
|
+
if hasattr(client_secret, "get_secret_value"):
|
|
55
|
+
client_secret = client_secret.get_secret_value()
|
|
56
|
+
|
|
57
|
+
if not client_secret or not client_secret.strip():
|
|
58
|
+
return "Client secret cannot be empty"
|
|
59
|
+
|
|
60
|
+
# Validate site_url
|
|
61
|
+
site_url = settings.get("site_url")
|
|
62
|
+
if site_url is None or site_url == "":
|
|
63
|
+
if site_url == "":
|
|
64
|
+
return "Site URL cannot be empty"
|
|
65
|
+
return "Site URL is required"
|
|
66
|
+
|
|
67
|
+
if not isinstance(site_url, str):
|
|
68
|
+
return "Site URL must be a string"
|
|
69
|
+
|
|
70
|
+
site_url = site_url.strip()
|
|
71
|
+
if not site_url:
|
|
72
|
+
return "Site URL cannot be empty"
|
|
73
|
+
|
|
74
|
+
if not site_url.startswith(("http://", "https://")):
|
|
75
|
+
return "Site URL must start with http:// or https://"
|
|
76
|
+
|
|
77
|
+
# Remove trailing slash for consistency
|
|
78
|
+
site_url = site_url.rstrip("/")
|
|
79
|
+
|
|
80
|
+
# Extract tenant and resource from site URL
|
|
81
|
+
# Expected format: https://<tenant>.sharepoint.com/sites/<site>
|
|
82
|
+
try:
|
|
83
|
+
if ".sharepoint.com" not in site_url:
|
|
84
|
+
return "Site URL must be a valid SharePoint URL (*.sharepoint.com)"
|
|
85
|
+
|
|
86
|
+
# Extract tenant (e.g., "contoso" from "contoso.sharepoint.com")
|
|
87
|
+
parts = site_url.split("//")[1].split(".")
|
|
88
|
+
if len(parts) < 3:
|
|
89
|
+
return "Invalid SharePoint URL format"
|
|
90
|
+
tenant = parts[0]
|
|
91
|
+
|
|
92
|
+
# Build token endpoint
|
|
93
|
+
token_url = f"https://accounts.accesscontrol.windows.net/{tenant}.onmicrosoft.com/tokens/OAuth/2"
|
|
94
|
+
|
|
95
|
+
# Build resource (the site URL with /_api appended)
|
|
96
|
+
resource = f"{site_url.split('/sites/')[0]}@{site_url.split('//')[1].split('/')[0].split('.')[0]}"
|
|
97
|
+
|
|
98
|
+
except Exception:
|
|
99
|
+
return "Failed to parse SharePoint URL - ensure it's in format: https://<tenant>.sharepoint.com/sites/<site>"
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Step 1: Get OAuth2 access token using client credentials
|
|
103
|
+
token_response = requests.post(
|
|
104
|
+
token_url,
|
|
105
|
+
data={
|
|
106
|
+
"grant_type": "client_credentials",
|
|
107
|
+
"client_id": f"{client_id}@{tenant}.onmicrosoft.com",
|
|
108
|
+
"client_secret": client_secret,
|
|
109
|
+
"resource": f"00000003-0000-0ff1-ce00-000000000000/{site_url.split('//')[1].split('/')[0]}@{tenant}.onmicrosoft.com"
|
|
110
|
+
},
|
|
111
|
+
timeout=10,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if token_response.status_code == 400:
|
|
115
|
+
try:
|
|
116
|
+
error_data = token_response.json()
|
|
117
|
+
error_desc = error_data.get("error_description", "")
|
|
118
|
+
if "not found in the directory" in error_desc.lower():
|
|
119
|
+
return "Invalid client ID. Please check if you provide a correct client ID and try again."
|
|
120
|
+
elif "client_secret" in error_desc.lower():
|
|
121
|
+
return "Invalid client secret"
|
|
122
|
+
else:
|
|
123
|
+
return f"OAuth2 authentication failed: {error_desc}"
|
|
124
|
+
except Exception:
|
|
125
|
+
return "Invalid client credentials"
|
|
126
|
+
|
|
127
|
+
elif token_response.status_code == 401:
|
|
128
|
+
return "Invalid client secret provided. Please check if you provide a correct client secret and try again."
|
|
129
|
+
elif token_response.status_code != 200:
|
|
130
|
+
return f"Failed to obtain access token (status {token_response.status_code})"
|
|
131
|
+
|
|
132
|
+
# Extract access token
|
|
133
|
+
try:
|
|
134
|
+
token_data = token_response.json()
|
|
135
|
+
access_token = token_data.get("access_token")
|
|
136
|
+
if not access_token:
|
|
137
|
+
return "No access token received from SharePoint"
|
|
138
|
+
except Exception:
|
|
139
|
+
return "Failed to parse token response"
|
|
140
|
+
|
|
141
|
+
# Step 2: Test the access token by calling SharePoint API
|
|
142
|
+
api_url = f"{site_url}/_api/web"
|
|
143
|
+
api_response = requests.get(
|
|
144
|
+
api_url,
|
|
145
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
|
146
|
+
timeout=10,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if api_response.status_code == 200:
|
|
150
|
+
return None # Connection successful
|
|
151
|
+
elif api_response.status_code == 401:
|
|
152
|
+
return "Access token is invalid or expired"
|
|
153
|
+
elif api_response.status_code == 403:
|
|
154
|
+
return "Access forbidden - client may lack required permissions for this site"
|
|
155
|
+
elif api_response.status_code == 404:
|
|
156
|
+
return f"Site not found or not accessible: {site_url}"
|
|
157
|
+
else:
|
|
158
|
+
return f"SharePoint API request failed with status {api_response.status_code}"
|
|
159
|
+
|
|
160
|
+
except requests.exceptions.Timeout:
|
|
161
|
+
return "Connection timeout - SharePoint is not responding"
|
|
162
|
+
except requests.exceptions.ConnectionError:
|
|
163
|
+
return "Connection error - unable to reach SharePoint"
|
|
164
|
+
except requests.exceptions.RequestException as e:
|
|
165
|
+
return f"Request failed: {str(e)}"
|
|
166
|
+
except Exception as e:
|
|
167
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import requests
|
|
1
2
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
2
3
|
|
|
3
4
|
|
|
@@ -16,3 +17,85 @@ class TestIOConfiguration(BaseModel):
|
|
|
16
17
|
)
|
|
17
18
|
endpoint: str = Field(description="TestIO endpoint")
|
|
18
19
|
api_key: SecretStr = Field(description="TestIO API Key")
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def check_connection(settings: dict) -> str | None:
|
|
23
|
+
"""
|
|
24
|
+
Test the connection to TestIO API.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
settings: Dictionary containing 'endpoint' and 'api_key' (both required)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
None if connection is successful, error message string otherwise
|
|
31
|
+
"""
|
|
32
|
+
endpoint = settings.get("endpoint")
|
|
33
|
+
if endpoint is None or endpoint == "":
|
|
34
|
+
if endpoint == "":
|
|
35
|
+
return "Endpoint cannot be empty"
|
|
36
|
+
return "Endpoint is required"
|
|
37
|
+
|
|
38
|
+
# Validate endpoint format
|
|
39
|
+
if not isinstance(endpoint, str):
|
|
40
|
+
return "Endpoint must be a string"
|
|
41
|
+
|
|
42
|
+
endpoint = endpoint.strip()
|
|
43
|
+
if not endpoint:
|
|
44
|
+
return "Endpoint cannot be empty"
|
|
45
|
+
if not endpoint.startswith(("http://", "https://")):
|
|
46
|
+
return "Endpoint must start with http:// or https://"
|
|
47
|
+
|
|
48
|
+
# Remove trailing slash for consistency
|
|
49
|
+
endpoint = endpoint.rstrip("/")
|
|
50
|
+
|
|
51
|
+
api_key = settings.get("api_key")
|
|
52
|
+
if api_key is None:
|
|
53
|
+
return "API key is required"
|
|
54
|
+
|
|
55
|
+
# Extract secret value if it's a SecretStr
|
|
56
|
+
if hasattr(api_key, "get_secret_value"):
|
|
57
|
+
api_key = api_key.get_secret_value()
|
|
58
|
+
|
|
59
|
+
# Validate API key is not empty
|
|
60
|
+
if not api_key or not api_key.strip():
|
|
61
|
+
return "API key cannot be empty"
|
|
62
|
+
|
|
63
|
+
# Verification strategy:
|
|
64
|
+
# Use an auth-required endpoint and a single, explicit auth scheme:
|
|
65
|
+
# Authorization: Token <token>
|
|
66
|
+
url = f"{endpoint}/customer/v2/products"
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
resp = TestIOConfiguration._get_with_token(url, api_key)
|
|
70
|
+
|
|
71
|
+
if resp.status_code == 200:
|
|
72
|
+
return None # Connection successful
|
|
73
|
+
if resp.status_code == 401:
|
|
74
|
+
return "Invalid token"
|
|
75
|
+
if resp.status_code == 403:
|
|
76
|
+
return "Access forbidden - token has no access to /customer/v2/products"
|
|
77
|
+
if resp.status_code == 404:
|
|
78
|
+
return "Invalid endpoint. Verify TestIO base endpoint."
|
|
79
|
+
if resp.status_code == 429:
|
|
80
|
+
return "Rate limited - please try again later"
|
|
81
|
+
if 500 <= resp.status_code <= 599:
|
|
82
|
+
return f"TestIO service error (HTTP {resp.status_code})"
|
|
83
|
+
return f"Connection failed (HTTP {resp.status_code})"
|
|
84
|
+
|
|
85
|
+
except requests.exceptions.Timeout:
|
|
86
|
+
return "Connection timeout - TestIO did not respond within 10 seconds"
|
|
87
|
+
except requests.exceptions.ConnectionError:
|
|
88
|
+
return "Connection error - unable to reach TestIO. Check endpoint URL and network."
|
|
89
|
+
except requests.exceptions.RequestException as e:
|
|
90
|
+
return f"Request failed: {str(e)}"
|
|
91
|
+
except Exception:
|
|
92
|
+
return "Unexpected error during TestIO connection check"
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _get_with_token(url: str, token: str) -> requests.Response:
|
|
96
|
+
"""Perform an authenticated GET using `Authorization: Token <token>`."""
|
|
97
|
+
return requests.get(
|
|
98
|
+
url,
|
|
99
|
+
headers={"Authorization": f"Token {token}"},
|
|
100
|
+
timeout=10,
|
|
101
|
+
)
|
|
@@ -19,3 +19,91 @@ class TestRailConfiguration(BaseModel):
|
|
|
19
19
|
url: str = Field(description="Testrail URL")
|
|
20
20
|
email: str = Field(description="TestRail Email")
|
|
21
21
|
password: SecretStr = Field(description="TestRail Password")
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def check_connection(settings: dict) -> str | None:
|
|
25
|
+
"""
|
|
26
|
+
Check the connection to TestRail.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
settings: Dictionary containing TestRail configuration
|
|
30
|
+
- url: TestRail instance URL (required)
|
|
31
|
+
- email: User email for authentication (required)
|
|
32
|
+
- password: Password or API key for authentication (required)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
None if connection successful, error message string if failed
|
|
36
|
+
"""
|
|
37
|
+
import requests
|
|
38
|
+
from requests.auth import HTTPBasicAuth
|
|
39
|
+
|
|
40
|
+
# Validate url
|
|
41
|
+
url = settings.get("url", "").strip()
|
|
42
|
+
if not url:
|
|
43
|
+
return "TestRail URL is required"
|
|
44
|
+
|
|
45
|
+
# Normalize URL - remove trailing slashes
|
|
46
|
+
url = url.rstrip("/")
|
|
47
|
+
|
|
48
|
+
# Basic URL validation
|
|
49
|
+
if not url.startswith(("http://", "https://")):
|
|
50
|
+
return "TestRail URL must start with http:// or https://"
|
|
51
|
+
|
|
52
|
+
# Validate email
|
|
53
|
+
email = settings.get("email", "").strip()
|
|
54
|
+
if not email:
|
|
55
|
+
return "TestRail email is required"
|
|
56
|
+
|
|
57
|
+
# Validate password
|
|
58
|
+
password = settings.get("password")
|
|
59
|
+
if not password:
|
|
60
|
+
return "TestRail password is required"
|
|
61
|
+
|
|
62
|
+
# Extract password value if it's a SecretStr
|
|
63
|
+
password_value = password.get_secret_value() if hasattr(password, 'get_secret_value') else password
|
|
64
|
+
|
|
65
|
+
if not password_value or not password_value.strip():
|
|
66
|
+
return "TestRail password cannot be empty"
|
|
67
|
+
|
|
68
|
+
# Test connection using /index.php?/api/v2/get_user_by_email endpoint
|
|
69
|
+
# This endpoint returns user info and validates authentication
|
|
70
|
+
test_url = f"{url}/index.php?/api/v2/get_user_by_email&email={email}"
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = requests.get(
|
|
74
|
+
test_url,
|
|
75
|
+
auth=HTTPBasicAuth(email, password_value),
|
|
76
|
+
timeout=10
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Check response status
|
|
80
|
+
if response.status_code == 200:
|
|
81
|
+
# Successfully connected and authenticated
|
|
82
|
+
return None
|
|
83
|
+
elif response.status_code == 401:
|
|
84
|
+
return "Authentication failed: invalid email or password"
|
|
85
|
+
elif response.status_code == 403:
|
|
86
|
+
return "Access forbidden: check user permissions"
|
|
87
|
+
elif response.status_code == 404:
|
|
88
|
+
return "TestRail API endpoint not found: verify the TestRail URL"
|
|
89
|
+
elif response.status_code == 400:
|
|
90
|
+
# Could be invalid email format or other bad request
|
|
91
|
+
try:
|
|
92
|
+
error_data = response.json()
|
|
93
|
+
error_msg = error_data.get("error", "Bad request")
|
|
94
|
+
return f"Bad request: {error_msg}"
|
|
95
|
+
except:
|
|
96
|
+
return "Bad request: check email format and URL"
|
|
97
|
+
else:
|
|
98
|
+
return f"TestRail API returned status code {response.status_code}"
|
|
99
|
+
|
|
100
|
+
except requests.exceptions.SSLError as e:
|
|
101
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
102
|
+
except requests.exceptions.ConnectionError:
|
|
103
|
+
return f"Cannot connect to TestRail at {url}: connection refused"
|
|
104
|
+
except requests.exceptions.Timeout:
|
|
105
|
+
return f"Connection to TestRail at {url} timed out"
|
|
106
|
+
except requests.exceptions.RequestException as e:
|
|
107
|
+
return f"Error connecting to TestRail: {str(e)}"
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return f"Unexpected error: {str(e)}"
|
alita_sdk/configurations/xray.py
CHANGED
|
@@ -30,3 +30,96 @@ class XrayConfiguration(BaseModel):
|
|
|
30
30
|
base_url: str = Field(description="Xray URL")
|
|
31
31
|
client_id: Optional[str] = Field(description="Client ID")
|
|
32
32
|
client_secret: Optional[SecretStr] = Field(description="Client secret")
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def check_connection(settings: dict) -> str | None:
|
|
36
|
+
"""
|
|
37
|
+
Check the connection to Xray Cloud.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
settings: Dictionary containing Xray configuration
|
|
41
|
+
- base_url: Xray Cloud URL (required)
|
|
42
|
+
- client_id: OAuth2 Client ID (required)
|
|
43
|
+
- client_secret: OAuth2 Client Secret (required)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
None if connection successful, error message string if failed
|
|
47
|
+
"""
|
|
48
|
+
import requests
|
|
49
|
+
|
|
50
|
+
# Validate base_url
|
|
51
|
+
base_url = settings.get("base_url", "").strip()
|
|
52
|
+
if not base_url:
|
|
53
|
+
return "Xray URL is required"
|
|
54
|
+
|
|
55
|
+
# Normalize URL - remove trailing slashes
|
|
56
|
+
base_url = base_url.rstrip("/")
|
|
57
|
+
|
|
58
|
+
# Basic URL validation
|
|
59
|
+
if not base_url.startswith(("http://", "https://")):
|
|
60
|
+
return "Xray URL must start with http:// or https://"
|
|
61
|
+
|
|
62
|
+
# Validate client_id
|
|
63
|
+
client_id = settings.get("client_id", "").strip() if settings.get("client_id") else ""
|
|
64
|
+
if not client_id:
|
|
65
|
+
return "Xray client ID is required"
|
|
66
|
+
|
|
67
|
+
# Validate client_secret
|
|
68
|
+
client_secret = settings.get("client_secret")
|
|
69
|
+
if not client_secret:
|
|
70
|
+
return "Xray client secret is required"
|
|
71
|
+
|
|
72
|
+
# Extract client_secret value if it's a SecretStr
|
|
73
|
+
client_secret_value = client_secret.get_secret_value() if hasattr(client_secret, 'get_secret_value') else client_secret
|
|
74
|
+
|
|
75
|
+
if not client_secret_value or not str(client_secret_value).strip():
|
|
76
|
+
return "Xray client secret cannot be empty"
|
|
77
|
+
|
|
78
|
+
# Test connection using /api/v2/authenticate endpoint
|
|
79
|
+
# This is the OAuth2 token generation endpoint for Xray Cloud
|
|
80
|
+
auth_url = f"{base_url}/api/v2/authenticate"
|
|
81
|
+
|
|
82
|
+
auth_payload = {
|
|
83
|
+
"client_id": client_id,
|
|
84
|
+
"client_secret": str(client_secret_value).strip()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
response = requests.post(
|
|
89
|
+
auth_url,
|
|
90
|
+
json=auth_payload,
|
|
91
|
+
headers={"Content-Type": "application/json"},
|
|
92
|
+
timeout=10
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Check response status
|
|
96
|
+
if response.status_code == 200:
|
|
97
|
+
# Successfully authenticated and got token
|
|
98
|
+
return None
|
|
99
|
+
elif response.status_code == 401:
|
|
100
|
+
return "Authentication failed: invalid client ID or secret"
|
|
101
|
+
elif response.status_code == 403:
|
|
102
|
+
return "Access forbidden: check client credentials"
|
|
103
|
+
elif response.status_code == 400:
|
|
104
|
+
# Bad request - could be invalid format
|
|
105
|
+
try:
|
|
106
|
+
error_data = response.json()
|
|
107
|
+
error_msg = error_data.get("error", "Bad request")
|
|
108
|
+
return f"Bad request: {error_msg}"
|
|
109
|
+
except:
|
|
110
|
+
return "Bad request: check client ID and secret format"
|
|
111
|
+
elif response.status_code == 404:
|
|
112
|
+
return "Xray API endpoint not found: verify the Xray URL"
|
|
113
|
+
else:
|
|
114
|
+
return f"Xray API returned status code {response.status_code}"
|
|
115
|
+
|
|
116
|
+
except requests.exceptions.SSLError as e:
|
|
117
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
118
|
+
except requests.exceptions.ConnectionError:
|
|
119
|
+
return f"Cannot connect to Xray at {base_url}: connection refused"
|
|
120
|
+
except requests.exceptions.Timeout:
|
|
121
|
+
return f"Connection to Xray at {base_url} timed out"
|
|
122
|
+
except requests.exceptions.RequestException as e:
|
|
123
|
+
return f"Error connecting to Xray: {str(e)}"
|
|
124
|
+
except Exception as e:
|
|
125
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -18,3 +18,96 @@ class ZephyrEnterpriseConfiguration(BaseModel):
|
|
|
18
18
|
)
|
|
19
19
|
base_url: str = Field(description="Zephyr base URL")
|
|
20
20
|
token: Optional[SecretStr] = Field(description="API token")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def check_connection(settings: dict) -> str | None:
|
|
24
|
+
"""
|
|
25
|
+
Check the connection to Zephyr Enterprise.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
settings: Dictionary containing Zephyr Enterprise configuration
|
|
29
|
+
- base_url: Zephyr Enterprise instance URL (required)
|
|
30
|
+
- token: API token for authentication (optional, anonymous access possible)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
None if connection successful, error message string if failed
|
|
34
|
+
"""
|
|
35
|
+
import requests
|
|
36
|
+
|
|
37
|
+
# Validate base_url
|
|
38
|
+
base_url = settings.get("base_url", "").strip()
|
|
39
|
+
if not base_url:
|
|
40
|
+
return "Zephyr Enterprise URL is required"
|
|
41
|
+
|
|
42
|
+
# Normalize URL - remove trailing slashes
|
|
43
|
+
base_url = base_url.rstrip("/")
|
|
44
|
+
|
|
45
|
+
# Basic URL validation
|
|
46
|
+
if not base_url.startswith(("http://", "https://")):
|
|
47
|
+
return "Zephyr Enterprise URL must start with http:// or https://"
|
|
48
|
+
|
|
49
|
+
# Get token (optional)
|
|
50
|
+
token = settings.get("token")
|
|
51
|
+
|
|
52
|
+
# Prepare headers
|
|
53
|
+
headers = {}
|
|
54
|
+
has_token = False
|
|
55
|
+
if token:
|
|
56
|
+
# Extract token value if it's a SecretStr
|
|
57
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
58
|
+
if token_value and str(token_value).strip():
|
|
59
|
+
headers["Authorization"] = f"Bearer {str(token_value).strip()}"
|
|
60
|
+
has_token = True
|
|
61
|
+
|
|
62
|
+
# Use different endpoints based on whether authentication is provided
|
|
63
|
+
# Note: /healthcheck may allow anonymous access, so we use authenticated endpoints when token is provided
|
|
64
|
+
if has_token:
|
|
65
|
+
# Test with an endpoint that requires authentication: /flex/services/rest/latest/project
|
|
66
|
+
# This endpoint lists projects and requires proper authentication
|
|
67
|
+
test_url = f"{base_url}/flex/services/rest/latest/user/current"
|
|
68
|
+
else:
|
|
69
|
+
# Without token, test basic connectivity with healthcheck
|
|
70
|
+
test_url = f"{base_url}/flex/services/rest/latest/healthcheck"
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = requests.get(
|
|
74
|
+
test_url,
|
|
75
|
+
headers=headers,
|
|
76
|
+
timeout=10
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Check response status
|
|
80
|
+
if response.status_code == 200:
|
|
81
|
+
# Successfully connected
|
|
82
|
+
return None
|
|
83
|
+
elif response.status_code == 401:
|
|
84
|
+
if has_token:
|
|
85
|
+
return "Authentication failed: invalid API token"
|
|
86
|
+
else:
|
|
87
|
+
return "Authentication required: provide API token"
|
|
88
|
+
elif response.status_code == 403:
|
|
89
|
+
return "Access forbidden: check token permissions"
|
|
90
|
+
elif response.status_code == 404:
|
|
91
|
+
# If user endpoint not found, try healthcheck as fallback
|
|
92
|
+
if has_token:
|
|
93
|
+
try:
|
|
94
|
+
fallback_url = f"{base_url}/flex/services/rest/latest/healthcheck"
|
|
95
|
+
fallback_response = requests.get(fallback_url, headers=headers, timeout=10)
|
|
96
|
+
if fallback_response.status_code == 200:
|
|
97
|
+
return None
|
|
98
|
+
except:
|
|
99
|
+
pass
|
|
100
|
+
return "Zephyr Enterprise API endpoint not found: verify the Zephyr URL"
|
|
101
|
+
else:
|
|
102
|
+
return f"Zephyr Enterprise API returned status code {response.status_code}"
|
|
103
|
+
|
|
104
|
+
except requests.exceptions.SSLError as e:
|
|
105
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
106
|
+
except requests.exceptions.ConnectionError:
|
|
107
|
+
return f"Cannot connect to Zephyr Enterprise at {base_url}: connection refused"
|
|
108
|
+
except requests.exceptions.Timeout:
|
|
109
|
+
return f"Connection to Zephyr Enterprise at {base_url} timed out"
|
|
110
|
+
except requests.exceptions.RequestException as e:
|
|
111
|
+
return f"Error connecting to Zephyr Enterprise: {str(e)}"
|
|
112
|
+
except Exception as e:
|
|
113
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -18,3 +18,78 @@ class ZephyrEssentialConfiguration(BaseModel):
|
|
|
18
18
|
)
|
|
19
19
|
base_url: Optional[str] = Field(description="Zephyr Essential API Base URL", default=None)
|
|
20
20
|
token: SecretStr = Field(description="Zephyr Essential API Token")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def check_connection(settings: dict) -> str | None:
|
|
24
|
+
"""
|
|
25
|
+
Check the connection to Zephyr Essential (Zephyr Scale).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
settings: Dictionary containing Zephyr Essential configuration
|
|
29
|
+
- base_url: Zephyr Essential API Base URL (optional, defaults to Zephyr Scale Cloud API)
|
|
30
|
+
- token: Zephyr Essential API Token (required)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
None if connection successful, error message string if failed
|
|
34
|
+
"""
|
|
35
|
+
import requests
|
|
36
|
+
|
|
37
|
+
# Get base_url or use default
|
|
38
|
+
base_url = settings.get("base_url")
|
|
39
|
+
if base_url:
|
|
40
|
+
base_url = base_url.strip().rstrip("/")
|
|
41
|
+
# Validate URL format if provided
|
|
42
|
+
if not base_url.startswith(("http://", "https://")):
|
|
43
|
+
return "Zephyr Essential URL must start with http:// or https://"
|
|
44
|
+
else:
|
|
45
|
+
# Default to Zephyr Scale Cloud API
|
|
46
|
+
base_url = "https://api.zephyrscale.smartbear.com/v2"
|
|
47
|
+
|
|
48
|
+
# Validate token
|
|
49
|
+
token = settings.get("token")
|
|
50
|
+
if not token:
|
|
51
|
+
return "Zephyr Essential API token is required"
|
|
52
|
+
|
|
53
|
+
# Extract token value if it's a SecretStr
|
|
54
|
+
token_value = token.get_secret_value() if hasattr(token, 'get_secret_value') else token
|
|
55
|
+
|
|
56
|
+
if not token_value or not str(token_value).strip():
|
|
57
|
+
return "Zephyr Essential API token cannot be empty"
|
|
58
|
+
|
|
59
|
+
# Test connection using /projects endpoint (requires authentication)
|
|
60
|
+
test_url = f"{base_url}/projects"
|
|
61
|
+
|
|
62
|
+
headers = {
|
|
63
|
+
"Authorization": f"Bearer {str(token_value).strip()}"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
response = requests.get(
|
|
68
|
+
test_url,
|
|
69
|
+
headers=headers,
|
|
70
|
+
timeout=10
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Check response status
|
|
74
|
+
if response.status_code == 200:
|
|
75
|
+
# Successfully connected and authenticated
|
|
76
|
+
return None
|
|
77
|
+
elif response.status_code == 401:
|
|
78
|
+
return "Authentication failed: invalid API token"
|
|
79
|
+
elif response.status_code == 403:
|
|
80
|
+
return "Access forbidden: token lacks required permissions"
|
|
81
|
+
elif response.status_code == 404:
|
|
82
|
+
return "Zephyr Essential API endpoint not found: verify the API URL"
|
|
83
|
+
else:
|
|
84
|
+
return f"Zephyr Essential API returned status code {response.status_code}"
|
|
85
|
+
|
|
86
|
+
except requests.exceptions.SSLError as e:
|
|
87
|
+
return f"SSL certificate verification failed: {str(e)}"
|
|
88
|
+
except requests.exceptions.ConnectionError:
|
|
89
|
+
return f"Cannot connect to Zephyr Essential at {base_url}: connection refused"
|
|
90
|
+
except requests.exceptions.Timeout:
|
|
91
|
+
return f"Connection to Zephyr Essential at {base_url} timed out"
|
|
92
|
+
except requests.exceptions.RequestException as e:
|
|
93
|
+
return f"Error connecting to Zephyr Essential: {str(e)}"
|
|
94
|
+
except Exception as e:
|
|
95
|
+
return f"Unexpected error: {str(e)}"
|
|
@@ -38,8 +38,8 @@ class Artifact:
|
|
|
38
38
|
if len(data) == 0:
|
|
39
39
|
# empty file might be created
|
|
40
40
|
return ""
|
|
41
|
-
if isinstance(data, dict) and data
|
|
42
|
-
return f"{data['error']}. {data
|
|
41
|
+
if isinstance(data, dict) and data.get('error'):
|
|
42
|
+
return f"{data['error']}. {data.get('content', '')}"
|
|
43
43
|
detected = chardet.detect(data)
|
|
44
44
|
if detected['encoding'] is not None:
|
|
45
45
|
try:
|
|
@@ -65,7 +65,7 @@ class Artifact:
|
|
|
65
65
|
def delete(self, artifact_name: str, bucket_name = None):
|
|
66
66
|
if not bucket_name:
|
|
67
67
|
bucket_name = self.bucket_name
|
|
68
|
-
self.client.delete_artifact(bucket_name, artifact_name)
|
|
68
|
+
return self.client.delete_artifact(bucket_name, artifact_name)
|
|
69
69
|
|
|
70
70
|
def list(self, bucket_name: str = None, return_as_string = True) -> str|dict:
|
|
71
71
|
if not bucket_name:
|