[build-system]
requires = ["setuptools>=80", "setuptools_scm>=8"]
build-backend = "setuptools.build_meta"

[project]
authors = [
    { name = "ESMValTool Development Team", email = "esmvaltool-dev@listserv.dfn.de" },
]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Environment :: Console",
    "Intended Audience :: Developers",
    "Intended Audience :: Science/Research",
    "Natural Language :: English",
    "Operating System :: POSIX :: Linux",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Topic :: Scientific/Engineering",
    "Topic :: Scientific/Engineering :: Atmospheric Science",
    "Topic :: Scientific/Engineering :: GIS",
    "Topic :: Scientific/Engineering :: Hydrology",
    "Topic :: Scientific/Engineering :: Physics",
]
dynamic = ["readme", "version"]
dependencies = [
    "cf-units",
    "dask!=2024.8.0",   # https://github.com/dask/dask/issues/11296
    "distributed",
    "esmvalcore",
    "matplotlib",
    "numpy>=2",
    "pyyaml",
    "rich",
    "scitools-iris>=3.11",
]
description = "A community tool for pre-processing data from Earth system models in CMIP and running analysis scripts"
license = "Apache-2.0"
license-files = ["LICENSE", "NOTICE"]
name = "ESMValTool"
requires-python = ">=3.12,<3.15"

[project.entry-points."console_scripts"]
nclcodestyle = "esmvaltool.utils.nclcodestyle.nclcodestyle:_main"

[project.entry-points."esmvaltool_commands"]
colortables = "esmvaltool.utils.color_tables.show_color_tables:ColorTables"
data = "esmvaltool.cmorizers.data.cmorizer:DataCommand"
develop = "esmvaltool.utils.develop:DevelopCommand"

[project.optional-dependencies]
all = [
    "aiohttp",
    "cartopy",
    "cdo",
    "cdsapi",
    "cfgrib",
    "cftime",
    "cmocean",
    "ecmwf-api-client",
    "eofs",
    # "ESMPy",               # not on PyPI
    "esmf-regrid>=0.10.0", # iris-esmf-regrid #342
    "fiona",
    "fire",
    "fsspec",
    "GDAL",
    "ipython",
    "jinja2",
    "joblib",
    "lime",
    "mapgenerator>=1.0.5",
    "natsort",
    "nc-time-axis",
    "netCDF4",
    "numba",
    "openpyxl",
    "packaging",
    "pandas",
    "progressbar2",
    "pyproj>=2.1",
    "python-dateutil",
    "rasterio>=1.3.10",
    "requests",
    "scikit-image",
    "scikit-learn>=1.4.0", # github.com/ESMValGroup/ESMValTool/issues/3504
    "scipy",
    "seaborn",
    "seawater",
    "shapely>=2",
    "xarray>=0.12.0",
    "xesmf>=0.7.1",
    "xgboost>1.6.1",       # github.com/ESMValGroup/ESMValTool/issues/2779
    "xlsxwriter",
    "zarr",
]

[project.urls]
Code = "https://github.com/ESMValGroup/ESMValTool"
Community = "https://github.com/ESMValGroup/Community"
Documentation = "https://docs.esmvaltool.org"
Homepage = "https://esmvaltool.org"
Issues = "https://github.com/ESMValGroup/ESMValTool/issues"

[tool.setuptools]
include-package-data = true
packages = ["esmvaltool"]
zip-safe = false

[tool.setuptools.dynamic]
readme = { file = "README.md", content-type = "text/markdown" }

[tool.setuptools_scm]
version_scheme = "release-branch-semver"

# Configure tests

[tool.pytest.ini_options]
addopts = [
    "--import-mode=importlib",
    "--doctest-modules",
    "--ignore=archive",
    "--ignore=doc/sphinx/source/conf.py",
    "--cov=esmvaltool",
    "--cov-report=xml:test-reports/coverage.xml",
    "--cov-report=html:test-reports/coverage_html",
    "--html=test-reports/report.html",
]
log_cli_level = "INFO"
env = { MPLBACKEND = "Agg" }
log_level = "WARNING"
minversion = "6"
markers = [
    "diagnostic_image_output: test produces images that will be compared with imagehash",
]
testpaths = ["tests"]
xfail_strict = true

[tool.coverage.run]
parallel = true
source = ["esmvaltool"]

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "if __name__ == .__main__.:",
    "if TYPE_CHECKING:",
]

# Configure linters

[tool.codespell]
ignore-words-list = "emac,hist,oce,pres,vas,runn"

[tool.ruff]
line-length = 79
show-fixes = true

[tool.ruff.lint]
select = ["ALL"]
ignore = [
    "ANN",    # Type hints are not required for diagnostic scripts.
    "D105",   # Disable missing docstring in magic method as these are well defined.
    'D107',   # __init__ docstring is usually part of the class docstring.
    "E501",   # Disable line-too-long as this is taken care of by the formatter.
    "FIX002", # TODOs can be useful when something cannot be done immediately.
    "T20",    # Allow printing things in diagnostic scripts.
    "TD",     # Do not impose a specific format on TODOs.
    "S603",   # Uncheckable
    "TRY003", # Fixing this would require many exceptions
]
exclude = [
    "archive/*",                          # No longer maintained code.
    "doc/sphinx/source/conf.py",          # Partly automatically generated.
    "esmvaltool/utils/nclcodestyle/*.py", # This is a slightly modified copy of pycodestyle.
]

