/
/
/
1"""Generate updated constraint and requirements files."""
2
3from __future__ import annotations
4
5import json
6import os
7import re
8import sys
9import tomllib
10from pathlib import Path
11
12PACKAGE_REGEX = re.compile(r"^(?:--.+\s)?([-_\.\w\d]+).*==.+$")
13GIT_REPO_REGEX = re.compile(r"^(git\+https:\/\/[-_\.\w\d\/]+[@-_\.\w\d\/]*)$")
14
15# ruff: noqa: T201
16
17
18def gather_core_requirements() -> list[str]:
19 """Gather core requirements out of pyproject.toml."""
20 with open("pyproject.toml", "rb") as fp:
21 data = tomllib.load(fp)
22 return data["project"]["dependencies"]
23
24
25def gather_requirements_from_manifests() -> list[str]:
26 """Gather all of the requirements from provider manifests."""
27 dependencies: list[str] = []
28 providers_path = "music_assistant/providers"
29 for dir_str in os.listdir(providers_path): # noqa: PTH208, RUF100
30 dir_path = os.path.join(providers_path, dir_str)
31 if not os.path.isdir(dir_path):
32 continue
33 # get files in subdirectory
34 for file_str in os.listdir(dir_path): # noqa: PTH208, RUF100
35 file_path = os.path.join(dir_path, file_str)
36 if not os.path.isfile(file_path):
37 continue
38 if file_str != "manifest.json":
39 continue
40
41 with open(file_path) as _file:
42 provider_manifest = json.loads(_file.read())
43 if "requirements" in provider_manifest:
44 dependencies += provider_manifest["requirements"]
45 return dependencies
46
47
48def main() -> int:
49 """Run the script."""
50 if not os.path.isfile("requirements_all.txt"):
51 print("Run this from MA root dir")
52 return 1
53
54 core_reqs = gather_core_requirements()
55 extra_reqs = gather_requirements_from_manifests()
56
57 # use intermediate dict to detect duplicates
58 # TODO: compare versions and only store most recent
59 final_requirements: dict[str, str] = {}
60 for req_str in core_reqs + extra_reqs:
61 package_name = req_str
62 if match := PACKAGE_REGEX.search(req_str):
63 package_name = match.group(1).lower().replace("_", "-")
64 elif match := GIT_REPO_REGEX.search(req_str):
65 package_name = match.group(1)
66 elif package_name in final_requirements:
67 # duplicate package without version is safe to ignore
68 continue
69 else:
70 print(f"Found requirement without (exact) version specifier: {req_str}")
71 package_name = req_str
72
73 existing = final_requirements.get(package_name)
74 if existing:
75 print(f"WARNING: ignore duplicate package: {package_name} - existing: {existing}")
76 continue
77 final_requirements[package_name] = req_str
78
79 content = "# WARNING: this file is autogenerated!\n\n"
80 for req_key in sorted(final_requirements):
81 req_str = final_requirements[req_key]
82 content += f"{req_str}\n"
83 # Always use LF line endings for cross-platform compatibility
84 Path("requirements_all.txt").write_text(content, newline="\n")
85
86 return 0
87
88
89if __name__ == "__main__":
90 sys.exit(main())
91