alita-sdk 0.3.462__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.
Files changed (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,604 @@
1
+ """
2
+ Python Parser - Uses built-in ast module for AST parsing.
3
+
4
+ This parser provides comprehensive Python AST analysis with support for:
5
+ - Complete symbol extraction (functions, classes, methods, variables)
6
+ - Relationship detection (inheritance, composition, calls, imports)
7
+ - Cross-file resolution with multi-file parsing
8
+ - Type annotation support
9
+ - Decorator support
10
+
11
+ No external dependencies required - uses Python's built-in ast module.
12
+ """
13
+
14
+ import ast
15
+ import hashlib
16
+ import logging
17
+ import time
18
+ import concurrent.futures
19
+ from pathlib import Path
20
+ from typing import Dict, List, Optional, Set, Union, Any, Tuple
21
+
22
+ from .base import (
23
+ BaseParser, ParseResult, Symbol, Relationship,
24
+ SymbolType, RelationshipType, Scope, Position, Range,
25
+ parser_registry
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def _parse_single_file(file_path: str) -> Tuple[str, ParseResult]:
32
+ """Parse a single Python file - used by parallel workers."""
33
+ try:
34
+ parser = PythonParser()
35
+ result = parser.parse_file(file_path)
36
+ return file_path, result
37
+ except Exception as e:
38
+ logger.warning(f"Failed to parse {file_path}: {e}")
39
+ return file_path, ParseResult(
40
+ file_path=file_path,
41
+ language="python",
42
+ symbols=[],
43
+ relationships=[]
44
+ )
45
+
46
+
47
+ class PythonParser(BaseParser):
48
+ """
49
+ Python AST parser using built-in ast module.
50
+
51
+ Extracts symbols and relationships from Python source code.
52
+ """
53
+
54
+ def __init__(self):
55
+ super().__init__("python")
56
+ self._current_file = ""
57
+ self._current_content = ""
58
+
59
+ # Cross-file resolution support
60
+ self._global_class_locations: Dict[str, str] = {}
61
+ self._global_function_locations: Dict[str, str] = {}
62
+ self._global_symbol_registry: Dict[str, str] = {}
63
+
64
+ def _get_supported_extensions(self) -> Set[str]:
65
+ return {'.py', '.pyx', '.pyi', '.pyw'}
66
+
67
+ def parse_file(self, file_path: Union[str, Path], content: Optional[str] = None) -> ParseResult:
68
+ """Parse a Python file and extract symbols and relationships."""
69
+ start_time = time.time()
70
+ file_path = str(file_path)
71
+ self._current_file = file_path
72
+
73
+ try:
74
+ if content is None:
75
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
76
+ content = f.read()
77
+
78
+ self._current_content = content
79
+ file_hash = hashlib.md5(content.encode()).hexdigest()
80
+
81
+ try:
82
+ ast_tree = ast.parse(content, filename=file_path)
83
+ except SyntaxError as e:
84
+ return ParseResult(
85
+ file_path=file_path,
86
+ language="python",
87
+ symbols=[],
88
+ relationships=[],
89
+ parse_time=time.time() - start_time,
90
+ errors=[f"Syntax error: {e}"]
91
+ )
92
+
93
+ symbols = self._extract_symbols(ast_tree, file_path)
94
+ relationships = self._extract_relationships(ast_tree, symbols, file_path)
95
+ imports, exports = self._extract_module_info(ast_tree)
96
+ module_docstring = ast.get_docstring(ast_tree)
97
+
98
+ result = ParseResult(
99
+ file_path=file_path,
100
+ language="python",
101
+ symbols=symbols,
102
+ relationships=relationships,
103
+ imports=imports,
104
+ exports=exports,
105
+ module_docstring=module_docstring,
106
+ parse_time=time.time() - start_time
107
+ )
108
+
109
+ return self.validate_result(result)
110
+
111
+ except Exception as e:
112
+ logger.error(f"Failed to parse Python file {file_path}: {e}")
113
+ return ParseResult(
114
+ file_path=file_path,
115
+ language="python",
116
+ symbols=[],
117
+ relationships=[],
118
+ parse_time=time.time() - start_time,
119
+ errors=[f"Parse error: {e}"]
120
+ )
121
+
122
+ def _extract_symbols(self, ast_tree: ast.AST, file_path: str) -> List[Symbol]:
123
+ """Extract symbols from Python AST."""
124
+ symbols = []
125
+
126
+ class SymbolVisitor(ast.NodeVisitor):
127
+ def __init__(self, parser):
128
+ self.parser = parser
129
+ self.scope_stack = []
130
+
131
+ def visit_Module(self, node):
132
+ symbols.append(Symbol(
133
+ name=Path(file_path).stem,
134
+ symbol_type=SymbolType.MODULE,
135
+ scope=Scope.GLOBAL,
136
+ range=Range(Position(1, 0), Position(len(self.parser._current_content.split('\n')), 0)),
137
+ file_path=file_path,
138
+ docstring=ast.get_docstring(node)
139
+ ))
140
+ self.generic_visit(node)
141
+
142
+ def visit_ClassDef(self, node):
143
+ parent = '.'.join(self.scope_stack) if self.scope_stack else None
144
+ full_name = f"{parent}.{node.name}" if parent else node.name
145
+
146
+ symbols.append(Symbol(
147
+ name=node.name,
148
+ symbol_type=SymbolType.CLASS,
149
+ scope=Scope.CLASS if self.scope_stack else Scope.GLOBAL,
150
+ range=self.parser._node_to_range(node),
151
+ file_path=file_path,
152
+ parent_symbol=parent,
153
+ full_name=full_name,
154
+ docstring=ast.get_docstring(node),
155
+ source_text=self.parser._extract_node_source(node)
156
+ ))
157
+
158
+ self.scope_stack.append(node.name)
159
+ self.generic_visit(node)
160
+ self.scope_stack.pop()
161
+
162
+ def visit_FunctionDef(self, node):
163
+ self._visit_function(node, is_async=False)
164
+
165
+ def visit_AsyncFunctionDef(self, node):
166
+ self._visit_function(node, is_async=True)
167
+
168
+ def _visit_function(self, node, is_async=False):
169
+ parent = '.'.join(self.scope_stack) if self.scope_stack else None
170
+ full_name = f"{parent}.{node.name}" if parent else node.name
171
+
172
+ # Determine if method or function
173
+ is_method = any(s in [sym.name for sym in symbols if sym.symbol_type == SymbolType.CLASS]
174
+ for s in self.scope_stack)
175
+
176
+ return_type = self.parser._get_type_annotation(node.returns) if node.returns else None
177
+
178
+ symbols.append(Symbol(
179
+ name=node.name,
180
+ symbol_type=SymbolType.METHOD if is_method else SymbolType.FUNCTION,
181
+ scope=Scope.FUNCTION,
182
+ range=self.parser._node_to_range(node),
183
+ file_path=file_path,
184
+ parent_symbol=parent,
185
+ full_name=full_name,
186
+ docstring=ast.get_docstring(node),
187
+ return_type=return_type,
188
+ is_async=is_async,
189
+ source_text=self.parser._extract_node_source(node),
190
+ signature=self.parser._build_signature(node, return_type)
191
+ ))
192
+
193
+ self.scope_stack.append(node.name)
194
+ self.generic_visit(node)
195
+ self.scope_stack.pop()
196
+
197
+ def visit_Assign(self, node):
198
+ for target in node.targets:
199
+ if isinstance(target, ast.Name):
200
+ parent = '.'.join(self.scope_stack) if self.scope_stack else None
201
+ scope = Scope.FUNCTION if self.scope_stack else Scope.GLOBAL
202
+ sym_type = SymbolType.CONSTANT if target.id.isupper() else SymbolType.VARIABLE
203
+
204
+ symbols.append(Symbol(
205
+ name=target.id,
206
+ symbol_type=sym_type,
207
+ scope=scope,
208
+ range=self.parser._node_to_range(target),
209
+ file_path=file_path,
210
+ parent_symbol=parent
211
+ ))
212
+ self.generic_visit(node)
213
+
214
+ def visit_Import(self, node):
215
+ for alias in node.names:
216
+ name = alias.asname if alias.asname else alias.name
217
+ symbols.append(Symbol(
218
+ name=name,
219
+ symbol_type=SymbolType.IMPORT,
220
+ scope=Scope.GLOBAL,
221
+ range=self.parser._node_to_range(node),
222
+ file_path=file_path,
223
+ metadata={'original': alias.name, 'alias': alias.asname}
224
+ ))
225
+ self.generic_visit(node)
226
+
227
+ def visit_ImportFrom(self, node):
228
+ module = node.module or ""
229
+ for alias in node.names:
230
+ name = alias.asname if alias.asname else alias.name
231
+ symbols.append(Symbol(
232
+ name=name,
233
+ symbol_type=SymbolType.IMPORT,
234
+ scope=Scope.GLOBAL,
235
+ range=self.parser._node_to_range(node),
236
+ file_path=file_path,
237
+ metadata={'module': module, 'original': alias.name, 'alias': alias.asname}
238
+ ))
239
+ self.generic_visit(node)
240
+
241
+ visitor = SymbolVisitor(self)
242
+ visitor.visit(ast_tree)
243
+ return symbols
244
+
245
+ def _extract_relationships(self, ast_tree: ast.AST, symbols: List[Symbol], file_path: str) -> List[Relationship]:
246
+ """Extract relationships from Python AST."""
247
+ relationships = []
248
+
249
+ class RelVisitor(ast.NodeVisitor):
250
+ def __init__(self, parser):
251
+ self.parser = parser
252
+ self.current_symbol = None
253
+ self.scope_stack = []
254
+
255
+ def visit_ClassDef(self, node):
256
+ old_symbol = self.current_symbol
257
+ self.current_symbol = node.name
258
+
259
+ # Inheritance
260
+ for base in node.bases:
261
+ base_name = self.parser._get_name(base)
262
+ if base_name:
263
+ relationships.append(Relationship(
264
+ source_symbol=node.name,
265
+ target_symbol=base_name,
266
+ relationship_type=RelationshipType.INHERITANCE,
267
+ source_file=file_path,
268
+ source_range=self.parser._node_to_range(base),
269
+ confidence=0.95
270
+ ))
271
+
272
+ # Decorators
273
+ for dec in node.decorator_list:
274
+ dec_name = self.parser._get_name(dec)
275
+ if dec_name:
276
+ relationships.append(Relationship(
277
+ source_symbol=dec_name,
278
+ target_symbol=node.name,
279
+ relationship_type=RelationshipType.DECORATES,
280
+ source_file=file_path,
281
+ source_range=self.parser._node_to_range(dec),
282
+ confidence=0.95
283
+ ))
284
+
285
+ self.scope_stack.append(node.name)
286
+ self.generic_visit(node)
287
+ self.scope_stack.pop()
288
+ self.current_symbol = old_symbol
289
+
290
+ def visit_FunctionDef(self, node):
291
+ self._visit_func(node)
292
+
293
+ def visit_AsyncFunctionDef(self, node):
294
+ self._visit_func(node)
295
+
296
+ def _visit_func(self, node):
297
+ old_symbol = self.current_symbol
298
+ self.current_symbol = node.name
299
+
300
+ # Decorators
301
+ for dec in node.decorator_list:
302
+ dec_name = self.parser._get_name(dec)
303
+ if dec_name:
304
+ relationships.append(Relationship(
305
+ source_symbol=dec_name,
306
+ target_symbol=node.name,
307
+ relationship_type=RelationshipType.DECORATES,
308
+ source_file=file_path,
309
+ source_range=self.parser._node_to_range(dec),
310
+ confidence=0.95
311
+ ))
312
+
313
+ self.scope_stack.append(node.name)
314
+ self.generic_visit(node)
315
+ self.scope_stack.pop()
316
+ self.current_symbol = old_symbol
317
+
318
+ def visit_Call(self, node):
319
+ if self.current_symbol:
320
+ called = self.parser._get_name(node.func)
321
+ if called:
322
+ relationships.append(Relationship(
323
+ source_symbol=self.current_symbol,
324
+ target_symbol=called,
325
+ relationship_type=RelationshipType.CALLS,
326
+ source_file=file_path,
327
+ source_range=self.parser._node_to_range(node),
328
+ confidence=0.85
329
+ ))
330
+ self.generic_visit(node)
331
+
332
+ def visit_Import(self, node):
333
+ module_name = Path(file_path).stem
334
+ for alias in node.names:
335
+ name = alias.asname if alias.asname else alias.name
336
+ relationships.append(Relationship(
337
+ source_symbol=module_name,
338
+ target_symbol=name,
339
+ relationship_type=RelationshipType.IMPORTS,
340
+ source_file=file_path,
341
+ source_range=self.parser._node_to_range(node),
342
+ confidence=1.0
343
+ ))
344
+ self.generic_visit(node)
345
+
346
+ def visit_ImportFrom(self, node):
347
+ module_name = Path(file_path).stem
348
+ from_module = node.module or ""
349
+ for alias in node.names:
350
+ if alias.name == "*":
351
+ target = f"{from_module}.*"
352
+ else:
353
+ target = f"{from_module}.{alias.name}" if from_module else alias.name
354
+
355
+ relationships.append(Relationship(
356
+ source_symbol=module_name,
357
+ target_symbol=target,
358
+ relationship_type=RelationshipType.IMPORTS,
359
+ source_file=file_path,
360
+ source_range=self.parser._node_to_range(node),
361
+ confidence=1.0
362
+ ))
363
+ self.generic_visit(node)
364
+
365
+ visitor = RelVisitor(self)
366
+ visitor.visit(ast_tree)
367
+ return relationships
368
+
369
+ def _node_to_range(self, node: ast.AST) -> Range:
370
+ """Convert AST node to Range."""
371
+ start_line = getattr(node, 'lineno', 1)
372
+ start_col = getattr(node, 'col_offset', 0)
373
+ end_line = getattr(node, 'end_lineno', start_line)
374
+ end_col = getattr(node, 'end_col_offset', start_col + 1)
375
+ return Range(Position(start_line, start_col), Position(end_line, end_col))
376
+
377
+ def _extract_node_source(self, node: ast.AST) -> str:
378
+ """Extract source code for an AST node."""
379
+ try:
380
+ lines = self._current_content.split('\n')
381
+ start = getattr(node, 'lineno', 1) - 1
382
+ end = getattr(node, 'end_lineno', start + 1)
383
+ if start < len(lines) and end <= len(lines):
384
+ return '\n'.join(lines[start:end])
385
+ except Exception:
386
+ pass
387
+ return ""
388
+
389
+ def _get_name(self, node: ast.AST) -> Optional[str]:
390
+ """Extract name from various AST node types."""
391
+ if isinstance(node, ast.Name):
392
+ return node.id
393
+ elif isinstance(node, ast.Attribute):
394
+ parts = []
395
+ current = node
396
+ while isinstance(current, ast.Attribute):
397
+ parts.append(current.attr)
398
+ current = current.value
399
+ if isinstance(current, ast.Name):
400
+ parts.append(current.id)
401
+ return '.'.join(reversed(parts))
402
+ elif isinstance(node, ast.Call):
403
+ return self._get_name(node.func)
404
+ elif isinstance(node, ast.Subscript):
405
+ return self._get_name(node.value)
406
+ return None
407
+
408
+ def _get_type_annotation(self, node: ast.AST) -> str:
409
+ """Extract type annotation as string."""
410
+ try:
411
+ if isinstance(node, ast.Name):
412
+ return node.id
413
+ elif isinstance(node, ast.Attribute):
414
+ return self._get_name(node) or "Any"
415
+ elif isinstance(node, ast.Constant):
416
+ return str(node.value)
417
+ elif isinstance(node, ast.Subscript):
418
+ value = self._get_type_annotation(node.value)
419
+ slice_val = self._get_type_annotation(node.slice)
420
+ return f"{value}[{slice_val}]"
421
+ elif hasattr(ast, 'unparse'):
422
+ return ast.unparse(node)
423
+ except Exception:
424
+ pass
425
+ return "Any"
426
+
427
+ def _build_signature(self, node: ast.FunctionDef, return_type: Optional[str]) -> str:
428
+ """Build function signature string."""
429
+ try:
430
+ params = []
431
+ for arg in node.args.args:
432
+ p = arg.arg
433
+ if arg.annotation:
434
+ p += f": {self._get_type_annotation(arg.annotation)}"
435
+ params.append(p)
436
+
437
+ if node.args.vararg:
438
+ p = f"*{node.args.vararg.arg}"
439
+ if node.args.vararg.annotation:
440
+ p += f": {self._get_type_annotation(node.args.vararg.annotation)}"
441
+ params.append(p)
442
+
443
+ if node.args.kwarg:
444
+ p = f"**{node.args.kwarg.arg}"
445
+ if node.args.kwarg.annotation:
446
+ p += f": {self._get_type_annotation(node.args.kwarg.annotation)}"
447
+ params.append(p)
448
+
449
+ sig = f"{node.name}({', '.join(params)})"
450
+ if return_type:
451
+ sig += f" -> {return_type}"
452
+ return sig
453
+ except Exception:
454
+ return node.name
455
+
456
+ def _extract_module_info(self, ast_tree: ast.Module) -> Tuple[List[str], List[str]]:
457
+ """Extract imports and exports."""
458
+ imports = []
459
+ exports = []
460
+
461
+ for node in ast.walk(ast_tree):
462
+ if isinstance(node, ast.Import):
463
+ for alias in node.names:
464
+ imports.append(alias.name)
465
+ elif isinstance(node, ast.ImportFrom):
466
+ module = node.module or ""
467
+ for alias in node.names:
468
+ if alias.name == "*":
469
+ imports.append(f"{module}.*")
470
+ else:
471
+ imports.append(f"{module}.{alias.name}")
472
+ elif isinstance(node, ast.Assign):
473
+ if (len(node.targets) == 1 and
474
+ isinstance(node.targets[0], ast.Name) and
475
+ node.targets[0].id == "__all__" and
476
+ isinstance(node.value, ast.List)):
477
+ for elt in node.value.elts:
478
+ if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
479
+ exports.append(elt.value)
480
+
481
+ return imports, exports
482
+
483
+ def parse_multiple_files(self, file_paths: List[str], max_workers: int = 4) -> Dict[str, ParseResult]:
484
+ """
485
+ Parse multiple Python files with cross-file resolution.
486
+
487
+ Args:
488
+ file_paths: List of file paths
489
+ max_workers: Number of parallel workers
490
+
491
+ Returns:
492
+ Dict mapping file paths to ParseResult
493
+ """
494
+ results: Dict[str, ParseResult] = {}
495
+ total = len(file_paths)
496
+
497
+ # Reset global registries
498
+ self._global_class_locations = {}
499
+ self._global_function_locations = {}
500
+ self._global_symbol_registry = {}
501
+
502
+ # First pass: Parse all files in parallel
503
+ logger.info(f"Parsing {total} Python files with {max_workers} workers")
504
+ start_time = time.time()
505
+
506
+ batch_size = 500
507
+ num_batches = (total + batch_size - 1) // batch_size
508
+
509
+ for batch_idx in range(num_batches):
510
+ start_idx = batch_idx * batch_size
511
+ end_idx = min(start_idx + batch_size, total)
512
+ batch_files = file_paths[start_idx:end_idx]
513
+
514
+ try:
515
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
516
+ futures = {executor.submit(_parse_single_file, fp): fp for fp in batch_files}
517
+
518
+ for future in concurrent.futures.as_completed(futures):
519
+ try:
520
+ fp, result = future.result(timeout=60)
521
+ results[fp] = result
522
+ except Exception as e:
523
+ fp = futures[future]
524
+ logger.error(f"Failed to parse {fp}: {e}")
525
+ results[fp] = ParseResult(
526
+ file_path=fp,
527
+ language="python",
528
+ symbols=[],
529
+ relationships=[],
530
+ errors=[str(e)]
531
+ )
532
+ except Exception as e:
533
+ logger.error(f"Batch {batch_idx + 1} failed: {e}")
534
+ for fp in batch_files:
535
+ if fp not in results:
536
+ try:
537
+ _, result = _parse_single_file(fp)
538
+ results[fp] = result
539
+ except Exception as inner_e:
540
+ results[fp] = ParseResult(
541
+ file_path=fp,
542
+ language="python",
543
+ symbols=[],
544
+ relationships=[],
545
+ errors=[str(inner_e)]
546
+ )
547
+
548
+ # Build global symbol registry
549
+ for fp, result in results.items():
550
+ if result.symbols:
551
+ self._extract_global_symbols(fp, result)
552
+
553
+ # Enhance relationships with cross-file info
554
+ for fp, result in results.items():
555
+ if result.symbols:
556
+ self._enhance_relationships(fp, result)
557
+
558
+ elapsed = time.time() - start_time
559
+ logger.info(f"Parsed {len(results)} Python files in {elapsed:.2f}s")
560
+
561
+ return results
562
+
563
+ def _extract_global_symbols(self, file_path: str, result: ParseResult):
564
+ """Extract symbols for global cross-file resolution."""
565
+ file_name = Path(file_path).stem
566
+
567
+ for symbol in result.symbols:
568
+ if symbol.symbol_type == SymbolType.CLASS:
569
+ self._global_class_locations[symbol.name] = file_path
570
+ self._global_symbol_registry[symbol.name] = f"{file_name}.{symbol.name}"
571
+ elif symbol.symbol_type == SymbolType.FUNCTION:
572
+ self._global_function_locations[symbol.name] = file_path
573
+ self._global_symbol_registry[symbol.name] = f"{file_name}.{symbol.name}"
574
+
575
+ def _enhance_relationships(self, file_path: str, result: ParseResult):
576
+ """Enhance relationships with cross-file information."""
577
+ for rel in result.relationships:
578
+ target = rel.target_symbol
579
+
580
+ # Check if target is in another file
581
+ if target in self._global_class_locations:
582
+ target_file = self._global_class_locations[target]
583
+ if target_file != file_path:
584
+ rel.target_file = target_file
585
+ rel.is_cross_file = True
586
+ elif target in self._global_function_locations:
587
+ target_file = self._global_function_locations[target]
588
+ if target_file != file_path:
589
+ rel.target_file = target_file
590
+ rel.is_cross_file = True
591
+ elif '.' in target:
592
+ # Check partial match
593
+ parts = target.split('.')
594
+ for part in parts:
595
+ if part in self._global_class_locations:
596
+ target_file = self._global_class_locations[part]
597
+ if target_file != file_path:
598
+ rel.target_file = target_file
599
+ rel.is_cross_file = True
600
+ break
601
+
602
+
603
+ # Register the parser
604
+ parser_registry.register_parser(PythonParser())