[tool.ruff.lint.per-file-ignores]
"esmvaltool/cmorizers/**.py" = [
    "ARG001", # Ignore unused function argument for CMORizers because these are caused by implementing the interface.
]
"tests/**.py" = [
    "B011",    # `assert False` is valid test code.
    "S101",    # Use of assert in tests code is required.
    "SLF001",  # Testing private members is ok
    "PLR2004", # Magic values in tests are fine.
    # Docstrings in tests are only needed if the code is not self-explanatory.
    "D100", # Missing docstring in public module
    "D101", # Missing docstring in public class
    "D102", # Missing docstring in public method
    "D103", # Missing docstring in public function
    "D104", # Missing docstring in public package
]

[tool.ruff.lint.isort]
known-first-party = ["esmvaltool"]

[tool.ruff.lint.pydocstyle]
convention = "numpy"

[tool.ruff.lint.pylint]
max-args = 7 # Use a more relaxed value than the default.

[tool.ruff.lint.mccabe]
max-complexity = 15 # Use a more relaxed value than the default.

[tool.pydocstyle]
convention = "numpy"

[tool.pixi.dependencies]
"cf-units" = "*"
"dask" = "!=2024.8.0"  # https://github.com/dask/dask/issues/11296
"distributed" = "*"
"iris" = ">=3.11"
"matplotlib-base" = "*"
"numpy" = ">=2"
"pyyaml" = "*"
"rich" = "*"

[tool.pixi.workspace]
channels = [
    "conda-forge",
    # "conda-forge/label/esmvalcore_rc",  # Uncomment to use ESMValCore release candidates.
    "nodefaults",
]
platforms = ["linux-64", "osx-arm64"]

[tool.pixi.pypi-dependencies]
"ESMValTool" = { path = "." , editable = true }

[tool.pixi.environments]
# The environments below correspond to the different packages we have on conda-forge:
# esmvaltool-python (default), esmvaltool-ncl, esmvaltool-r, and esmvaltool.
# For each package, we have a development environment (with the `dev`, `doc`,
# and `test` features) and a regular environment without these features.
#
# Environment for running diagnostics that only require Python.
default = { features = ["esmvalcore", "all", "dev", "doc", "test"], solve-group = "default" }
esmvaltool-python = { features = ["esmvalcore", "all"], solve-group = "default" }
# Environment for running the minimum set of dependencies, just enough to run
# the Python example diagnostic (examples/recipe_python.yml).
esmvaltool-base-dev = { features = ["esmvalcore", "dev", "doc", "test"], solve-group = "default" }
esmvaltool-base = { features = ["esmvalcore"], solve-group = "default" }
# Environment for running diagnostics that also require NCL.
esmvaltool-ncl-dev = { features = ["esmvalcore", "all", "ncl", "dev", "doc", "pys2index", "test"], solve-group = "ncl" }
esmvaltool-ncl = { features = ["esmvalcore", "all", "ncl", "pys2index"], solve-group = "ncl" }
# Environment for running diagnostics that also require R.
esmvaltool-r-dev = { features = ["esmvalcore", "all", "r", "dev", "doc", "test"], solve-group = "r" }
esmvaltool-r = { features = ["esmvalcore", "all", "r"], solve-group = "r" }
# Environment for running any diagnostic.
esmvaltool-dev = { features = ["esmvalcore", "all", "ncl", "r", "dev", "doc", "pys2index", "test"], solve-group = "esmvaltool" }
esmvaltool = { features = ["esmvalcore", "all", "ncl", "pys2index", "r"], solve-group = "esmvaltool" }
# Environment with the development version of ESMValCore for testing new features and bug fixes.
esmvalcore-dev = { features = ["esmvalcore-dev", "all", "dev", "doc", "test"], solve-group = "esmvalcore-dev" }
# Environments for running tests with specific Python versions.
test-py312 = { features = ["py312", "esmvalcore", "all", "test", "pys2index"], solve-group = "py312" }
test-py313 = { features = ["py313", "esmvalcore", "all", "test", "pys2index"], solve-group = "py313" }
test-py314 = { features = ["py314", "esmvalcore", "all", "test"], solve-group = "default" }

# Default ESMValCore version.
[tool.pixi.feature.esmvalcore.dependencies]
"ESMValCore" = ">=2.15.0"

# Use the development version of ESMValCore to test new features and bug fixes.
[tool.pixi.feature.esmvalcore-dev.pypi-dependencies]
"ESMValCore" = { git = "https://github.com/ESMValGroup/ESMValCore.git", branch = "main" }
# Replace the line above with the following to use a local ESMValCore repository instead.
# "ESMValCore" = { path = "../ESMValCore", editable = true }

