[project] name = "compose-farm" dynamic = ["version"] description = "Compose Farm - run docker compose commands across multiple hosts" readme = "README.md" authors = [ { name = "Bas Nijholt", email = "bas@nijho.lt" } ] requires-python = ">=3.11" dependencies = [ "typer>=0.9.0", "pydantic>=2.0.0", "asyncssh>=2.14.0", "pyyaml>=6.0", "rich>=13.0.0", ] [project.scripts] compose-farm = "compose_farm.cli:app" cf = "compose_farm.cli:app" [build-system] requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" [tool.hatch.version] source = "vcs" [tool.hatch.build.hooks.vcs] version-file = "src/compose_farm/_version.py" [tool.hatch.build.targets.wheel] packages = ["src/compose_farm"] [tool.ruff] target-version = "py311" line-length = 100 [tool.ruff.lint] select = ["ALL"] ignore = [ "T20", # allow print-style streaming output "S101", # assertions are fine in tests "S603", # `subprocess` is constrained and intentional "ANN002", # allow args without type comments in some call sites "ANN003", # allow kwargs without type comments in some call sites "ANN401", # allow `Any` for external library hooks "D401", # short docstrings are acceptable "D402", # allow "Return" in first line "PLW0603", # global statements not used here "SLF001", # internal attributes may be accessed in helpers "PLR0913", # typer command signatures naturally take many params "TD002", # allow TODOs without issue links "E501", # formatter handles line length "TRY300", # nested try blocks sometimes clearer for SSH flows "FBT001", # boolean positional args in CLI are acceptable "FBT002", # boolean keyword-only args in CLI are acceptable "BLE001", # broad exceptions acceptable when surfacing remote errors "COM812", # avoid formatter conflicts "ISC001", # allow implicit string concat on single line when clearer ] [tool.ruff.lint.per-file-ignores] "tests/*" = ["S101", "PLR2004", "S108", "D102", "D103"] # relaxed docstrings + asserts in tests [tool.ruff.lint.mccabe] max-complexity = 18 [tool.mypy] python_version = "3.11" strict = true plugins = ["pydantic.mypy"] [[tool.mypy.overrides]] module = "asyncssh.*" ignore_missing_imports = true [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_decorators = false [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] asyncio_default_fixture_loop_scope = "function" addopts = [ "--cov=compose_farm", "--cov-report=term", "--cov-report=xml", "--cov-report=html", "--no-cov-on-fail", "-v", ] [tool.coverage.run] omit = [] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "raise NotImplementedError", "if TYPE_CHECKING:", 'if __name__ == "__main__":', ] [dependency-groups] dev = [ "mypy>=1.19.0", "pre-commit>=4.5.0", "pytest>=9.0.2", "pytest-asyncio>=1.3.0", "pytest-cov>=6.0.0", "ruff>=0.14.8", "types-pyyaml>=6.0.12.20250915", "markdown-code-runner>=0.7.0", ]