01

Single attack

The simplest usage: instantiate with a seed, call one attack, inspect the result. No health check required, just a quick look at what an attack does to your data.

"""Standalone quickstart: run a single attack against a DataFrame."""
import pandas as pd
from havoc_monkey import HavocMonkey

df = pd.read_csv("sample.csv")
monkey = HavocMonkey(seed=42)

attacked = monkey.null_flood(df, cols=["amount"], pct=0.15)
print(f"Nulls injected into 'amount': {attacked['amount'].isna().sum()}")
# → Nulls injected into 'amount': 75

attacked = monkey.schema_drift(df, attack="drop", col="category")
print(f"Columns after schema_drift: {list(attacked.columns)}")
# → Columns after schema_drift: ['id', 'amount', 'timestamp', 'score', 'count']

02

Campaign with health check

The full pattern: define what "working" means for your pipeline as a health_check callable, then run a campaign. Each attack gets classified as PASSED, FAILED, or ERROR against that definition.

"""Run a full campaign with a health_check callback."""
import pandas as pd
from havoc_monkey import HavocMonkey


def my_pipeline(df: pd.DataFrame) -> pd.DataFrame:
    assert "category" in df.columns, "missing required column: category"
    assert df["amount"].notnull().all(), "amount column contains nulls"
    return df.groupby("category")["amount"].sum().reset_index()


def health_check(df: pd.DataFrame) -> bool:
    result = my_pipeline(df)
    assert len(result) > 0, "empty output"
    return True


df = pd.read_csv("sample.csv")
monkey = HavocMonkey(seed=42)

report = monkey.campaign(
    df,
    attacks=[
        {"name": "null_flood",   "params": {"cols": ["amount"], "pct": 0.3}},
        {"name": "schema_drift", "params": {"attack": "drop", "col": "category"}},
        {"name": "volume_shock", "params": {"attack": "empty"}},
    ],
    health_check=health_check,
)

print(report)
# HAVOC-MONKEY CAMPAIGN REPORT  seed=42  attacks=3  3/3 FAILED
# [HIGH]      null_flood   → nulls injected: 150  health_check: FAILED
# [CRITICAL]  schema_drift → col dropped: category  health_check: FAILED
# [CRITICAL]  volume_shock → rows: 500→0  health_check: FAILED

03

Pytest plugin

The plugin auto-registers on install, no imports needed. Pattern A uses the havoc_monkey_instance fixture for direct single-attack assertions. Pattern B uses the @pytest.mark.havoc_monkey marker to run a full campaign and assert on the report.

examples/pytest_example/test_my_pipeline.py
import pandas as pd
import pytest


def my_pipeline(df: pd.DataFrame) -> pd.DataFrame:
    assert "category" in df.columns, "missing required column: category"
    assert df["amount"].notnull().all(), "amount column contains nulls"
    return df.groupby("category")["amount"].sum().reset_index()


@pytest.fixture
def my_df():
    return pd.DataFrame({
        "amount": [10.0, 20.0, 30.0, 40.0],
        "category": ["A", "B", "A", "B"],
    })


# Pattern A, fixture: attack directly, assert on pipeline output.
def test_handles_nulls(havoc_monkey_instance, my_df):
    attacked = havoc_monkey_instance.null_flood(my_df, cols=["amount"], pct=0.5)
    with pytest.raises(AssertionError):
        my_pipeline(attacked)


# Pattern B, marker: run a full campaign and assert on the report.
@pytest.mark.havoc_monkey(attacks=["null_flood", "schema_drift", "volume_shock"])
def test_pipeline_resilience(havoc_monkey_instance, my_df):
    def hc(df):
        result = my_pipeline(df)
        assert len(result) > 0, "empty output"
        return True

    report = havoc_monkey_instance.campaign(
        my_df,
        attacks=[
            {"name": "null_flood",   "params": {"cols": ["amount"], "pct": 0.5}},
            {"name": "schema_drift", "params": {"attack": "drop", "col": "category"}},
            {"name": "volume_shock", "params": {"attack": "empty"}},
        ],
        health_check=hc,
    )
    assert report.failed + report.errors == report.total

# Run with: pytest examples/pytest_example/ --havoc-seed 42