alita-sdk 0.3.257__py3-none-any.whl → 0.3.584__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.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +155 -0
- alita_sdk/cli/agent_loader.py +215 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3794 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +11 -0
- alita_sdk/configurations/ado.py +148 -2
- alita_sdk/configurations/azure_search.py +1 -1
- alita_sdk/configurations/bigquery.py +1 -1
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/browser.py +18 -0
- alita_sdk/configurations/carrier.py +19 -0
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/delta_lake.py +1 -1
- alita_sdk/configurations/figma.py +76 -5
- alita_sdk/configurations/github.py +65 -1
- alita_sdk/configurations/gitlab.py +81 -0
- alita_sdk/configurations/google_places.py +17 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +323 -0
- alita_sdk/configurations/postman.py +1 -1
- alita_sdk/configurations/qtest.py +72 -3
- alita_sdk/configurations/report_portal.py +115 -0
- alita_sdk/configurations/salesforce.py +19 -0
- alita_sdk/configurations/service_now.py +1 -12
- alita_sdk/configurations/sharepoint.py +167 -0
- alita_sdk/configurations/sonar.py +18 -0
- alita_sdk/configurations/sql.py +20 -0
- alita_sdk/configurations/testio.py +101 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +94 -1
- alita_sdk/configurations/zephyr_enterprise.py +94 -1
- alita_sdk/configurations/zephyr_essential.py +95 -0
- alita_sdk/runtime/clients/artifact.py +21 -4
- alita_sdk/runtime/clients/client.py +458 -67
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +352 -0
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +183 -43
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
- alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
- alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
- alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
- alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +493 -105
- alita_sdk/runtime/langchain/utils.py +118 -8
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +28 -0
- alita_sdk/runtime/toolkits/application.py +14 -4
- alita_sdk/runtime/toolkits/artifact.py +25 -9
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +782 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +11 -6
- alita_sdk/runtime/toolkits/tools.py +314 -70
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +24 -0
- alita_sdk/runtime/tools/application.py +16 -4
- alita_sdk/runtime/tools/artifact.py +367 -33
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +100 -4
- alita_sdk/runtime/tools/graph.py +81 -0
- alita_sdk/runtime/tools/image_generation.py +218 -0
- alita_sdk/runtime/tools/llm.py +1032 -177
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -1
- alita_sdk/runtime/tools/sandbox.py +375 -0
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +69 -65
- alita_sdk/runtime/tools/vectorstore_base.py +163 -90
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/streamlit.py +41 -14
- alita_sdk/runtime/utils/toolkit_utils.py +28 -9
- alita_sdk/runtime/utils/utils.py +48 -0
- alita_sdk/tools/__init__.py +135 -37
- alita_sdk/tools/ado/__init__.py +2 -2
- alita_sdk/tools/ado/repos/__init__.py +16 -19
- alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
- alita_sdk/tools/ado/test_plan/__init__.py +27 -8
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
- alita_sdk/tools/ado/wiki/__init__.py +28 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
- alita_sdk/tools/ado/work_item/__init__.py +28 -12
- alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +13 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +15 -11
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +14 -8
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +454 -110
- alita_sdk/tools/bitbucket/__init__.py +28 -19
- alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
- alita_sdk/tools/browser/__init__.py +41 -16
- alita_sdk/tools/browser/crawler.py +3 -1
- alita_sdk/tools/browser/utils.py +15 -6
- alita_sdk/tools/carrier/__init__.py +18 -17
- alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
- alita_sdk/tools/carrier/excel_reporter.py +8 -4
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/codeparser.py +1 -1
- alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +12 -7
- alita_sdk/tools/cloud/azure/__init__.py +12 -7
- alita_sdk/tools/cloud/gcp/__init__.py +12 -7
- alita_sdk/tools/cloud/k8s/__init__.py +12 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +21 -13
- alita_sdk/tools/code_indexer_toolkit.py +199 -0
- alita_sdk/tools/confluence/__init__.py +22 -14
- alita_sdk/tools/confluence/api_wrapper.py +197 -58
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +546 -64
- alita_sdk/tools/figma/__init__.py +60 -11
- alita_sdk/tools/figma/api_wrapper.py +1400 -167
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +18 -17
- alita_sdk/tools/github/api_wrapper.py +9 -26
- alita_sdk/tools/github/github_client.py +81 -12
- alita_sdk/tools/github/schemas.py +2 -1
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +19 -13
- alita_sdk/tools/gitlab/api_wrapper.py +256 -80
- alita_sdk/tools/gitlab_org/__init__.py +14 -10
- alita_sdk/tools/google/bigquery/__init__.py +14 -13
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +21 -11
- alita_sdk/tools/jira/__init__.py +22 -11
- alita_sdk/tools/jira/api_wrapper.py +315 -168
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +38 -14
- alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1357 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +40 -45
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/postman/api_wrapper.py +19 -8
- alita_sdk/tools/postman/postman_analysis.py +8 -1
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/qtest/__init__.py +22 -14
- alita_sdk/tools/qtest/api_wrapper.py +1784 -88
- alita_sdk/tools/rally/__init__.py +13 -10
- alita_sdk/tools/report_portal/__init__.py +23 -16
- alita_sdk/tools/salesforce/__init__.py +22 -16
- alita_sdk/tools/servicenow/__init__.py +21 -16
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +17 -14
- alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +13 -8
- alita_sdk/tools/sql/__init__.py +22 -19
- alita_sdk/tools/sql/api_wrapper.py +71 -23
- alita_sdk/tools/testio/__init__.py +21 -13
- alita_sdk/tools/testrail/__init__.py +13 -11
- alita_sdk/tools/testrail/api_wrapper.py +214 -46
- alita_sdk/tools/utils/__init__.py +28 -4
- alita_sdk/tools/utils/content_parser.py +241 -55
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
- alita_sdk/tools/xray/__init__.py +18 -14
- alita_sdk/tools/xray/api_wrapper.py +58 -113
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +12 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +16 -9
- alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
- alita_sdk/tools/zephyr_essential/__init__.py +16 -10
- alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
- alita_sdk/tools/zephyr_essential/client.py +6 -4
- alita_sdk/tools/zephyr_scale/__init__.py +13 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
- alita_sdk/tools/zephyr_squad/__init__.py +12 -7
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/METADATA +184 -37
- alita_sdk-0.3.584.dist-info/RECORD +452 -0
- alita_sdk-0.3.584.dist-info/entry_points.txt +2 -0
- alita_sdk/tools/bitbucket/tools.py +0 -304
- alita_sdk-0.3.257.dist-info/RECORD +0 -343
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.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())
|