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