/
/
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.188",
30 "music-assistant-frontend==2.17.277",
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.0.3",
65 "pytest-aiohttp==1.1.0",
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# instructions/*.instructions.md quotes reviewer comments verbatim
88ignore-words-list = "provid,hass,followings,childs,explict,additionals,commitish,nam,connexion,claus,childrens,speaches,"
89skip = """*.js,*.svg,\
90.github/instructions/music-assistant-standards.instructions.md,\
91music_assistant/helpers/resources/countries.json,\
92music_assistant/helpers/resources/genres/genre_mapping.json,\
93music_assistant/providers/sonic_analysis/vendored_clap/**,\
94music_assistant/translations/*.json,\
95NOTICE,\
96"""
97
98[tool.setuptools]
99include-package-data = true
100packages = ["music_assistant"]
101platforms = ["any"]
102zip-safe = false
103
104[tool.setuptools.package-data]
105music_assistant = ["py.typed", "helpers/app_secrets.json"]
106
107[tool.ruff]
108fix = true
109show-fixes = true
110
111line-length = 100
112target-version = "py314"
113
114[tool.ruff.lint.pydocstyle]
115# Use Sphinx-style docstrings with :param: syntax
116# pep257 is the base convention, we enforce Sphinx-style parameters separately
117convention = "pep257"
118
119[tool.ruff.lint.pylint]
120
121max-args = 10
122max-branches = 25
123max-returns = 15
124max-statements = 50
125
126[tool.mypy]
127platform = "linux"
128python_version = "3.14"
129
130# set this to normal when we have fixed all exclusions
131follow_imports = "silent"
132
133# suppress errors about unsatisfied imports
134ignore_missing_imports = true
135
136# be strict
137check_untyped_defs = true
138disable_error_code = [
139 "annotation-unchecked",
140 "import-not-found",
141 "import-untyped",
142]
143disallow_any_generics = true
144disallow_incomplete_defs = true
145disallow_subclassing_any = true
146disallow_untyped_calls = true
147disallow_untyped_decorators = true
148disallow_untyped_defs = true
149enable_error_code = [
150 "ignore-without-code",
151 "redundant-self",
152 "truthy-iterable",
153]
154exclude = [
155 '^music_assistant/providers/sonic_analysis/vendored_clap/.*$',
156]
157extra_checks = false
158local_partial_types = true
159no_implicit_optional = true
160no_implicit_reexport = true
161packages = [
162 "tests",
163 "music_assistant",
164]
165show_error_codes = true
166strict_equality = true
167strict_optional = true
168warn_incomplete_stub = true
169warn_no_return = true
170warn_redundant_casts = true
171warn_return_any = true
172warn_unreachable = true
173warn_unused_configs = true
174warn_unused_ignores = true
175
176# plexapi ships a py.typed marker but its annotations are incomplete (unannotated
177# methods, attributes declared as None and only set dynamically). Treat it as an
178# untyped dependency rather than trusting its type info.
179[[tool.mypy.overrides]]
180follow_imports = "skip"
181module = ["plexapi", "plexapi.*"]
182
183[tool.ruff.format]
184# Force Linux/macOS line endings
185line-ending = "lf"
186
187[tool.pytest.ini_options]
188asyncio_mode = "auto"
189timeout = 120
190filterwarnings = [
191 # Suppress Python 3.14 AsyncMock internal warnings about unawaited coroutines
192 "ignore:coroutine.*was never awaited:RuntimeWarning",
193]
194
195[tool.ruff.lint]
196ignore = [
197 "ANN002", # Just annoying, not really useful
198 "ANN003", # Just annoying, not really useful
199 "ANN401", # Opinioated warning on disallowing dynamically typed expressions
200 "D203", # Conflicts with other rules
201 "D417", # False positives in some occasions
202 "EM101", # Just annoying, not really useful
203 "EM102", # Just annoying, not really useful
204 "FIX002", # Just annoying, not really useful
205 "PLR2004", # Just annoying, not really useful
206 "PGH004", # Just annoying, not really useful
207 "PD011", # Just annoying, not really useful
208 "S101", # assert is often used to satisfy type checking
209 "TC001", # Just annoying, not really useful
210 "TC003", # Just annoying, not really useful
211 "TD002", # Just annoying, not really useful
212 "TD003", # Just annoying, not really useful
213 "TD004", # Just annoying, not really useful
214 "TRY003", # Just annoying, not really useful
215 "TRY400", # Just annoying, not really useful
216 "COM812", # Conflicts with the Ruff formatter
217 "ASYNC109", # Not relevant, since we use helpers with configurable timeouts
218 "ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
219 "N818", # Just annoying, not really useful
220 "E501", # Handled by ruff formatter
221 "ASYNC240", # Not relevant, we use asyncio not trio/anyio
222 "UP047", # Not yet ready to migrate to type parameters
223 "PLW0108", # Just annoying, not really useful
224 "FURB110", # Just annoying, not really useful # TEMPORARY DISABLED rules # The below rules must be enabled later one-by-one !
225 "BLE001",
226 "FBT001",
227 "FBT002",
228 "FBT003",
229 "ANN001",
230 "ANN201",
231 "ANN202",
232 "TRY002",
233 "PTH103",
234 "PTH100",
235 "PTH110",
236 "PTH111",
237 "PTH112",
238 "PTH113",
239 "PTH118",
240 "PTH120",
241 "PTH123",
242 "G004",
243 "S104",
244 "S105",
245 "S106",
246 "SLF001",
247 "SIM102",
248 "PERF401",
249 "PERF402",
250 "ARG002",
251 "S311",
252 "TRY301",
253 "PLR0912",
254 "B904",
255 "TRY401",
256 "S324",
257 "ERA001",
258 "PTH206",
259 "C901",
260 "S304",
261 "TRY300",
262 "S608",
263 "B007",
264 "ANN204",
265]
266
267select = ["ALL"]
268# Enforce the multi-line docstring summary on the second line (the pep257 convention disables both
269# D212 and D213; we re-enable D213 so our "summary starts on the next line" style is checked).
270extend-select = ["D213"]
271
272[tool.ruff.lint.flake8-pytest-style]
273fixture-parentheses = false
274mark-parentheses = false
275
276[tool.ruff.lint.isort]
277known-first-party = ["music_assistant"]
278
279[tool.ruff.lint.mccabe]
280max-complexity = 25
281
282[[tool.uv.index]]
283explicit = true
284name = "pytorch-cpu"
285url = "https://download.pytorch.org/whl/cpu"
286
287[tool.uv]
288# Build urllib3-future (pulled in via niquests) from source so URLLIB3_NO_OVERRIDE=1 set during
289# installs takes effect, stopping it from hijacking the stock urllib3 namespace server-wide.
290no-binary-package = ["urllib3-future"]
291
292[tool.uv.sources]
293torch = {index = "pytorch-cpu"}
294torchaudio = {index = "pytorch-cpu"}
295
296[build-system]
297build-backend = "setuptools.build_meta"
298requires = ["setuptools>=61"]
299