alita-sdk 0.3.457b0__py3-none-any.whl → 0.3.458__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.
@@ -23,11 +23,7 @@ class ArtifactToolkit(BaseToolkit):
23
23
  # client = (Any, FieldInfo(description="Client object", required=True, autopopulate=True)),
24
24
  bucket=(str, FieldInfo(
25
25
  description="Bucket name",
26
- pattern=r'^[a-z][a-z0-9-]*$',
27
- json_schema_extra={
28
- 'toolkit_name': True,
29
- 'max_toolkit_length': ArtifactToolkit.toolkit_max_length
30
- }
26
+ pattern=r'^[a-z][a-z0-9-]*$'
31
27
  )),
32
28
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
33
29
  # indexer settings
@@ -37,7 +33,10 @@ class ArtifactToolkit(BaseToolkit):
37
33
  embedding_model=(Optional[str], Field(default=None, description="Embedding configuration.",
38
34
  json_schema_extra={'configuration_model': 'embedding'})),
39
35
 
40
- __config__=ConfigDict(json_schema_extra={'metadata': {"label": "Artifact", "icon_url": None}})
36
+ __config__=ConfigDict(json_schema_extra={'metadata': {"label": "Artifact",
37
+ "icon_url": None,
38
+ "max_length": ArtifactToolkit.toolkit_max_length
39
+ }})
41
40
  )
42
41
 
43
42
  @classmethod
@@ -78,6 +78,73 @@ Steps: Array of test steps with Description and Expected Result.
78
78
  Json object
79
79
  """
80
80
 
81
+ # DQL Syntax Documentation - reusable across all DQL-based search tools
82
+ DQL_SYNTAX_DOCS = """
83
+ CRITICAL: USE SINGLE QUOTES ONLY - DQL does not support double quotes!
84
+ - ✓ CORRECT: Description ~ 'Forgot Password'
85
+ - ✗ WRONG: Description ~ "Forgot Password"
86
+
87
+ LIMITATION - CANNOT SEARCH BY LINKED OBJECTS:
88
+ - ✗ Searching by linked requirements, test cases, defects is NOT supported
89
+ - Use dedicated find_*_by_*_id tools for relationship queries
90
+
91
+ SEARCHABLE FIELDS:
92
+ - Direct fields: Id, Name, Description, Status, Type, Priority, etc.
93
+ - Custom fields: Use exact field name from project configuration
94
+ - Date fields: MUST use ISO DateTime format (e.g., '2024-01-01T00:00:00.000Z')
95
+
96
+ ENTITY-SPECIFIC NOTES:
97
+ - test-logs: Only support 'Execution Start Date' and 'Execution End Date' queries
98
+ - builds/test-cycles: Also support 'Created Date' and 'Last Modified Date'
99
+ - defects: Can use 'Affected Release/Build' and 'Fixed Release/Build'
100
+
101
+ SYNTAX RULES:
102
+ 1. ALL string values MUST use single quotes (never double quotes)
103
+ 2. Field names with spaces MUST be in single quotes: 'Created Date' > '2024-01-01T00:00:00.000Z'
104
+ 3. Use ~ for 'contains', !~ for 'not contains': Description ~ 'login'
105
+ 4. Use 'is not empty' for non-empty check: Name is 'not empty'
106
+ 5. Operators: =, !=, <, >, <=, >=, in, ~, !~
107
+
108
+ EXAMPLES:
109
+ - Id = 'TC-123' or Id = 'RQ-15' or Id = 'DF-100' (depending on entity type)
110
+ - Description ~ 'Forgot Password'
111
+ - Status = 'New' and Priority = 'High'
112
+ - Name ~ 'login'
113
+ - 'Created Date' > '2024-01-01T00:00:00.000Z'
114
+ - 'Execution Start Date' > '2024-01-01T00:00:00.000Z' (for test-logs)
115
+ """
116
+
117
+ # Supported object types for DQL search (based on QTest Search API documentation)
118
+ # Note: Prefixes are configurable per-project but these are standard defaults
119
+ # Modules (MD) are NOT searchable via DQL - use get_modules tool instead
120
+ # Test-logs have NO prefix - they are internal records accessed via test runs
121
+
122
+ # Entity types with ID prefixes (can be looked up by ID like TC-123)
123
+ QTEST_OBJECT_TYPES = {
124
+ # Core test management entities
125
+ 'test-cases': {'prefix': 'TC', 'name': 'Test Case', 'description': 'Test case definitions with steps'},
126
+ 'test-runs': {'prefix': 'TR', 'name': 'Test Run', 'description': 'Execution instances of test cases'},
127
+ 'defects': {'prefix': 'DF', 'name': 'Defect', 'description': 'Bugs/issues found during testing'},
128
+ 'requirements': {'prefix': 'RQ', 'name': 'Requirement', 'description': 'Requirements to be tested'},
129
+
130
+ # Test organization entities
131
+ 'test-suites': {'prefix': 'TS', 'name': 'Test Suite', 'description': 'Collections of test runs'},
132
+ 'test-cycles': {'prefix': 'CL', 'name': 'Test Cycle', 'description': 'Test execution cycles'},
133
+
134
+ # Release management entities
135
+ 'releases': {'prefix': 'RL', 'name': 'Release', 'description': 'Software releases'},
136
+ 'builds': {'prefix': 'BL', 'name': 'Build', 'description': 'Builds within releases'},
137
+ }
138
+
139
+ # Entity types searchable via DQL but without ID prefixes
140
+ # These can be searched by specific fields only, not by ID
141
+ QTEST_SEARCHABLE_ONLY_TYPES = {
142
+ 'test-logs': {
143
+ 'name': 'Test Log',
144
+ 'description': "Execution logs. Only date queries supported (Execution Start Date, Execution End Date). For specific log details, use test run's 'Latest Test Log' field."
145
+ },
146
+ }
147
+
81
148
  logger = logging.getLogger(__name__)
82
149
 
83
150
  QtestDataQuerySearch = create_model(
@@ -154,6 +221,34 @@ FindTestCasesByRequirementId = create_model(
154
221
  include_details=(Optional[bool], Field(description="If true, returns full test case details. If false (default), returns Id, QTest Id, Name, and Description fields.", default=False)),
155
222
  )
156
223
 
224
+ FindRequirementsByTestCaseId = create_model(
225
+ "FindRequirementsByTestCaseId",
226
+ test_case_id=(str, Field(description="Test case ID in format TC-123. This will find all requirements linked to this test case.")),
227
+ )
228
+
229
+ FindTestRunsByTestCaseId = create_model(
230
+ "FindTestRunsByTestCaseId",
231
+ test_case_id=(str, Field(description="Test case ID in format TC-123. This will find all test runs associated with this test case.")),
232
+ )
233
+
234
+ FindDefectsByTestRunId = create_model(
235
+ "FindDefectsByTestRunId",
236
+ test_run_id=(str, Field(description="Test run ID in format TR-123. This will find all defects associated with this test run.")),
237
+ )
238
+
239
+ # Generic search model for any entity type
240
+ GenericDqlSearch = create_model(
241
+ "GenericDqlSearch",
242
+ object_type=(str, Field(description="Entity type to search: 'test-cases', 'test-runs', 'defects', 'requirements', 'test-suites', 'test-cycles', 'test-logs', 'releases', or 'builds'. Note: test-logs only support date queries; modules are NOT searchable - use get_modules tool.")),
243
+ dql=(str, Field(description="QTest Data Query Language (DQL) query string")),
244
+ )
245
+
246
+ # Generic find by ID model - only for entities with ID prefixes (NOT test-logs)
247
+ FindEntityById = create_model(
248
+ "FindEntityById",
249
+ entity_id=(str, Field(description="Entity ID with prefix: TC-123 (test case), RQ-15 (requirement), DF-100 (defect), TR-39 (test run), TS-5 (test suite), CL-3 (test cycle), RL-1 (release), or BL-2 (build). Note: test-logs and modules do NOT have ID prefixes.")),
250
+ )
251
+
157
252
  NoInput = create_model(
158
253
  "NoInput"
159
254
  )
@@ -837,38 +932,139 @@ class QtestApiWrapper(BaseToolApiWrapper):
837
932
  parsed_data = self.__perform_search_by_dql(dql)
838
933
  return parsed_data[0]['QTest Id']
839
934
 
840
- def __find_qtest_requirement_id_by_id(self, requirement_id: str) -> int:
841
- """Search for requirement's internal QTest ID using requirement ID (RQ-xxx format).
935
+ def __find_qtest_internal_id(self, object_type: str, entity_id: str) -> int:
936
+ """Generic search for an entity's internal QTest ID using its external ID (e.g., TR-xxx, DF-xxx, RQ-xxx).
937
+
938
+ This is the unified method for looking up internal IDs. Use this instead of
939
+ the entity-specific methods (__find_qtest_requirement_id_by_id, etc.).
842
940
 
843
941
  Args:
844
- requirement_id: Requirement ID in format RQ-123
942
+ object_type: QTest object type ('test-runs', 'defects', 'requirements', etc.)
943
+ entity_id: Entity ID in format TR-123, DF-456, etc.
845
944
 
846
945
  Returns:
847
- int: Internal QTest ID for the requirement
946
+ int: Internal QTest ID for the entity
848
947
 
849
948
  Raises:
850
- ValueError: If requirement is not found
949
+ ValueError: If entity is not found
851
950
  """