# Tools for running the tests.
[tool.pixi.feature.test.dependencies]
"pytest" = ">=6.0.0"
"pytest-cov" = ">=2.10.1"
"pytest-env" = "*"
"pytest-html" = "!=2.1.0"
"pytest-metadata" = ">=1.5.1"
"pytest-mock" = "*"
"pytest-xdist" = "*"

# Tools for building the documentation.
[tool.pixi.feature.doc.dependencies]
"autodocsumm" = ">=0.2.2"
"myst-nb" = "*"
"pydata-sphinx-theme" = "*"
"sphinx" = ">=6.1.3,<9"     # <9 due to ESMValTool/issues/4316
"sphinx-design" = "*"

# Other development tools.
[tool.pixi.feature.dev.dependencies]
"codespell" = "*"
"jupyterlab" = "*"
"pre-commit" = "*"
"py-spy" = "*"
"ruff" = "==0.14.5" # keep in sync with .pre-commit-config.yaml
"yamllint" = "*"

# Feature for running tests with Python 3.12.
[tool.pixi.feature.py312.dependencies]
"python" = "3.12.*"

# Feature for running tests with Python 3.13.
[tool.pixi.feature.py313.dependencies]
"python" = "3.13.*"

# Feature for running tests with Python 3.14.
[tool.pixi.feature.py314.dependencies]
"python" = "3.14.*"

# Feature for running Python diagnostics and CMORizers.
# This list should match the `all` extra under [project.optional-dependencies].
[tool.pixi.feature.all.dependencies]
"aiohttp" = "*"
"cartopy" = "*"
"cdo" = ">=2.3.0"
"cdsapi" = "*"
"cfgrib" = "*"
"cftime" = "*"
"cmocean" = "*"
"cython" = "*"
"ecmwf-api-client" = "*"
"eofs" = "*"
"esmpy" = "*"
"fiona" = "*"
"fire" = "*"
"fsspec" = "*"
"gdal" = ">=3.9.0"
"ipython" = "*"
"imagehash" = "*"
"iris-esmf-regrid" = ">=0.10.0"  # github.com/SciTools-incubator/iris-esmf-regrid/pull/342
"jinja2" = "*"
"joblib" = "*"
"lime" = "*"
"mapgenerator" = ">=1.0.5"
"natsort" = "*"
"nc-time-axis" = "*"
"netCDF4" = "*"
"numba" = "*"
"openpyxl" = "*"
"packaging" = "*"
"pandas" = "*"
"pip" = "!=21.3"
"progressbar2" = "*"
"prov" = "*"
"pyproj" = ">=2.1"
"python" = ">=3.12"
"python-cdo" = "*"
"python-dateutil" = "*"
"rasterio" = ">=1.3.10"
"requests" = "*"
"rich" = "*"
"scikit-image" = "*"
"scikit-learn" = ">=1.4.0"  # github.com/ESMValGroup/ESMValTool/issues/3504
"scipy" = "*"
"seaborn" = "*"
"seawater" = "*"
"shapely" = ">=2.0.2"
"xarray" = ">=0.12.0"
"xesmf" = ">=0.7.1"
"xgboost" = ">1.6.1"  # github.com/ESMValGroup/ESMValTool/issues/2779
"xlsxwriter" = "*"
"zarr" = "*"

# Feature for running diagnostics and CMORizers that also require NCL.
[tool.pixi.feature.ncl]
platforms = ["linux-64"]

[tool.pixi.feature.ncl.dependencies]
"ncl" = ">=6.6.2"
"cdo" = "*"
"nco" = "*"
"imagemagick" = "*"

[tool.pixi.feature.pys2index.dependencies]
"pys2index" = "*"

# Feature for running diagnostics that also require R.
[tool.pixi.feature.r]
platforms = ["linux-64"]

[tool.pixi.feature.r.dependencies]
"cdo" = "*"
"r-base" = ">=4.4"
"r-abind" = "*"
"r-akima" = "*"
"r-climdex.pcic" = "*"
"r-climprojdiags" = "*"
"r-docopt" = "*"
"r-dotcall64" = "*"
"r-functional" = "*"
"r-ggplot2" = "*"
"r-gridextra" = "*"
"r-logging" = "*"
"r-mapproj" = "*"
"r-maps" = "*"
"r-multiapply" = "*"
"r-ncdf4" = "*"
"r-ncdf4.helpers" = "*"
"r-pcict" = "*"
"r-plyr" = "*"
"r-r.utils" = "*"
"r-rcolorbrewer" = "*"
"r-rcpp" = "*"
"r-s2dverification" = "*"
"r-snow" = "*"
"r-spei" = "*"
"r-udunits2" = "*"
"r-yaml" = "*"

# Tasks for use with `pixi run <task>`.
[tool.pixi.tasks]
test = {cmd = "pytest", description = "Run the tests"}
doc = {cmd = "sphinx-build doc/sphinx/source doc/sphinx/build", description = "Build the documentation"}
