llm-tool-maker 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.
- llm_tool_maker-0.1.0.dist-info/METADATA +199 -0
- llm_tool_maker-0.1.0.dist-info/RECORD +48 -0
- llm_tool_maker-0.1.0.dist-info/WHEEL +4 -0
- llm_tool_maker-0.1.0.dist-info/entry_points.txt +2 -0
- tool_maker/__init__.py +31 -0
- tool_maker/analyzer/__init__.py +7 -0
- tool_maker/analyzer/project_scanner.py +202 -0
- tool_maker/cli/__init__.py +7 -0
- tool_maker/cli/main.py +306 -0
- tool_maker/config.py +57 -0
- tool_maker/db/__init__.py +17 -0
- tool_maker/db/config.py +31 -0
- tool_maker/db/connection.py +157 -0
- tool_maker/db/migrations/001_initial.sql +60 -0
- tool_maker/db/migrator.py +121 -0
- tool_maker/db/models.py +226 -0
- tool_maker/db/pipeline.py +340 -0
- tool_maker/dotenv.py +75 -0
- tool_maker/flask/__init__.py +7 -0
- tool_maker/flask/extension.py +165 -0
- tool_maker/llm/__init__.py +7 -0
- tool_maker/llm/provider.py +227 -0
- tool_maker/planner/__init__.py +22 -0
- tool_maker/planner/executor.py +87 -0
- tool_maker/planner/models.py +61 -0
- tool_maker/planner/planner.py +112 -0
- tool_maker/planner/reviewer.py +73 -0
- tool_maker/planner/validator.py +109 -0
- tool_maker/planner/writer.py +34 -0
- tool_maker/tool/__init__.py +15 -0
- tool_maker/tool/executor.py +183 -0
- tool_maker/tool/generator.py +197 -0
- tool_maker/tool/sandbox.py +269 -0
- tool_maker/tool_fixer.py +134 -0
- tool_maker/tool_maker.py +385 -0
- tool_maker/ui/__init__.py +5 -0
- tool_maker/ui/app.py +62 -0
- tool_maker/ui/log_handler.py +61 -0
- tool_maker/ui/routes.py +552 -0
- tool_maker/ui/static/style.css +394 -0
- tool_maker/ui/templates/analyze.html +65 -0
- tool_maker/ui/templates/base.html +27 -0
- tool_maker/ui/templates/config.html +132 -0
- tool_maker/ui/templates/execute.html +289 -0
- tool_maker/ui/templates/generate.html +38 -0
- tool_maker/ui/templates/index.html +60 -0
- tool_maker/ui/templates/pipeline.html +377 -0
- tool_maker/ui/templates/provider.html +60 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-tool-maker
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An intelligent tool-making package that leverages LLMs to analyze projects and generate tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/codewithwest/project_tool-maker
|
|
6
|
+
Project-URL: Repository, https://github.com/codewithwest/project_tool-maker
|
|
7
|
+
Project-URL: Documentation, https://github.com/codewithwest/project_tool-maker#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/codewithwest/project_tool-maker/issues
|
|
9
|
+
Author-email: west <westdynamics.tech@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx>=0.24.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
25
|
+
Requires-Dist: requests>=2.31.0
|
|
26
|
+
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: flask>=2.3.0; extra == 'all'
|
|
29
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
|
|
30
|
+
Provides-Extra: flask
|
|
31
|
+
Requires-Dist: flask>=2.3.0; extra == 'flask'
|
|
32
|
+
Provides-Extra: postgres
|
|
33
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'postgres'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Tool Maker Package
|
|
37
|
+
|
|
38
|
+
[](https://github.com/codewithwest/project_tool-maker/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/llm-tool-maker/)
|
|
40
|
+
[](https://pypi.org/project/llm-tool-maker/)
|
|
41
|
+
[](https://opensource.org/licenses/MIT)
|
|
42
|
+
|
|
43
|
+
An intelligent tool-making package that leverages LLMs to analyze projects and generate tools.
|
|
44
|
+
|
|
45
|
+
## Overview
|
|
46
|
+
|
|
47
|
+
Tool Maker is a Python package that:
|
|
48
|
+
- Analyzes projects to understand their structure and capabilities
|
|
49
|
+
- Uses LLMs to formulate intelligent prompts and generate tools
|
|
50
|
+
- Can be integrated into Flask applications or used standalone
|
|
51
|
+
- Provides a CLI for easy command-line usage
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Project Analysis**: Automatically scans and analyzes project structure
|
|
56
|
+
- **LLM Integration**: Supports multiple LLM providers (OpenAI, Anthropic, local models)
|
|
57
|
+
- **Tool Generation**: Creates appropriate tools based on project capabilities
|
|
58
|
+
- **Flask Integration**: Easy integration with Flask applications
|
|
59
|
+
- **CLI Interface**: Command-line tool for quick usage
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
### Using uv (Recommended)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Install globally
|
|
67
|
+
uv tool install llm-tool-maker
|
|
68
|
+
|
|
69
|
+
# Or add to a project
|
|
70
|
+
uv add llm-tool-maker
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### From Source
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone https://github.com/codewithwest/project_tool-maker.git
|
|
77
|
+
cd tool-maker
|
|
78
|
+
uv sync
|
|
79
|
+
uv run tool-maker --help
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
### Standalone Usage
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from tool_maker import ToolMaker
|
|
88
|
+
|
|
89
|
+
# Initialize with your LLM provider
|
|
90
|
+
tm = ToolMaker(
|
|
91
|
+
llm_provider="openai",
|
|
92
|
+
api_key="your-api-key"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Analyze a project
|
|
96
|
+
project_info = tm.analyze_project("/path/to/project")
|
|
97
|
+
|
|
98
|
+
# Create and execute a tool
|
|
99
|
+
result = tm.create_and_execute_tool("Create a function to parse CSV files")
|
|
100
|
+
print(result)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Flask Integration
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from flask import Flask, request, jsonify
|
|
107
|
+
from tool_maker.flask import ToolMakerExtension
|
|
108
|
+
|
|
109
|
+
app = Flask(__name__)
|
|
110
|
+
|
|
111
|
+
# Initialize Tool Maker
|
|
112
|
+
tm = ToolMakerExtension(
|
|
113
|
+
app,
|
|
114
|
+
llm_provider="openai",
|
|
115
|
+
api_key="your-api-key"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@app.route('/analyze', methods=['POST'])
|
|
119
|
+
def analyze():
|
|
120
|
+
project_path = request.json.get('project_path')
|
|
121
|
+
return jsonify(tm.analyze_project(project_path))
|
|
122
|
+
|
|
123
|
+
@app.route('/tools', methods=['POST'])
|
|
124
|
+
def create_tool():
|
|
125
|
+
query = request.json.get('query')
|
|
126
|
+
return jsonify(tm.create_and_execute_tool(query))
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Project Structure
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
tool_maker/
|
|
133
|
+
├── analyzer/ # Project analysis module
|
|
134
|
+
├── llm/ # LLM integration module
|
|
135
|
+
├── tool/ # Tool generation and execution
|
|
136
|
+
├── flask/ # Flask integration
|
|
137
|
+
└── cli/ # Command-line interface
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
### LLM Providers
|
|
143
|
+
|
|
144
|
+
Tool Maker supports multiple LLM providers:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# OpenAI
|
|
148
|
+
tm = ToolMaker(llm_provider="openai", api_key="sk-...")
|
|
149
|
+
|
|
150
|
+
# Anthropic (Claude)
|
|
151
|
+
tm = ToolMaker(llm_provider="anthropic", api_key="sk-...")
|
|
152
|
+
|
|
153
|
+
# Local LLM (e.g., Ollama)
|
|
154
|
+
tm = ToolMaker(
|
|
155
|
+
llm_provider="ollama",
|
|
156
|
+
base_url="http://localhost:11434",
|
|
157
|
+
model="llama2"
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Set up development environment
|
|
165
|
+
uv sync
|
|
166
|
+
uv run pytest
|
|
167
|
+
|
|
168
|
+
# Run linter
|
|
169
|
+
uv run ruff check .
|
|
170
|
+
|
|
171
|
+
# Format code
|
|
172
|
+
uv run black .
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Examples
|
|
176
|
+
|
|
177
|
+
See the `examples/` directory for more detailed examples:
|
|
178
|
+
- `basic_usage.py` - Standalone usage
|
|
179
|
+
- `flask_integration.py` - Flask integration
|
|
180
|
+
- `custom_analyzer.py` - Custom project analysis
|
|
181
|
+
|
|
182
|
+
## Contributing
|
|
183
|
+
|
|
184
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
185
|
+
|
|
186
|
+
1. Fork the repository
|
|
187
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
188
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
189
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
190
|
+
5. Open a Pull Request
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
195
|
+
|
|
196
|
+
## Acknowledgments
|
|
197
|
+
|
|
198
|
+
- Built with [uv](https://github.com/astral-sh/uv) for fast Python package management
|
|
199
|
+
- Inspired by AI-powered code generation tools
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
tool_maker/__init__.py,sha256=Fu-PmlqhGH17lYK6_6FHS0uQ6ZcnZVYn1HLhNftLu7Q,792
|
|
2
|
+
tool_maker/config.py,sha256=fLVdB0xp_tFmEurGKkQqMurFzEdyu_LeJo6w8TM6dSI,1504
|
|
3
|
+
tool_maker/dotenv.py,sha256=oT4RFo30JhXma6sf0IiWgxFTjLuNYeipmLE0k7EMQtI,2009
|
|
4
|
+
tool_maker/tool_fixer.py,sha256=U-Ekf1H-ckSZvzMhxaqAwoT2J5IBP9tnvX7T_h6Ftl0,4503
|
|
5
|
+
tool_maker/tool_maker.py,sha256=5C5wUpJJlNrrRtElcxp9duJPbvhS1le3lIgRh7XlAG0,15660
|
|
6
|
+
tool_maker/analyzer/__init__.py,sha256=fec7Oy7Dd4CjNhmFCBnGmtwij-0uh3ZJVn2Y6D32DmA,140
|
|
7
|
+
tool_maker/analyzer/project_scanner.py,sha256=dTQL0Aa0QSmANMKJWt06uF-Lw60nKSyWYsirkXI2Jr4,7246
|
|
8
|
+
tool_maker/cli/__init__.py,sha256=0FLqjwBp_1KGnsjyuixCA4q2VrZmHzX28TxeGOd2AB0,91
|
|
9
|
+
tool_maker/cli/main.py,sha256=I_U2qc5OZ2xTz2uQTczrkNYaxoiJ7FcgcNoAdxE2h9o,10735
|
|
10
|
+
tool_maker/db/__init__.py,sha256=UFrVdyPkqnVO3Xa2HpiHslQGsqngMwGO_hr05LN8HZM,356
|
|
11
|
+
tool_maker/db/config.py,sha256=D8ITwvcyXqtOaOTc5ylGuvTD6lDJqVoGIiQX3vjHSqw,666
|
|
12
|
+
tool_maker/db/connection.py,sha256=7qoZ3C8lOX379zLIGt3OHzH43FIBwgzgj9pciIajy0E,4769
|
|
13
|
+
tool_maker/db/migrator.py,sha256=BtPjI0Orgh9RJtTilLdedEqxfxlF0upZb4aHPl6o4Ic,3392
|
|
14
|
+
tool_maker/db/models.py,sha256=nrxOnL_2H3sz4G6yV7sm5Vdzb5hQ2OUcrdeRYqMXzYU,7979
|
|
15
|
+
tool_maker/db/pipeline.py,sha256=aF3jeE2jrA0G88otDfnzjBnipUqPRZjodhgPkXtS1rg,18400
|
|
16
|
+
tool_maker/db/migrations/001_initial.sql,sha256=Jqxh8SsrRyJl44xXye4svVYFC5t52mQTnwfP4cHduxk,1883
|
|
17
|
+
tool_maker/flask/__init__.py,sha256=kLZQhh9FKTEeelmu8_3y3N_3lL4_jNB0hHuRwH71rcI,152
|
|
18
|
+
tool_maker/flask/extension.py,sha256=oiW7GLHr1W-4aWywdFOLZGsSOKWDFApeh6d-tJ0WFvA,5254
|
|
19
|
+
tool_maker/llm/__init__.py,sha256=_SynbkHqhcgixvxgEsAarOCjEjImG73bX-SLArnMT-s,133
|
|
20
|
+
tool_maker/llm/provider.py,sha256=944shnSzZx1Sv1MvnLFjW9OFZ-aeWARqeDFpJiAIHzY,8182
|
|
21
|
+
tool_maker/planner/__init__.py,sha256=Uy_SmYzRGcOo7B4uGOmcfgBR3TgUXuqO8OIiG4e5RfI,483
|
|
22
|
+
tool_maker/planner/executor.py,sha256=_ec5v9Or5yUpK49kpUH4Tur6re85HKRgPoljpWfOJmc,2992
|
|
23
|
+
tool_maker/planner/models.py,sha256=Glxf3sLqG-fhvaxplFP67s84veyiTiHzFrh_r5fuHF8,1354
|
|
24
|
+
tool_maker/planner/planner.py,sha256=GTFD_JE5tniB21sm3Z60kL9qDuD1adKv5PeDeMxA4Z4,3726
|
|
25
|
+
tool_maker/planner/reviewer.py,sha256=cxG_2tbz1FLwOGx2CI0NieO8R8uOOxENBr666wnkhDE,2357
|
|
26
|
+
tool_maker/planner/validator.py,sha256=0qY_js8mNWLhd8HxwLQsFLW5A4rsR9CXoWwm7URP8NI,3446
|
|
27
|
+
tool_maker/planner/writer.py,sha256=pgisuWufvX1qnpqx8atlA-EixJjIqj4hd2Bp7Ve7WlA,1011
|
|
28
|
+
tool_maker/tool/__init__.py,sha256=OWVMRXdKKdOABkYdn6EPldQfwfA3mWt0q3XjsuVJ1Pc,317
|
|
29
|
+
tool_maker/tool/executor.py,sha256=H6N6zziqqHpHq6_WXMOgfVEkIepml3jlJVVgtCiORAg,5411
|
|
30
|
+
tool_maker/tool/generator.py,sha256=gIJvWNStEBszSVJpwe7mNtcIVvwUxjwP9mtetNbvtYU,6962
|
|
31
|
+
tool_maker/tool/sandbox.py,sha256=-bMSPJTOxDK6Dqq_YUbIs8KBPNKTAbloahEPjA5BmgU,7070
|
|
32
|
+
tool_maker/ui/__init__.py,sha256=Z-QaTL2IfyA4n8Wpy3-BVHz-wNQd_HQAFmVGQSyZXLE,113
|
|
33
|
+
tool_maker/ui/app.py,sha256=IQOPk5iszjLWn2sOxA52lpAFUl1yCDhu53xq78Jgc6U,1756
|
|
34
|
+
tool_maker/ui/log_handler.py,sha256=LJumsSBKNd-Lkk6vNxw5D6AWneggiImP9lN-Jp5IhTg,1813
|
|
35
|
+
tool_maker/ui/routes.py,sha256=a4_qo9wmuaOzrBA6BHVDUhM29_g9f04WGe3lHR8Na-o,17390
|
|
36
|
+
tool_maker/ui/static/style.css,sha256=a0vphjk0gEYC-DViGBXe7ZF3rqHyuUNqsoPSHhc7x4A,11637
|
|
37
|
+
tool_maker/ui/templates/analyze.html,sha256=XjXv1H05Ib44eSQIyeSYOZ16NKVvgYKbxUXtLtnPujM,1613
|
|
38
|
+
tool_maker/ui/templates/base.html,sha256=kDga9Drs-CfI3HNQOv-8H0-Zf0YYUi2t2yqINL4Idcw,881
|
|
39
|
+
tool_maker/ui/templates/config.html,sha256=oa_gpIV8OxhIfUVY29FgRDohX3_yahkfxUMp02MZ8Hc,4138
|
|
40
|
+
tool_maker/ui/templates/execute.html,sha256=5z63ImLnVgz53t4REU55BTR-n2XWGEoYzTX8kqb8-Z4,11101
|
|
41
|
+
tool_maker/ui/templates/generate.html,sha256=Cg3JDwjZrNRbWEWSwuXGZDWfEmHm7uaer6YY1R4Jbks,1447
|
|
42
|
+
tool_maker/ui/templates/index.html,sha256=97ziv9r3Xf6s74JFQBGthu88K6rKOEPTUrmxfzUZXDc,1901
|
|
43
|
+
tool_maker/ui/templates/pipeline.html,sha256=Hjel9HShHN0MatiTyKnKV_HqTUXMa0FcUXWeS_HOEzY,13236
|
|
44
|
+
tool_maker/ui/templates/provider.html,sha256=WxCJKpxV4AvNvNrCNQxLCLLUO487ZgHeJPp9_STAr4Q,1917
|
|
45
|
+
llm_tool_maker-0.1.0.dist-info/METADATA,sha256=OuG5joYDEL_xRBEOJJtW1NWfeRTEfQ2g3eb0f4Ufp74,5768
|
|
46
|
+
llm_tool_maker-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
47
|
+
llm_tool_maker-0.1.0.dist-info/entry_points.txt,sha256=J_34bQd3noKx-P4tmSB0K9ZMo-i5vN-thBukoqDwV90,60
|
|
48
|
+
llm_tool_maker-0.1.0.dist-info/RECORD,,
|
tool_maker/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool Maker - An intelligent tool-making package that leverages LLMs.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__author__ = "Your Name"
|
|
7
|
+
|
|
8
|
+
from tool_maker.analyzer.project_scanner import ProjectScanner
|
|
9
|
+
from tool_maker.config import ToolMakerConfigFile
|
|
10
|
+
from tool_maker.llm.provider import LLMProvider, get_provider
|
|
11
|
+
from tool_maker.tool.generator import ToolGenerator
|
|
12
|
+
from tool_maker.tool.executor import ToolExecutor
|
|
13
|
+
from tool_maker.tool_fixer import ToolFixer
|
|
14
|
+
from tool_maker.tool_maker import (
|
|
15
|
+
ToolMaker,
|
|
16
|
+
create_tool_maker,
|
|
17
|
+
create_tool_maker_from_env,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ProjectScanner",
|
|
22
|
+
"LLMProvider",
|
|
23
|
+
"get_provider",
|
|
24
|
+
"ToolGenerator",
|
|
25
|
+
"ToolExecutor",
|
|
26
|
+
"ToolFixer",
|
|
27
|
+
"ToolMakerConfigFile",
|
|
28
|
+
"ToolMaker",
|
|
29
|
+
"create_tool_maker",
|
|
30
|
+
"create_tool_maker_from_env",
|
|
31
|
+
]
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project Scanner - Scans and analyzes project structure.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, List, Any, Optional
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectScanner:
|
|
12
|
+
"""Scans and analyzes project structure to understand capabilities."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, project_path: str):
|
|
15
|
+
self.project_path = Path(project_path)
|
|
16
|
+
self.modules: List[Dict[str, Any]] = []
|
|
17
|
+
self.dependencies: List[str] = []
|
|
18
|
+
self.entry_points: List[str] = []
|
|
19
|
+
self.project_info: Dict[str, Any] = {}
|
|
20
|
+
|
|
21
|
+
def scan(self) -> Dict[str, Any]:
|
|
22
|
+
"""Scan the project and return analysis results."""
|
|
23
|
+
self._scan_modules()
|
|
24
|
+
self._scan_dependencies()
|
|
25
|
+
self._scan_entry_points()
|
|
26
|
+
self._analyze_project()
|
|
27
|
+
|
|
28
|
+
return self.project_info
|
|
29
|
+
|
|
30
|
+
def _scan_modules(self) -> None:
|
|
31
|
+
"""Scan Python modules in the project."""
|
|
32
|
+
for py_file in self.project_path.rglob("*.py"):
|
|
33
|
+
if self._should_skip_file(py_file):
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with open(py_file, 'r', encoding='utf-8') as f:
|
|
38
|
+
content = f.read()
|
|
39
|
+
|
|
40
|
+
module_info = {
|
|
41
|
+
"path": str(py_file.relative_to(self.project_path)),
|
|
42
|
+
"name": self._get_module_name(py_file),
|
|
43
|
+
"functions": [],
|
|
44
|
+
"classes": [],
|
|
45
|
+
"imports": []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
tree = ast.parse(content)
|
|
50
|
+
module_info = self._parse_ast(tree, module_info)
|
|
51
|
+
except SyntaxError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
self.modules.append(module_info)
|
|
55
|
+
except Exception:
|
|
56
|
+
# Skip files that can't be read or parsed
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
def _parse_ast(self, tree: ast.AST, module_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
60
|
+
"""Parse AST to extract functions, classes, and imports."""
|
|
61
|
+
for node in ast.iter_child_nodes(tree):
|
|
62
|
+
if isinstance(node, ast.FunctionDef):
|
|
63
|
+
module_info["functions"].append({
|
|
64
|
+
"name": node.name,
|
|
65
|
+
"line": node.lineno,
|
|
66
|
+
"docstring": ast.get_docstring(node)
|
|
67
|
+
})
|
|
68
|
+
elif isinstance(node, ast.ClassDef):
|
|
69
|
+
module_info["classes"].append({
|
|
70
|
+
"name": node.name,
|
|
71
|
+
"line": node.lineno,
|
|
72
|
+
"docstring": ast.get_docstring(node)
|
|
73
|
+
})
|
|
74
|
+
elif isinstance(node, ast.Import):
|
|
75
|
+
for alias in node.names:
|
|
76
|
+
module_info["imports"].append(alias.name)
|
|
77
|
+
elif isinstance(node, ast.ImportFrom):
|
|
78
|
+
if node.module:
|
|
79
|
+
for alias in node.names:
|
|
80
|
+
module_info["imports"].append(f"{node.module}.{alias.name}")
|
|
81
|
+
|
|
82
|
+
return module_info
|
|
83
|
+
|
|
84
|
+
def _should_skip_file(self, file_path: Path) -> bool:
|
|
85
|
+
"""Determine if a file should be skipped during scanning."""
|
|
86
|
+
skip_patterns = [
|
|
87
|
+
"__pycache__",
|
|
88
|
+
".venv",
|
|
89
|
+
"venv",
|
|
90
|
+
".git",
|
|
91
|
+
".env",
|
|
92
|
+
"node_modules",
|
|
93
|
+
".mypy_cache",
|
|
94
|
+
".pytest_cache",
|
|
95
|
+
"dist",
|
|
96
|
+
"build"
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
return any(pattern in str(file_path) for pattern in skip_patterns)
|
|
100
|
+
|
|
101
|
+
def _get_module_name(self, file_path: Path) -> str:
|
|
102
|
+
"""Get the module name from a file path."""
|
|
103
|
+
parts = []
|
|
104
|
+
for part in file_path.relative_to(self.project_path).parts:
|
|
105
|
+
if part == "__init__.py":
|
|
106
|
+
continue
|
|
107
|
+
parts.append(part.replace(".py", ""))
|
|
108
|
+
|
|
109
|
+
return ".".join(parts)
|
|
110
|
+
|
|
111
|
+
def _scan_dependencies(self) -> None:
|
|
112
|
+
"""Scan for project dependencies."""
|
|
113
|
+
# Check for pyproject.toml
|
|
114
|
+
pyproject_path = self.project_path / "pyproject.toml"
|
|
115
|
+
if pyproject_path.exists():
|
|
116
|
+
try:
|
|
117
|
+
import tomli
|
|
118
|
+
with open(pyproject_path, 'rb') as f:
|
|
119
|
+
data = tomli.load(f)
|
|
120
|
+
|
|
121
|
+
if "project" in data and "dependencies" in data["project"]:
|
|
122
|
+
self.dependencies = data["project"]["dependencies"]
|
|
123
|
+
except ImportError:
|
|
124
|
+
# Try basic parsing if tomli is not available
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Check for requirements.txt
|
|
128
|
+
requirements_path = self.project_path / "requirements.txt"
|
|
129
|
+
if requirements_path.exists():
|
|
130
|
+
with open(requirements_path, 'r') as f:
|
|
131
|
+
self.dependencies = [
|
|
132
|
+
line.strip() for line in f
|
|
133
|
+
if line.strip() and not line.startswith("#")
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
def _scan_entry_points(self) -> None:
|
|
137
|
+
"""Scan for entry points (main files, Flask apps, etc.)."""
|
|
138
|
+
# Look for common entry point patterns
|
|
139
|
+
entry_point_patterns = [
|
|
140
|
+
"main.py",
|
|
141
|
+
"app.py",
|
|
142
|
+
"wsgi.py",
|
|
143
|
+
"run.py",
|
|
144
|
+
"server.py"
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
for pattern in entry_point_patterns:
|
|
148
|
+
entry_path = self.project_path / pattern
|
|
149
|
+
if entry_path.exists():
|
|
150
|
+
self.entry_points.append(str(entry_path.relative_to(self.project_path)))
|
|
151
|
+
|
|
152
|
+
def _analyze_project(self) -> None:
|
|
153
|
+
"""Analyze the project and create a summary."""
|
|
154
|
+
self.project_info = {
|
|
155
|
+
"path": str(self.project_path),
|
|
156
|
+
"name": self.project_path.name,
|
|
157
|
+
"modules_count": len(self.modules),
|
|
158
|
+
"total_functions": sum(len(m["functions"]) for m in self.modules),
|
|
159
|
+
"total_classes": sum(len(m["classes"]) for m in self.modules),
|
|
160
|
+
"dependencies": self.dependencies,
|
|
161
|
+
"entry_points": self.entry_points,
|
|
162
|
+
"modules": self.modules,
|
|
163
|
+
"summary": self._generate_summary()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def _generate_summary(self) -> str:
|
|
167
|
+
"""Generate a summary of the project."""
|
|
168
|
+
if not self.modules:
|
|
169
|
+
return "No Python modules found."
|
|
170
|
+
|
|
171
|
+
summary_parts = [
|
|
172
|
+
f"Project '{self.project_path.name}' contains {len(self.modules)} modules",
|
|
173
|
+
f"with {sum(len(m['functions']) for m in self.modules)} functions and "
|
|
174
|
+
f"{sum(len(m['classes']) for m in self.modules)} classes."
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
if self.dependencies:
|
|
178
|
+
summary_parts.append(f"Has {len(self.dependencies)} dependencies.")
|
|
179
|
+
|
|
180
|
+
if self.entry_points:
|
|
181
|
+
summary_parts.append(f"Entry points: {', '.join(self.entry_points)}.")
|
|
182
|
+
|
|
183
|
+
return " ".join(summary_parts)
|
|
184
|
+
|
|
185
|
+
def get_module_by_name(self, name: str) -> Optional[Dict[str, Any]]:
|
|
186
|
+
"""Get a module by its name."""
|
|
187
|
+
for module in self.modules:
|
|
188
|
+
if module["name"] == name:
|
|
189
|
+
return module
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
def get_functions_by_module(self, module_name: str) -> List[Dict[str, Any]]:
|
|
193
|
+
"""Get all functions in a specific module."""
|
|
194
|
+
module = self.get_module_by_name(module_name)
|
|
195
|
+
if module:
|
|
196
|
+
return module["functions"]
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
def save_analysis(self, output_path: str) -> None:
|
|
200
|
+
"""Save the analysis to a JSON file."""
|
|
201
|
+
with open(output_path, 'w') as f:
|
|
202
|
+
json.dump(self.project_info, f, indent=2)
|