852
- dql = f"Id = '{requirement_id}'"
951
+ dql = f"Id = '{entity_id}'"
853
952
  search_instance: SearchApi = swagger_client.SearchApi(self._client)
854
- body = swagger_client.ArtifactSearchParams(object_type='requirements', fields=['*'], query=dql)
953
+ body = swagger_client.ArtifactSearchParams(object_type=object_type, fields=['*'], query=dql)
855
954
 
856
955
  try:
857
956
  response = search_instance.search_artifact(self.qtest_project_id, body)
858
957
  if response['total'] == 0:
859
958
  raise ValueError(
860
- f"Requirement '{requirement_id}' not found in project {self.qtest_project_id}. "
861
- f"Please verify the requirement ID exists."
959
+ f"{object_type.capitalize()} '{entity_id}' not found in project {self.qtest_project_id}. "
960
+ f"Please verify the {entity_id} ID exists."
862
961
  )
863
962
  return response['items'][0]['id']
864
963
  except ApiException as e:
865
964
  stacktrace = format_exc()
866
- logger.error(f"Exception when searching for requirement: \n {stacktrace}")
965
+ logger.error(f"Exception when searching for '{object_type}': '{entity_id}': \n {stacktrace}")
867
966
  raise ToolException(
868
- f"Unable to search for requirement '{requirement_id}' in project {self.qtest_project_id}. "
967
+ f"Unable to search for {object_type} '{entity_id}' in project {self.qtest_project_id}. "
869
968
  f"Exception: \n{stacktrace}"
870
969
  ) from e
871
970
 
971
+ def __find_qtest_requirement_id_by_id(self, requirement_id: str) -> int:
972
+ """Search for requirement's internal QTest ID using requirement ID (RQ-xxx format).
973
+
974
+ Args:
975
+ requirement_id: Requirement ID in format RQ-123
976
+
977
+ Returns:
978
+ int: Internal QTest ID for the requirement
979
+
980
+ Raises:
981
+ ValueError: If requirement is not found
982
+ """
983
+ return self.__find_qtest_internal_id('requirements', requirement_id)
984
+
985
+ def __find_qtest_defect_id_by_id(self, defect_id: str) -> int:
986
+ """Search for defect's internal QTest ID using defect ID (DF-xxx format).
987
+
988
+ Args:
989
+ defect_id: Defect ID in format DF-123
990
+
991
+ Returns:
992
+ int: Internal QTest ID for the defect
993
+
994
+ Raises:
995
+ ValueError: If defect is not found
996
+ """
997
+ return self.__find_qtest_internal_id('defects', defect_id)
998
+
999
+ def __search_entity_by_id(self, object_type: str, entity_id: str) -> dict:
1000
+ """Generic search for any entity by its ID (RQ-xxx, DF-xxx, etc.).
1001
+
1002
+ Uses the unified __parse_entity_item method for consistent parsing.
1003
+
1004
+ Args:
1005
+ object_type: QTest object type ('requirements', 'defects', etc.)
1006
+ entity_id: Entity ID in format prefix-number (RQ-123, DF-456)
1007
+
1008
+ Returns:
1009
+ dict: Entity data with all parsed fields, or None if not found
1010
+ """
1011
+ dql = f"Id = '{entity_id}'"
1012
+ search_instance: SearchApi = swagger_client.SearchApi(self._client)
1013
+ body = swagger_client.ArtifactSearchParams(object_type=object_type, fields=['*'], query=dql)
1014
+
1015
+ try:
1016
+ response = search_instance.search_artifact(self.qtest_project_id, body)
1017
+ if response['total'] == 0:
1018
+ return None # Not found, but don't raise - caller handles this
1019
+
1020
+ # Use the unified parser
1021
+ return self.__parse_entity_item(object_type, response['items'][0])
1022
+
1023
+ except ApiException as e:
1024
+ logger.warning(f"Could not fetch details for {entity_id}: {e}")
1025
+ return None
1026
+
1027
+ def __get_entity_pid_by_internal_id(self, object_type: str, internal_id: int) -> str:
1028
+ """Reverse lookup: get entity PID (TC-xxx, TR-xxx, etc.) from internal QTest ID.
1029
+
1030
+ Args:
1031
+ object_type: QTest object type ('test-cases', 'test-runs', 'defects', 'requirements')
1032
+ internal_id: Internal QTest ID (numeric)
1033
+
1034
+ Returns:
1035
+ str: Entity PID in format prefix-number (TC-123, TR-456, etc.) or None if not found
1036
+ """
1037
+ search_instance = swagger_client.SearchApi(self._client)
1038
+ # Note: 'id' needs quotes for DQL when searching by internal ID
1039
+ body = swagger_client.ArtifactSearchParams(
1040
+ object_type=object_type,
1041
+ fields=['id', 'pid'],
1042
+ query=f"'id' = '{internal_id}'"
1043
+ )
1044
+
1045
+ try:
1046
+ response = search_instance.search_artifact(self.qtest_project_id, body)
1047
+ if response['total'] > 0:
1048
+ return response['items'][0].get('pid')
1049
+ return None
1050
+ except ApiException as e:
1051
+ logger.warning(f"Could not get PID for {object_type} internal ID {internal_id}: {e}")
1052
+ return None
1053
+
1054
+ def __find_qtest_test_run_id_by_id(self, test_run_id: str) -> int:
1055
+ """Search for test run's internal QTest ID using test run ID (TR-xxx format).
1056
+
1057
+ Args:
1058
+ test_run_id: Test run ID in format TR-123
1059
+
1060
+ Returns:
1061
+ int: Internal QTest ID for the test run
1062
+
1063
+ Raises:
1064
+ ValueError: If test run is not found
1065
+ """
1066
+ return self.__find_qtest_internal_id('test-runs', test_run_id)
1067
+
872
1068
  def __is_jira_requirement_present(self, jira_issue_id: str) -> tuple[bool, dict]:
