[project]
name = "mcma-backend"
version = "0.1.0"
description = "Self-hosted, offline-first music service — backend (hexagonal architecture)"
requires-python = ">=3.14"
dependencies = [
    # web
    "fastapi>=0.115",
    "uvicorn[standard]>=0.32",
    "python-multipart>=0.0.12",
    # data
    "sqlalchemy[asyncio]>=2.0.36",
    "asyncpg>=0.30",
    "alembic>=1.14",
    # config / validation
    "pydantic>=2.10",
    "pydantic-settings>=2.6",
    # cache / queue
    "redis>=5.2",
    "arq>=0.26",
    # auth
    "pyjwt>=2.10",
    "pwdlib[argon2]>=0.2.1",
    # symmetric encryption for the recoverable Subsonic app-password (Fernet)
    "cryptography>=44.0",
    # outbound http (ML client, MusicBrainz, AcoustID)
    "httpx>=0.28",
    # embedded audio tag reading (enrichment tag pre-pass)
    "mutagen>=1.47",
    # S3-compatible object storage
    "aioboto3>=13.0",
    # logging
    "structlog>=24.4",
]

[project.scripts]
mcma = "app.cli:main"

[dependency-groups]
dev = [
    "pytest>=8.3",
    "pytest-asyncio>=0.24",
    "ruff>=0.8",
    "mypy>=1.13",
    "asgi-lifespan>=2.1",
]

[tool.uv]
package = true

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["app"]

# ---------------------------------------------------------------------------
# Tooling
# ---------------------------------------------------------------------------
[tool.ruff]
target-version = "py314"
line-length = 100
src = ["app", "tests"]

[tool.ruff.lint]
select = [
    "E", "F", "W",   # pycodestyle / pyflakes
    "I",             # isort
    "N",             # pep8-naming
    "UP",            # pyupgrade
    "B",             # bugbear
    "ASYNC",         # async pitfalls
    "RUF",
]
ignore = ["B008"]  # FastAPI Depends() in defaults is idiomatic

[tool.ruff.lint.per-file-ignores]
# Subsonic query params are camelCase by spec (artistCount, songId, …); the
# handler arg names must match the wire names exactly.
"app/api/rest/*" = ["N803"]

[tool.mypy]
python_version = "3.14"
strict = true
plugins = ["pydantic.mypy"]
warn_unused_ignores = true
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = ["arq.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["aioboto3.*", "aiobotocore.*", "botocore.*"]
ignore_missing_imports = true

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
addopts = "-ra"
