requirements-installer 1.0.0__tar.gz
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.
- requirements_installer-1.0.0/LICENSE +21 -0
- requirements_installer-1.0.0/MANIFEST.in +4 -0
- requirements_installer-1.0.0/PKG-INFO +247 -0
- requirements_installer-1.0.0/README.md +220 -0
- requirements_installer-1.0.0/pyproject.toml +37 -0
- requirements_installer-1.0.0/requirements_installer.egg-info/PKG-INFO +247 -0
- requirements_installer-1.0.0/requirements_installer.egg-info/SOURCES.txt +10 -0
- requirements_installer-1.0.0/requirements_installer.egg-info/dependency_links.txt +1 -0
- requirements_installer-1.0.0/requirements_installer.egg-info/entry_points.txt +2 -0
- requirements_installer-1.0.0/requirements_installer.egg-info/top_level.txt +1 -0
- requirements_installer-1.0.0/requirements_installer.py +436 -0
- requirements_installer-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sina Mirshahi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: requirements-installer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Automatically detect and install Python dependencies from your code
|
|
5
|
+
Author-email: Sina Mirshahi <sina7th@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FH-Prevail/requirements_installer
|
|
8
|
+
Project-URL: Repository, https://github.com/FH-Prevail/requirements-installer
|
|
9
|
+
Project-URL: Issues, https://github.com/FH-Prevail/requirements_installer/issues
|
|
10
|
+
Keywords: pip,requirements,dependencies,automation,installer
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
23
|
+
Requires-Python: >=3.7
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Requirements Installer π
|
|
29
|
+
|
|
30
|
+
**Tired of hunting down missing Python dependencies?** Requirements Installer automatically detects and installs all third-party packages your Python script needsβno `requirements.txt` needed!
|
|
31
|
+
|
|
32
|
+
Simply point it at your Python file, and it handles the rest. Perfect for running unfamiliar scripts, quick prototyping, or setting up development environments.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## β¨ Features
|
|
37
|
+
|
|
38
|
+
- **π Smart Import Detection** - Automatically scans your Python files for all third-party imports
|
|
39
|
+
- **π¦ One-Command Install** - Detects and installs dependencies in a single step
|
|
40
|
+
- **π― Interactive Version Selection** - Optionally choose specific versions for each package
|
|
41
|
+
- **π Virtual Environment Support** - Create and install into isolated environments
|
|
42
|
+
- **π§ Intelligent Filtering** - Excludes stdlib and local modules automatically
|
|
43
|
+
- **π Module-to-Package Mapping** - Handles tricky cases like `cv2` β `opencv-python`, `PIL` β `Pillow`
|
|
44
|
+
- **π One-Hop Local Import Scanning** - Follows local imports to catch all dependencies
|
|
45
|
+
- **β‘ Fast & Lightweight** - Pure Python with no external dependencies
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## π₯ Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install requirements-installer
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or install from source:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/FH-Prevail/requirements_installer.git
|
|
59
|
+
cd requirements-installer
|
|
60
|
+
pip install -e .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## π Quick Start
|
|
66
|
+
|
|
67
|
+
### Basic Usage
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Scan and install dependencies for a Python file
|
|
71
|
+
python requirements_installer.py --file mycode.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### With Virtual Environment
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Create a venv and install dependencies there
|
|
78
|
+
python requirements_installer.py --file mycode.py --use-venv --venv-path .venv
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Interactive Version Selection
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Choose specific versions for each package
|
|
85
|
+
python requirements_installer.py --file mycode.py --ask-version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Example interaction:
|
|
89
|
+
```
|
|
90
|
+
==================================================
|
|
91
|
+
Version Selection
|
|
92
|
+
==================================================
|
|
93
|
+
|
|
94
|
+
Install latest version of 'numpy'? [Y/n]: n
|
|
95
|
+
Enter version for 'numpy' (e.g., 1.2.3): 1.24.0
|
|
96
|
+
|
|
97
|
+
Install latest version of 'pandas'? [Y/n]: y
|
|
98
|
+
|
|
99
|
+
Install latest version of 'requests'? [Y/n]:
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Print Requirements Only
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Just show what would be installed, don't install
|
|
106
|
+
python requirements_installer.py --file mycode.py --print-requirements
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## π Usage Examples
|
|
112
|
+
|
|
113
|
+
### Example 1: Quick Script Setup
|
|
114
|
+
You clone a repo without a `requirements.txt`:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python requirements_installer.py --file app.py
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Output:
|
|
121
|
+
```
|
|
122
|
+
Detected environment: current Python at /usr/bin/python3
|
|
123
|
+
Entry file: /home/user/project/app.py
|
|
124
|
+
Installing: numpy pandas requests
|
|
125
|
+
|
|
126
|
+
==================================================
|
|
127
|
+
Installation Summary
|
|
128
|
+
==================================================
|
|
129
|
+
Installed: numpy, pandas, requests
|
|
130
|
+
Already satisfied: (none)
|
|
131
|
+
All requested packages are now present.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Example 2: Isolated Development Environment
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python requirements_installer.py --file main.py --use-venv
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Creates a `.venv` folder and installs all dependencies there, keeping your system Python clean.
|
|
141
|
+
|
|
142
|
+
### Example 3: Precise Version Control
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python requirements_installer.py --file analysis.py --ask-version
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Prompts you for each package, letting you pin versions for reproducibility.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## π§ Command-Line Options
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
usage: requirements_installer.py [-h] --file FILE [--use-venv]
|
|
156
|
+
[--venv-path VENV_PATH]
|
|
157
|
+
[--print-requirements] [--ask-version]
|
|
158
|
+
|
|
159
|
+
options:
|
|
160
|
+
-h, --help Show this help message and exit
|
|
161
|
+
--file FILE Entry Python file to scan (e.g., mycode.py)
|
|
162
|
+
--use-venv Create and use a virtual environment
|
|
163
|
+
--venv-path VENV_PATH
|
|
164
|
+
Where to create the venv (default: .venv)
|
|
165
|
+
--print-requirements Only print inferred packages and exit
|
|
166
|
+
--ask-version Interactively ask for version preference for each package
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## π― How It Works
|
|
172
|
+
|
|
173
|
+
1. **Parse Imports**: Uses Python's AST to find all `import` and `from ... import` statements
|
|
174
|
+
2. **Filter Standard Library**: Removes built-in Python modules (e.g., `os`, `sys`, `json`)
|
|
175
|
+
3. **Filter Local Modules**: Excludes your project's own modules
|
|
176
|
+
4. **Map Module Names**: Converts import names to PyPI package names (e.g., `cv2` β `opencv-python`)
|
|
177
|
+
5. **Check Installation**: Queries what's already installed to avoid redundant work
|
|
178
|
+
6. **Install Packages**: Uses pip to install missing packages
|
|
179
|
+
7. **Verify**: Confirms all required packages are present
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## π Comparison with Other Tools
|
|
184
|
+
|
|
185
|
+
| Feature | requirements-installer | pipreqs | pigar | pythonrunscript |
|
|
186
|
+
|---------|----------------------|---------|-------|-----------------|
|
|
187
|
+
| Auto-detect imports | β
| β
| β
| β |
|
|
188
|
+
| Auto-install packages | β
| β | β | β
|
|
|
189
|
+
| Interactive version selection | β
| β | β | β |
|
|
190
|
+
| Virtual environment support | β
| β | β | β
|
|
|
191
|
+
| No source file modification | β
| β
| β
| β |
|
|
192
|
+
| One-command operation | β
| β | β | β
|
|
|
193
|
+
|
|
194
|
+
**Why requirements-installer?**
|
|
195
|
+
- **pipreqs/pigar**: Generate `requirements.txt` but don't install (requires 2 steps)
|
|
196
|
+
- **pythonrunscript**: Installs but requires metadata comments in your code
|
|
197
|
+
- **requirements-installer**: Does both detection and installation, no modifications needed!
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## π§ͺ Module-to-Package Mappings
|
|
202
|
+
|
|
203
|
+
The tool includes smart mappings for common cases where the import name differs from the PyPI package name:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
cv2 β opencv-python
|
|
207
|
+
PIL β Pillow
|
|
208
|
+
sklearn β scikit-learn
|
|
209
|
+
yaml β PyYAML
|
|
210
|
+
bs4 β beautifulsoup4
|
|
211
|
+
dotenv β python-dotenv
|
|
212
|
+
# ... and many more!
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## β οΈ Limitations
|
|
218
|
+
|
|
219
|
+
- **One-hop scanning**: Only follows local imports one level deep (for speed)
|
|
220
|
+
- **Dynamic imports**: Limited detection of `importlib.import_module()` calls
|
|
221
|
+
- **Conditional imports**: Treats all imports equally (no context analysis)
|
|
222
|
+
- **Version conflicts**: Doesn't resolve complex dependency conflicts (delegates to pip)
|
|
223
|
+
|
|
224
|
+
These are deliberate trade-offs for simplicity and speed. For complex projects with intricate dependency trees, consider using Poetry or Pipenv.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## π€ Contributing
|
|
229
|
+
|
|
230
|
+
Contributions are welcome! Here are some ways you can help:
|
|
231
|
+
|
|
232
|
+
- π Report bugs and issues
|
|
233
|
+
- π‘ Suggest new features or improvements
|
|
234
|
+
- π Improve documentation
|
|
235
|
+
- π§ Submit pull requests
|
|
236
|
+
- β Add more module-to-package mappings
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## β Star History
|
|
242
|
+
|
|
243
|
+
If you find this tool useful, please consider giving it a star on GitHub! It helps others discover the project.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
**Made with β€οΈ by developers, for developers**
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Requirements Installer π
|
|
2
|
+
|
|
3
|
+
**Tired of hunting down missing Python dependencies?** Requirements Installer automatically detects and installs all third-party packages your Python script needsβno `requirements.txt` needed!
|
|
4
|
+
|
|
5
|
+
Simply point it at your Python file, and it handles the rest. Perfect for running unfamiliar scripts, quick prototyping, or setting up development environments.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## β¨ Features
|
|
10
|
+
|
|
11
|
+
- **π Smart Import Detection** - Automatically scans your Python files for all third-party imports
|
|
12
|
+
- **π¦ One-Command Install** - Detects and installs dependencies in a single step
|
|
13
|
+
- **π― Interactive Version Selection** - Optionally choose specific versions for each package
|
|
14
|
+
- **π Virtual Environment Support** - Create and install into isolated environments
|
|
15
|
+
- **π§ Intelligent Filtering** - Excludes stdlib and local modules automatically
|
|
16
|
+
- **π Module-to-Package Mapping** - Handles tricky cases like `cv2` β `opencv-python`, `PIL` β `Pillow`
|
|
17
|
+
- **π One-Hop Local Import Scanning** - Follows local imports to catch all dependencies
|
|
18
|
+
- **β‘ Fast & Lightweight** - Pure Python with no external dependencies
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## π₯ Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install requirements-installer
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install from source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/FH-Prevail/requirements_installer.git
|
|
32
|
+
cd requirements-installer
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## π Quick Start
|
|
39
|
+
|
|
40
|
+
### Basic Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Scan and install dependencies for a Python file
|
|
44
|
+
python requirements_installer.py --file mycode.py
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### With Virtual Environment
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Create a venv and install dependencies there
|
|
51
|
+
python requirements_installer.py --file mycode.py --use-venv --venv-path .venv
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Interactive Version Selection
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Choose specific versions for each package
|
|
58
|
+
python requirements_installer.py --file mycode.py --ask-version
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Example interaction:
|
|
62
|
+
```
|
|
63
|
+
==================================================
|
|
64
|
+
Version Selection
|
|
65
|
+
==================================================
|
|
66
|
+
|
|
67
|
+
Install latest version of 'numpy'? [Y/n]: n
|
|
68
|
+
Enter version for 'numpy' (e.g., 1.2.3): 1.24.0
|
|
69
|
+
|
|
70
|
+
Install latest version of 'pandas'? [Y/n]: y
|
|
71
|
+
|
|
72
|
+
Install latest version of 'requests'? [Y/n]:
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Print Requirements Only
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Just show what would be installed, don't install
|
|
79
|
+
python requirements_installer.py --file mycode.py --print-requirements
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## π Usage Examples
|
|
85
|
+
|
|
86
|
+
### Example 1: Quick Script Setup
|
|
87
|
+
You clone a repo without a `requirements.txt`:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python requirements_installer.py --file app.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Output:
|
|
94
|
+
```
|
|
95
|
+
Detected environment: current Python at /usr/bin/python3
|
|
96
|
+
Entry file: /home/user/project/app.py
|
|
97
|
+
Installing: numpy pandas requests
|
|
98
|
+
|
|
99
|
+
==================================================
|
|
100
|
+
Installation Summary
|
|
101
|
+
==================================================
|
|
102
|
+
Installed: numpy, pandas, requests
|
|
103
|
+
Already satisfied: (none)
|
|
104
|
+
All requested packages are now present.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Example 2: Isolated Development Environment
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python requirements_installer.py --file main.py --use-venv
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Creates a `.venv` folder and installs all dependencies there, keeping your system Python clean.
|
|
114
|
+
|
|
115
|
+
### Example 3: Precise Version Control
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python requirements_installer.py --file analysis.py --ask-version
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Prompts you for each package, letting you pin versions for reproducibility.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## π§ Command-Line Options
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
usage: requirements_installer.py [-h] --file FILE [--use-venv]
|
|
129
|
+
[--venv-path VENV_PATH]
|
|
130
|
+
[--print-requirements] [--ask-version]
|
|
131
|
+
|
|
132
|
+
options:
|
|
133
|
+
-h, --help Show this help message and exit
|
|
134
|
+
--file FILE Entry Python file to scan (e.g., mycode.py)
|
|
135
|
+
--use-venv Create and use a virtual environment
|
|
136
|
+
--venv-path VENV_PATH
|
|
137
|
+
Where to create the venv (default: .venv)
|
|
138
|
+
--print-requirements Only print inferred packages and exit
|
|
139
|
+
--ask-version Interactively ask for version preference for each package
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## π― How It Works
|
|
145
|
+
|
|
146
|
+
1. **Parse Imports**: Uses Python's AST to find all `import` and `from ... import` statements
|
|
147
|
+
2. **Filter Standard Library**: Removes built-in Python modules (e.g., `os`, `sys`, `json`)
|
|
148
|
+
3. **Filter Local Modules**: Excludes your project's own modules
|
|
149
|
+
4. **Map Module Names**: Converts import names to PyPI package names (e.g., `cv2` β `opencv-python`)
|
|
150
|
+
5. **Check Installation**: Queries what's already installed to avoid redundant work
|
|
151
|
+
6. **Install Packages**: Uses pip to install missing packages
|
|
152
|
+
7. **Verify**: Confirms all required packages are present
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## π Comparison with Other Tools
|
|
157
|
+
|
|
158
|
+
| Feature | requirements-installer | pipreqs | pigar | pythonrunscript |
|
|
159
|
+
|---------|----------------------|---------|-------|-----------------|
|
|
160
|
+
| Auto-detect imports | β
| β
| β
| β |
|
|
161
|
+
| Auto-install packages | β
| β | β | β
|
|
|
162
|
+
| Interactive version selection | β
| β | β | β |
|
|
163
|
+
| Virtual environment support | β
| β | β | β
|
|
|
164
|
+
| No source file modification | β
| β
| β
| β |
|
|
165
|
+
| One-command operation | β
| β | β | β
|
|
|
166
|
+
|
|
167
|
+
**Why requirements-installer?**
|
|
168
|
+
- **pipreqs/pigar**: Generate `requirements.txt` but don't install (requires 2 steps)
|
|
169
|
+
- **pythonrunscript**: Installs but requires metadata comments in your code
|
|
170
|
+
- **requirements-installer**: Does both detection and installation, no modifications needed!
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## π§ͺ Module-to-Package Mappings
|
|
175
|
+
|
|
176
|
+
The tool includes smart mappings for common cases where the import name differs from the PyPI package name:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
cv2 β opencv-python
|
|
180
|
+
PIL β Pillow
|
|
181
|
+
sklearn β scikit-learn
|
|
182
|
+
yaml β PyYAML
|
|
183
|
+
bs4 β beautifulsoup4
|
|
184
|
+
dotenv β python-dotenv
|
|
185
|
+
# ... and many more!
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## β οΈ Limitations
|
|
191
|
+
|
|
192
|
+
- **One-hop scanning**: Only follows local imports one level deep (for speed)
|
|
193
|
+
- **Dynamic imports**: Limited detection of `importlib.import_module()` calls
|
|
194
|
+
- **Conditional imports**: Treats all imports equally (no context analysis)
|
|
195
|
+
- **Version conflicts**: Doesn't resolve complex dependency conflicts (delegates to pip)
|
|
196
|
+
|
|
197
|
+
These are deliberate trade-offs for simplicity and speed. For complex projects with intricate dependency trees, consider using Poetry or Pipenv.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## π€ Contributing
|
|
202
|
+
|
|
203
|
+
Contributions are welcome! Here are some ways you can help:
|
|
204
|
+
|
|
205
|
+
- π Report bugs and issues
|
|
206
|
+
- π‘ Suggest new features or improvements
|
|
207
|
+
- π Improve documentation
|
|
208
|
+
- π§ Submit pull requests
|
|
209
|
+
- β Add more module-to-package mappings
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
## β Star History
|
|
215
|
+
|
|
216
|
+
If you find this tool useful, please consider giving it a star on GitHub! It helps others discover the project.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
**Made with β€οΈ by developers, for developers**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "requirements-installer"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Automatically detect and install Python dependencies from your code"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Sina Mirshahi", email = "sina7th@gmail.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["pip", "requirements", "dependencies", "automation", "installer"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.7",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Topic :: System :: Installation/Setup",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/FH-Prevail/requirements_installer"
|
|
33
|
+
Repository = "https://github.com/FH-Prevail/requirements-installer"
|
|
34
|
+
Issues = "https://github.com/FH-Prevail/requirements_installer/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
requirements-installer = "requirements_installer:main"
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: requirements-installer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Automatically detect and install Python dependencies from your code
|
|
5
|
+
Author-email: Sina Mirshahi <sina7th@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FH-Prevail/requirements_installer
|
|
8
|
+
Project-URL: Repository, https://github.com/FH-Prevail/requirements-installer
|
|
9
|
+
Project-URL: Issues, https://github.com/FH-Prevail/requirements_installer/issues
|
|
10
|
+
Keywords: pip,requirements,dependencies,automation,installer
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
23
|
+
Requires-Python: >=3.7
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Requirements Installer π
|
|
29
|
+
|
|
30
|
+
**Tired of hunting down missing Python dependencies?** Requirements Installer automatically detects and installs all third-party packages your Python script needsβno `requirements.txt` needed!
|
|
31
|
+
|
|
32
|
+
Simply point it at your Python file, and it handles the rest. Perfect for running unfamiliar scripts, quick prototyping, or setting up development environments.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## β¨ Features
|
|
37
|
+
|
|
38
|
+
- **π Smart Import Detection** - Automatically scans your Python files for all third-party imports
|
|
39
|
+
- **π¦ One-Command Install** - Detects and installs dependencies in a single step
|
|
40
|
+
- **π― Interactive Version Selection** - Optionally choose specific versions for each package
|
|
41
|
+
- **π Virtual Environment Support** - Create and install into isolated environments
|
|
42
|
+
- **π§ Intelligent Filtering** - Excludes stdlib and local modules automatically
|
|
43
|
+
- **π Module-to-Package Mapping** - Handles tricky cases like `cv2` β `opencv-python`, `PIL` β `Pillow`
|
|
44
|
+
- **π One-Hop Local Import Scanning** - Follows local imports to catch all dependencies
|
|
45
|
+
- **β‘ Fast & Lightweight** - Pure Python with no external dependencies
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## π₯ Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install requirements-installer
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or install from source:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/FH-Prevail/requirements_installer.git
|
|
59
|
+
cd requirements-installer
|
|
60
|
+
pip install -e .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## π Quick Start
|
|
66
|
+
|
|
67
|
+
### Basic Usage
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Scan and install dependencies for a Python file
|
|
71
|
+
python requirements_installer.py --file mycode.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### With Virtual Environment
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Create a venv and install dependencies there
|
|
78
|
+
python requirements_installer.py --file mycode.py --use-venv --venv-path .venv
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Interactive Version Selection
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Choose specific versions for each package
|
|
85
|
+
python requirements_installer.py --file mycode.py --ask-version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Example interaction:
|
|
89
|
+
```
|
|
90
|
+
==================================================
|
|
91
|
+
Version Selection
|
|
92
|
+
==================================================
|
|
93
|
+
|
|
94
|
+
Install latest version of 'numpy'? [Y/n]: n
|
|
95
|
+
Enter version for 'numpy' (e.g., 1.2.3): 1.24.0
|
|
96
|
+
|
|
97
|
+
Install latest version of 'pandas'? [Y/n]: y
|
|
98
|
+
|
|
99
|
+
Install latest version of 'requests'? [Y/n]:
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Print Requirements Only
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Just show what would be installed, don't install
|
|
106
|
+
python requirements_installer.py --file mycode.py --print-requirements
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## π Usage Examples
|
|
112
|
+
|
|
113
|
+
### Example 1: Quick Script Setup
|
|
114
|
+
You clone a repo without a `requirements.txt`:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python requirements_installer.py --file app.py
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Output:
|
|
121
|
+
```
|
|
122
|
+
Detected environment: current Python at /usr/bin/python3
|
|
123
|
+
Entry file: /home/user/project/app.py
|
|
124
|
+
Installing: numpy pandas requests
|
|
125
|
+
|
|
126
|
+
==================================================
|
|
127
|
+
Installation Summary
|
|
128
|
+
==================================================
|
|
129
|
+
Installed: numpy, pandas, requests
|
|
130
|
+
Already satisfied: (none)
|
|
131
|
+
All requested packages are now present.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Example 2: Isolated Development Environment
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python requirements_installer.py --file main.py --use-venv
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Creates a `.venv` folder and installs all dependencies there, keeping your system Python clean.
|
|
141
|
+
|
|
142
|
+
### Example 3: Precise Version Control
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python requirements_installer.py --file analysis.py --ask-version
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Prompts you for each package, letting you pin versions for reproducibility.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## π§ Command-Line Options
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
usage: requirements_installer.py [-h] --file FILE [--use-venv]
|
|
156
|
+
[--venv-path VENV_PATH]
|
|
157
|
+
[--print-requirements] [--ask-version]
|
|
158
|
+
|
|
159
|
+
options:
|
|
160
|
+
-h, --help Show this help message and exit
|
|
161
|
+
--file FILE Entry Python file to scan (e.g., mycode.py)
|
|
162
|
+
--use-venv Create and use a virtual environment
|
|
163
|
+
--venv-path VENV_PATH
|
|
164
|
+
Where to create the venv (default: .venv)
|
|
165
|
+
--print-requirements Only print inferred packages and exit
|
|
166
|
+
--ask-version Interactively ask for version preference for each package
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## π― How It Works
|
|
172
|
+
|
|
173
|
+
1. **Parse Imports**: Uses Python's AST to find all `import` and `from ... import` statements
|
|
174
|
+
2. **Filter Standard Library**: Removes built-in Python modules (e.g., `os`, `sys`, `json`)
|
|
175
|
+
3. **Filter Local Modules**: Excludes your project's own modules
|
|
176
|
+
4. **Map Module Names**: Converts import names to PyPI package names (e.g., `cv2` β `opencv-python`)
|
|
177
|
+
5. **Check Installation**: Queries what's already installed to avoid redundant work
|
|
178
|
+
6. **Install Packages**: Uses pip to install missing packages
|
|
179
|
+
7. **Verify**: Confirms all required packages are present
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## π Comparison with Other Tools
|
|
184
|
+
|
|
185
|
+
| Feature | requirements-installer | pipreqs | pigar | pythonrunscript |
|
|
186
|
+
|---------|----------------------|---------|-------|-----------------|
|
|
187
|
+
| Auto-detect imports | β
| β
| β
| β |
|
|
188
|
+
| Auto-install packages | β
| β | β | β
|
|
|
189
|
+
| Interactive version selection | β
| β | β | β |
|
|
190
|
+
| Virtual environment support | β
| β | β | β
|
|
|
191
|
+
| No source file modification | β
| β
| β
| β |
|
|
192
|
+
| One-command operation | β
| β | β | β
|
|
|
193
|
+
|
|
194
|
+
**Why requirements-installer?**
|
|
195
|
+
- **pipreqs/pigar**: Generate `requirements.txt` but don't install (requires 2 steps)
|
|
196
|
+
- **pythonrunscript**: Installs but requires metadata comments in your code
|
|
197
|
+
- **requirements-installer**: Does both detection and installation, no modifications needed!
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## π§ͺ Module-to-Package Mappings
|
|
202
|
+
|
|
203
|
+
The tool includes smart mappings for common cases where the import name differs from the PyPI package name:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
cv2 β opencv-python
|
|
207
|
+
PIL β Pillow
|
|
208
|
+
sklearn β scikit-learn
|
|
209
|
+
yaml β PyYAML
|
|
210
|
+
bs4 β beautifulsoup4
|
|
211
|
+
dotenv β python-dotenv
|
|
212
|
+
# ... and many more!
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## β οΈ Limitations
|
|
218
|
+
|
|
219
|
+
- **One-hop scanning**: Only follows local imports one level deep (for speed)
|
|
220
|
+
- **Dynamic imports**: Limited detection of `importlib.import_module()` calls
|
|
221
|
+
- **Conditional imports**: Treats all imports equally (no context analysis)
|
|
222
|
+
- **Version conflicts**: Doesn't resolve complex dependency conflicts (delegates to pip)
|
|
223
|
+
|
|
224
|
+
These are deliberate trade-offs for simplicity and speed. For complex projects with intricate dependency trees, consider using Poetry or Pipenv.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## π€ Contributing
|
|
229
|
+
|
|
230
|
+
Contributions are welcome! Here are some ways you can help:
|
|
231
|
+
|
|
232
|
+
- π Report bugs and issues
|
|
233
|
+
- π‘ Suggest new features or improvements
|
|
234
|
+
- π Improve documentation
|
|
235
|
+
- π§ Submit pull requests
|
|
236
|
+
- β Add more module-to-package mappings
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## β Star History
|
|
242
|
+
|
|
243
|
+
If you find this tool useful, please consider giving it a star on GitHub! It helps others discover the project.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
**Made with β€οΈ by developers, for developers**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
requirements_installer.py
|
|
6
|
+
requirements_installer.egg-info/PKG-INFO
|
|
7
|
+
requirements_installer.egg-info/SOURCES.txt
|
|
8
|
+
requirements_installer.egg-info/dependency_links.txt
|
|
9
|
+
requirements_installer.egg-info/entry_points.txt
|
|
10
|
+
requirements_installer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requirements_installer
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
requirements_installer.py
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python requirements_installer.py --file mycode.py
|
|
7
|
+
python requirements_installer.py --file mycode.py --use-venv
|
|
8
|
+
python requirements_installer.py --file mycode.py --use-venv --venv-path .venv
|
|
9
|
+
python requirements_installer.py --file mycode.py --ask-version
|
|
10
|
+
|
|
11
|
+
What it does:
|
|
12
|
+
- Parses your Python file (and any local modules it directly imports) for imports.
|
|
13
|
+
- Filters out stdlib and local modules; infers third-party packages.
|
|
14
|
+
- Maps common module names to PyPI package names.
|
|
15
|
+
- Installs everything (optionally inside a freshly created venv).
|
|
16
|
+
- Prints a concise summary of what was installed and what was already satisfied.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import ast
|
|
21
|
+
import importlib.util
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import subprocess
|
|
25
|
+
import textwrap
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Set, Tuple, Dict, Iterable, List
|
|
28
|
+
|
|
29
|
+
# ------- Mapping: module name -> PyPI distribution name -------
|
|
30
|
+
MODULE_TO_DIST: Dict[str, str] = {
|
|
31
|
+
"cv2": "opencv-python",
|
|
32
|
+
"PIL": "Pillow",
|
|
33
|
+
"skimage": "scikit-image",
|
|
34
|
+
"sklearn": "scikit-learn",
|
|
35
|
+
"yaml": "PyYAML",
|
|
36
|
+
"bs4": "beautifulsoup4",
|
|
37
|
+
"Crypto": "pycryptodome",
|
|
38
|
+
"crypto": "pycryptodome",
|
|
39
|
+
"mpl_toolkits": "matplotlib",
|
|
40
|
+
"BeautifulSoup": "beautifulsoup4",
|
|
41
|
+
"OpenGL": "PyOpenGL",
|
|
42
|
+
"ruamel": "ruamel.yaml",
|
|
43
|
+
"dotenv": "python-dotenv",
|
|
44
|
+
"weasyprint": "WeasyPrint",
|
|
45
|
+
"orjson": "orjson",
|
|
46
|
+
"ujson": "ujson",
|
|
47
|
+
"yamlpath": "yamlpath",
|
|
48
|
+
# Often identical, listed to be explicit:
|
|
49
|
+
"numpy": "numpy",
|
|
50
|
+
"pandas": "pandas",
|
|
51
|
+
"matplotlib": "matplotlib",
|
|
52
|
+
"seaborn": "seaborn",
|
|
53
|
+
"requests": "requests",
|
|
54
|
+
"httpx": "httpx",
|
|
55
|
+
"fastapi": "fastapi",
|
|
56
|
+
"starlette": "starlette",
|
|
57
|
+
"uvicorn": "uvicorn",
|
|
58
|
+
"flask": "Flask",
|
|
59
|
+
"Django": "Django",
|
|
60
|
+
"jinja2": "Jinja2",
|
|
61
|
+
"lxml": "lxml",
|
|
62
|
+
"tqdm": "tqdm",
|
|
63
|
+
"dateutil": "python-dateutil",
|
|
64
|
+
"psutil": "psutil",
|
|
65
|
+
"Pillow": "Pillow",
|
|
66
|
+
"tabulate": "tabulate",
|
|
67
|
+
"rich": "rich",
|
|
68
|
+
"typer": "typer",
|
|
69
|
+
"click": "click",
|
|
70
|
+
"loguru": "loguru",
|
|
71
|
+
"pyyaml": "PyYAML",
|
|
72
|
+
"PILLOW": "Pillow",
|
|
73
|
+
"opencv": "opencv-python",
|
|
74
|
+
"opencv_python": "opencv-python",
|
|
75
|
+
"transformers": "transformers",
|
|
76
|
+
"torch": "torch",
|
|
77
|
+
"torchvision": "torchvision",
|
|
78
|
+
"torchaudio": "torchaudio",
|
|
79
|
+
"tensorflow": "tensorflow",
|
|
80
|
+
"jax": "jax",
|
|
81
|
+
"xgboost": "xgboost",
|
|
82
|
+
"lightgbm": "lightgbm",
|
|
83
|
+
"catboost": "catboost",
|
|
84
|
+
"skops": "skops",
|
|
85
|
+
"safetensors": "safetensors",
|
|
86
|
+
"scipy": "scipy",
|
|
87
|
+
"statsmodels": "statsmodels",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Some stdlib fallbacks for older Python (<3.10 without sys.stdlib_module_names)
|
|
91
|
+
STDLIB_FALLBACK = {
|
|
92
|
+
"abc","argparse","array","asyncio","base64","binascii","bisect","builtins","calendar","cmath",
|
|
93
|
+
"collections","concurrent","configparser","contextlib","copy","csv","ctypes","datetime","decimal",
|
|
94
|
+
"difflib","email","enum","errno","faulthandler","fnmatch","fractions","functools","gc","getopt",
|
|
95
|
+
"getpass","gettext","glob","gzip","hashlib","heapq","hmac","html","http","imaplib","importlib",
|
|
96
|
+
"inspect","io","ipaddress","itertools","json","keyword","linecache","locale","logging","lzma",
|
|
97
|
+
"math","mimetypes","multiprocessing","numbers","operator","os","pathlib","pickle","pkgutil","platform",
|
|
98
|
+
"plistlib","pprint","profile","pstats","queue","random","re","resource","sched","secrets","select",
|
|
99
|
+
"selectors","shlex","shutil","signal","site","smtplib","socket","sqlite3","ssl","stat","statistics",
|
|
100
|
+
"string","stringprep","struct","subprocess","sys","sysconfig","tarfile","tempfile","textwrap","threading",
|
|
101
|
+
"time","timeit","tkinter","token","traceback","types","typing","unicodedata","unittest","urllib","uuid",
|
|
102
|
+
"venv","warnings","weakref","xml","xmlrpc","zipfile","zoneinfo",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def stdlib_names() -> Set[str]:
|
|
106
|
+
names = set()
|
|
107
|
+
try:
|
|
108
|
+
names.update(getattr(sys, "stdlib_module_names")) # Py3.10+
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
if not names:
|
|
112
|
+
names.update(STDLIB_FALLBACK)
|
|
113
|
+
return names
|
|
114
|
+
|
|
115
|
+
def top_level_module(name: str) -> str:
|
|
116
|
+
return name.split(".", 1)[0]
|
|
117
|
+
|
|
118
|
+
def is_relative_import(mod: str) -> bool:
|
|
119
|
+
return mod.startswith(".")
|
|
120
|
+
|
|
121
|
+
def find_dynamic_imports(node: ast.AST) -> Set[str]:
|
|
122
|
+
"""Very simple detection for importlib.import_module('pkg[.sub]')"""
|
|
123
|
+
found = set()
|
|
124
|
+
for n in ast.walk(node):
|
|
125
|
+
if isinstance(n, ast.Call):
|
|
126
|
+
# importlib.import_module("x") or import_module("x")
|
|
127
|
+
target = ""
|
|
128
|
+
if isinstance(n.func, ast.Attribute):
|
|
129
|
+
if getattr(n.func.value, "id", None) == "importlib" and n.func.attr == "import_module":
|
|
130
|
+
target = "importlib.import_module"
|
|
131
|
+
elif isinstance(n.func, ast.Name) and n.func.id == "import_module":
|
|
132
|
+
target = "import_module"
|
|
133
|
+
if target and n.args and isinstance(n.args[0], ast.Constant) and isinstance(n.args[0].value, str):
|
|
134
|
+
found.add(top_level_module(n.args[0].value))
|
|
135
|
+
return found
|
|
136
|
+
|
|
137
|
+
def parse_imports_from_file(path: Path) -> Tuple[Set[str], Set[str]]:
|
|
138
|
+
"""
|
|
139
|
+
Returns (modules, local_modules)
|
|
140
|
+
modules: top-level imported module names (absolute)
|
|
141
|
+
local_modules: modules likely referring to local files/packages (within project)
|
|
142
|
+
"""
|
|
143
|
+
modules: Set[str] = set()
|
|
144
|
+
local: Set[str] = set()
|
|
145
|
+
code = path.read_text(encoding="utf-8", errors="ignore")
|
|
146
|
+
tree = ast.parse(code, filename=str(path))
|
|
147
|
+
for node in ast.walk(tree):
|
|
148
|
+
if isinstance(node, ast.Import):
|
|
149
|
+
for alias in node.names:
|
|
150
|
+
mod = top_level_module(alias.name)
|
|
151
|
+
if not is_relative_import(mod):
|
|
152
|
+
modules.add(mod)
|
|
153
|
+
elif isinstance(node, ast.ImportFrom):
|
|
154
|
+
if node.module is None:
|
|
155
|
+
continue
|
|
156
|
+
if node.level and node.level > 0:
|
|
157
|
+
# relative import -> likely local
|
|
158
|
+
continue
|
|
159
|
+
mod = top_level_module(node.module)
|
|
160
|
+
if not is_relative_import(mod):
|
|
161
|
+
modules.add(mod)
|
|
162
|
+
modules |= find_dynamic_imports(tree)
|
|
163
|
+
return modules, local
|
|
164
|
+
|
|
165
|
+
def is_stdlib_module(mod: str, stdlib: Set[str]) -> bool:
|
|
166
|
+
if mod in stdlib:
|
|
167
|
+
return True
|
|
168
|
+
if mod in sys.builtin_module_names:
|
|
169
|
+
return True
|
|
170
|
+
# Heuristic: if spec exists and lives under base_prefix + "lib"
|
|
171
|
+
try:
|
|
172
|
+
spec = importlib.util.find_spec(mod)
|
|
173
|
+
if spec and spec.origin:
|
|
174
|
+
origin = str(spec.origin).lower()
|
|
175
|
+
base = str(Path(sys.base_prefix)).lower()
|
|
176
|
+
# stdlib often under .../lib/pythonX.Y/...
|
|
177
|
+
if "python" in origin and base in origin and "site-packages" not in origin:
|
|
178
|
+
return True
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def is_local_module(mod: str, project_root: Path) -> bool:
|
|
184
|
+
"""
|
|
185
|
+
Decide if 'mod' resolves to a module/package under project_root.
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
spec = importlib.util.find_spec(mod)
|
|
189
|
+
if not spec or not spec.origin:
|
|
190
|
+
# Could be uninstalled third-party (desired) OR local not in sys.path.
|
|
191
|
+
# If a file/folder with that name exists next to project, treat as local.
|
|
192
|
+
pkg_dir = project_root / mod
|
|
193
|
+
py_file = project_root / f"{mod}.py"
|
|
194
|
+
return pkg_dir.exists() or py_file.exists()
|
|
195
|
+
origin = Path(spec.origin).resolve()
|
|
196
|
+
try:
|
|
197
|
+
return project_root.resolve() in origin.parents
|
|
198
|
+
except Exception:
|
|
199
|
+
return False
|
|
200
|
+
except Exception:
|
|
201
|
+
# If importable resolution fails, do a filesystem guess:
|
|
202
|
+
pkg_dir = project_root / mod
|
|
203
|
+
py_file = project_root / f"{mod}.py"
|
|
204
|
+
return pkg_dir.exists() or py_file.exists()
|
|
205
|
+
|
|
206
|
+
def map_to_distribution(mod: str) -> str:
|
|
207
|
+
# Prefer an explicit mapping, else use the module name itself
|
|
208
|
+
return MODULE_TO_DIST.get(mod, mod)
|
|
209
|
+
|
|
210
|
+
def collect_requirements(entry_file: Path, project_root: Path) -> Set[str]:
|
|
211
|
+
"""
|
|
212
|
+
Parse entry file and any immediately local-imported files (one hop) to collect modules.
|
|
213
|
+
We don't recurse deep to keep it fast and simple.
|
|
214
|
+
"""
|
|
215
|
+
stdlib = stdlib_names()
|
|
216
|
+
all_modules: Set[str] = set()
|
|
217
|
+
|
|
218
|
+
def add_from(path: Path):
|
|
219
|
+
mods, _ = parse_imports_from_file(path)
|
|
220
|
+
all_modules.update(mods)
|
|
221
|
+
|
|
222
|
+
add_from(entry_file)
|
|
223
|
+
|
|
224
|
+
# One-hop: if an import is local, parse that file/package's __init__.py
|
|
225
|
+
local_files_to_parse: Set[Path] = set()
|
|
226
|
+
for mod in list(all_modules):
|
|
227
|
+
if is_local_module(mod, project_root):
|
|
228
|
+
# Prefer package __init__.py if package; else module.py
|
|
229
|
+
pkg_dir = project_root / mod
|
|
230
|
+
if (pkg_dir / "__init__.py").exists():
|
|
231
|
+
local_files_to_parse.add(pkg_dir / "__init__.py")
|
|
232
|
+
elif (project_root / f"{mod}.py").exists():
|
|
233
|
+
local_files_to_parse.add(project_root / f"{mod}.py")
|
|
234
|
+
|
|
235
|
+
for lf in local_files_to_parse:
|
|
236
|
+
mods, _ = parse_imports_from_file(lf)
|
|
237
|
+
all_modules.update(mods)
|
|
238
|
+
|
|
239
|
+
# Filter stdlib & local
|
|
240
|
+
third_party = set()
|
|
241
|
+
for mod in all_modules:
|
|
242
|
+
if not mod or is_stdlib_module(mod, stdlib) or is_local_module(mod, project_root):
|
|
243
|
+
continue
|
|
244
|
+
third_party.add(mod)
|
|
245
|
+
|
|
246
|
+
# Map to distributions (deduplicate case-insensitively)
|
|
247
|
+
dists = {map_to_distribution(m) for m in third_party}
|
|
248
|
+
# Normalize "-" vs "_" inconsistencies to avoid duplicates
|
|
249
|
+
normed = set()
|
|
250
|
+
for d in dists:
|
|
251
|
+
normed.add(d.replace("_", "-"))
|
|
252
|
+
return normed
|
|
253
|
+
|
|
254
|
+
def ensure_venv(venv_path: Path) -> Tuple[Path, List[str]]:
|
|
255
|
+
"""
|
|
256
|
+
Create a venv at venv_path if missing. Return (python_exe, pip_cmd_as_list)
|
|
257
|
+
"""
|
|
258
|
+
from venv import EnvBuilder
|
|
259
|
+
if not venv_path.exists():
|
|
260
|
+
print(f"Creating virtual environment at: {venv_path}")
|
|
261
|
+
builder = EnvBuilder(with_pip=True, clear=False, upgrade=False, symlinks=True)
|
|
262
|
+
builder.create(str(venv_path))
|
|
263
|
+
# Paths
|
|
264
|
+
if os.name == "nt":
|
|
265
|
+
py = venv_path / "Scripts" / "python.exe"
|
|
266
|
+
else:
|
|
267
|
+
py = venv_path / "bin" / "python"
|
|
268
|
+
pip_cmd = [str(py), "-m", "pip"]
|
|
269
|
+
return py, pip_cmd
|
|
270
|
+
|
|
271
|
+
def current_pip_cmd() -> Tuple[Path, List[str]]:
|
|
272
|
+
py = Path(sys.executable)
|
|
273
|
+
return py, [str(py), "-m", "pip"]
|
|
274
|
+
|
|
275
|
+
def installed_distributions_lower(pip_python: Path) -> Set[str]:
|
|
276
|
+
"""
|
|
277
|
+
Use importlib.metadata from the given python to list installed dists (lowercased).
|
|
278
|
+
"""
|
|
279
|
+
code = (
|
|
280
|
+
"import importlib.metadata as m, json; "
|
|
281
|
+
"print(json.dumps([d.metadata['Name'].lower() for d in m.distributions() if 'Name' in d.metadata]))"
|
|
282
|
+
)
|
|
283
|
+
out = subprocess.check_output([str(pip_python), "-c", code], text=True)
|
|
284
|
+
import json
|
|
285
|
+
return set(json.loads(out))
|
|
286
|
+
|
|
287
|
+
def ask_for_version(package: str) -> str:
|
|
288
|
+
"""
|
|
289
|
+
Interactively ask user if they want the latest version or a specific version.
|
|
290
|
+
Returns the package spec (e.g., 'package' or 'package==1.2.3')
|
|
291
|
+
"""
|
|
292
|
+
while True:
|
|
293
|
+
response = input(f"\nInstall latest version of '{package}'? [Y/n]: ").strip().lower()
|
|
294
|
+
if response in ('', 'y', 'yes'):
|
|
295
|
+
return package
|
|
296
|
+
elif response in ('n', 'no'):
|
|
297
|
+
version = input(f"Enter version for '{package}' (e.g., 1.2.3): ").strip()
|
|
298
|
+
if version:
|
|
299
|
+
return f"{package}=={version}"
|
|
300
|
+
else:
|
|
301
|
+
print("No version specified, using latest.")
|
|
302
|
+
return package
|
|
303
|
+
else:
|
|
304
|
+
print("Please answer 'y' or 'n'.")
|
|
305
|
+
|
|
306
|
+
def install_packages(pip_cmd: List[str], pkgs: Set[str], ask_version: bool = False) -> Tuple[bool, str]:
|
|
307
|
+
"""
|
|
308
|
+
Install packages. If ask_version is True, prompt for version preferences.
|
|
309
|
+
"""
|
|
310
|
+
if not pkgs:
|
|
311
|
+
return True, "Nothing to install."
|
|
312
|
+
|
|
313
|
+
packages_to_install = []
|
|
314
|
+
if ask_version:
|
|
315
|
+
print("\n" + "="*50)
|
|
316
|
+
print("Version Selection")
|
|
317
|
+
print("="*50)
|
|
318
|
+
for pkg in sorted(pkgs):
|
|
319
|
+
pkg_spec = ask_for_version(pkg)
|
|
320
|
+
packages_to_install.append(pkg_spec)
|
|
321
|
+
else:
|
|
322
|
+
packages_to_install = sorted(pkgs)
|
|
323
|
+
|
|
324
|
+
cmd = list(pip_cmd) + ["install", "--upgrade"] + packages_to_install
|
|
325
|
+
print(f"\nInstalling: {' '.join(packages_to_install)}")
|
|
326
|
+
proc = subprocess.run(cmd, text=True, capture_output=True)
|
|
327
|
+
success = proc.returncode == 0
|
|
328
|
+
return success, proc.stdout + "\n" + proc.stderr
|
|
329
|
+
|
|
330
|
+
def summarize_install(before: Set[str], after: Set[str], requested: Set[str]) -> Tuple[Set[str], Set[str]]:
|
|
331
|
+
"""
|
|
332
|
+
Summarize what was installed vs what was already present.
|
|
333
|
+
Note: requested contains base package names without version specs.
|
|
334
|
+
"""
|
|
335
|
+
newly_installed = {p for p in requested if p.lower() in after and p.lower() not in before}
|
|
336
|
+
already_present = {p for p in requested if p.lower() in before}
|
|
337
|
+
return newly_installed, already_present
|
|
338
|
+
|
|
339
|
+
def main():
|
|
340
|
+
ap = argparse.ArgumentParser(
|
|
341
|
+
description="Scan a Python file for third-party imports and install them.",
|
|
342
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
343
|
+
epilog=textwrap.dedent("""\
|
|
344
|
+
Examples:
|
|
345
|
+
python requirements_installer.py --file mycode.py
|
|
346
|
+
python requirements_installer.py --file mycode.py --use-venv
|
|
347
|
+
python requirements_installer.py --file mycode.py --use-venv --venv-path .venv
|
|
348
|
+
python requirements_installer.py --file mycode.py --ask-version
|
|
349
|
+
"""),
|
|
350
|
+
)
|
|
351
|
+
ap.add_argument("--file", required=True, help="Entry Python file to scan (e.g., mycode.py).")
|
|
352
|
+
ap.add_argument("--use-venv", action="store_true",
|
|
353
|
+
help="Create and use a virtual environment for installation.")
|
|
354
|
+
ap.add_argument("--venv-path", default=".venv", help="Where to create the venv (default: .venv).")
|
|
355
|
+
ap.add_argument("--print-requirements", action="store_true", help="Only print inferred packages and exit.")
|
|
356
|
+
ap.add_argument("--ask-version", action="store_true",
|
|
357
|
+
help="Interactively ask for version preference for each package.")
|
|
358
|
+
args = ap.parse_args()
|
|
359
|
+
|
|
360
|
+
entry_file = Path(args.file).resolve()
|
|
361
|
+
if not entry_file.exists():
|
|
362
|
+
ap.error(f"File not found: {entry_file}")
|
|
363
|
+
|
|
364
|
+
project_root = entry_file.parent
|
|
365
|
+
|
|
366
|
+
# Collect required distributions
|
|
367
|
+
required = collect_requirements(entry_file, project_root)
|
|
368
|
+
|
|
369
|
+
# Early exit if requested
|
|
370
|
+
if args.print_requirements:
|
|
371
|
+
if required:
|
|
372
|
+
print("\n".join(sorted(required)))
|
|
373
|
+
else:
|
|
374
|
+
print("(No external packages detected.)")
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
# Decide environment
|
|
378
|
+
if args.use_venv:
|
|
379
|
+
venv_path = Path(args.venv_path).resolve()
|
|
380
|
+
py_exe, pip_cmd = ensure_venv(venv_path)
|
|
381
|
+
env_desc = f"virtual environment at {venv_path}"
|
|
382
|
+
else:
|
|
383
|
+
py_exe, pip_cmd = current_pip_cmd()
|
|
384
|
+
env_desc = f"current Python at {py_exe}"
|
|
385
|
+
|
|
386
|
+
print(f"Detected environment: {env_desc}")
|
|
387
|
+
print(f"Entry file: {entry_file}")
|
|
388
|
+
|
|
389
|
+
if not required:
|
|
390
|
+
print("No external packages detected. Nothing to install.")
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
# Read installed packages before
|
|
394
|
+
try:
|
|
395
|
+
before = installed_distributions_lower(py_exe)
|
|
396
|
+
except Exception:
|
|
397
|
+
before = set()
|
|
398
|
+
|
|
399
|
+
# Determine what actually needs to be installed (case-insensitive)
|
|
400
|
+
needed = {p for p in required if p.lower() not in before}
|
|
401
|
+
|
|
402
|
+
if not needed:
|
|
403
|
+
print("All required packages are already installed.")
|
|
404
|
+
print("Packages:", ", ".join(sorted(required)))
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
ok, output = install_packages(pip_cmd, needed, ask_version=args.ask_version)
|
|
408
|
+
|
|
409
|
+
# Read installed packages after
|
|
410
|
+
try:
|
|
411
|
+
after = installed_distributions_lower(py_exe)
|
|
412
|
+
except Exception:
|
|
413
|
+
after = set()
|
|
414
|
+
|
|
415
|
+
newly, already = summarize_install(before, after, required)
|
|
416
|
+
print("\n" + "="*50)
|
|
417
|
+
print("Installation Summary")
|
|
418
|
+
print("="*50)
|
|
419
|
+
if newly:
|
|
420
|
+
print("Installed:", ", ".join(sorted(newly)))
|
|
421
|
+
else:
|
|
422
|
+
print("Installed: (none)")
|
|
423
|
+
if already:
|
|
424
|
+
print("Already satisfied:", ", ".join(sorted(already)))
|
|
425
|
+
missing = sorted({p for p in required if p.lower() not in after})
|
|
426
|
+
if missing:
|
|
427
|
+
print("Failed / Missing:", ", ".join(missing))
|
|
428
|
+
print("\n--- pip output (for debugging) ---")
|
|
429
|
+
print(output)
|
|
430
|
+
sys.exit(1)
|
|
431
|
+
else:
|
|
432
|
+
print("All requested packages are now present.")
|
|
433
|
+
sys.exit(0)
|
|
434
|
+
|
|
435
|
+
if __name__ == "__main__":
|
|
436
|
+
main()
|