873
1069
  """ Define if particular Jira requirement is present in qtest or not """
874
1070
  dql = f"'External Id' = '{jira_issue_id}'"
@@ -1106,14 +1302,458 @@ class QtestApiWrapper(BaseToolApiWrapper):
1106
1302
  f"in project {self.qtest_project_id}. Exception: \n{stacktrace}"
1107
1303
  ) from e
1108
1304
 
1305
+ def find_requirements_by_test_case_id(self, test_case_id: str) -> dict:
1306
+ """Find all requirements linked to a test case.
1307
+
1308
+ This method uses the ObjectLinkApi.find() to discover requirements that are
1309
+ linked to a specific test case (reverse lookup).
1310
+
1311
+ Args:
1312
+ test_case_id: Test case ID in format TC-123
1313
+
1314
+ Returns:
1315
+ dict with test_case_id, total count, and requirements list
1316
+
1317
+ Raises:
1318
+ ValueError: If test case is not found
1319
+ ToolException: If API call fails
1320
+ """
1321
+ # Get internal QTest ID for the test case
1322
+ qtest_test_case_id = self.__find_qtest_id_by_test_id(test_case_id)
1323
+
1324
+ link_object_api_instance = swagger_client.ObjectLinkApi(self._client)
1325
+
1326
+ try:
1327
+ # Use ObjectLinkApi.find() to get linked artifacts
1328
+ # type='test-cases' means we're searching from test cases
1329
+ response = link_object_api_instance.find(
1330
+ self.qtest_project_id,
1331
+ type='test-cases',
1332
+ ids=[qtest_test_case_id]
1333
+ )
1334
+
1335
+ # Parse the response to extract linked requirement IDs
1336
+ linked_requirement_ids = []
1337
+ if response and len(response) > 0:
1338
+ for container in response:
1339
+ container_data = container.to_dict() if hasattr(container, 'to_dict') else container
1340
+ objects = container_data.get('objects', []) if isinstance(container_data, dict) else []
1341
+
1342
+ for obj in objects:
1343
+ obj_data = obj.to_dict() if hasattr(obj, 'to_dict') else obj
1344
+ if isinstance(obj_data, dict):
1345
+ pid = obj_data.get('pid', '')
1346
+ # Requirements have RQ- prefix
1347
+ if pid and pid.startswith('RQ-'):
1348
+ linked_requirement_ids.append(pid)
1349
+
1350
+ if not linked_requirement_ids:
1351
+ return {
1352
+ 'test_case_id': test_case_id,
1353
+ 'total': 0,
1354
+ 'requirements': [],
1355
+ 'message': f"No requirements are linked to test case '{test_case_id}'"
1356
+ }
1357
+
1358
+ # Fetch actual requirement details via DQL search
1359
+ requirements_result = []
1360
+ for req_id in linked_requirement_ids:
1361
+ req_data = self.__search_entity_by_id('requirements', req_id)
1362
+ if req_data:
1363
+ requirements_result.append(req_data)
1364
+ else:
1365
+ # Fallback if search fails
1366
+ requirements_result.append({
1367
+ 'Id': req_id,
1368
+ 'QTest Id': None,
1369
+ 'Name': 'Unable to fetch',
1370
+ 'Description': ''
1371
+ })
1372
+
1373
+ return {
1374
+ 'test_case_id': test_case_id,
1375
+ 'total': len(requirements_result),
1376
+ 'requirements': requirements_result
1377
+ }
1378
+
1379
+ except ApiException as e:
1380
+ stacktrace = format_exc()
1381
+ logger.error(f"Error finding requirements by test case: {stacktrace}")
1382
+ raise ToolException(
1383
+ f"Unable to find requirements linked to test case '{test_case_id}' "
1384
+ f"in project {self.qtest_project_id}. Exception: \n{stacktrace}"
1385
+ ) from e
1386
+
1387
+ def find_test_runs_by_test_case_id(self, test_case_id: str) -> dict:
1388
+ """Find all test runs associated with a test case.
1389
+
1390
+ A test run represents an execution instance of a test case. Each test run
1391
+ tracks execution details, status, and any defects found during that run.
1392
+
1393
+ IMPORTANT: In QTest's data model, defects are linked to test runs, not directly
1394
+ to test cases. To find defects related to a test case:
1395
+ 1. Use this tool to find test runs for the test case
1396
+ 2. Use find_defects_by_test_run_id for each test run to get related defects
1397
+
1398
+ Each test run in the result includes 'Test Case Id' showing which test case
1399
+ it executes, and 'Latest Test Log' with execution status and log ID.
1400
+
1401
+ Args:
1402
+ test_case_id: Test case ID in format TC-123
1403
+
1404
+ Returns:
1405
+ dict with test_case_id, total count, and test_runs list with full details
1406
+
1407
+ Raises:
1408
+ ValueError: If test case is not found
1409
+ ToolException: If API call fails
1410
+ """
1411
+ # Get internal QTest ID for the test case
1412
+ qtest_test_case_id = self.__find_qtest_id_by_test_id(test_case_id)
1413
+
1414
+ link_object_api_instance = swagger_client.ObjectLinkApi(self._client)
1415
+
1416
+ try:
1417
+ # Use ObjectLinkApi.find() to get linked artifacts
1418
+ response = link_object_api_instance.find(
1419
+ self.qtest_project_id,
1420
+ type='test-cases',
1421
+ ids=[qtest_test_case_id]
1422
+ )
1423
+
1424
+ # Parse the response to extract linked test run IDs
1425
+ linked_test_run_ids = []
1426
+ if response and len(response) > 0:
1427
+ for container in response:
1428
+ container_data = container.to_dict() if hasattr(container, 'to_dict') else container
1429
+ objects = container_data.get('objects', []) if isinstance(container_data, dict) else []
1430
+
1431
+ for obj in objects:
1432
+ obj_data = obj.to_dict() if hasattr(obj, 'to_dict') else obj
1433
+ if isinstance(obj_data, dict):
1434
+ pid = obj_data.get('pid', '')
1435
+ # Test runs have TR- prefix
1436
+ if pid and pid.startswith('TR-'):
1437
+ linked_test_run_ids.append(pid)
1438
+
1439
+ if not linked_test_run_ids:
1440
+ return {
1441
+ 'test_case_id': test_case_id,
1442
+ 'total': 0,
1443
+ 'test_runs': [],
1444
+ 'message': f"No test runs are associated with test case '{test_case_id}'"
1445
+ }
1446
+
1447
+ # Fetch actual test run details via DQL search
1448
+ test_runs_result = []
1449
+ for tr_id in linked_test_run_ids:
1450
+ tr_data = self.__search_entity_by_id('test-runs', tr_id)
1451
+ if tr_data:
1452
+ test_runs_result.append(tr_data)
1453
+ else:
1454
+ # Fallback if search fails
1455
+ test_runs_result.append({
1456
+ 'Id': tr_id,
1457
+ 'QTest Id': None,
1458
+ 'Name': 'Unable to fetch',
1459
+ 'Description': ''
1460
+ })
1461
+
1462
+ return {
1463
+ 'test_case_id': test_case_id,
1464
+ 'total': len(test_runs_result),
1465
+ 'test_runs': test_runs_result,
1466
+ 'hint': 'To find defects, use find_defects_by_test_run_id for each test run.'
1467
+ }
1468
+
1469
+ except ApiException as e:
1470
+ stacktrace = format_exc()
1471
+ logger.error(f"Error finding test runs by test case: {stacktrace}")
1472
+ raise ToolException(
1473
+ f"Unable to find test runs associated with test case '{test_case_id}' "
1474
+ f"in project {self.qtest_project_id}. Exception: \n{stacktrace}"
1475
+ ) from e
1476
+
1477
+ def find_defects_by_test_run_id(self, test_run_id: str) -> dict:
1478
+ """Find all defects associated with a test run.
1479
+
1480
+ In QTest, defects are linked to test runs (not directly to test cases).
1481
+ A test run executes a specific test case, so defects found here are
1482
+ related to that test case through the test run execution context.
1483
+
1484
+ Use this tool after find_test_runs_by_test_case_id to discover defects.
1485
+ The result includes source context (test run and test case IDs) for traceability.
1486
+
1487
+ Args:
1488
+ test_run_id: Test run ID in format TR-123
1489
+
1490
+ Returns:
1491
+ dict with test_run_id, source_test_case_id, total count, and defects list with full details
1492
+
1493
+ Raises:
1494
+ ValueError: If test run is not found
1495
+ ToolException: If API call fails
1496
+ """
1497
+ # First, get test run details to get the source test case context
1498
+ test_run_data = self.__search_entity_by_id('test-runs', test_run_id)
1499
+ source_test_case_id = None
1500
+ if test_run_data:
1501
+ # testCaseId is the internal ID, we need the PID (TC-xxx format)
1502
+ internal_tc_id = test_run_data.get('Test Case Id')
1503
+ if internal_tc_id:
1504
+ source_test_case_id = self.__get_entity_pid_by_internal_id('test-cases', internal_tc_id)
1505
+ else:
1506
+ raise ValueError(f"Test run '{test_run_id}' not found")
1507
+
1508
+ # Get internal QTest ID for the test run from test_run_data (avoids duplicate API call)
1509
+ qtest_test_run_id = test_run_data.get('QTest Id')
1510
+ if not qtest_test_run_id:
1511
+ raise ValueError(f"QTest Id not found in test run data for '{test_run_id}'")
1512
+
1513
+ link_object_api_instance = swagger_client.ObjectLinkApi(self._client)
1514
+
1515
+ try:
1516
+ # Use ObjectLinkApi.find() to get linked artifacts
1517
+ response = link_object_api_instance.find(
1518
+ self.qtest_project_id,
1519
+ type='test-runs',
1520
+ ids=[qtest_test_run_id]
1521
+ )
1522
+
1523
+ # Parse the response to extract linked defect IDs
1524
+ linked_defect_ids = []
1525
+ if response and len(response) > 0:
1526
+ for container in response:
1527
+ container_data = container.to_dict() if hasattr(container, 'to_dict') else container
1528
+ objects = container_data.get('objects', []) if isinstance(container_data, dict) else []
1529
+
1530
+ for obj in objects:
1531
+ obj_data = obj.to_dict() if hasattr(obj, 'to_dict') else obj
1532
+ if isinstance(obj_data, dict):
1533
+ pid = obj_data.get('pid', '')
1534
+ # Defects have DF- prefix
1535
+ if pid and pid.startswith('DF-'):
1536
+ linked_defect_ids.append(pid)
1537
+
1538
+ if not linked_defect_ids:
1539
+ result = {
1540
+ 'test_run_id': test_run_id,
1541
+ 'total': 0,
1542
+ 'defects': [],
1543
+ 'message': f"No defects are associated with test run '{test_run_id}'"
1544
+ }
1545
+ if source_test_case_id:
1546
+ result['source_test_case_id'] = source_test_case_id
1547
+ return result
1548
+
1549
+ # Fetch actual defect details via DQL search
1550
+ defects_result = []
1551
+ for defect_id in linked_defect_ids:
1552
+ defect_data = self.__search_entity_by_id('defects', defect_id)
1553
+ if defect_data:
1554
+ defects_result.append(defect_data)
1555
+ else:
1556
+ # Fallback if search fails
1557
+ defects_result.append({
1558
+ 'Id': defect_id,
1559
+ 'QTest Id': None,
1560
+ 'Name': 'Unable to fetch',
1561
+ 'Description': ''
1562
+ })
1563
+
1564
+ result = {
1565
+ 'test_run_id': test_run_id,
1566
+ 'total': len(defects_result),
1567
+ 'defects': defects_result
1568
+ }
1569
+ if source_test_case_id:
1570
+ result['source_test_case_id'] = source_test_case_id
1571
+ return result
1572
+
1573
+ except ApiException as e:
1574
+ stacktrace = format_exc()
1575
+ logger.error(f"Error finding defects by test run: {stacktrace}")
1576
+ raise ToolException(
1577
+ f"Unable to find defects associated with test run '{test_run_id}' "
1578
+ f"in project {self.qtest_project_id}. Exception: \n{stacktrace}"
1579
+ ) from e
1580
+
1109
1581
  def search_by_dql(self, dql: str, extract_images:bool=False, prompt: str=None):
