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

[project]
name = "gpumod"
version = "0.1.6"
description = "GPU Service Manager for ML workloads"
readme = "README.md"
requires-python = ">=3.12"
license = "Apache-2.0"
authors = [{ name = "Jaigouk Kim" }]
keywords = ["gpu", "vram", "ml", "vllm", "llama-cpp", "service-manager"]
classifiers = [
    "Development Status :: 3 - Alpha",
    "License :: OSI Approved :: Apache Software License",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: System :: Hardware",
]
dependencies = [
    "httpx>=0.27,<1.0",
    "pydantic>=2.0,<3.0",
    "pydantic-settings>=2.0,<3.0",
    "aiosqlite>=0.20,<1.0",
    "typer>=0.9,<1.0",
    "rich>=14.0,<15.0",
    "jinja2>=3.1,<4.0",
    "huggingface-hub>=1.4.1,<2.0",
    "fastmcp>=3.0,<4.0",
    "docker>=7.0,<8.0",
    "textual>=1.0,<2.0",
    "watchfiles>=1.1.1,<2.0",
    "nvidia-ml-py>=12.560,<13.0",  # pynvml for GPU memory polling
    "rlms>=0.1.0,<1.0",            # RLM for knowledge-base consulting
]

[project.scripts]
gpumod = "gpumod.cli:run_cli"

[project.urls]
Homepage = "https://github.com/jaigouk/gpumod"
Repository = "https://github.com/jaigouk/gpumod"
Issues = "https://github.com/jaigouk/gpumod/issues"

[dependency-groups]
dev = [
    "pytest>=8.0,<10.0",
    "pytest-asyncio>=1.0,<2.0",
    "pytest-cov>=5.0,<8.0",
    "mypy>=1.10,<2.0",
    "ruff>=0.5,<1.0",
    "types-aiofiles>=24.1,<26.0",
    "types-pyyaml>=6.0.12.20250915,<7.0",
    "types-docker>=7.0,<8.0",
    "pytest-timeout>=2.3,<3.0",
    "matplotlib>=3.9,<4.0",
]
docs = [
    "mkdocs>=1.6,<2.0",
    "mkdocs-material>=9.5,<10.0",
    "mkdocs-exclude>=1.0,<2.0",
    "pillow>=10.0,<12.0",
    "cairosvg>=2.7,<3.0",
]

[tool.hatch.build.targets.wheel]
packages = ["src/gpumod"]

[tool.ruff]
target-version = "py312"
line-length = 99

[tool.ruff.lint]
select = [
    # Core
    "E",  # pycodestyle errors
    "F",  # pyflakes
    "W",  # pycodestyle warnings
    "I",  # isort (import ordering)
    "UP", # pyupgrade (modern Python)
    # Bug prevention
    "B",     # flake8-bugbear (common bugs)
    "SIM",   # flake8-simplify
    "ASYNC", # flake8-async (async correctness)
    "PERF",  # perflint (performance anti-patterns)
    "FURB",  # refurb (modern idioms)
    # SOLID: Single Responsibility
    "C90", # mccabe complexity (functions doing too much)
    "PLR", # pylint refactor (too many args/returns/branches)
    # SOLID: Dependency Inversion + Liskov
    "TCH", # type-checking imports
    "ANN", # type annotations (enables DI contracts)
    # Security (critical for service manager with sudo)
    "S", # flake8-bandit
    # Code quality
    "RUF", # ruff-specific rules
    "N",   # pep8-naming conventions
    "ARG", # unused arguments (dead code)
    "RET", # flake8-return (clean return patterns)
    "PIE", # flake8-pie (misc lints)
    "RSE", # flake8-raise (exception best practices)
    "T20", # flake8-print (no print in production)
    "PT",  # pytest style (test quality)
    "FLY", # flynt (f-string conversion)
]
ignore = [
    # Too noisy / not useful
    "PLR2004", # magic-value-comparison (too many false positives)
    "S101",    # assert (needed in tests, and fine for invariants)
    "ANN401",  # Any type (sometimes necessary for generic wrappers)
    # Test-specific (handled by per-file-ignores)
    "ARG001", # unused-function-argument (fixtures, mocks)
    "ARG002", # unused-method-argument (ABC overrides)
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = [
    "S101",    # assert is the whole point of tests
    "S105",    # hardcoded passwords in test fixtures are fine
    "S108",    # /tmp usage in tests is fine
    "S607",    # partial paths in test subprocess calls are fine
    "ANN",     # don't require annotations in tests
    "ARG",     # unused args common in fixtures
    "PLR2004", # magic values fine in test assertions
    "PLR0913", # many args fine in test helpers
    "RUF012",  # mutable class defaults in test classes
    "RUF015",  # next() vs [0] fine in test assertions
    "RUF043",  # ambiguous pytest.raises patterns (existing tests)
    "PT012",   # multi-statement pytest.raises blocks (existing tests)
    "PT017",   # assert in except blocks (existing test patterns)
    "T20",     # print in tests is fine for debugging
]
"src/gpumod/cli*.py" = [
    "T20", # typer CLI output uses print
]
"scripts/**/*.py" = [
    "T20",     # scripts use print for output
    "ANN",     # don't require annotations in utility scripts
    "E501",    # line length relaxed for scripts with long strings
    "PERF401", # list comprehension not required
    "PT028",   # test-like functions with defaults are fine
    "C901",    # complexity limit relaxed for test harnesses
    "TC003",   # type checking imports not critical
    "E402",    # imports not at top (code examples in docstrings)
    "PLR0912", # too many branches (test harnesses)
    "PLR0915", # too many statements (test harnesses)
]

[tool.ruff.lint.mccabe]
max-complexity = 10

[tool.ruff.lint.pylint]
max-args = 7
max-returns = 6
max-branches = 12

[tool.mypy]
strict = true
python_version = "3.12"
disallow_untyped_defs = true
warn_return_any = true
warn_unused_configs = true

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

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
markers = [
    "integration: marks tests as integration tests",
    "slow: marks tests as slow-running",
    "gpu_required: marks tests requiring a real NVIDIA GPU (skipped on CPU-only)",
    "docker_required: marks tests requiring Docker daemon (skipped when unavailable)",
]

[tool.coverage.run]
source = ["src/gpumod"]

[tool.coverage.report]
fail_under = 80
