/
/
/
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.12",
10 "Programming Language :: Python :: 3.13",
11]
12dependencies = [
13 "aiodns>=3.2.0", # Pin pycares to 4.11.0 until aiodns is updated to support pycares 5.0 API changes # pycares 5.0.0 (released 2025-12-10) has breaking changes incompatible with current aiodns
14 "pycares==4.11.0",
15 "aiohttp_asyncmdnsresolver==0.1.1",
16 "Brotli>=1.0.9",
17 "aiohttp==3.13.3",
18 "aiohttp-fast-zlib==0.3.0",
19 "aiofiles==24.1.0",
20 "aiorun==2025.1.1",
21 "aiosqlite==0.22.1",
22 "awesomeversion>=24.6.0",
23 "certifi==2025.11.12",
24 "colorlog==6.10.1",
25 "cryptography==46.0.5",
26 "chardet>=5.2.0",
27 "ifaddr==0.2.0",
28 "getmac==0.9.5",
29 "mashumaro==3.18",
30 "music-assistant-frontend==2.17.96",
31 "music-assistant-models==1.1.99",
32 "mutagen==1.47.0",
33 "orjson==3.11.5",
34 "pillow==12.1.1",
35 "podcastparser==0.6.11",
36 "propcache>=0.2.1",
37 "python-slugify==8.0.4",
38 "unidecode==1.4.0",
39 "xmltodict==1.0.2",
40 "shortuuid==1.0.13",
41 "zeroconf==0.148.0",
42 "uv>=0.8.0",
43 "librosa==0.11.0",
44 "gql[all]==4.0.0",
45 "aiovban>=0.6.3",
46 "aiortc>=1.6.0",
47 "pyjwt[crypto]>=2.10.1",
48]
49description = "Music Assistant"
50license = {text = "Apache-2.0"}
51readme = "README.md"
52requires-python = ">=3.12"
53version = "0.0.1.dev1"
54
55[project.optional-dependencies]
56test = [
57 "codespell==2.4.1",
58 "mypy==1.19.1",
59 "pre-commit==4.5.1",
60 "pre-commit-hooks==6.0.0",
61 "pytest==9.0.2",
62 "pytest-aiohttp==1.1.0",
63 "pytest-cov==7.0.0",
64 "syrupy==5.0.0",
65 "tomli==2.3.0",
66 "ruff==0.14.13",
67]
68
69[project.scripts]
70mass = "music_assistant.__main__:main"
71
72[tool.codespell]
73# explicit is misspelled in the iTunes API
74# "additionals" is a fixed field name from the Nicovideo API fixtures
75ignore-words-list = "provid,hass,followings,childs,explict,additionals,commitish,nam,"
76skip = """*.js,*.svg,\
77music_assistant/providers/itunes_podcasts/itunes_country_codes.json,\
78music_assistant/helpers/resources/genres/genre_mapping.json,\
79"""
80
81[tool.setuptools]
82include-package-data = true
83packages = ["music_assistant"]
84platforms = ["any"]
85zip-safe = false
86
87[tool.setuptools.package-data]
88music_assistant = ["py.typed"]
89
90[tool.ruff]
91fix = true
92show-fixes = true
93
94line-length = 100
95target-version = "py312"
96
97[tool.ruff.lint.pydocstyle]
98# Use Sphinx-style docstrings with :param: syntax
99# pep257 is the base convention, we enforce Sphinx-style parameters separately
100convention = "pep257"
101
102[tool.ruff.lint.pylint]
103
104max-args = 10
105max-branches = 25
106max-returns = 15
107max-statements = 50
108
109[tool.mypy]
110platform = "linux"
111python_version = "3.12"
112
113# set this to normal when we have fixed all exclusions
114follow_imports = "silent"
115
116# suppress errors about unsatisfied imports
117ignore_missing_imports = true
118
119# be strict
120check_untyped_defs = true
121disable_error_code = [
122 "annotation-unchecked",
123 "import-not-found",
124 "import-untyped",
125]
126disallow_any_generics = true
127disallow_incomplete_defs = true
128disallow_subclassing_any = true
129disallow_untyped_calls = true
130disallow_untyped_decorators = true
131disallow_untyped_defs = true
132enable_error_code = [
133 "ignore-without-code",
134 "redundant-self",
135 "truthy-iterable",
136]
137exclude = [
138 '^music_assistant/controllers/music.py$',
139 '^music_assistant/helpers/app_vars.py',
140 '^music_assistant/providers/apple_music/.*$',
141 '^music_assistant/providers/bluesound/.*$',
142 '^music_assistant/providers/chromecast/.*$',
143 '^music_assistant/providers/sonos/.*$',
144 '^music_assistant/providers/ytmusic/.*$',
145]
146extra_checks = false
147local_partial_types = true
148no_implicit_optional = true
149no_implicit_reexport = true
150packages = [
151 "tests",
152 "music_assistant",
153]
154show_error_codes = true
155strict_equality = true
156strict_optional = true
157warn_incomplete_stub = true
158warn_no_return = true
159warn_redundant_casts = true
160warn_return_any = true
161warn_unreachable = true
162warn_unused_configs = true
163warn_unused_ignores = true
164
165[tool.ruff.format]
166# Force Linux/macOS line endings
167line-ending = "lf"
168
169[tool.pytest.ini_options]
170addopts = "--cov music_assistant"
171asyncio_mode = "auto"
172filterwarnings = [
173 # Suppress Python 3.13 AsyncMock internal warnings about unawaited coroutines
174 "ignore:coroutine.*was never awaited:RuntimeWarning",
175]
176
177[tool.ruff.lint]
178ignore = [
179 "ANN002", # Just annoying, not really useful
180 "ANN003", # Just annoying, not really useful
181 "ANN401", # Opinioated warning on disallowing dynamically typed expressions
182 "D203", # Conflicts with other rules
183 "D213", # Conflicts with other rules
184 "D417", # False positives in some occasions
185 "EM101", # Just annoying, not really useful
186 "EM102", # Just annoying, not really useful
187 "FIX002", # Just annoying, not really useful
188 "PLR2004", # Just annoying, not really useful
189 "PGH004", # Just annoying, not really useful
190 "PD011", # Just annoying, not really useful
191 "S101", # assert is often used to satisfy type checking
192 "TC001", # Just annoying, not really useful
193 "TC003", # Just annoying, not really useful
194 "TD002", # Just annoying, not really useful
195 "TD003", # Just annoying, not really useful
196 "TD004", # Just annoying, not really useful
197 "TRY003", # Just annoying, not really useful
198 "TRY400", # Just annoying, not really useful
199 "COM812", # Conflicts with the Ruff formatter
200 "ASYNC109", # Not relevant, since we use helpers with configurable timeouts
201 "ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
202 "N818", # Just annoying, not really useful # TEMPORARY DISABLED rules # The below rules must be enabled later one-by-one !
203 "BLE001",
204 "FBT001",
205 "FBT002",
206 "FBT003",
207 "ANN001",
208 "ANN201",
209 "ANN202",
210 "TRY002",
211 "PTH103",
212 "PTH100",
213 "PTH110",
214 "PTH111",
215 "PTH112",
216 "PTH113",
217 "PTH118",
218 "PTH120",
219 "PTH123",
220 "PYI034",
221 "G004",
222 "PGH003",
223 "DTZ005",
224 "S104",
225 "S105",
226 "S106",
227 "SLF001",
228 "SIM102",
229 "PERF401",
230 "PERF402",
231 "ARG002",
232 "S311",
233 "TRY301",
234 "PLR0912",
235 "B904",
236 "TRY401",
237 "S324",
238 "DTZ006",
239 "ERA001",
240 "PTH206",
241 "C901",
242 "PTH119",
243 "PTH116",
244 "RUF012",
245 "S304",
246 "RUF006",
247 "TRY300",
248 "S608",
249 "S307",
250 "B007",
251 "ANN204",
252]
253
254select = ["ALL"]
255
256[tool.ruff.lint.flake8-pytest-style]
257fixture-parentheses = false
258mark-parentheses = false
259
260[tool.ruff.lint.isort]
261known-first-party = ["music_assistant"]
262
263[tool.ruff.lint.mccabe]
264max-complexity = 25
265