1110
1582
  """Search for the test cases in qTest using Data Query Language """
1111
1583
  parsed_data = self.__perform_search_by_dql(dql, extract_images, prompt)
1112
1584
  return "Found " + str(
1113
1585
  len(parsed_data)) + f" Qtest test cases:\n" + str(parsed_data[:self.no_of_tests_shown_in_dql_search])
1114
1586
 
1587
+ def search_entities_by_dql(self, object_type: str, dql: str) -> dict:
1588
+ """Generic DQL search for any entity type (test-cases, requirements, defects, test-runs, etc.).
1589
+
1590
+ This is the unified search method that works for all QTest searchable entity types.
1591
+ Each entity type has its own properties structure, but this method parses
1592
+ them consistently using the generic entity parser.
1593
+
1594
+ Args:
1595
+ object_type: Entity type to search (see QTEST_OBJECT_TYPES and QTEST_SEARCHABLE_ONLY_TYPES)
1596
+ dql: QTest Data Query Language query string
1597
+
1598
+ Returns:
1599
+ dict with object_type, total count, and items list with full entity details
1600
+ """
1601
+ # Check if object_type is valid (either has prefix or is searchable-only)
1602
+ all_searchable = {**QTEST_OBJECT_TYPES, **QTEST_SEARCHABLE_ONLY_TYPES}
1603
+ if object_type not in all_searchable:
1604
+ raise ValueError(
1605
+ f"Invalid object_type '{object_type}'. "
1606
+ f"Must be one of: {', '.join(all_searchable.keys())}"
1607
+ )
1608
+
1609
+ entity_info = all_searchable[object_type]
1610
+ search_instance = swagger_client.SearchApi(self._client)
1611
+ body = swagger_client.ArtifactSearchParams(
1612
+ object_type=object_type,
1613
+ fields=['*'],
1614
+ query=dql
1615
+ )
1616
+
1617
+ try:
1618
+ response = search_instance.search_artifact(self.qtest_project_id, body)
1619
+
1620
+ # Parse all items using the generic parser
1621
+ items = []
1622
+ for item in response.get('items', []):
1623
+ parsed = self.__parse_entity_item(object_type, item)
1624
+ items.append(parsed)
1625
+
1626
+ return {
1627
+ 'object_type': object_type,
1628
+ 'entity_name': entity_info['name'],
1629
+ 'total': response.get('total', 0),
1630
+ 'returned': len(items),
1631
+ 'items': items[:self.no_of_tests_shown_in_dql_search]
1632
+ }
1633
+
1634
+ except ApiException as e:
1635
+ stacktrace = format_exc()
1636
+ logger.error(f"Error searching {object_type} by DQL: {stacktrace}")
1637
+ raise ToolException(
1638
+ f"Unable to search {entity_info['name']}s with DQL '{dql}' "
1639
+ f"in project {self.qtest_project_id}. Exception: \n{stacktrace}"
1640
+ ) from e
1641
+
1642
+ def find_entity_by_id(self, entity_id: str) -> dict:
1643
+ """Find any QTest entity by its ID (TC-xxx, RQ-xxx, DF-xxx, TR-xxx).
1644
+
1645
+ This is a universal lookup tool that works for any entity type.
1646
+ The entity type is automatically determined from the ID prefix.
1647
+
1648
+ Args:
1649
+ entity_id: Entity ID with prefix (TC-123, RQ-15, DF-100, TR-39, etc.)
1650
+
1651
+ Returns:
1652
+ dict with full entity details including all properties
1653
+ """
1654
+ # Determine object type from prefix - dynamically built from registry
1655
+ prefix = entity_id.split('-')[0].upper() if '-' in entity_id else ''
1656
+
1657
+ # Build reverse mapping: prefix -> object_type from QTEST_OBJECT_TYPES
1658
+ prefix_to_type = {
1659
+ info['prefix']: obj_type
1660
+ for obj_type, info in QTEST_OBJECT_TYPES.items()
1661
+ }
1662
+
1663
+ if prefix not in prefix_to_type:
1664
+ valid_prefixes = ', '.join(sorted(prefix_to_type.keys()))
1665
+ raise ValueError(
1666
+ f"Invalid entity ID format '{entity_id}'. "
1667
+ f"Expected prefix to be one of: {valid_prefixes}"
1668
+ )
1669
+
1670
+ object_type = prefix_to_type[prefix]
1671
+ result = self.__search_entity_by_id(object_type, entity_id)
1672
+
1673
+ if result is None:
1674
+ entity_name = QTEST_OBJECT_TYPES[object_type]['name']
1675
+ raise ValueError(
1676
+ f"{entity_name} '{entity_id}' not found in project {self.qtest_project_id}"
1677
+ )
1678
+
1679
+ return result
1680
+
1681
+ def __parse_entity_item(self, object_type: str, item: dict) -> dict:
1682
+ """Generic parser for any entity type from DQL search response.
1683
+
1684
+ This parses the raw API response item into a clean dictionary,
1685
+ handling the differences between entity types (some have name at top level,
1686
+ some have it in properties as Summary, etc.)
1687
+
1688
+ Args:
1689
+ object_type: QTest object type
1690
+ item: Raw item from search response
1691
+
1692
+ Returns:
1693
+ dict with parsed entity data
1694
+ """
1695
+ import html
1696
+
1697
+ result = {
1698
+ 'Id': item.get('pid'),
1699
+ 'QTest Id': item.get('id'),
1700
+ }
1701
+
1702
+ # Add top-level fields if present
1703
+ if item.get('name'):
1704
+ result['Name'] = item.get('name')
1705
+ if item.get('description'):
1706
+ result['Description'] = html.unescape(strip_tags(item.get('description', '') or ''))
1707
+ if item.get('web_url'):
1708
+ result['Web URL'] = item.get('web_url')
1709
+
1710
+ # Test-case specific fields
1711
+ if object_type == 'test-cases':
1712
+ if item.get('precondition'):
1713
+ result['Precondition'] = html.unescape(strip_tags(item.get('precondition', '') or ''))
1714
+ if item.get('test_steps'):
1715
+ result['Steps'] = [
1716
+ {
1717
+ 'Test Step Number': idx + 1,
1718
+ 'Test Step Description': html.unescape(strip_tags(step.get('description', '') or '')),
1719
+ 'Test Step Expected Result': html.unescape(strip_tags(step.get('expected', '') or ''))
1720
+ }
1721
+ for idx, step in enumerate(item.get('test_steps', []))
1722
+ ]
1723
+
1724
+ # Test-run specific fields
1725
+ if object_type == 'test-runs':
1726
+ if item.get('testCaseId'):
1727
+ result['Test Case Id'] = item.get('testCaseId')
1728
+ if item.get('automation'):
1729
+ result['Automation'] = item.get('automation')
1730
+ if item.get('latest_test_log'):
1731
+ log = item.get('latest_test_log')
1732
+ result['Latest Test Log'] = {
1733
+ 'Log Id': log.get('id'),
1734
+ 'Status': log.get('status'),
1735
+ 'Execution Start': log.get('exe_start_date'),
1736
+ 'Execution End': log.get('exe_end_date')
1737
+ }
1738
+ if item.get('test_case_version'):
1739
+ result['Test Case Version'] = item.get('test_case_version')
1740
+
1741
+ # Parse all properties - works for all entity types
1742
+ for prop in item.get('properties', []):
1743
+ field_name = prop.get('field_name')
1744
+ if not field_name:
1745
+ continue
1746
+ # Use field_value_name if available (for dropdowns/users), otherwise field_value
1747
+ field_value = prop.get('field_value_name') or prop.get('field_value') or ''
1748
+ # Strip HTML from text fields
1749
+ if isinstance(field_value, str) and ('<' in field_value or '&' in field_value):
1750
+ field_value = html.unescape(strip_tags(field_value))
1751
+ result[field_name] = field_value
1752
+
1753
+ return result
1754
+
1115
1755
  def create_test_cases(self, test_case_content: str, folder_to_place_test_cases_to: str) -> dict:
