/
/
/
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.91",
31 "music-assistant-models==1.1.98",
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.0"
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,\
78"""
79
80[tool.setuptools]
81include-package-data = true
82packages = ["music_assistant"]
83platforms = ["any"]
84zip-safe = false
85
86[tool.setuptools.package-data]
87music_assistant = ["py.typed"]
88
89[tool.ruff]
90fix = true
91show-fixes = true
92
93line-length = 100
94target-version = "py312"
95
96[tool.ruff.lint.pydocstyle]
97# Use Sphinx-style docstrings with :param: syntax
98# pep257 is the base convention, we enforce Sphinx-style parameters separately
99convention = "pep257"
100
101[tool.ruff.lint.pylint]
102
103max-args = 10
104max-branches = 25
105max-returns = 15
106max-statements = 50
107
108[tool.mypy]
109platform = "linux"
110python_version = "3.12"
111
112# set this to normal when we have fixed all exclusions
113follow_imports = "silent"
114
115# suppress errors about unsatisfied imports
116ignore_missing_imports = true
117
118# be strict
119check_untyped_defs = true
120disable_error_code = [
121 "annotation-unchecked",
122 "import-not-found",
123 "import-untyped",
124]
125disallow_any_generics = true
126disallow_incomplete_defs = true
127disallow_subclassing_any = true
128disallow_untyped_calls = true
129disallow_untyped_decorators = true
130disallow_untyped_defs = true
131enable_error_code = [
132 "ignore-without-code",
133 "redundant-self",
134 "truthy-iterable",
135]
136exclude = [
137 '^music_assistant/controllers/music.py$',
138 '^music_assistant/helpers/app_vars.py',
139 '^music_assistant/providers/apple_music/.*$',
140 '^music_assistant/providers/bluesound/.*$',
141 '^music_assistant/providers/chromecast/.*$',
142 '^music_assistant/providers/sonos/.*$',
143 '^music_assistant/providers/ytmusic/.*$',
144]
145extra_checks = false
146local_partial_types = true
147no_implicit_optional = true
148no_implicit_reexport = true
149packages = [
150 "tests",
151 "music_assistant",
152]
153show_error_codes = true
154strict_equality = true
155strict_optional = true
156warn_incomplete_stub = true
157warn_no_return = true
158warn_redundant_casts = true
159warn_return_any = true
160warn_unreachable = true
161warn_unused_configs = true
162warn_unused_ignores = true
163
164[tool.ruff.format]
165# Force Linux/macOS line endings
166line-ending = "lf"
167
168[tool.pytest.ini_options]
169addopts = "--cov music_assistant"
170asyncio_mode = "auto"
171filterwarnings = [
172 # Suppress Python 3.13 AsyncMock internal warnings about unawaited coroutines
173 "ignore:coroutine.*was never awaited:RuntimeWarning",
174]
175
176[tool.ruff.lint]
177ignore = [
178 "ANN002", # Just annoying, not really useful
179 "ANN003", # Just annoying, not really useful
180 "ANN401", # Opinioated warning on disallowing dynamically typed expressions
181 "D203", # Conflicts with other rules
182 "D213", # Conflicts with other rules
183 "D417", # False positives in some occasions
184 "EM101", # Just annoying, not really useful
185 "EM102", # Just annoying, not really useful
186 "FIX002", # Just annoying, not really useful
187 "PLR2004", # Just annoying, not really useful
188 "PGH004", # Just annoying, not really useful
189 "PD011", # Just annoying, not really useful
190 "S101", # assert is often used to satisfy type checking
191 "TC001", # Just annoying, not really useful
192 "TC003", # Just annoying, not really useful
193 "TD002", # Just annoying, not really useful
194 "TD003", # Just annoying, not really useful
195 "TD004", # Just annoying, not really useful
196 "TRY003", # Just annoying, not really useful
197 "TRY400", # Just annoying, not really useful
198 "COM812", # Conflicts with the Ruff formatter
199 "ASYNC109", # Not relevant, since we use helpers with configurable timeouts
200 "ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
201 "N818", # Just annoying, not really useful # TEMPORARY DISABLED rules # The below rules must be enabled later one-by-one !
202 "BLE001",
203 "FBT001",
204 "FBT002",
205 "FBT003",
206 "ANN001",
207 "ANN201",
208 "ANN202",
209 "TRY002",
210 "PTH103",
211 "PTH100",
212 "PTH110",
213 "PTH111",
214 "PTH112",
215 "PTH113",
216 "PTH118",
217 "PTH120",
218 "PTH123",
219 "PYI034",
220 "G004",
221 "PGH003",
222 "DTZ005",
223 "S104",
224 "S105",
225 "S106",
226 "SLF001",
227 "SIM102",
228 "PERF401",
229 "PERF402",
230 "ARG002",
231 "S311",
232 "TRY301",
233 "PLR0912",
234 "B904",
235 "TRY401",
236 "S324",
237 "DTZ006",
238 "ERA001",
239 "PTH206",
240 "C901",
241 "PTH119",
242 "PTH116",
243 "RUF012",
244 "S304",
245 "RUF006",
246 "TRY300",
247 "S608",
248 "S307",
249 "B007",
250 "ANN204",
251]
252
253select = ["ALL"]
254
255[tool.ruff.lint.flake8-pytest-style]
256fixture-parentheses = false
257mark-parentheses = false
258
259[tool.ruff.lint.isort]
260known-first-party = ["music_assistant"]
261
262[tool.ruff.lint.mccabe]
263max-complexity = 25
264