[build-system]
requires = [
    "hatchling>=1.8.1",
]
build-backend = "hatchling.build"

[project]
name = "music21"
dynamic = [
    "version",
]
description = "A Toolkit for Computer-Aided Musical Analysis and Computational Musicology."
readme = "README.md"
license = "BSD-3-Clause"
requires-python = ">=3.11"
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Environment :: Console",
    "Environment :: Web Environment",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
    "Intended Audience :: End Users/Desktop",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: BSD License",
    "Natural Language :: English",
    "Operating System :: MacOS",
    "Operating System :: Microsoft :: Windows",
    "Operating System :: OS Independent",
    "Operating System :: POSIX",
    "Programming Language :: Python",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Topic :: Artistic Software",
    "Topic :: Multimedia :: Sound/Audio",
    "Topic :: Multimedia :: Sound/Audio :: Conversion",
    "Topic :: Multimedia :: Sound/Audio :: MIDI",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Typing :: Typed",
]

dependencies = [
    "chardet",
    "joblib",
    "jsonpickle",
    "matplotlib",
    "more_itertools",
    "numpy>=1.26.4",
    "requests",
    "webcolors>=1.5",
]

[project.optional-dependencies]
extras = [
    'python-Levenshtein',
    'scipy',
]

[dependency-groups]
# `dev` pulls in every extra via the `music21[extras]` self-reference
# so `uv sync` alone is enough to develop and test music21.
dev = [
    'music21[extras]',
    'coverage',
    'coveralls',
    'docutils',
    'hatch',
    'hatchling',
    'ipython',
    'jupyter',
    'mypy>=1.16.0',
    'nbconvert',
    'nbval',
    'pylint>=3.2.0',
    'pytest',
    'ruff',
    'sphinx',
    'twine',
    'types-requests',
    'wheel',
]

[[project.authors]]
name = "Michael Scott Asato Cuthbert"
email = "michael.asato.cuthbert@gmail.com"

[project.urls]
Download = "https://github.com/cuthbertLab/music21/releases/"
Homepage = "https://github.com/cuthbertLab/music21"
Documentation = "https://www.music21.org/music21docs/"

[tool.uv]
default-groups = ["dev"]

[tool.hatch.version]
path = "music21/_version.py"

[tool.hatch.build.targets.sdist]
include = [
    "music21",
]

[tool.mypy]
allow_redefinition = true
warn_no_return = false

[tool.ruff]
line-length = 100
exclude = [
    'documentation/source/usersGuide/usersGuide_02_notes.ipynb',  # demonstrates invalid syntax
]

[tool.ruff.lint]
# Flake8, pycodestyle, flake8-quotes + pyflakes
select = ['E', 'W', 'Q', 'F', 'B']

dummy-variable-rgx = "^(_.*|unused.*|i|j|counter|junk|dummy)"

# add once allowed: E122 (continuation line missing indentation) and E124 (bracket align)

ignore = [
    # ignore E127 (over idented) once implemented by Ruff
    # ignore E128 (under idented) once implemented by Ruff

    # space around | -- nice to omit in int|str
    'E227',
    # 0 blank lines -- good test but something going wrong in new algorithm
    'E301',
    # blank lines -- expected 2 blank lines
    'E302',
    # too many blank lines (maybe turn on soon)
    'E303',
    # assignment to lambda is sometimes fine by me.
    'E731',
    # "except" and then "raise without from" -- should do soon.
    'B904',
    # zip without strict= ... nice to turn this off someday, when code more mature
    'B905',
]

[tool.ruff.lint.per-file-ignores]
'music21/abcFormat/testFiles.py' = ['E501']  # line-too-long
# when allowed again -- chord/tables omit E122, E127
'music21/chord/tables.py' = [
    'E201',
    'E202',
    'E203',
    'E221',
    'E231',
    'E241',
]
'music21/common/__init__.py' = ['F403']  # F403 -- star imports
'music21/features/__init__.py' = ['F403']
'music21/humdrum/__init__.py' = ['E501']  # line-too-long
'music21/humdrum/testFiles.py' = ['E501']  # line-too-long
'music21/musicxml/testFiles.py' = ['E501']  # line-too-long
'music21/musicxml/testPrimitive.py' = ['E501']  # line-too-long
'music21/romanText/testFiles.py' = ['E501']  # line-too-long
'music21/search/__init__.py' = [
    'F403',
    'F405',
]  # F405 -- star exports
'music21/variant.py' = ['E501']  # line-too-long
'*.ipynb' = [
    'F403',
    'F405',
    'W293',  # blank-line contains whitespace -- it happens
]

[tool.ruff.lint.flake8-quotes]
inline-quotes = 'single'
multiline-quotes = 'single'
docstring-quotes = 'single'

[tool.pytest.ini_options]
addopts = ['--doctest-modules', '-p', 'music21.test.pytest_plugin']
doctest_optionflags = ['NORMALIZE_WHITESPACE', 'ELLIPSIS']
# defaults are test_*.py and *_test.py; add tests.py so that
# music21's house style (e.g. stream/tests.py, humdrum/tests.py) is picked up
# during directory-based collection.  --doctest-modules collects doctests from
# every .py regardless of this list.
python_files = ['test_*.py', '*_test.py', 'tests.py']
