naive-knowledge-base 0.1.0__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.
agents/common/utils.py ADDED
@@ -0,0 +1,161 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import List, Optional, Any, Dict
4
+ import json
5
+ import re
6
+
7
+ from smolagents import tool
8
+
9
+ from .exceptions import FileOperationError, wrap_exceptions
10
+ from .logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ @wrap_exceptions
15
+ def save_content_to_file(content: str, source_directory: str, file_name: str) -> str:
16
+ """
17
+ Saves content to a file, creating parent directories if needed.
18
+
19
+ Args:
20
+ content: The content to save
21
+ source_directory: The directory where the file will be saved
22
+ file_name: The name of the file
23
+
24
+ Returns:
25
+ The absolute path to the saved file
26
+
27
+ Raises:
28
+ FileOperationError: If there's an issue with the file operation
29
+ """
30
+ try:
31
+ path = Path(file_path)
32
+ path.parent.mkdir(parents=True, exist_ok=True)
33
+
34
+ with open(file_path, 'w', encoding='utf-8') as f:
35
+ f.write(content)
36
+
37
+ logger.debug(f"Content saved to {file_path}")
38
+ return str(path.absolute())
39
+ except Exception as e:
40
+ raise FileOperationError(f"Failed to save content to {file_path}: {str(e)}")
41
+
42
+ @wrap_exceptions
43
+ def read_file_content(file_path: str) -> str:
44
+ """
45
+ Reads content from a file.
46
+
47
+ Args:
48
+ file_path: The path to the file
49
+
50
+ Returns:
51
+ The file content as a string
52
+
53
+ Raises:
54
+ FileOperationError: If there's an issue with the file operation
55
+ """
56
+ try:
57
+ with open(file_path, 'r', encoding='utf-8') as f:
58
+ return f.read()
59
+ except Exception as e:
60
+ raise FileOperationError(f"Failed to read content from {file_path}: {str(e)}")
61
+
62
+ @wrap_exceptions
63
+ def delete_folder_or_file(path: str) -> bool:
64
+ """
65
+ Deletes a folder or file.
66
+
67
+ Args:
68
+ path: The path to the folder or file
69
+
70
+ Returns:
71
+ True if the deletion was successful, False otherwise
72
+
73
+ Raises:
74
+ FileOperationError: If there's an issue with the file operation
75
+ """
76
+ try:
77
+ path_obj = Path(path)
78
+
79
+ if path_obj.is_file():
80
+ path_obj.unlink()
81
+ elif path_obj.is_dir():
82
+ # Import here to avoid circular imports
83
+ import shutil
84
+ shutil.rmtree(path)
85
+ else:
86
+ logger.warning(f"Path {path} does not exist")
87
+ return False
88
+
89
+ logger.debug(f"Deleted {path}")
90
+ return True
91
+ except Exception as e:
92
+ raise FileOperationError(f"Failed to delete {path}: {str(e)}")
93
+
94
+ @wrap_exceptions
95
+ def extract_code_blocks(markdown_content: str, language: Optional[str] = None) -> List[str]:
96
+ """
97
+ Extracts code blocks from markdown content.
98
+
99
+ Args:
100
+ markdown_content: The markdown content
101
+ language: Optional language filter
102
+
103
+ Returns:
104
+ List of extracted code blocks
105
+ """
106
+ if language:
107
+ pattern = r'```(?:' + language + r')?\s*([\s\S]*?)\s*```(?:\n|$)'
108
+ else:
109
+ pattern = r'```(?:\w*)?\s*([\s\S]*?)\s*```(?:\n|$)'
110
+
111
+ return re.findall(pattern, markdown_content)
112
+
113
+ @wrap_exceptions
114
+ def load_json_file(file_path: str) -> Any:
115
+ """
116
+ Loads a JSON file.
117
+
118
+ Args:
119
+ file_path: The path to the JSON file
120
+
121
+ Returns:
122
+ The parsed JSON content
123
+
124
+ Raises:
125
+ FileOperationError: If there's an issue with the file operation
126
+ """
127
+ try:
128
+ with open(file_path, 'r', encoding='utf-8') as f:
129
+ return json.load(f)
130
+ except json.JSONDecodeError as e:
131
+ raise FileOperationError(f"Invalid JSON in {file_path}: {str(e)}")
132
+ except Exception as e:
133
+ raise FileOperationError(f"Failed to read JSON from {file_path}: {str(e)}")
134
+
135
+ @wrap_exceptions
136
+ def save_json_file(content: Dict[str, Any], file_path: str, indent: int = 2) -> str:
137
+ """
138
+ Saves content as a JSON file.
139
+
140
+ Args:
141
+ content: The content to save
142
+ file_path: The path to the file
143
+ indent: JSON indentation level
144
+
145
+ Returns:
146
+ The absolute path to the saved file
147
+
148
+ Raises:
149
+ FileOperationError: If there's an issue with the file operation
150
+ """
151
+ try:
152
+ path = Path(file_path)
153
+ path.parent.mkdir(parents=True, exist_ok=True)
154
+
155
+ with open(file_path, 'w', encoding='utf-8') as f:
156
+ json.dump(content, f, indent=indent)
157
+
158
+ logger.debug(f"JSON content saved to {file_path}")
159
+ return str(path.absolute())
160
+ except Exception as e:
161
+ raise FileOperationError(f"Failed to save JSON content to {file_path}: {str(e)}")
@@ -0,0 +1,7 @@
1
+ from .agent import generate_dependency_graph
2
+ from .model import DependencyGraph
3
+
4
+ __all__ = [
5
+ "generate_dependency_graph",
6
+ "DependencyGraph"
7
+ ]
@@ -0,0 +1,205 @@
1
+ from smolagents import tool
2
+ from emerge.appear import Emerge
3
+ from .model import DependencyGraph
4
+ import os
5
+ import json
6
+ from toon_format import encode
7
+ from tools import save_content_to_file, delete_folder_or_file
8
+
9
+ @tool
10
+ def generate_dependency_graph(source_directory: str, permit_file_extensions: list[str], ignore_directories_containing: list[str]) -> str:
11
+ """
12
+ Generates a dependency graph for the given source directory.
13
+ Args:
14
+ source_directory: The source directory to analyze.
15
+ permit_file_extensions: The file extensions to permit in the analysis.
16
+ ignore_directories_containing: The directories to ignore in the analysis.
17
+ Returns:
18
+ str: The dependency graph file path.
19
+ Raises:
20
+ ValueError: If the source directory does not exist or is not a directory.
21
+ """
22
+ # Check if the source directory exists
23
+ if not os.path.exists(source_directory):
24
+ raise ValueError(f"Source directory does not exist: {source_directory}")
25
+
26
+ # Check if the source directory is a directory
27
+ if not os.path.isdir(source_directory):
28
+ raise ValueError(f"Source path is not a directory: {source_directory}")
29
+
30
+ export_directory = os.path.join(source_directory, "export")
31
+
32
+ # Create the Emerge configuration YAML file
33
+ yaml_content = _create_emerge_config_yaml(source_directory, export_directory, permit_file_extensions, ignore_directories_containing)
34
+
35
+ # Write the YAML content to a file
36
+ yaml_file_path = _write_emerge_config_yaml(yaml_content)
37
+
38
+ # Run Emerge with the generated YAML file
39
+ try:
40
+ _run_emerge(yaml_file_path)
41
+ except Exception as e:
42
+ raise ValueError(f"Failed to generate dependency graph: {e}")
43
+
44
+ dependency_graph = _transform_graph(source_directory)
45
+
46
+ # Delete the Emerge configuration YAML file
47
+ try:
48
+ delete_folder_or_file(yaml_file_path)
49
+ delete_folder_or_file(export_directory)
50
+ print(f"Deleting Emerge configuration file: {yaml_file_path}")
51
+ except Exception as e:
52
+ raise ValueError(f"Failed to delete Emerge configuration file: {e}")
53
+
54
+ # Transform the dependency graph into a more readable format
55
+ content = encode(dependency_graph)
56
+
57
+ file_path = save_content_to_file(
58
+ content=content,
59
+ source_directory=source_directory,
60
+ file_name="dependency_graph.toon"
61
+ )
62
+
63
+ return f"Dependency graph saved to {file_path}"
64
+
65
+ def _create_emerge_config_yaml(source_directory: str, export_dir: str, permit_file_extensions: list[str], ignore_directories_containing: list[str]) -> str:
66
+ """
67
+ Generates a sample YAML configuration for Emerge to be used by write_emerge_config_yaml.
68
+ Args:
69
+ source_directory: The source directory to analyze.
70
+ permit_file_extensions: The file extensions to permit in the analysis.
71
+ ignore_directories_containing: The directories to ignore in the analysis.
72
+ Returns:
73
+ str: The YAML configuration as a string.
74
+ """
75
+ export_directory = export_dir
76
+ if not os.path.exists(export_directory):
77
+ os.makedirs(export_directory)
78
+ return f"""
79
+ ---
80
+ project_name: java_project_example
81
+ loglevel: info
82
+ analyses:
83
+ - analysis_name: full typescript check
84
+ source_directory: {source_directory}
85
+ only_permit_file_extensions: {permit_file_extensions}
86
+ ignore_dependencies_containing:
87
+ - java
88
+ - javax
89
+ - org
90
+ - och
91
+ - lambda
92
+ - javafx
93
+ - jakarta
94
+ - io.cloudevents
95
+ - fasterxml
96
+ - lombok
97
+ - dtcloyalty.lib.idempotentconsumer
98
+ - dtcloyalty.lib.outboxpattern
99
+ - feign
100
+ - io
101
+ - lib.programawaredatasource
102
+ - lib.programproperties
103
+ - com.algolia
104
+ ignore_directories_containing: {ignore_directories_containing}
105
+ file_scan:
106
+ - number_of_methods
107
+ - source_lines_of_code
108
+ - dependency_graph
109
+ - fan_in_out
110
+ - louvain_modularity
111
+ - tfidf
112
+ export:
113
+ - directory: {export_directory}
114
+ - graphml
115
+ - json
116
+ - tabular_file
117
+ - tabular_console_overall
118
+ - d3
119
+ """
120
+
121
+ def _write_emerge_config_yaml(yaml_content: str) -> str:
122
+ """
123
+ Creates a YAML configuration file for Emerge to be used by run_emerge.
124
+ Args:
125
+ yaml_content: The content of the YAML configuration.
126
+ Returns:
127
+ str: The path to the generated YAML configuration file.
128
+ """
129
+ yaml_file_path = "/tmp/emerge_config.yaml"
130
+ with open(yaml_file_path, "w") as yaml_file:
131
+ yaml_file.write(yaml_content)
132
+
133
+ return yaml_file_path
134
+
135
+ def _run_emerge(yaml_file_path: str) -> None:
136
+ """
137
+ Runs Emerge on the given source path.
138
+ Args:
139
+ yaml_file_path: The yaml path to the YAML configuration file.
140
+ Returns:
141
+ str: The result of the Emerge analysis.
142
+ """
143
+ try:
144
+ emerge = Emerge()
145
+ emerge.load_config(yaml_file_path)
146
+ emerge.start_analyzing()
147
+ except Exception as e:
148
+ raise ValueError(f"Failed to run Emerge: {e}")
149
+
150
+ def _transform_graph(source_directory: str) -> list[DependencyGraph]:
151
+ """
152
+ Transforms the dependency graph into a more readable format.
153
+ Args:
154
+ source_directory: The source directory to transform the emerge-file_result_dependency_graph-data.json file.
155
+ Returns:
156
+ object: The transformed dependency graph.
157
+ """
158
+
159
+ emerge_file_path = os.path.join(source_directory, "export", "emerge-file_result_dependency_graph-data.json")
160
+ if not os.path.exists(emerge_file_path):
161
+ raise ValueError(f"Emerge file not found: {emerge_file_path}")
162
+ with open(emerge_file_path, "r") as emerge_file:
163
+ emerge_data = emerge_file.read()
164
+
165
+ return _process_data(json.loads(emerge_data), source_directory)
166
+
167
+ def _process_data(json_data, source_directory) -> list[DependencyGraph]:
168
+ # Step 1: Filter nodes with absolute_name
169
+ relevant_nodes = [node for node in json_data['nodes'] if 'absolute_name' in node]
170
+
171
+ # Step 2: Map dependencies based on links
172
+ dependency_map = {}
173
+
174
+ for link in json_data['links']:
175
+ if link['source'] not in dependency_map:
176
+ dependency_map[link['source']] = []
177
+ target = os.path.join(source_directory, link['target'])
178
+ dependency_map[link['source']].append(target)
179
+
180
+ # Step 3: Create the final array
181
+ result = []
182
+ for node in relevant_nodes:
183
+ absolute_name = node['absolute_name']
184
+ file_path = os.path.join(source_directory, absolute_name.split('/', 1)[1])
185
+
186
+
187
+ # Get metrics (with proper error handling for missing values)
188
+ fan_in = node.get('metric_fan-in-dependency-graph', 0)
189
+ fan_out = node.get('metric_fan-out-dependency-graph', 0)
190
+ modularity_level = node.get('metric_file_result_dependency_graph_louvain-modularity-in-file', 0)
191
+ number_of_methods = node.get('metric_number-of-methods-in-file', 0)
192
+ source_lines_of_code = node.get('metric_sloc-in-file', 0)
193
+
194
+ result.append({
195
+ "absolutePath": str(file_path),
196
+ "fanIn": fan_in,
197
+ "fanOut": fan_out,
198
+ "sourceLinesOfCode": source_lines_of_code,
199
+ "numberOfMethods": number_of_methods,
200
+ "louvainModularity": modularity_level,
201
+ "displayName": node.get('display_name', ''),
202
+ "dependencies":dependency_map.get(absolute_name, [])
203
+ })
204
+
205
+ return result
@@ -0,0 +1,38 @@
1
+ import pydantic
2
+ from typing import Any, Dict, List, Optional
3
+ from pydantic import BaseModel, Field
4
+ from pydantic.networks import AnyUrl
5
+
6
+ class DependencyGraph(BaseModel):
7
+ """
8
+ A class representing a dependency graph for a software project.
9
+ """
10
+
11
+ absolutePath: str = Field(
12
+ ...,
13
+ description="The absolute path to the project directory.",
14
+ )
15
+ fanIn: int = Field(
16
+ ...,
17
+ description="The number of incoming dependencies for the project.",
18
+ )
19
+ fanOut: int = Field(
20
+ ...,
21
+ description="The number of outgoing dependencies for the project.",
22
+ )
23
+ numberOfMethods: int = Field(
24
+ ...,
25
+ description="The number of methods in the project.",
26
+ )
27
+ sourceLinesOfCode: int = Field(
28
+ ...,
29
+ description="The number of source lines of code in the project.",
30
+ )
31
+ louvainModularity: float = Field(
32
+ ...,
33
+ description="The Louvain modularity score of the project.",
34
+ )
35
+ dependencies: List[str] = Field(
36
+ ...,
37
+ description="A list of dependencies for the project.",
38
+ )
api_models/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .flow_api_model import FlowApiModel, ModelConfig
2
+
3
+ __all__ = ['FlowApiModel', 'ModelConfig']