/
/
/
1[project]
2name = "music_assistant"
3# The version is set by GH action on release
4authors = [
5 {name = "The Music Assistant Authors", email = "[email protected]"},
6]
7classifiers = [
8 "Environment :: Console",
9 "Programming Language :: Python :: 3.14",
10]
11dependencies = [
12 "aiodns>=4.0.4",
13 "aiohttp_asyncmdnsresolver==0.2.0",
14 "Brotli>=1.0.9",
15 "aiohttp==3.14.3",
16 "aiohttp-fast-zlib==0.3.0",
17 "aiohttp-socks==0.11.0",
18 "aiofiles==24.1.0",
19 "aiosqlite==0.22.1",
20 "awesomeversion>=24.6.0",
21 "certifi==2025.11.12",
22 "colorlog==6.12.0",
23 "modern_colorthief==0.2.1",
24 "cryptography==50.0.0",
25 "chardet==7.6.0",
26 "ifaddr==0.2.0",
27 "getmac==0.9.5",
28 "mashumaro==3.20",
29 "music-assistant-models==1.1.205",
30 "music-assistant-frontend==2.17.297",
31 "mutagen==1.48.1",
32 "orjson==3.11.6",
33 "pillow==12.3.0",
34 "podcastparser==0.6.11",
35 "propcache>=0.2.1",
36 "python-slugify==8.0.4",
37 "xmltodict==1.0.4",
38 "markdownify==1.2.3",
39 "shortuuid==1.0.13",
40 "zeroconf==0.149.16",
41 "uv>=0.8.0",
42 "librosa==0.11.0",
43 "numpy==2.3.5", # numpy 2.4.0+ uses X86_V2 CPU baseline (requires SSE4.2) which breaks older CPUs https://github.com/music-assistant/support/issues/5005#issuecomment-3972476728
44 "torch==2.13.0",
45 "torchaudio==2.11.0",
46 "gql[all]==4.0.0",
47 # TEMPORARY test-drive pin â replace with PyPI release before merge (see webrtc_aiolibdatachannel_plan.md Phase C)
48 "aiolibdatachannel @ git+https://github.com/music-assistant/aiolibdatachannel@feat/certpem-testdrive",
49 "pyjwt[crypto]>=2.10.1",
50]
51description = "Music Assistant"
52license = {text = "Apache-2.0"}
53readme = "README.md"
54requires-python = ">=3.14"
55version = "0.0.0"
56
57[project.optional-dependencies]
58test = [
59 "codespell==2.4.3",
60 "mypy==2.3.0",
61 "pre-commit==4.6.2",
62 "pre-commit-hooks==6.0.0",
63 "psutil==7.2.2",
64 "pytest==9.1.1",
65 "pytest-aiohttp==1.1.1",
66 "pytest-codspeed==5.0.3",
67 "pytest-cov==7.0.0",
68 "pytest-timeout==2.4.0",
69 "pytest-xdist==3.8.0",
70 "syrupy==5.5.3",
71 "tomli==2.4.1",
72 "ruff==0.15.22",
73 "yappi==1.7.6",
74]
75
76[project.scripts]
77mass = "music_assistant.__main__:main"
78
79[tool.codespell]
80# explicit is misspelled in the iTunes API
81# "additionals" is a fixed field name from the Nicovideo API fixtures
82# "connexion" is a French word in the MusicMe login URL path
83# "claus" is Santa Claus, in the christmas_music genre description
84# "childrens" is the normalized form of the "Children's" audiobook genre (apostrophe stripped)
85# "speaches" is the name of a self-hostable OpenAI-compatible speech server
86# translations/*.json are machine-translated locale files (Lokalise); foreign words aren't typos
87# tidal fixtures are captured API responses; foreign album/track titles aren't typos
88# instructions/*.instructions.md quotes reviewer comments verbatim (incl. their original typos)
89ignore-words-list = "provid,hass,followings,childs,explict,additionals,commitish,nam,connexion,claus,childrens,speaches,"
90skip = """*.js,*.svg,\
91.github/instructions/music-assistant-standards.instructions.md,\
92music_assistant/helpers/resources/countries.json,\
93music_assistant/helpers/resources/genres/genre_mapping.json,\
94music_assistant/providers/sonic_analysis/vendored_clap/**,\
95music_assistant/translations/*.json,\
96tests/providers/tidal/fixtures/**,\
97NOTICE,\
98"""
99
100[tool.setuptools]
101include-package-data = true
102packages = ["music_assistant"]
103platforms = ["any"]
104zip-safe = false
105
106[tool.setuptools.package-data]
107music_assistant = ["py.typed", "helpers/app_secrets.json"]
108
109[tool.ruff]
110fix = true
111show-fixes = true
112
113line-length = 100
114target-version = "py314"
115
116[tool.ruff.lint.pydocstyle]
117# Use Sphinx-style docstrings with :param: syntax
118# pep257 is the base convention, we enforce Sphinx-style parameters separately
119convention = "pep257"
120
121[tool.ruff.lint.pylint]
122
123max-args = 10
124max-branches = 25
125max-returns = 15
126max-statements = 50
127
128[tool.mypy]
129platform = "linux"
130python_version = "3.14"
131
132# set this to normal when we have fixed all exclusions
133follow_imports = "silent"
134
135# suppress errors about unsatisfied imports
136ignore_missing_imports = true
137
138# be strict
139check_untyped_defs = true
140disable_error_code = [
141 "annotation-unchecked",
142 "import-not-found",
143 "import-untyped",
144]
145disallow_any_generics = true
146disallow_incomplete_defs = true
147disallow_subclassing_any = true
148disallow_untyped_calls = true
149disallow_untyped_decorators = true
150disallow_untyped_defs = true
151enable_error_code = [
152 "ignore-without-code",
153 "redundant-self",
154 "truthy-iterable",
155]
156exclude = [
157 '^music_assistant/providers/sonic_analysis/vendored_clap/.*$',
158]
159extra_checks = false
160local_partial_types = true
161no_implicit_optional = true
162no_implicit_reexport = true
163packages = [
164 "tests",
165 "music_assistant",
166]
167show_error_codes = true
168strict_equality = true
169strict_optional = true
170warn_incomplete_stub = true
171warn_no_return = true
172warn_redundant_casts = true
173warn_return_any = true
174warn_unreachable = true
175warn_unused_configs = true
176warn_unused_ignores = true
177
178# plexapi ships a py.typed marker but its annotations are incomplete (unannotated
179# methods, attributes declared as None and only set dynamically). Treat it as an
180# untyped dependency rather than trusting its type info.
181[[tool.mypy.overrides]]
182follow_imports = "skip"
183module = ["plexapi", "plexapi.*"]
184
185[tool.ruff.format]
186# Force Linux/macOS line endings
187line-ending = "lf"
188
189[tool.pytest.ini_options]
190asyncio_mode = "auto"
191timeout = 120
192filterwarnings = [
193 # Suppress Python 3.14 AsyncMock internal warnings about unawaited coroutines
194 "ignore:coroutine.*was never awaited:RuntimeWarning",
195]
196
197[tool.ruff.lint]
198ignore = [
199 "ANN002", # Just annoying, not really useful
200 "ANN003", # Just annoying, not really useful
201 "ANN401", # Opinioated warning on disallowing dynamically typed expressions
202 "D203", # Conflicts with other rules
203 "D417", # False positives in some occasions
204 "EM101", # Just annoying, not really useful
205 "EM102", # Just annoying, not really useful
206 "FIX002", # Just annoying, not really useful
207 "PLR2004", # Just annoying, not really useful
208 "PGH004", # Just annoying, not really useful
209 "PD011", # Just annoying, not really useful
210 "S101", # assert is often used to satisfy type checking
211 "TC001", # Just annoying, not really useful
212 "TC003", # Just annoying, not really useful
213 "TD002", # Just annoying, not really useful
214 "TD003", # Just annoying, not really useful
215 "TD004", # Just annoying, not really useful
216 "TRY003", # Just annoying, not really useful
217 "TRY400", # Just annoying, not really useful
218 "COM812", # Conflicts with the Ruff formatter
219 "ASYNC109", # Not relevant, since we use helpers with configurable timeouts
220 "ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
221 "N818", # Just annoying, not really useful
222 "E501", # Handled by ruff formatter
223 "ASYNC240", # Not relevant, we use asyncio not trio/anyio
224 "UP047", # Not yet ready to migrate to type parameters
225 "PLW0108", # Just annoying, not really useful
226 "FURB110", # Just annoying, not really useful # TEMPORARY DISABLED rules # The below rules must be enabled later one-by-one !
227 "BLE001",
228 "FBT001",
229 "FBT002",
230 "FBT003",
231 "ANN001",
232 "ANN202",
233 "PTH118",
234 "PTH120",
235 "PTH123",
236 "G004",
237 "S104",
238 "S105",
239 "S106",
240 "SLF001",
241 "SIM102",
242 "PERF401",
243 "ARG002",
244 "S311",
245 "TRY301",
246 "PLR0912",
247 "B904",
248 "S324",
249 "ERA001",
250 "C901",
251 "S304",
252 "TRY300",
253 "S608",
254 "ANN204",
255]
256
257select = ["ALL"]
258# Enforce the multi-line docstring summary on the second line (the pep257 convention disables both
259# D212 and D213; we re-enable D213 so our "summary starts on the next line" style is checked).
260extend-select = ["D213"]
261
262[tool.ruff.lint.flake8-pytest-style]
263fixture-parentheses = false
264mark-parentheses = false
265
266[tool.ruff.lint.isort]
267known-first-party = ["music_assistant"]
268
269[tool.ruff.lint.mccabe]
270max-complexity = 25
271
272[[tool.uv.index]]
273explicit = true
274name = "pytorch-cpu"
275url = "https://download.pytorch.org/whl/cpu"
276
277[tool.uv]
278# Build urllib3-future (pulled in via niquests) from source so URLLIB3_NO_OVERRIDE=1 set during
279# installs takes effect, stopping it from hijacking the stock urllib3 namespace server-wide.
280no-binary-package = ["urllib3-future"]
281
282[tool.uv.sources]
283torch = {index = "pytorch-cpu"}
284torchaudio = {index = "pytorch-cpu"}
285
286[build-system]
287build-backend = "setuptools.build_meta"
288requires = ["setuptools>=61"]
289