comfy-env 0.0.41__py3-none-any.whl → 0.0.43__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.
- comfy_env/__init__.py +8 -4
- comfy_env/cli.py +77 -147
- comfy_env/env/config.py +2 -0
- comfy_env/env/config_file.py +27 -3
- comfy_env/env/manager.py +30 -129
- comfy_env/install.py +124 -498
- comfy_env/registry.py +48 -9
- comfy_env/resolver.py +10 -187
- comfy_env/wheel_sources.yml +52 -103
- {comfy_env-0.0.41.dist-info → comfy_env-0.0.43.dist-info}/METADATA +118 -46
- {comfy_env-0.0.41.dist-info → comfy_env-0.0.43.dist-info}/RECORD +14 -15
- comfy_env/index_resolver.py +0 -132
- {comfy_env-0.0.41.dist-info → comfy_env-0.0.43.dist-info}/WHEEL +0 -0
- {comfy_env-0.0.41.dist-info → comfy_env-0.0.43.dist-info}/entry_points.txt +0 -0
- {comfy_env-0.0.41.dist-info → comfy_env-0.0.43.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comfy-env
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.43
|
|
4
4
|
Summary: Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation
|
|
5
5
|
Project-URL: Homepage, https://github.com/PozzettiAndrea/comfy-env
|
|
6
6
|
Project-URL: Repository, https://github.com/PozzettiAndrea/comfy-env
|
|
@@ -67,17 +67,12 @@ curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
|
67
67
|
Create a `comfy-env.toml` in your node directory:
|
|
68
68
|
|
|
69
69
|
```toml
|
|
70
|
-
[
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
cuda = "auto"
|
|
70
|
+
[cuda]
|
|
71
|
+
nvdiffrast = "0.4.0"
|
|
72
|
+
pytorch3d = "0.7.9"
|
|
74
73
|
|
|
75
74
|
[packages]
|
|
76
75
|
requirements = ["transformers>=4.56", "pillow"]
|
|
77
|
-
no_deps = ["nvdiffrast==0.4.0", "pytorch3d>=0.7.8"]
|
|
78
|
-
|
|
79
|
-
[sources]
|
|
80
|
-
wheel_sources = ["https://github.com/PozzettiAndrea/nvdiffrast-full-wheels/releases/download/"]
|
|
81
76
|
```
|
|
82
77
|
|
|
83
78
|
Then in your `__init__.py`:
|
|
@@ -122,56 +117,124 @@ comfy-env install --dry-run
|
|
|
122
117
|
# Resolve wheel URLs without installing
|
|
123
118
|
comfy-env resolve nvdiffrast==0.4.0
|
|
124
119
|
|
|
120
|
+
# List all packages in the built-in registry
|
|
121
|
+
comfy-env list-packages
|
|
122
|
+
|
|
125
123
|
# Verify installation
|
|
126
124
|
comfy-env doctor
|
|
127
125
|
```
|
|
128
126
|
|
|
129
|
-
##
|
|
127
|
+
## Configuration
|
|
130
128
|
|
|
131
|
-
### comfy-env.toml
|
|
129
|
+
### Simple Format (comfy-env.toml)
|
|
132
130
|
|
|
133
131
|
```toml
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
# CUDA packages (uses built-in registry)
|
|
133
|
+
[cuda]
|
|
134
|
+
nvdiffrast = "0.4.0"
|
|
135
|
+
pytorch3d = "0.7.9"
|
|
136
|
+
torch-scatter = "2.1.2"
|
|
138
137
|
|
|
138
|
+
# Regular pip packages
|
|
139
139
|
[packages]
|
|
140
|
-
requirements = [
|
|
141
|
-
"transformers>=4.56",
|
|
142
|
-
"pillow",
|
|
143
|
-
]
|
|
144
|
-
no_deps = [ # CUDA packages (installed with --no-deps)
|
|
145
|
-
"nvdiffrast==0.4.0",
|
|
146
|
-
"pytorch3d>=0.7.8",
|
|
147
|
-
]
|
|
148
|
-
|
|
149
|
-
[sources]
|
|
150
|
-
wheel_sources = [ # GitHub releases with pre-built wheels
|
|
151
|
-
"https://github.com/.../releases/download/",
|
|
152
|
-
]
|
|
153
|
-
index_urls = [ # Extra pip index URLs
|
|
154
|
-
"https://pypi.org/simple/",
|
|
155
|
-
]
|
|
156
|
-
|
|
157
|
-
[worker] # For isolation mode
|
|
158
|
-
package = "worker" # worker/__main__.py
|
|
140
|
+
requirements = ["transformers>=4.56", "pillow"]
|
|
159
141
|
```
|
|
160
142
|
|
|
161
|
-
###
|
|
143
|
+
### Full Format
|
|
144
|
+
|
|
145
|
+
```toml
|
|
146
|
+
[system]
|
|
147
|
+
linux = ["libgl1", "libopengl0"] # apt packages
|
|
148
|
+
|
|
149
|
+
[local.cuda]
|
|
150
|
+
nvdiffrast = "0.4.0"
|
|
151
|
+
|
|
152
|
+
[local.packages]
|
|
153
|
+
requirements = ["pillow", "numpy"]
|
|
154
|
+
|
|
155
|
+
# For isolated environments (creates separate venv)
|
|
156
|
+
[myenv]
|
|
157
|
+
python = "3.10"
|
|
158
|
+
cuda = "12.8"
|
|
159
|
+
|
|
160
|
+
[myenv.cuda]
|
|
161
|
+
torch-scatter = "2.1.2"
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
[myenv.packages]
|
|
164
|
+
requirements = ["transformers>=4.56"]
|
|
165
|
+
|
|
166
|
+
# Custom wheel templates (override built-in registry)
|
|
167
|
+
[wheel_sources]
|
|
168
|
+
my-custom-pkg = "https://my-server.com/my-pkg-{version}+cu{cuda_short}-{py_tag}-{platform}.whl"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Writing Wheel Templates
|
|
172
|
+
|
|
173
|
+
### Template Variables
|
|
164
174
|
|
|
165
175
|
| Variable | Example | Description |
|
|
166
176
|
|----------|---------|-------------|
|
|
177
|
+
| `{version}` | `0.4.0` | Package version |
|
|
167
178
|
| `{cuda_version}` | `12.8` | Full CUDA version |
|
|
168
179
|
| `{cuda_short}` | `128` | CUDA without dot |
|
|
169
|
-
| `{
|
|
170
|
-
| `{
|
|
180
|
+
| `{cuda_major}` | `12` | CUDA major only |
|
|
181
|
+
| `{torch_version}` | `2.8.0` | Full PyTorch version |
|
|
182
|
+
| `{torch_mm}` | `28` | PyTorch major.minor no dot |
|
|
183
|
+
| `{torch_dotted_mm}` | `2.8` | PyTorch major.minor with dot |
|
|
171
184
|
| `{py_version}` | `3.10` | Python version |
|
|
172
185
|
| `{py_short}` | `310` | Python without dot |
|
|
186
|
+
| `{py_tag}` | `cp310` | Python wheel tag |
|
|
173
187
|
| `{platform}` | `linux_x86_64` | Platform tag |
|
|
174
188
|
|
|
189
|
+
### Common Wheel URL Patterns
|
|
190
|
+
|
|
191
|
+
**Pattern 1: Simple CUDA + Python**
|
|
192
|
+
```
|
|
193
|
+
https://example.com/{package}-{version}+cu{cuda_short}-{py_tag}-{py_tag}-{platform}.whl
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Pattern 2: CUDA + PyTorch**
|
|
197
|
+
```
|
|
198
|
+
https://example.com/{package}-{version}+cu{cuda_short}torch{torch_mm}-{py_tag}-{py_tag}-{platform}.whl
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Pattern 3: GitHub Releases**
|
|
202
|
+
```
|
|
203
|
+
https://github.com/org/repo/releases/download/v{version}/{package}-{version}+cu{cuda_short}-{py_tag}-{platform}.whl
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### How to Find the Right Template
|
|
207
|
+
|
|
208
|
+
1. Download a wheel manually from the source
|
|
209
|
+
2. Look at the filename pattern: `nvdiffrast-0.4.0+cu128torch28-cp310-cp310-linux_x86_64.whl`
|
|
210
|
+
3. Replace values with variables: `nvdiffrast-{version}+cu{cuda_short}torch{torch_mm}-{py_tag}-{py_tag}-{platform}.whl`
|
|
211
|
+
4. Prepend the base URL
|
|
212
|
+
|
|
213
|
+
### Testing Your Template
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
comfy-env resolve my-package==1.0.0
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This shows the resolved URL without installing.
|
|
220
|
+
|
|
221
|
+
### Adding Custom Wheel Sources
|
|
222
|
+
|
|
223
|
+
If a package isn't in the built-in registry, add it to your `comfy-env.toml`:
|
|
224
|
+
|
|
225
|
+
```toml
|
|
226
|
+
[cuda]
|
|
227
|
+
my-custom-pkg = "1.0.0"
|
|
228
|
+
|
|
229
|
+
[wheel_sources]
|
|
230
|
+
my-custom-pkg = "https://my-server.com/my-custom-pkg-{version}+cu{cuda_short}-{py_tag}-{platform}.whl"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Resolution order:
|
|
234
|
+
1. User's `[wheel_sources]` in comfy-env.toml (highest priority)
|
|
235
|
+
2. Built-in `wheel_sources.yml` registry
|
|
236
|
+
3. Error if not found
|
|
237
|
+
|
|
175
238
|
## API Reference
|
|
176
239
|
|
|
177
240
|
### install()
|
|
@@ -185,23 +248,22 @@ install()
|
|
|
185
248
|
# Explicit config
|
|
186
249
|
install(config="comfy-env.toml")
|
|
187
250
|
|
|
188
|
-
# Isolated mode (creates separate venv)
|
|
189
|
-
install(mode="isolated")
|
|
190
|
-
|
|
191
251
|
# Dry run
|
|
192
252
|
install(dry_run=True)
|
|
193
253
|
```
|
|
194
254
|
|
|
195
|
-
###
|
|
255
|
+
### RuntimeEnv
|
|
196
256
|
|
|
197
257
|
```python
|
|
198
|
-
from comfy_env import RuntimeEnv
|
|
258
|
+
from comfy_env import RuntimeEnv
|
|
199
259
|
|
|
200
260
|
env = RuntimeEnv.detect()
|
|
201
|
-
|
|
261
|
+
print(env)
|
|
262
|
+
# Python 3.10, CUDA 12.8, PyTorch 2.8.0, GPU: NVIDIA GeForce RTX 4090
|
|
202
263
|
|
|
203
|
-
|
|
204
|
-
|
|
264
|
+
# Get template variables
|
|
265
|
+
vars_dict = env.as_dict()
|
|
266
|
+
# {'cuda_version': '12.8', 'cuda_short': '128', 'torch_mm': '28', ...}
|
|
205
267
|
```
|
|
206
268
|
|
|
207
269
|
### Workers (for isolation)
|
|
@@ -224,6 +286,16 @@ print(get_gpu_summary())
|
|
|
224
286
|
# GPU 0: NVIDIA GeForce RTX 5090 (sm_120) [Blackwell - CUDA 12.8]
|
|
225
287
|
```
|
|
226
288
|
|
|
289
|
+
## Built-in Package Registry
|
|
290
|
+
|
|
291
|
+
Run `comfy-env list-packages` to see all packages in the built-in registry.
|
|
292
|
+
|
|
293
|
+
The registry includes:
|
|
294
|
+
- PyTorch Geometric packages (torch-scatter, torch-cluster, torch-sparse)
|
|
295
|
+
- NVIDIA packages (nvdiffrast, pytorch3d, gsplat)
|
|
296
|
+
- Flash Attention (flash-attn)
|
|
297
|
+
- And more
|
|
298
|
+
|
|
227
299
|
## License
|
|
228
300
|
|
|
229
301
|
MIT - see LICENSE file.
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
comfy_env/__init__.py,sha256=
|
|
2
|
-
comfy_env/cli.py,sha256=
|
|
1
|
+
comfy_env/__init__.py,sha256=OQJFNjmArjLcgrfHAFxgDJQFH_IhxibqMXbU5bu_j9Q,3822
|
|
2
|
+
comfy_env/cli.py,sha256=hZv_oJsmaMoG62Fr2Fjp778P_32BHr4fzS7G4lULSwU,13153
|
|
3
3
|
comfy_env/decorator.py,sha256=6JCKwLHaZtOLVDexs_gh_-NtS2ZK0V7nGCPqkyeYEAA,16688
|
|
4
4
|
comfy_env/errors.py,sha256=8hN8NDlo8oBUdapc-eT3ZluigI5VBzfqsSBvQdfWlz4,9943
|
|
5
|
-
comfy_env/
|
|
6
|
-
comfy_env/install.py,sha256=Az72DmrDpyD9KZ8KUK1nna7v19jjPVQXGSncSeAkNSY,32320
|
|
5
|
+
comfy_env/install.py,sha256=lKkW55mDLut3zpJOUgjAi-BdLluBRPN-bblONHjo-Ws,16595
|
|
7
6
|
comfy_env/nodes.py,sha256=CWUe35jU5SKk4ur-SddZePdqWgxJDlxGhpcJiu5pAK4,4354
|
|
8
7
|
comfy_env/pixi.py,sha256=y25mUDhB3bCqhPMGF0h23Tf8ZHykK4gLJrkvOhsPWmE,14398
|
|
9
|
-
comfy_env/registry.py,sha256=
|
|
10
|
-
comfy_env/resolver.py,sha256=
|
|
8
|
+
comfy_env/registry.py,sha256=w-QwvAPFlCrBYRAv4cXkp2zujQPZn8Fk5DUxKCtox8o,3430
|
|
9
|
+
comfy_env/resolver.py,sha256=WoNIo2IfTR2RlEf_HQl66eAeMa2R2pmLof_UdK-0RNE,6714
|
|
11
10
|
comfy_env/env/__init__.py,sha256=imQdoQEQvrRT-QDtyNpFlkVbm2fBzgACdpQwRPd09fI,1157
|
|
12
|
-
comfy_env/env/config.py,sha256=
|
|
13
|
-
comfy_env/env/config_file.py,sha256=
|
|
11
|
+
comfy_env/env/config.py,sha256=Ila-5Yal3bj6jENbBeYJlZtkbgdwnzJzImVZK3ZF1lg,7645
|
|
12
|
+
comfy_env/env/config_file.py,sha256=HzFKeQh9zQ--K1V-XuvgE6DiE_bYrXrChL1ZT8Tzlq4,24684
|
|
14
13
|
comfy_env/env/cuda_gpu_detection.py,sha256=YLuXUdWg6FeKdNyLlQAHPlveg4rTenXJ2VbeAaEi9QE,9755
|
|
15
|
-
comfy_env/env/manager.py,sha256=
|
|
14
|
+
comfy_env/env/manager.py,sha256=eDcrtJeNrW3jYb0q0R_DmUfAYjGo5Cs4BMuZUxWEzeg,21789
|
|
16
15
|
comfy_env/env/security.py,sha256=dNSitAnfBNVdvxgBBntYw33AJaCs_S1MHb7KJhAVYzM,8171
|
|
17
16
|
comfy_env/env/platform/__init__.py,sha256=Nb5MPZIEeanSMEWwqU4p4bnEKTJn1tWcwobnhq9x9IY,614
|
|
18
17
|
comfy_env/env/platform/base.py,sha256=iS0ptTTVjXRwPU4qWUdvHI7jteuzxGSjWr5BUQ7hGiU,2453
|
|
@@ -37,9 +36,9 @@ comfy_env/workers/pool.py,sha256=MtjeOWfvHSCockq8j1gfnxIl-t01GSB79T5N4YB82Lg,695
|
|
|
37
36
|
comfy_env/workers/tensor_utils.py,sha256=TCuOAjJymrSbkgfyvcKtQ_KbVWTqSwP9VH_bCaFLLq8,6409
|
|
38
37
|
comfy_env/workers/torch_mp.py,sha256=4YSNPn7hALrvMVbkO4RkTeFTcc0lhfLMk5QTWjY4PHw,22134
|
|
39
38
|
comfy_env/workers/venv.py,sha256=PmsVOu5i89tBYkGRupo2bjOLPBmk06q4GNUwDWsd9F8,32088
|
|
40
|
-
comfy_env/wheel_sources.yml,sha256=
|
|
41
|
-
comfy_env-0.0.
|
|
42
|
-
comfy_env-0.0.
|
|
43
|
-
comfy_env-0.0.
|
|
44
|
-
comfy_env-0.0.
|
|
45
|
-
comfy_env-0.0.
|
|
39
|
+
comfy_env/wheel_sources.yml,sha256=07-ruJ7HHGtXJUs7rDmud8X6OsBcfogZKG6cyU7Gacg,7950
|
|
40
|
+
comfy_env-0.0.43.dist-info/METADATA,sha256=HzyWA3jqZjM9YsoFQEhZkHMTB8b8agepqMvo2YkOQY8,7138
|
|
41
|
+
comfy_env-0.0.43.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
42
|
+
comfy_env-0.0.43.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
|
|
43
|
+
comfy_env-0.0.43.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
|
|
44
|
+
comfy_env-0.0.43.dist-info/RECORD,,
|
comfy_env/index_resolver.py
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Simple index resolver for finding wheel URLs from PEP 503 indexes.
|
|
3
|
-
|
|
4
|
-
This module fetches and parses simple index HTML to find the exact wheel URL
|
|
5
|
-
for a package given the runtime environment (CUDA, torch, python versions).
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import re
|
|
9
|
-
import urllib.request
|
|
10
|
-
from typing import Optional, Dict
|
|
11
|
-
from urllib.parse import urljoin
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def resolve_wheel_from_index(
|
|
15
|
-
index_url: str,
|
|
16
|
-
package: str,
|
|
17
|
-
vars_dict: Dict[str, str],
|
|
18
|
-
version: Optional[str] = None,
|
|
19
|
-
) -> Optional[str]:
|
|
20
|
-
"""
|
|
21
|
-
Resolve the exact wheel URL from a PEP 503 simple index or find-links page.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
index_url: Base URL of the simple index (e.g., https://pozzettiandrea.github.io/cuda-wheels)
|
|
25
|
-
or find-links page (e.g., https://data.pyg.org/whl/torch-2.8.0+cu128.html)
|
|
26
|
-
package: Package name (e.g., "cumesh")
|
|
27
|
-
vars_dict: Environment variables dict with cuda_short, torch_mm, py_tag, platform
|
|
28
|
-
version: Specific version to match, or None for latest
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
Full wheel URL if found, None otherwise
|
|
32
|
-
"""
|
|
33
|
-
# PEP 503: normalize package name (lowercase, replace _ with -)
|
|
34
|
-
normalized = package.lower().replace("_", "-")
|
|
35
|
-
|
|
36
|
-
# Try two URL patterns:
|
|
37
|
-
# 1. PEP 503 index: {index_url}/{package}/
|
|
38
|
-
# 2. Find-links page: {index_url} directly (e.g., .html file)
|
|
39
|
-
urls_to_try = []
|
|
40
|
-
|
|
41
|
-
if index_url.endswith('.html'):
|
|
42
|
-
# Direct HTML page (find-links style)
|
|
43
|
-
urls_to_try = [index_url]
|
|
44
|
-
else:
|
|
45
|
-
# PEP 503 style - try package subdirectory first, then root
|
|
46
|
-
urls_to_try = [
|
|
47
|
-
f"{index_url.rstrip('/')}/{normalized}/",
|
|
48
|
-
index_url,
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
html = None
|
|
52
|
-
base_url = None
|
|
53
|
-
for url in urls_to_try:
|
|
54
|
-
try:
|
|
55
|
-
with urllib.request.urlopen(url, timeout=10) as response:
|
|
56
|
-
html = response.read().decode('utf-8')
|
|
57
|
-
base_url = url
|
|
58
|
-
break
|
|
59
|
-
except Exception:
|
|
60
|
-
continue
|
|
61
|
-
|
|
62
|
-
if not html:
|
|
63
|
-
return None
|
|
64
|
-
|
|
65
|
-
# Extract wheel links from HTML
|
|
66
|
-
# Simple index format: <a href="...">wheel_filename</a>
|
|
67
|
-
wheel_pattern = re.compile(r'<a[^>]+href="([^"]+\.whl)"[^>]*>([^<]+)</a>', re.IGNORECASE)
|
|
68
|
-
wheels = wheel_pattern.findall(html)
|
|
69
|
-
|
|
70
|
-
if not wheels:
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
# Build matching criteria from vars_dict
|
|
74
|
-
cuda_short = vars_dict.get("cuda_short", "")
|
|
75
|
-
torch_mm = vars_dict.get("torch_mm", "")
|
|
76
|
-
py_tag = vars_dict.get("py_tag", "")
|
|
77
|
-
platform = vars_dict.get("platform", "")
|
|
78
|
-
|
|
79
|
-
# Find matching wheel
|
|
80
|
-
# Wheel filename format: {package}-{version}+cu{cuda}torch{torch}-{pytag}-{pytag}-{platform}.whl
|
|
81
|
-
best_match = None
|
|
82
|
-
best_version = None
|
|
83
|
-
|
|
84
|
-
for href, filename in wheels:
|
|
85
|
-
# Check if wheel matches our environment
|
|
86
|
-
if cuda_short and f"cu{cuda_short}" not in filename.lower():
|
|
87
|
-
continue
|
|
88
|
-
if torch_mm and f"torch{torch_mm}" not in filename.lower():
|
|
89
|
-
continue
|
|
90
|
-
if py_tag and py_tag not in filename:
|
|
91
|
-
continue
|
|
92
|
-
if platform and platform not in filename:
|
|
93
|
-
continue
|
|
94
|
-
|
|
95
|
-
# Extract version from filename
|
|
96
|
-
# Format: package-version+... or package-version-...
|
|
97
|
-
# Package name in wheel can use underscore or hyphen interchangeably
|
|
98
|
-
pkg_pattern = re.escape(package).replace(r'\_', '[-_]').replace(r'\-', '[-_]')
|
|
99
|
-
norm_pattern = re.escape(normalized).replace(r'\_', '[-_]').replace(r'\-', '[-_]')
|
|
100
|
-
match = re.match(rf'{pkg_pattern}[-_]([^+\-]+)', filename, re.IGNORECASE)
|
|
101
|
-
if not match:
|
|
102
|
-
# Try with normalized name
|
|
103
|
-
match = re.match(rf'{norm_pattern}[-_]([^+\-]+)', filename, re.IGNORECASE)
|
|
104
|
-
|
|
105
|
-
if match:
|
|
106
|
-
wheel_version = match.group(1)
|
|
107
|
-
|
|
108
|
-
# If specific version requested, must match
|
|
109
|
-
if version and version != "*" and wheel_version != version:
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
|
-
# Track best (highest) version
|
|
113
|
-
if best_version is None or _version_gt(wheel_version, best_version):
|
|
114
|
-
best_version = wheel_version
|
|
115
|
-
# Resolve relative URL
|
|
116
|
-
if href.startswith('http'):
|
|
117
|
-
best_match = href
|
|
118
|
-
else:
|
|
119
|
-
best_match = urljoin(base_url, href)
|
|
120
|
-
|
|
121
|
-
return best_match
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _version_gt(v1: str, v2: str) -> bool:
|
|
125
|
-
"""Compare version strings (simple comparison)."""
|
|
126
|
-
try:
|
|
127
|
-
# Split into parts and compare numerically
|
|
128
|
-
parts1 = [int(x) for x in re.split(r'[.\-+]', v1) if x.isdigit()]
|
|
129
|
-
parts2 = [int(x) for x in re.split(r'[.\-+]', v2) if x.isdigit()]
|
|
130
|
-
return parts1 > parts2
|
|
131
|
-
except (ValueError, AttributeError):
|
|
132
|
-
return v1 > v2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|