alita-sdk 0.3.450__py3-none-any.whl → 0.3.452__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.
@@ -475,10 +475,10 @@ def create_graph(
475
475
  if toolkit_name:
476
476
  tool_name = f"{clean_string(toolkit_name)}{TOOLKIT_SPLITTER}{tool_name}"
477
477
  logger.info(f"Node: {node_id} : {node_type} - {tool_name}")
478
- if node_type in ['function', 'tool', 'loop', 'loop_from_tool', 'indexer', 'subgraph', 'pipeline', 'agent']:
478
+ if node_type in ['function', 'toolkit', 'mcp', 'tool', 'loop', 'loop_from_tool', 'indexer', 'subgraph', 'pipeline', 'agent']:
479
479
  for tool in tools:
480
480
  if tool.name == tool_name:
481
- if node_type == 'function':
481
+ if node_type in ['function', 'toolkit', 'mcp']:
482
482
  lg_builder.add_node(node_id, FunctionTool(
483
483
  tool=tool, name=node_id, return_type='dict',
484
484
  output_variables=node.get('output', []),
@@ -663,7 +663,7 @@ def create_graph(
663
663
  lg_builder.add_node(reset_node_id, PrinterNode(
664
664
  input_mapping={'printer': {'type': 'fixed', 'value': ''}}
665
665
  ))
666
- lg_builder.add_edge(node_id, reset_node_id)
666
+ lg_builder.add_conditional_edges(node_id, TransitionalEdge(reset_node_id))
667
667
  lg_builder.add_conditional_edges(reset_node_id, TransitionalEdge(clean_string(node['transition'])))
668
668
  continue
669
669
  if node.get('transition'):
@@ -1,9 +1,9 @@
1
1
  import json
2
- import math
3
2
  from collections import OrderedDict
4
3
  from logging import getLogger
5
4
  from typing import Any, Optional, List, Dict, Generator
6
5
 
6
+ import math
7
7
  from langchain_core.documents import Document
8
8
  from langchain_core.messages import HumanMessage
9
9
  from langchain_core.tools import ToolException
@@ -12,7 +12,7 @@ from pydantic import BaseModel, model_validator, Field
12
12
 
13
13
  from alita_sdk.tools.elitea_base import BaseToolApiWrapper
14
14
  from alita_sdk.tools.vector_adapters.VectorStoreAdapter import VectorStoreAdapterFactory
15
- from ..utils.logging import dispatch_custom_event
15
+ from ...runtime.utils.utils import IndexerKeywords
16
16
 
17
17
  logger = getLogger(__name__)
18
18
 
@@ -222,6 +222,21 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
222
222
  raise RuntimeError(f"Multiple index_meta documents found: {index_metas}")
223
223
  return index_metas[0] if index_metas else None
224
224
 
225
+ def get_indexed_count(self, index_name: str) -> int:
226
+ from sqlalchemy.orm import Session
227
+ from sqlalchemy import func, or_
228
+
229
+ with Session(self.vectorstore.session_maker.bind) as session:
230
+ return session.query(
231
+ self.vectorstore.EmbeddingStore.id,
232
+ ).filter(
233
+ func.jsonb_extract_path_text(self.vectorstore.EmbeddingStore.cmetadata, 'collection') == index_name,
234
+ or_(
235
+ func.jsonb_extract_path_text(self.vectorstore.EmbeddingStore.cmetadata, 'type').is_(None),
236
+ func.jsonb_extract_path_text(self.vectorstore.EmbeddingStore.cmetadata, 'type') != IndexerKeywords.INDEX_META_TYPE.value
237
+ )
238
+ ).count()
239
+
225
240
  def _clean_collection(self, index_name: str = ''):
226
241
  """
227
242
  Clean the vectorstore collection by deleting all indexed data.
@@ -470,6 +470,7 @@ class BaseIndexerToolkit(VectorStoreWrapperBase):
470
470
  "collection": index_name,
471
471
  "type": IndexerKeywords.INDEX_META_TYPE.value,
472
472
  "indexed": 0,
473
+ "updated": 0,
473
474
  "state": IndexerKeywords.INDEX_META_IN_PROGRESS.value,
474
475
  "index_configuration": index_configuration,
475
476
  "created_on": created_on,
@@ -487,7 +488,8 @@ class BaseIndexerToolkit(VectorStoreWrapperBase):
487
488
  #
488
489
  if index_meta_raw:
489
490
  metadata = copy.deepcopy(index_meta_raw.get("metadata", {}))
490
- metadata["indexed"] = result
491
+ metadata["indexed"] = self.get_indexed_count(index_name)
492
+ metadata["updated"] = result
491
493
  metadata["state"] = state
492
494
  metadata["updated_on"] = time.time()
493
495
  #
@@ -5,6 +5,7 @@ import re
5
5
  from traceback import format_exc
6
6
  from typing import Any, Optional
7
7
 
8
+ import requests
8
9
  import swagger_client
9
10
  from langchain_core.tools import ToolException
10
11
  from pydantic import Field, PrivateAttr, model_validator, create_model, SecretStr
@@ -414,6 +415,143 @@ class QtestApiWrapper(BaseToolApiWrapper):
414
415
  Exception: \n {stacktrace}""")
415
416
  return modules
416
417
 
418
+ def __get_field_definitions_from_properties_api(self) -> dict:
419
+ """
420
+ Fallback method: Get field definitions using /properties and /properties-info APIs.
421
+
422
+ These APIs don't require Field Management permission and are available to all users.
423
+ Requires 2 API calls + 1 search to get a test case ID.
424
+
425
+ Returns:
426
+ dict: Same structure as __get_project_field_definitions()
427
+ """
428
+ logger.info(
429
+ "Using properties API fallback (no Field Management permission). "
430
+ "This requires getting a template test case first."
431
+ )
432
+
433
+ # Step 1: Get any test case ID to query properties
434
+ search_instance = swagger_client.SearchApi(self._client)
435
+ body = swagger_client.ArtifactSearchParams(
436
+ object_type='test-cases',
437
+ fields=['*'],
438
+ query='' # Empty query returns all test cases
439
+ )
440
+
441
+ try:
442
+ # Search for any test case - just need one
443
+ response = search_instance.search_artifact(
444
+ self.qtest_project_id,
445
+ body,
446
+ page_size=1,
447
+ page=1
448
+ )
449
+ except ApiException as e:
450
+ stacktrace = format_exc()
451
+ logger.error(f"Failed to find test case for properties API: {stacktrace}")
452
+ raise ValueError(
453
+ f"Cannot find any test case to query field definitions. "
454
+ f"Please create at least one test case in project {self.qtest_project_id}"
455
+ ) from e
456
+
457
+ if not response or not response.get('items') or len(response['items']) == 0:
458
+ raise ValueError(
459
+ f"No test cases found in project {self.qtest_project_id}. "
460
+ f"Please create at least one test case to retrieve field definitions."
461
+ )
462
+
463
+ test_case_id = response['items'][0]['id']
464
+ logger.info(f"Using test case ID {test_case_id} to retrieve field definitions")
465
+
466
+ # Step 2: Call /properties API
467
+ headers = {
468
+ "Authorization": f"Bearer {self.qtest_api_token.get_secret_value()}"
469
+ }
470
+
471
+ properties_url = f"{self.base_url}/api/v3/projects/{self.qtest_project_id}/test-cases/{test_case_id}/properties"
472
+ properties_info_url = f"{self.base_url}/api/v3/projects/{self.qtest_project_id}/test-cases/{test_case_id}/properties-info"
473
+
474
+ try:
475
+ # Get properties with current values and field metadata
476
+ props_response = requests.get(
477
+ properties_url,
478
+ headers=headers,
479
+ params={'calledBy': 'testcase_properties'}
480
+ )
481
+ props_response.raise_for_status()
482
+ properties_data = props_response.json()
483
+
484
+ # Get properties-info with data types and allowed values
485
+ info_response = requests.get(properties_info_url, headers=headers)
486
+ info_response.raise_for_status()
487
+ info_data = info_response.json()
488
+
489
+ except requests.exceptions.RequestException as e:
490
+ stacktrace = format_exc()
491
+ logger.error(f"Failed to call properties API: {stacktrace}")
492
+ raise ValueError(
493
+ f"Unable to retrieve field definitions using properties API. "
494
+ f"Error: {stacktrace}"
495
+ ) from e
496
+
497
+ # Step 3: Build field mapping by merging both responses
498
+ field_mapping = {}
499
+
500
+ # Create lookup by field ID from properties-info
501
+ metadata_by_id = {item['id']: item for item in info_data['metadata']}
502
+
503
+ # Data type mapping to determine 'multiple' flag
504
+ MULTI_SELECT_TYPES = {
505
+ 'UserListDataType',
506
+ 'MultiSelectionDataType',
507
+ 'CheckListDataType'
508
+ }
509
+
510
+ USER_FIELD_TYPES = {'UserListDataType'}
511
+
512
+ # System fields to exclude (same as in property mapping)
513
+ excluded_fields = {'Shared', 'Projects Shared to'}
514
+
515
+ for prop in properties_data:
516
+ field_name = prop.get('name')
517
+ field_id = prop.get('id')
518
+
519
+ if not field_name or field_name in excluded_fields:
520
+ continue
521
+
522
+ # Get metadata for this field
523
+ metadata = metadata_by_id.get(field_id, {})
524
+ data_type_str = metadata.get('data_type')
525
+
526
+ # Determine data_type number (5 for user fields, None for others)
527
+ data_type = 5 if data_type_str in USER_FIELD_TYPES else None
528
+
529
+ # Determine if multi-select
530
+ is_multiple = data_type_str in MULTI_SELECT_TYPES
531
+
532
+ field_mapping[field_name] = {
533
+ 'field_id': field_id,
534
+ 'required': prop.get('required', False),
535
+ 'data_type': data_type,
536
+ 'multiple': is_multiple,
537
+ 'values': {}
538
+ }
539
+
540
+ # Map allowed values from metadata
541
+ allowed_values = metadata.get('allowed_values', [])
542
+ for allowed_val in allowed_values:
543
+ value_text = allowed_val.get('value_text')
544
+ value_id = allowed_val.get('id')
545
+ if value_text and value_id:
546
+ field_mapping[field_name]['values'][value_text] = value_id
547
+
548
+ logger.info(
549
+ f"Retrieved {len(field_mapping)} field definitions using properties API. "
550
+ f"This method works for all users without Field Management permission."
551
+ )
552
+
553
+ return field_mapping
554
+
417
555
  def __get_project_field_definitions(self) -> dict:
418
556
  """
419
557
  Get structured field definitions for test cases in the project.
@@ -439,6 +577,15 @@ class QtestApiWrapper(BaseToolApiWrapper):
439
577
  try:
440
578
  fields = fields_api.get_fields(self.qtest_project_id, qtest_object)
441
579
  except ApiException as e:
580
+ # Check if permission denied (403) - use fallback
581
+ if e.status == 403:
582
+ logger.warning(
583
+ "get_fields permission denied (Field Management permission required). "
584
+ "Using properties API fallback..."
585
+ )
586
+ return self.__get_field_definitions_from_properties_api()
587
+
588
+ # Other API errors
442
589
  stacktrace = format_exc()
443
590
  logger.error(f"Exception when calling FieldAPI->get_fields:\n {stacktrace}")
444
591
  raise ValueError(
@@ -556,8 +703,8 @@ class QtestApiWrapper(BaseToolApiWrapper):
556
703
  def __parse_data(self, response_to_parse: dict, parsed_data: list, extract_images: bool=False, prompt: str=None):
557
704
  import html
558
705
 
559
- # Get field definitions to ensure all fields are included (uses cached version)
560
- field_definitions = self.__get_field_definitions_cached()
706
+ # PERMISSION-FREE: Parse properties directly from API response
707
+ # No get_fields call needed - works for all users
561
708
 
562
709
  for item in response_to_parse['items']:
563
710
  # Start with core fields (always present)
@@ -574,29 +721,15 @@ class QtestApiWrapper(BaseToolApiWrapper):
574
721
  }, enumerate(item['test_steps']))),
575
722
  }
576
723
 
577
- # Dynamically add all custom fields from project configuration
578
- # This ensures consistency and includes fields even if they have null/empty values
579
- for field_name in field_definitions.keys():
580
- field_def = field_definitions[field_name]
581
- is_multiple = field_def.get('multiple', False)
582
-
583
- # Find the property value in the response (if exists)
584
- field_value = None
585
- for prop in item['properties']:
586
- if prop['field_name'] == field_name:
587
- # Use field_value_name if available (for dropdowns), otherwise field_value
588
- field_value = prop.get('field_value_name') or prop.get('field_value') or ''
589
- break
590
-
591
- # Format based on field type
592
- if is_multiple and (field_value is None or field_value == ''):
593
- # Multi-select field with no value: show empty array with hint
594
- parsed_data_row[field_name] = '[] (multi-select)'
595
- elif field_value is not None:
596
- parsed_data_row[field_name] = field_value
597
- else:
598
- # Regular field with no value
599
- parsed_data_row[field_name] = ''
724
+ # Add custom fields directly from API response properties
725
+ for prop in item['properties']:
726
+ field_name = prop.get('field_name')
727
+ if not field_name:
728
+ continue
729
+
730
+ # Use field_value_name if available (for dropdowns/users), otherwise field_value
731
+ field_value = prop.get('field_value_name') or prop.get('field_value') or ''
732
+ parsed_data_row[field_name] = field_value
600
733
 
601
734
  parsed_data.append(parsed_data_row)
602
735
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alita_sdk
3
- Version: 0.3.450
3
+ Version: 0.3.452
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
@@ -47,7 +47,7 @@ alita_sdk/runtime/langchain/assistant.py,sha256=t93SNBcdki59gvW_Osl68E-x0ohcO2z3
47
47
  alita_sdk/runtime/langchain/chat_message_template.py,sha256=kPz8W2BG6IMyITFDA5oeb5BxVRkHEVZhuiGl4MBZKdc,2176
48
48
  alita_sdk/runtime/langchain/constants.py,sha256=oiEHg1h_IYUA5NE8O6nEF24hpxahi9BTvJWrkXjbVcU,3405
49
49
  alita_sdk/runtime/langchain/indexer.py,sha256=0ENHy5EOhThnAiYFc7QAsaTNp9rr8hDV_hTK8ahbatk,37592
50
- alita_sdk/runtime/langchain/langraph_agent.py,sha256=CxDFfUTCG-i8koMR9PwOktvlcdUe5cyG4D8CQHrTH1E,51836
50
+ alita_sdk/runtime/langchain/langraph_agent.py,sha256=YzL8hzXhrI8LUz_Ct6lj4EKLr27fGI5fzLOkQh_rph8,51905
51
51
  alita_sdk/runtime/langchain/mixedAgentParser.py,sha256=M256lvtsL3YtYflBCEp-rWKrKtcY1dJIyRGVv7KW9ME,2611
52
52
  alita_sdk/runtime/langchain/mixedAgentRenderes.py,sha256=asBtKqm88QhZRILditjYICwFVKF5KfO38hu2O-WrSWE,5964
53
53
  alita_sdk/runtime/langchain/store_manager.py,sha256=i8Fl11IXJhrBXq1F1ukEVln57B1IBe-tqSUvfUmBV4A,2218
@@ -130,7 +130,7 @@ alita_sdk/runtime/tools/router.py,sha256=p7e0tX6YAWw2M2Nq0A_xqw1E2P-Xz1DaJvhUstf
130
130
  alita_sdk/runtime/tools/sandbox.py,sha256=LTCHC9xnuYThWX30PCTVEtjeg50FX7w2c29KU5E68W0,15251
131
131
  alita_sdk/runtime/tools/tool.py,sha256=lE1hGi6qOAXG7qxtqxarD_XMQqTghdywf261DZawwno,5631
132
132
  alita_sdk/runtime/tools/vectorstore.py,sha256=0SzfY1dYrGr7YUapJzXY01JFyzLv36dPjwHzs1XZIM4,34392
133
- alita_sdk/runtime/tools/vectorstore_base.py,sha256=k_6LAhhBJEs5SXCQJI3bBvJLQli6_3pHjqF6SCQGJGc,28312
133
+ alita_sdk/runtime/tools/vectorstore_base.py,sha256=wTVgX9MHPGqJ3JTKJd1BL3hQze-1a1CK2tVVPw3qXY4,29064
134
134
  alita_sdk/runtime/utils/AlitaCallback.py,sha256=E4LlSBuCHWiUq6W7IZExERHZY0qcmdjzc_rJlF2iQIw,7356
135
135
  alita_sdk/runtime/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
136
  alita_sdk/runtime/utils/constants.py,sha256=Xntx1b_uxUzT4clwqHA_U6K8y5bBqf_4lSQwXdcWrp4,13586
@@ -144,7 +144,7 @@ alita_sdk/runtime/utils/toolkit_runtime.py,sha256=MU63Fpxj0b5_r1IUUc0Q3-PN9VwL7r
144
144
  alita_sdk/runtime/utils/toolkit_utils.py,sha256=_6cKy514v4ueIZynXTA8LwGC9Q447MfgrQKkDwDI4qM,5886
145
145
  alita_sdk/runtime/utils/utils.py,sha256=PJK8A-JVIzY1IowOjGG8DIqsIiEFe65qDKvFcjJCKWA,1041
146
146
  alita_sdk/tools/__init__.py,sha256=uQzvtnyOsgfdHl3pdo2LqK49Hb3SKFXDBXW_szN2R3k,10992
147
- alita_sdk/tools/base_indexer_toolkit.py,sha256=hK1Q2dvNctZCw2K1-khlQrR1Q0JDQviZjqDUiqpnazg,27180
147
+ alita_sdk/tools/base_indexer_toolkit.py,sha256=f93O8bvemshJtUpSABeWx6_rOR_TfI1-7js3Cl2tSBs,27279
148
148
  alita_sdk/tools/code_indexer_toolkit.py,sha256=2VkOC8JfBDc25_jp-NWyMYqpaYRETIzTJFLrIYrfBpE,7814
149
149
  alita_sdk/tools/elitea_base.py,sha256=34fmVdYgd2YXifU5LFNjMQysr4OOIZ6AOZjq4GxLgSw,34417
150
150
  alita_sdk/tools/non_code_indexer_toolkit.py,sha256=6Lrqor1VeSLbPLDHAfg_7UAUqKFy1r_n6bdsc4-ak98,1315
@@ -311,7 +311,7 @@ alita_sdk/tools/postman/postman_analysis.py,sha256=ckc2BfKEop0xnmLPksVRE_Y94ixuq
311
311
  alita_sdk/tools/pptx/__init__.py,sha256=vVUrWnj7KWJgEk9oxGSsCAQ2SMSXrp_SFOdUHYQKcAo,3444
312
312
  alita_sdk/tools/pptx/pptx_wrapper.py,sha256=yyCYcTlIY976kJ4VfPo4dyxj4yeii9j9TWP6W8ZIpN8,29195
313
313
  alita_sdk/tools/qtest/__init__.py,sha256=Jf0xo5S_4clXR2TfCbJbB1sFgCbcFQRM-YYX2ltWBzo,4461
314
- alita_sdk/tools/qtest/api_wrapper.py,sha256=5YJPcEGUT_V_SdgiYBoPDJHTEDfCrDgzoXTdXuGoH4I,46652
314
+ alita_sdk/tools/qtest/api_wrapper.py,sha256=6ETGLYTzlAGFaIjZxt_1EyaPMS6Imqj-ZoZzqW79-_Q,51871
315
315
  alita_sdk/tools/qtest/tool.py,sha256=kKzNPS4fUC76WQQttQ6kdVANViHEvKE8Kf174MQiNYU,562
316
316
  alita_sdk/tools/rally/__init__.py,sha256=2BPPXJxAOKgfmaxVFVvxndfK0JxOXDLkoRmzu2dUwOE,3512
317
317
  alita_sdk/tools/rally/api_wrapper.py,sha256=mouzU6g0KML4UNapdk0k6Q0pU3MpJuWnNo71n9PSEHM,11752
@@ -361,8 +361,8 @@ alita_sdk/tools/zephyr_scale/api_wrapper.py,sha256=kT0TbmMvuKhDUZc0i7KO18O38JM9S
361
361
  alita_sdk/tools/zephyr_squad/__init__.py,sha256=0ne8XLJEQSLOWfzd2HdnqOYmQlUliKHbBED5kW_Vias,2895
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.450.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
365
- alita_sdk-0.3.450.dist-info/METADATA,sha256=ef9IMngx_rnicjlwr5pbK6rWNCbb8oGxTS4NbiAatuw,19101
366
- alita_sdk-0.3.450.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
367
- alita_sdk-0.3.450.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
368
- alita_sdk-0.3.450.dist-info/RECORD,,
364
+ alita_sdk-0.3.452.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
365
+ alita_sdk-0.3.452.dist-info/METADATA,sha256=PSg0IHeE1PKWRfGipZFSBkugH5dg3eXnyOpcYTghQOc,19101
366
+ alita_sdk-0.3.452.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
367
+ alita_sdk-0.3.452.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
368
+ alita_sdk-0.3.452.dist-info/RECORD,,