Coverage for rust2rpm/cratesio.py: 24%
59 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-27 15:21 +0100
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-27 15:21 +0100
1import os
2import re
3from typing import Optional
4from urllib.parse import urljoin
6import requests
7import tqdm
9from cargo2rpm.semver import Version
11from rust2rpm import log
12from rust2rpm.utils import remove_on_error
14CRATES_IO_API_URL = "https://crates.io/api/v1/"
15XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
16CACHE_DIR = os.path.join(XDG_CACHE_HOME, "rust2rpm")
19class NoVersionsError(Exception):
20 pass
23def query_available_versions(crate: str, stable: bool = True) -> list[Version]:
24 url = urljoin(CRATES_IO_API_URL, f"crates/{crate}/versions")
25 req = requests.get(url, headers={"User-Agent": "rust2rpm"})
26 req.raise_for_status()
27 versions = req.json()["versions"]
29 parsed_versions = map(lambda x: Version.parse(x["num"]), filter(lambda x: not x["yanked"], versions))
31 if stable:
32 return list(filter(lambda x: x.pre is None, parsed_versions))
33 else:
34 return list(parsed_versions)
37def query_newest_version(crate: str) -> str:
38 url = urljoin(CRATES_IO_API_URL, f"crates/{crate}/versions")
39 req = requests.get(url, headers={"User-Agent": "rust2rpm"})
40 req.raise_for_status()
41 versions = req.json()["versions"]
43 def is_stable(s):
44 return not re.search("alpha|beta|rc|pre", s["num"])
46 def is_not_yanked(s):
47 return not s["yanked"]
49 # return the most recent, non-yanked stable version
51 def is_not_yanked_and_stable(s):
52 return is_stable(s) and is_not_yanked(s)
54 not_yanked_and_stable = [*filter(is_not_yanked_and_stable, versions)]
55 if len(not_yanked_and_stable) > 0:
56 version = not_yanked_and_stable[0]["num"]
57 return version
59 # there are no non-yanked stable versions:
60 # fall back to the latest pre-release
62 not_yanked = [*filter(is_not_yanked, versions)]
63 if len(not_yanked) > 0:
64 version = not_yanked[0]["num"]
65 log.warn(f"No stable versions available. Falling back to the latest pre-release version {version!r}.")
66 return version
68 # there are no non-yanked versions: fatal
69 raise NoVersionsError()
72def download_crate(name: str, version: Optional[str] = None) -> tuple[str, str]:
73 if version is None:
74 # query crates.io for the latest version
75 version = query_newest_version(name)
77 os.makedirs(CACHE_DIR, exist_ok=True)
79 crate_file_name = f"{name}-{version}.crate"
80 crate_file_path = os.path.join(CACHE_DIR, crate_file_name)
82 if not os.path.isfile(crate_file_path):
83 url = urljoin(CRATES_IO_API_URL, f"crates/{name}/{version}/download#")
84 req = requests.get(url, stream=True, headers={"User-Agent": "rust2rpm"})
85 req.raise_for_status()
86 total = int(req.headers["Content-Length"])
88 with remove_on_error(crate_file_path), open(crate_file_path, "wb") as f:
89 for chunk in tqdm.tqdm(
90 req.iter_content(),
91 f"Downloading {crate_file_name}",
92 total=total,
93 unit="B",
94 unit_scale=True,
95 ):
96 f.write(chunk)
98 return crate_file_path, version