1116
- """ Create the tes case base on the incoming content. The input should be in json format. """
1756
+ """ Create the test case based on the incoming content. The input should be in json format. """
1117
1757
  test_cases_api_instance: TestCaseApi = self.__instantiate_test_api_instance()
1118
1758
  input_obj = json.loads(test_case_content)
1119
1759
  test_cases = input_obj if isinstance(input_obj, list) else [input_obj]
@@ -1207,11 +1847,11 @@ SEARCHABLE FIELDS:
1207
1847
  - Direct fields: Id, Name, Description, Status, Type, Priority, Automation, etc.
1208
1848
  - Module: Use 'Module in' syntax
1209
1849
  - Custom fields: Use exact field name from project configuration
1210
- - Date fields: Use ISO DateTime format (e.g., 'Created Date' > '2021-05-07T03:15:37.652Z')
1850
+ - Date fields: MUST use ISO DateTime format (e.g., '2024-01-01T00:00:00.000Z')
1211
1851
 
1212
1852
  SYNTAX RULES:
1213
1853
  1. ALL string values MUST use single quotes (never double quotes)
1214
- 2. Field names with spaces MUST be in single quotes: 'Created Date' > '2024-01-01'
1854
+ 2. Field names with spaces MUST be in single quotes: 'Created Date' > '2024-01-01T00:00:00.000Z'
1215
1855
  3. Use ~ for 'contains', !~ for 'not contains': Description ~ 'login'
