[project]
name = "wafer-core"
version = "0.1.36"
description = "Core utilities and environments for Wafer GPU kernel optimization"
requires-python = ">=3.10"
dependencies = [
    "anthropic>=0.40.0",
    "anyio>=4.0.0",
    "trio>=0.24.0",
    "httpx>=0.25.0",
    "modal>=0.64.0",
    "paramiko>=3.0.0",
    "pyyaml>=6.0",
    # SSH clients for remote GPU execution
    "paramiko>=3.4.0", # sync SSH client
    "asyncssh>=2.14.0", # async SSH client
    "trio-asyncio>=0.15.0", # bridge asyncssh (asyncio) to trio
    # rollouts dependencies (now bundled in wafer_core.rollouts)
    "openai>=1.0.0",
    "dacite>=1.8.0",
    "aiohttp>=3.9.0",
    "markdownify>=0.11.0",
    "plotext>=5.3.2",
    # ROCprof dependencies
    "dnspython>=2.8.0",
    "pymongo>=4.16.0",
    "dash>=3.0.0", # Web framework for GUI
    "dash-bootstrap-components>=1.0.0", # UI components
    "dash-svg>=0.0.11", # SVG components for Dash
    "pandas~=3.0.0", # Data processing - allows 3.0.x patches
    "orjson>=3.9.0", # Fast JSON parsing (10-30x faster than stdlib)
    "ijson>=3.2.0", # Streaming JSON parser for progressive loading
    "numpy>=1.17.5", # Numerical computing
    "matplotlib>=3.0.0", # Additional plotting
    "tabulate>=0.8.0", # Table formatting
    "astunparse==1.6.2", # AST manipulation (required by some pandas operations)
    "colorlover>=0.3.0", # Color palettes for Plotly
    "plotille>=5.0.0", # Terminal plotting for memory charts
    "kaleido==0.2.1", # Static image export for Plotly
    "textual>=7.0.0", # Terminal UI framework
    "textual-plotext>=1.0.0", # Plotext integration for Textual
    "textual-fspicker>=0.6.0", # File picker for Textual
    "tqdm>=4.0.0", # Progress bars
    "setuptools", # Build tools (needed by some ROCm tools)
    "python-dotenv>=1.0.0", # Environment variable loading for rollouts CLI
    "supabase>=2.27.1",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "pytest-asyncio>=0.21.0",
    "ruff>=0.1.0",
]

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

[tool.hatch.metadata]
allow-direct-references = true

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

[tool.ruff]
line-length = 100
target-version = "py310"
preview = true  # Required for PLR1702 (too-many-nested-blocks)

[tool.ruff.lint]
select = [
    "E",       # pycodestyle errors
    "F",       # pyflakes
    "I",       # isort (import sorting)
    "ANN",     # flake8-annotations (enforce type annotations)
    "ASYNC",   # flake8-async (trio/asyncio best practices)
    "B",       # flake8-bugbear (common bugs)
    "UP",      # pyupgrade (modern Python patterns)
    "PLR0913", # too-many-arguments (Tiger Style: "hourglass shape: few parameters")
    "PLR0915", # too-many-statements (Tiger Style: 70 line limit)
    "PLR1702", # too-many-nested-blocks (Tiger Style: "centralize control flow")
    "PLW2901", # redefined-loop-variable (Carmack SSA: single assignment)
    "RET506",  # superfluous-else-raise (explicit control flow)
    "RET507",  # superfluous-else-continue (explicit control flow)
    "A",       # flake8-builtins (shadowing builtins like list, str)
    "RUF018",  # assignment-in-assert (catches typos)
    "TRY002",  # raise-vanilla-exception (use specific exceptions)
    "TRY003",  # raise-vanilla-args (proper exception messages)
    "TRY004",  # type-check-without-type-error (use TypeError for type checks)
    "TRY201",  # verbose-raise (use bare raise)
    "TRY300",  # try-consider-else (clear control flow)
    "TRY400",  # error-instead-of-exception (use logging.exception)
]
ignore = [
    "E501",    # Line too long (handled by formatter)
    "TRY003",  # Avoid specifying long messages outside exception class (too opinionated - revisit later)
    "TRY300",  # Consider moving to else block (too opinionated about try/except style - revisit later)
    "TRY400",  # Use logging.exception instead of logging.error (we prefer explicit error logging - revisit later)
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["ANN201", "ANN202"]  # Don't require return type annotations in tests
"wafer_core/utils/modal_execution/*.py" = ["PLR0913"]  # Modal RPC functions have many parameters
"wafer_core/utils/kernel_utils/targets/execution.py" = ["PLR0915"]  # run_operation_on_target has Modal+SSH paths (71 statements)
"wafer_core/environments/gpumode.py" = ["ANN401"]  # Environment.exec_tool uses Any for rollouts compatibility
"wafer_core/rollouts/providers/anthropic.py" = ["PLR0915", "PLR1702", "TRY002", "TRY004"]  # Complex provider with streaming, retry logic, and try/finally cleanup
"wafer_core/sandbox/*.py" = ["ASYNC109", "ASYNC240"]  # Sandbox uses subprocess timeout (not trio.fail_after) intentionally
"wafer_core/tools/bash_tool.py" = ["ASYNC109", "ASYNC221"]  # Bash tool uses subprocess with timeout
"wafer_core/utils/kernel_utils/defense.py" = ["ANN002", "ANN003", "ANN202", "PLR0913"]  # Defense functions use *args/**kwargs passthrough and have many config params
"wafer_core/utils/kernel_utils/test_reward_hacks.py" = ["ANN"]  # Test fixture file, annotations add noise
"tests/test_reward_hack_defenses.py" = ["ANN001"]  # Parametrized test args don't need annotations

[tool.ruff.lint.pylint]
max-args = 7             # Max function arguments (Tiger Style: few parameters)
max-statements = 70      # Max statements per function (Tiger Style: 70 line limit)
max-branches = 12        # Max if/elif branches
max-nested-blocks = 5    # Max nesting depth (Linus rule: "if you need more than 3 levels, you're screwed")

[tool.pytest.ini_options]
# Only run tests from the tests/ directory to avoid import issues with tests in package
testpaths = ["tests"]
# Exclude test files inside the package that aren't meant to be run directly
norecursedirs = [".venv", "node_modules", "dist", "build"]

[dependency-groups]
dev = [
    "pytest>=7.0.0",
    "pytest-asyncio>=0.21.0",
    "pytest-trio>=0.8.0",
    "pytest-cov>=4.0.0",
    "diff-cover>=7.0.0",
    "ruff>=0.1.0",
]
