Skip to content

Commit f7efcde

Browse files
committed
Fixes and added support for importing Python packages like 'mypackage[options]'
Signed-off-by: Matthew Ballance <matt.ballance@gmail.com>
1 parent 9e4893f commit f7efcde

File tree

8 files changed

+36
-11
lines changed

8 files changed

+36
-11
lines changed

src/ivpm/handlers/package_handler_python.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,13 +417,15 @@ def _write_requirements_txt(self,
417417
packages_dir.replace("\\","/"),
418418
pkg.name))
419419
else:
420-
# PyPi package
420+
# PyPi package — build PEP 508 specifier: name[extras]version
421+
extras = getattr(pkg, "extras", None)
422+
extras_str = "[%s]" % ",".join(extras) if extras else ""
421423
if pkg.version is not None:
422424
if pkg.version[0] in ['<','>','=']:
423-
fp.write("%s%s\n" % (pkg.name, pkg.version))
425+
fp.write("%s%s%s\n" % (pkg.name, extras_str, pkg.version))
424426
else:
425-
fp.write("%s==%s\n" % (pkg.name, pkg.version))
427+
fp.write("%s%s==%s\n" % (pkg.name, extras_str, pkg.version))
426428
else:
427-
fp.write("%s\n" % pkg.name)
429+
fp.write("%s%s\n" % (pkg.name, extras_str))
428430

429431

src/ivpm/package_updater.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,9 @@ def _update_pkg(self, pkg : Package) -> Tuple[Package, ProjInfo]:
221221
pkg.proj_info.target_dep_set = pkg.dep_set
222222
pkg.proj_info.process_deps = pkg.process_deps
223223

224-
# Signal package complete
225-
self.update_info.package_complete(pkg.name)
224+
# Signal package complete, passing resolved version if available (e.g., gh-rls)
225+
resolved_version = getattr(pkg, 'resolved_version', None)
226+
self.update_info.package_complete(pkg.name, version=resolved_version)
226227

227228
return (pkg, pkg.proj_info)
228229
except Exception as e:

src/ivpm/pkg_types/package_gh_rls.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ def _resolve_release(self):
145145
for r in rls_info:
146146
if r["prerelease"] and not self.prerelease:
147147
continue
148+
# Skip releases with no usable assets (no assets, or all with broken untagged URLs)
149+
if not self.source and not self._filter_valid_assets(r.get("assets", [])):
150+
continue
148151
rls = r
149152
break
150153
if rls is None:
@@ -502,7 +505,7 @@ def _parse_linux_generic(self, name):
502505
if m:
503506
arch = m.group(1)
504507
# Normalize arch names
505-
if arch in ("x86_64", "amd64"):
508+
if arch in ("x86_64", "amd64", "x64"):
506509
return "x86_64"
507510
elif arch in ("aarch_64", "aarch64", "arm64"):
508511
return "aarch64"
@@ -619,7 +622,12 @@ def _has_binary_assets(self, assets):
619622
return True
620623
return False
621624

625+
def _filter_valid_assets(self, assets):
626+
"""Filter out assets with broken untagged URLs (draft artifacts that weren't re-published)."""
627+
return [a for a in assets if "untagged-" not in a.get("browser_download_url", "")]
628+
622629
def _choose_source_url(self, rls):
630+
"""Return (url, forced_ext) for the source archive of a release, or (None, None)."""
623631
# Prefer tarball over zipball
624632
tarball = rls.get("tarball_url")
625633
if tarball:

src/ivpm/pkg_types/package_http.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ def _make_readonly(self, path: str):
196196

197197
def _download_file(self, url, dest):
198198
r = httpx.get(url, follow_redirects=True)
199+
if r.status_code < 200 or r.status_code >= 300:
200+
raise Exception("Failed to download %s: HTTP %d" % (url, r.status_code))
199201
with open(dest, "wb") as f:
200202
f.write(r.content)
201-
pass
202203

203204
@staticmethod
204205
def create(name, opts, si) -> 'PackageHttp':

src/ivpm/pkg_types/package_pypi.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
class PackagePyPi(Package):
2727
version : str = None
2828
resolved_version : str = None # actual installed version after pip install
29+
extras : list = None # PEP 508 extras, e.g. ["litellm"] -> package[litellm]
2930

3031
def process_options(self, opts, si):
3132
super().process_options(opts, si)
@@ -35,6 +36,13 @@ def process_options(self, opts, si):
3536
if "version" in opts.keys():
3637
self.version = opts["version"]
3738

39+
if "extras" in opts.keys():
40+
raw = opts["extras"]
41+
if isinstance(raw, list):
42+
self.extras = [str(e) for e in raw]
43+
else:
44+
self.extras = [str(raw)]
45+
3846
@staticmethod
3947
def create(name, opts, si) -> 'Package':
4048
pkg = PackagePyPi(name)

src/ivpm/project_ops_info.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def package_start(self, name: str, pkg_type: str = None, pkg_src: str = None):
118118
self.event_dispatcher.dispatch(event)
119119
_logger.debug("Package start: %s", name)
120120

121-
def package_complete(self, name: str):
121+
def package_complete(self, name: str, version: str = None):
122122
"""Signal that loading of a package has completed."""
123123
duration = None
124124
if self._current_package_start and self._current_package_name == name:
@@ -129,7 +129,8 @@ def package_complete(self, name: str):
129129
event_type=UpdateEventType.PACKAGE_COMPLETE,
130130
package_name=name,
131131
duration=duration,
132-
cache_hit=self._current_cache_hit
132+
cache_hit=self._current_cache_hit,
133+
version=version
133134
)
134135
self.event_dispatcher.dispatch(event)
135136
_logger.debug("Package complete: %s (%.2fs)", name, duration or 0)

src/ivpm/update_event.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class UpdateEvent:
4545
duration: Optional[float] = None
4646
cache_hit: Optional[bool] = None
4747
error_message: Optional[str] = None
48+
version: Optional[str] = None
4849
total_packages: int = 0
4950
cache_hits: int = 0
5051
cache_misses: int = 0

src/ivpm/update_tui.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(self, name: str, pkg_type: str, pkg_src: str):
4242
self.start_time = time.time()
4343
self.duration: Optional[float] = None
4444
self.cache_hit: Optional[bool] = None
45+
self.version: Optional[str] = None
4546
self.completed = False
4647
self.error: Optional[str] = None
4748

@@ -137,8 +138,9 @@ def _render(self):
137138
elif status.completed:
138139
marker = Text("✓", style="bold green")
139140
cache_str = "C" if status.cache_hit else ""
141+
version_str = f"@{status.version}" if status.version else ""
140142
duration_str = f"{status.duration:.1f}s" if status.duration else ""
141-
info = Text(f"{status.pkg_type} {status.pkg_src} {cache_str} {duration_str}".strip())
143+
info = Text(f"{status.pkg_type} {status.pkg_src} {version_str} {cache_str} {duration_str}".strip())
142144
else:
143145
# Use Rich's Spinner for in-progress packages
144146
marker = Spinner("dots", style="bold cyan")
@@ -198,6 +200,7 @@ def on_event(self, event: UpdateEvent):
198200
status.completed = True
199201
status.duration = event.duration
200202
status.cache_hit = event.cache_hit
203+
status.version = event.version
201204
self.completed_packages.append(event.package_name)
202205
self._update_display()
203206

0 commit comments

Comments
 (0)