1216
1856
  4. Use 'is not empty' for non-empty check: Name is 'not empty'
1217
1857
  5. Operators: =, !=, <, >, <=, >=, in, ~, !~
@@ -1301,5 +1941,141 @@ Examples:
1301
1941
  """,
1302
1942
  "args_schema": FindTestCasesByRequirementId,
1303
1943
  "ref": self.find_test_cases_by_requirement_id,
1944
+ },
1945
+ {
1946
+ "name": "find_requirements_by_test_case_id",
1947
+ "mode": "find_requirements_by_test_case_id",
1948
+ "description": """Find all requirements linked to a test case (direct link: test-case 'covers' requirements).
1949
+
1950
+ Use this tool to discover which requirements a specific test case covers.
1951
+
1952
+ Parameters:
1953
+ - test_case_id: Test case ID in format TC-123
1954
+
1955
+ Returns: List of linked requirements with Id, QTest Id, Name, and Description.
1956
+
1957
+ Examples:
1958
+ - Find requirements for TC-123: test_case_id='TC-123'
1959
+ """,
1960
+ "args_schema": FindRequirementsByTestCaseId,
1961
+ "ref": self.find_requirements_by_test_case_id,
1962
+ },
1963
+ {
1964
+ "name": "find_test_runs_by_test_case_id",
1965
+ "mode": "find_test_runs_by_test_case_id",
1966
+ "description": """Find all test runs associated with a test case.
1967
+
1968
+ IMPORTANT: In QTest, defects are NOT directly linked to test cases.
1969
+ Defects are linked to TEST RUNS. To find defects related to a test case:
1970
+ 1. First use this tool to find test runs for the test case
1971
+ 2. Then use find_defects_by_test_run_id for each test run
1972
+
1973
+ Parameters:
1974
+ - test_case_id: Test case ID in format TC-123
1975
+
1976
+ Returns: List of test runs with Id, QTest Id, Name, and Description.
1977
+ Also includes a hint about finding defects via test runs.
1978
+
1979
+ Examples:
1980
+ - Find test runs for TC-123: test_case_id='TC-123'
1981
+ """,
1982
+ "args_schema": FindTestRunsByTestCaseId,
1983
+ "ref": self.find_test_runs_by_test_case_id,
1984
+ },
1985
+ {
1986
+ "name": "find_defects_by_test_run_id",
1987
+ "mode": "find_defects_by_test_run_id",
1988
+ "description": """Find all defects associated with a test run.
1989
+
1990
+ In QTest data model, defects are linked to test runs (not directly to test cases).
1991
+ A defect found here means it was reported during execution of this specific test run.
1992
+
1993
+ To find defects related to a test case:
1994
+ 1. First use find_test_runs_by_test_case_id to get test runs
1995
+ 2. Then use this tool for each test run
1996
+
1997
+ Parameters:
1998
+ - test_run_id: Test run ID in format TR-123
1999
+
2000
+ Returns: List of defects with Id, QTest Id, Name, and Description.
2001
+
2002
+ Examples:
2003
+ - Find defects for TR-39: test_run_id='TR-39'
2004
+ """,
2005
+ "args_schema": FindDefectsByTestRunId,
2006
+ "ref": self.find_defects_by_test_run_id,
2007
+ },
2008
+ {
2009
+ "name": "search_entities_by_dql",
2010
+ "mode": "search_entities_by_dql",
2011
+ "description": f"""Search any QTest entity type using Data Query Language (DQL).
2012
+
2013
+ This is a unified search tool for all searchable QTest entity types.
2014
+
2015
+ SUPPORTED ENTITY TYPES (object_type parameter):
2016
+ - 'test-cases' (TC-xxx): Test case definitions with steps
2017
+ - 'test-runs' (TR-xxx): Execution instances of test cases
2018
+ - 'defects' (DF-xxx): Bugs/issues found during testing
2019
+ - 'requirements' (RQ-xxx): Requirements to be tested
2020
+ - 'test-suites' (TS-xxx): Collections of test runs
2021
+ - 'test-cycles' (CL-xxx): Test execution cycles
2022
+ - 'test-logs': Execution logs (date queries ONLY - see notes)
2023
+ - 'releases' (RL-xxx): Software releases
2024
+ - 'builds' (BL-xxx): Builds within releases
2025
+
2026
+ NOTES:
2027
+ - Modules (MD-xxx) are NOT searchable via DQL. Use 'get_modules' tool instead.
2028
+ - Test-logs: Only date queries work (Execution Start Date, Execution End Date).
2029
+ For specific test log details, use find_test_runs_by_test_case_id -
2030
+ the test run includes 'Latest Test Log' with status and execution times.
2031
+
2032
+ {DQL_SYNTAX_DOCS}
2033
+
2034
+ EXAMPLES BY ENTITY TYPE:
2035
+ - Test cases: object_type='test-cases', dql="Name ~ 'login'"
2036
+ - Requirements: object_type='requirements', dql="Status = 'Baselined'"
2037
+ - Defects: object_type='defects', dql="Priority = 'High'"
2038
+ - Test runs: object_type='test-runs', dql="Status = 'Failed'"
2039
+ - Test logs: object_type='test-logs', dql="'Execution Start Date' > '2024-01-01T00:00:00.000Z'" (date queries only)
2040
+ - Releases: object_type='releases', dql="Name ~ '2024'"
2041
+ """,
2042
+ "args_schema": GenericDqlSearch,
2043
+ "ref": self.search_entities_by_dql,
2044
+ },
2045
+ {
2046
+ "name": "find_entity_by_id",
2047
+ "mode": "find_entity_by_id",
2048
+ "description": """Find any QTest entity by its ID.
2049
+
2050
+ This universal lookup tool works for entity types that have ID prefixes.
2051
+ The entity type is automatically determined from the ID prefix.
2052
+
2053
+ SUPPORTED ID FORMATS:
2054
+ - TC-123: Test Case
2055
+ - TR-39: Test Run
2056
+ - DF-100: Defect
2057
+ - RQ-15: Requirement
2058
+ - TS-5: Test Suite
2059
+ - CL-3: Test Cycle
2060
+ - RL-1: Release
2061
+ - BL-2: Build
2062
+
2063
+ NOT SUPPORTED (no ID prefix):
2064
+ - Test Logs: Get details from test run's 'Latest Test Log' field (contains Log Id, Status, Execution Start/End Date)
2065
+ - Modules: Use 'get_modules' tool instead
2066
+
2067
+ Parameters:
2068
+ - entity_id: Entity ID with prefix (e.g., TC-123, RQ-15, DF-100, TR-39)
2069
+
2070
+ Returns: Full entity details including all properties.
2071
+
2072
+ Examples:
2073
+ - Find test case: entity_id='TC-123'
2074
+ - Find requirement: entity_id='RQ-15'
2075
+ - Find defect: entity_id='DF-100'
2076
+ - Find test run: entity_id='TR-39'
2077
+ """,
2078
+ "args_schema": FindEntityById,
2079
+ "ref": self.find_entity_by_id,
1304
2080
  }
1305
2081
  ]
@@ -130,9 +130,24 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
130
130
  if not limit_files:
131
131
  limit_files = 100
132
132
  #
133
+ site_segments = [seg for seg in self.site_url.strip('/').split('/') if seg][-2:]
134
+ full_path_prefix = '/'.join(site_segments)
135
+ #
133
136
  for lib in all_libraries:
134
137
  library_type = decode_sharepoint_string(lib.properties["EntityTypeName"])
135
- target_folder_url = f"{library_type}/{folder_name}" if folder_name else library_type
138
+ target_folder_url = library_type
139
+ if folder_name:
140
+ folder_path = folder_name.strip('/')
141
+ expected_prefix = f'{full_path_prefix}/{library_type}'
142
+ if folder_path.startswith(full_path_prefix):
143
+ if folder_path.startswith(expected_prefix):
144
+ target_folder_url = folder_path.removeprefix(f'{full_path_prefix}/')
145
+ else:
146
+ # ignore full path folder which is not targeted to current library
147
+ continue
148
+ else:
149
+ target_folder_url = f"{library_type}/{folder_name}"
150
+ #
136
151
  files = (self._client.web.get_folder_by_server_relative_path(target_folder_url)
137
152
  .get_files(True)
138
153
  .execute_query())
@@ -226,13 +241,18 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
226
241
  'skip_extensions': (Optional[List[str]], Field(
227
242
  description="List of file extensions to skip when processing: i.e. ['*.png', '*.jpg']",
228
243
  default=[])),
244
+ 'path': (Optional[str], Field(
245
+ description="Folder path. "
246
+ "Accepts either a full server-relative path (e.g., '/sites/SiteName/...') or a relative path. "
247
+ "If a relative path is provided, the search will be performed recursively under 'Shared Documents' and other private libraries.",
248
+ default=None)),
229
249
  }
230
250
 
231
251
  def _base_loader(self, **kwargs) -> Generator[Document, None, None]:
232
252
 
233
253
  self._log_tool_event(message="Starting SharePoint files extraction", tool_name="loader")
234
254
  try:
235
- all_files = self.get_files_list(limit_files=kwargs.get('limit_files', 10000))
255
+ all_files = self.get_files_list(kwargs.get('path'), kwargs.get('limit_files', 10000))
236
256
  self._log_tool_event(message="List of the files has been extracted", tool_name="loader")
237
257
  except Exception as e:
238
258
  raise ToolException(f"Unable to extract files: {e}")
@@ -147,12 +147,28 @@ class SharepointAuthorizationHelper:
147
147
  if limit_files is not None and len(result) + len(files) >= limit_files:
148
148
  return files[:limit_files - len(result)]
149
149
  return files
150
+ #
151
+ site_segments = [seg for seg in site_url.strip('/').split('/') if seg][-2:]
152
+ full_path_prefix = '/'.join(site_segments)
153
+ #
150
154
  for drive in drives:
151
155
  drive_id = drive.get("id")
152
156
  drive_path = unquote(urlparse(drive.get("webUrl")).path) if drive.get("webUrl") else ""
153
157
  if not drive_id:
154
158
  continue # skip drives without id
155
- files = _recurse_drive(drive_id, drive_path, folder_name, limit_files)
159
+ #
160
+ sub_folder = folder_name
161
+ if folder_name:
162
+ folder_path = folder_name.strip('/')
163
+ expected_prefix = drive_path.strip('/')#f'{full_path_prefix}/{library_type}'
164
+ if folder_path.startswith(full_path_prefix):
165
+ if folder_path.startswith(expected_prefix):
166
+ sub_folder = folder_path.removeprefix(f'{expected_prefix}').strip('/')#target_folder_url = folder_path.removeprefix(f'{full_path_prefix}/')
167
+ else:
168
+ # ignore full path folder which is not targeted to current drive
169
+ continue
170
+ #
171
+ files = _recurse_drive(drive_id, drive_path, sub_folder, limit_files)
156
172
  result.extend(files)
157
173
  if limit_files is not None and len(result) >= limit_files:
158
174
  return result[:limit_files]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alita_sdk
3
- Version: 0.3.457b0
3
+ Version: 0.3.458
4
4
  Summary: SDK for building langchain agents using resources from Alita
5
5
  Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedj27@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -100,7 +100,7 @@ alita_sdk/runtime/llms/preloaded.py,sha256=3AaUbZK3d8fvxAQMjR3ftOoYa0SnkCOL1Evdv
100
100
  alita_sdk/runtime/models/mcp_models.py,sha256=rbWCAtF8Jjb7uNgQHhVWyDttXaqPNbRLL087Lf0AjNU,2301
101
101
  alita_sdk/runtime/toolkits/__init__.py,sha256=IenSyI2SrXSFuiWT7c8YO_mRFLVE_zNge61U4bpoyPw,631
102
102
  alita_sdk/runtime/toolkits/application.py,sha256=HHAKgwKOckxc7EQG-AV7rz4POOzQJKFRr7AGEjmLudE,2688
103
- alita_sdk/runtime/toolkits/artifact.py,sha256=YChNCX4QhVpaQG7Jk4TS-Wl0Aruc4slQ2K21zh9nNO0,3176
103
+ alita_sdk/runtime/toolkits/artifact.py,sha256=HvunflqpLwrvpn8Jz8AKF_CIowIGEsfG96gqredMgO0,3251
104
104
  alita_sdk/runtime/toolkits/configurations.py,sha256=kIDAlnryPQfbZyFxV-9SzN2-Vefzx06TX1BBdIIpN90,141
105
105
  alita_sdk/runtime/toolkits/datasource.py,sha256=qk78OdPoReYPCWwahfkKLbKc4pfsu-061oXRryFLP6I,2498
106
106
  alita_sdk/runtime/toolkits/mcp.py,sha256=wa-47obeWm8WrIiaUw5BR40dsR5sLdQKZHzG7MCRnRM,37105
@@ -311,7 +311,7 @@ alita_sdk/tools/postman/postman_analysis.py,sha256=ckc2BfKEop0xnmLPksVRE_Y94ixuq
311
311
  alita_sdk/tools/pptx/__init__.py,sha256=JP4523kvgcRbKevy9s7KllDbzHV9-ArKMNBuzeT97VI,3378
312
312
  alita_sdk/tools/pptx/pptx_wrapper.py,sha256=yyCYcTlIY976kJ4VfPo4dyxj4yeii9j9TWP6W8ZIpN8,29195
313
313
  alita_sdk/tools/qtest/__init__.py,sha256=IGObKF5m7vCE1Pq4cQ7iAMBckryZ5gowdBbnAey5DFQ,4384
314
- alita_sdk/tools/qtest/api_wrapper.py,sha256=YiXkCPlzudHl7Hke-U8oWvix1mD_00poD8MNa9F2YR4,61841
314
+ alita_sdk/tools/qtest/api_wrapper.py,sha256=1zhIOePMGYJm0NwzyXkxh39DTTu0vnCEZ1CsQQxVcIA,95498
315
315
  alita_sdk/tools/qtest/tool.py,sha256=kKzNPS4fUC76WQQttQ6kdVANViHEvKE8Kf174MQiNYU,562
316
316
  alita_sdk/tools/rally/__init__.py,sha256=VESgUY_m0lQVyoSuuCRyLgURn7vHs-JIMS_0oH4e0LQ,3347
317
317
  alita_sdk/tools/rally/api_wrapper.py,sha256=mouzU6g0KML4UNapdk0k6Q0pU3MpJuWnNo71n9PSEHM,11752
@@ -325,8 +325,8 @@ alita_sdk/tools/servicenow/__init__.py,sha256=rI8WrlA5mKxdSe7NYX6Ehd0NTfGx2CEbE6
325
325
  alita_sdk/tools/servicenow/api_wrapper.py,sha256=WpH-bBLGFdhehs4g-K-WAkNuaD1CSrwsDpdgB3RG53s,6120
326
326
  alita_sdk/tools/servicenow/servicenow_client.py,sha256=Rdqfu-ll-qbnclMzChLZBsfXRDzgoX_FdeI2WLApWxc,3269
327
327
  alita_sdk/tools/sharepoint/__init__.py,sha256=mtBw8oBq2LWQpMAgidF7BDn3oOgD3QCWEQjBW80RN3M,4117
328
- alita_sdk/tools/sharepoint/api_wrapper.py,sha256=yOAZfa9-GcjohTak-TqcFL8f7s18hIA89FNO0qXjb_s,16614
329
- alita_sdk/tools/sharepoint/authorization_helper.py,sha256=GN_UkI_HOn9b5UFlxqck7sm6HuB_Kh4wCIGBYFPe2dg,11924
328
+ alita_sdk/tools/sharepoint/api_wrapper.py,sha256=wzwqEmsNRopGzOjOleSsrsfZON4zRyExYZpZqUdWp60,17809
329
+ alita_sdk/tools/sharepoint/authorization_helper.py,sha256=tlmsw3l2UGvVRZS3cHCKCy_arx_IkUuhBOCf9GZ9T6U,12818
330
330
  alita_sdk/tools/sharepoint/utils.py,sha256=CO1PNRC5CpQaVo2Gdenj_jqm2bReSqUT92Bk5s37d8M,573
331
331
  alita_sdk/tools/slack/__init__.py,sha256=AurOivHcpqvrrbg7rwTz8lrMfM6KPskWmHs6ml0jZVM,4057
332
332
  alita_sdk/tools/slack/api_wrapper.py,sha256=5VrV7iSGno8ZcDzEHdGPNhInhtODGPPvAzoZ9W9iQWE,14009
@@ -361,8 +361,8 @@ alita_sdk/tools/zephyr_scale/api_wrapper.py,sha256=kT0TbmMvuKhDUZc0i7KO18O38JM9S
361
361
  alita_sdk/tools/zephyr_squad/__init__.py,sha256=Gl1YAFaMMufTNjIpnuo_c_lbDsQkmproYdZz2ZppgJ4,3007
362
362
  alita_sdk/tools/zephyr_squad/api_wrapper.py,sha256=kmw_xol8YIYFplBLWTqP_VKPRhL_1ItDD0_vXTe_UuI,14906
363
363
  alita_sdk/tools/zephyr_squad/zephyr_squad_cloud_client.py,sha256=R371waHsms4sllHCbijKYs90C-9Yu0sSR3N4SUfQOgU,5066
364
- alita_sdk-0.3.457b0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
365
- alita_sdk-0.3.457b0.dist-info/METADATA,sha256=y8iIPCwCcawEBlaA7fFZJMRL-C-JG_ykWWiV35rZnGE,19103
366
- alita_sdk-0.3.457b0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
367
- alita_sdk-0.3.457b0.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
368
- alita_sdk-0.3.457b0.dist-info/RECORD,,
364
+ alita_sdk-0.3.458.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
365
+ alita_sdk-0.3.458.dist-info/METADATA,sha256=2rKUTBUSXzFWP9316T-IRjMtsAuwy-bvNtOOlkxOPLg,19101
366
+ alita_sdk-0.3.458.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
367
+ alita_sdk-0.3.458.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
368
+ alita_sdk-0.3.458.dist-info/RECORD,,