Skip to content

Commit 0defc22

Browse files
authored
Collect more download URLs in purl2url (#201)
* Add support for debian Signed-off-by: Tushar Goel <[email protected]> * Add support for APK download URLs Signed-off-by: Tushar Goel <[email protected]> * Add support for APK download URLs Signed-off-by: Tushar Goel <[email protected]> * Bump version and add changelog Signed-off-by: Tushar Goel <[email protected]> * Address review comments Signed-off-by: Tushar Goel <[email protected]> --------- Signed-off-by: Tushar Goel <[email protected]>
1 parent f79606d commit 0defc22

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
0.17.4 (2025-08-05)
5+
-------------------
6+
7+
- Add support for getting download URL for debian, apk, qpkg in ``purl2url``.
8+
https://github.com/package-url/packageurl-python/pull/201
9+
410
0.17.3 (2025-08-01)
511
-------------------
612

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = packageurl-python
3-
version = 0.17.3
3+
version = 0.17.4
44
license = MIT
55
description = A purl aka. Package URL parser and builder
66
long_description = file:README.rst

src/packageurl/contrib/purl2url.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,100 @@ def build_alpm_download_url(purl_str):
593593
return url
594594

595595

596+
def normalize_version(version: str) -> str:
597+
"""
598+
Remove the epoch (if any) from a Debian version.
599+
E.g., "1:2.4.47-2" becomes "2.4.47-2"
600+
"""
601+
if ":" in version:
602+
_, v = version.split(":", 1)
603+
return v
604+
return version
605+
606+
607+
@download_router.route("pkg:deb/.*")
608+
def build_deb_download_url(purl_str: str) -> str:
609+
"""
610+
Construct a download URL for a Debian or Ubuntu package PURL.
611+
Supports optional 'repository_url' in qualifiers.
612+
"""
613+
p = PackageURL.from_string(purl_str)
614+
615+
name = p.name
616+
version = p.version
617+
namespace = p.namespace
618+
qualifiers = p.qualifiers or {}
619+
arch = qualifiers.get("arch")
620+
repository_url = qualifiers.get("repository_url")
621+
622+
if not name or not version:
623+
raise ValueError("Both name and version must be present in deb purl")
624+
625+
if not arch:
626+
arch = "source"
627+
628+
if repository_url:
629+
base_url = repository_url.rstrip("/")
630+
else:
631+
if namespace == "debian":
632+
base_url = "https://deb.debian.org/debian"
633+
elif namespace == "ubuntu":
634+
base_url = "http://archive.ubuntu.com/ubuntu"
635+
else:
636+
raise NotImplementedError(f"Unsupported distro namespace: {namespace}")
637+
638+
norm_version = normalize_version(version)
639+
640+
if arch == "source":
641+
filename = f"{name}_{norm_version}.dsc"
642+
else:
643+
filename = f"{name}_{norm_version}_{arch}.deb"
644+
645+
pool_path = f"/pool/main/{name[0].lower()}/{name}"
646+
647+
return f"{base_url}{pool_path}/{filename}"
648+
649+
650+
@download_router.route("pkg:qpkg/.*")
651+
def build_qpkg_download_url(purl: str) -> str:
652+
purl = PackageURL.from_string(purl)
653+
repo_url = purl.qualifiers.get("repo_url")
654+
655+
if not repo_url:
656+
raise ValueError("repository_url qualifier is required for qpkg purl resolution")
657+
658+
if not purl.namespace or not purl.name or not purl.version:
659+
raise ValueError("namespace, name, and version must be present in qpkg purl")
660+
661+
path = f"{purl.namespace}/{purl.name}/{purl.version}.qpkg"
662+
return f"{repo_url.rstrip('/')}/{path}"
663+
664+
665+
@download_router.route("pkg:apk/.*")
666+
def build_apk_download_url(purl):
667+
"""
668+
Return a download URL for a fully qualified Alpine Linux package PURL.
669+
670+
Example:
671+
pkg:apk/[email protected]?arch=x86&alpine_version=v3.11&repo=main
672+
"""
673+
purl = PackageURL.from_string(purl)
674+
name = purl.name
675+
version = purl.version
676+
arch = purl.qualifiers.get("arch")
677+
repo = purl.qualifiers.get("repo")
678+
alpine_version = purl.qualifiers.get("alpine_version")
679+
680+
if not name or not version or not arch or not repo or not alpine_version:
681+
raise ValueError(
682+
"All qualifiers (arch, repo, alpine_version) and name/version must be present in apk purl"
683+
)
684+
685+
return (
686+
f"https://dl-cdn.alpinelinux.org/alpine/{alpine_version}/{repo}/{arch}/{name}-{version}.apk"
687+
)
688+
689+
596690
def get_repo_download_url(purl):
597691
"""
598692
Return ``download_url`` if present in ``purl`` qualifiers or

tests/contrib/test_purl2url.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ def test_purl2url_get_download_url():
110110
"pkg:luarocks/hisham/[email protected]": "https://luarocks.org/luafilesystem-1.8.0-1.src.rock",
111111
"pkg:conda/[email protected]?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2": "https://repo.anaconda.com/pkgs/main/linux-64/absl-py-0.4.1-py36h06a4308_0.tar.bz2",
112112
"pkg:alpm/arch/[email protected]?arch=x86_64": "https://archive.archlinux.org/packages/p/pacman/pacman-6.0.1-1-x86_64.pkg.tar.zst",
113+
"pkg:deb/debian/attr@1:2.4.48-6?arch=amd64": "https://deb.debian.org/debian/pool/main/a/attr/attr_2.4.48-6_amd64.deb",
114+
"pkg:deb/debian/attr@1:2.4.48-6?arch=amd64&repository_url=http://archive.debian.org/debian": "http://archive.debian.org/debian/pool/main/a/attr/attr_2.4.48-6_amd64.deb",
115+
"pkg:qpkg/blackberry/[email protected]?repo_url=http://repo.blackberry.com": "http://repo.blackberry.com/blackberry/com.qnx.sdp/7.0.0.SGA201702151847.qpkg",
116+
"pkg:apk/[email protected]?arch=x86&alpine_version=v3.11&repo=main": "https://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86/acct-6.6.4-r0.apk",
113117
# From `download_url` qualifier
114118
"pkg:github/yarnpkg/[email protected]?download_url=https://github.com/yarnpkg/yarn/releases/download/v1.3.2/yarn-v1.3.2.tar.gz&version_prefix=v": "https://github.com/yarnpkg/yarn/releases/download/v1.3.2/yarn-v1.3.2.tar.gz",
115119
"pkg:generic/lxc-master.tar.gz?download_url=https://salsa.debian.org/lxc-team/lxc/-/archive/master/lxc-master.tar.gz": "https://salsa.debian.org/lxc-team/lxc/-/archive/master/lxc-master.tar.gz",

0 commit comments

Comments
 (0)