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']
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
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