This file provides guidance for automated AI agents working with the DoWhy codebase. It covers the repository structure, architectural goals, and the checks that must pass before any PR can be merged.
DoWhy is a Python library for causal inference. It supports two complementary frameworks:
- Potential outcomes (classic treatment/control)
- Graphical causal models (GCM) (DAG-based)
Both frameworks are unified under a four-step workflow: Model → Identify → Estimate → Refute.
dowhy/ # Main package
__init__.py # Public exports: CausalModel, identify_effect_*, EstimandType
causal_model.py # CausalModel: orchestrates the 4-step workflow
causal_estimator.py # Base class for all estimators
causal_graph.py # Graph operations and DAG handling
causal_refuter.py # Base class for refuters
causal_estimators/ # 15+ estimation method implementations
causal_identifier/ # Identification algorithms (do-calculus)
causal_refuters/ # Robustness / sensitivity tests
gcm/ # Graphical Causal Models module (root cause analysis, anomaly detection, etc.)
api/ # Public API surface
do_samplers/ # Sampling from interventional distributions
graph_learners/ # Structure learning algorithms
data_transformers/ # Data preprocessing
timeseries/ # Time series causal inference
causal_prediction/ # ML-based prediction (neural networks, dataloaders)
utils/ # Shared utilities
datasets.py # Built-in datasets for testing/demos
tests/ # Test suite (mirrors dowhy/ structure)
docs/ # Sphinx documentation
source/
contributing/ # Developer & contribution guides
example_notebooks/ # Jupyter notebook examples
user_guide/ # End-user documentation
pyproject.toml # Build config, dependencies, tool config (Poetry)
.flake8 # flake8 config
- Four-step causal workflow: Every contribution should fit cleanly into Model, Identify, Estimate, or Refute. Do not conflate steps.
- Backwards compatibility: The
CausalModelpublic API is stable. New methods should extend, not replace, existing interfaces. - Two frameworks, one API: Changes that touch one framework (potential outcomes vs. GCM) should not break the other.
- Extensibility: New estimators inherit from
CausalEstimator; new refuters fromCausalRefuter. Follow existing class hierarchies. - Minimal required dependencies: Core functionality should remain available without optional extras (
plotting,pygraphviz,econml). Guard optional imports with try/except and raise informative errors. - Python 3.9–3.13: All code must be compatible with Python 3.9 through 3.13.
pip install --upgrade pip
poetry install -E "plotting" # Standard dev installOptional extras:
poetry install -E "pygraphviz" # Graph visualization via graphviz
poetry install -E "econml" # EconML CATE estimatorsOn Linux, pygraphviz may require installation of graphviz and its developer package
All three checks must pass before a PR can merge.
| Tool | Purpose | Config |
|---|---|---|
black |
Code formatting | Line length 120, targets py39–py313 |
isort |
Import sorting | Profile black, line length 120, multi-line output 3 |
flake8 |
Linting | Max line 127, max complexity 10, hard errors on E9/F63/F7/F82 |
# Auto-fix formatting
poetry run poe format
# Check only (what CI runs)
poetry run poe format_check
poetry run poe lint- Maximum line length: 120 characters (black) / 127 characters (flake8 hard limit).
- Cyclomatic complexity per function: ≤ 10.
- Import order: standard library → third-party → local, each group sorted.
isortenforces this automatically. - Do not add
# noqasilences unless genuinely unavoidable; document why if you do.
# Standard test run (excludes `advanced` and `econml` markers)
poetry run poe test
# Skip notebook execution (faster)
poetry run poe test_no_notebooks
# Run only EconML tests
poetry run poe test_econml
# Run the full test suite (including `advanced`)
poetry run poe test_advanced
# Run a specific subdirectory
poetry run pytest -v tests/causal_refuters
# Run only tests you marked `focused` (debug helper)
poetry run poe test_focused| Marker | Meaning |
|---|---|
advanced |
Skipped by default; run only on package-level updates |
notebook |
Executes Jupyter notebooks; slow |
econml |
Requires econml extra |
focused |
Temporary debug marker; never commit with this marker |
- Mirror the source layout:
tests/gcm/fordowhy/gcm/, etc. - New features must include tests.
- Avoid brittle assertions on floating-point results; use tolerances (
pytest.approx,np.testing.assert_allclose). - Use
@pytest.mark.advancedfor tests that are slow or depend on heavy external libraries. - Do not remove or weaken existing tests.
DCO sign-off is mandatory. Every commit must be signed off:
git commit --signoff -m "descriptive message"
# or shorthand:
git commit -s -m "descriptive message"If you forgot:
# Single commit
git commit --amend --no-edit --signoff
# Multiple commits — squash then sign
git reset --soft HEAD~<N>
git commit -s -m "new descriptive message"Commits without a DCO sign-off cannot be merged.
Before opening or updating a PR, confirm:
-
poetry run poe lintpasses (no hard flake8 errors). -
poetry run poe format_checkpasses (black + isort compliant). -
poetry run poe testpasses (or a justification is provided for a new failure). - New code is covered by tests.
- All commits include a DCO sign-off (
Signed-off-by:trailer in the commit message). For example, check withgit log --format='%h %s%n%n%b'and verify each commit body containsSigned-off-by:. - If a new dependency was added or
poetry.lockchanged, a justification is included in the PR description. - Optional imports are guarded and fail gracefully.
- Public API additions are documented (docstrings +
docs/RST where appropriate).
- DoWhy uses Poetry for dependency management.
- Do not edit
poetry.lockmanually; usepoetry add/poetry update. - Updating
poetry.lockin a PR requires a written justification explaining why the update is necessary. - Keep new dependencies to a minimum; prefer libraries already used in the project.
- Source docs are in
docs/source/(Sphinx + RST). - Jupyter notebook examples live in
docs/source/example_notebooks/. - New public functions/classes need numpy-style docstrings.
- After significant API additions, update the relevant
.rstfile underdocs/source/.
- Mixing frameworks: The
gcmmodule and the classicCausalModelworkflow are separate. Do not cross-wire them without a clear interface boundary. - Missing optional-import guards: Imports of
matplotlib,pygraphviz,pydot,torch, etc. must be inside atry/except ImportErrorblock with a user-friendly error message. - Breaking
CausalModelconstructor or method signatures: This is a stable public API; use keyword arguments and default values to extend it. - Removing or altering test markers: Changing an
advancedmarker to a default-run test may slow CI unexpectedly. - Long functions: flake8 enforces complexity ≤ 10; refactor complex logic into helper functions.
- Not running
formatbefore committing: black and isort will cause CI to fail if code is not formatted.