Contributing to Scherlok¶
Thanks for considering a contribution. Scherlok is small, opinionated, and pragmatic — most changes can land in a weekend.
Ways to contribute¶
- Pick a good first issue — bite-sized tasks (1–2 hours) with clear acceptance criteria
- Add a new connector — Postgres / BigQuery / Snowflake live in
src/scherlok/connectors/; each implementsBaseConnector(connect, list_tables, get_row_count, get_columns, get_column_stats, get_last_modified) - Add a new detector —
src/scherlok/detector/— a function that takes(table, current_profile, stored_profile)and returns anomaly dicts built withmake_anomaly(fields.py), so they carry the structured fields; a new anomaly type goes inanomaly_types.pywith a one-sentence hint inhints.py - Improve docs — module READMEs, examples, error messages
- Report bugs — use the bug template; include the failing connection scheme + connector version
Development setup¶
git clone https://github.com/rbmuller/scherlok
cd scherlok
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # base + tests + lint
pip install -e ".[dbt]" # if you'll touch dbt integration
ruff check src/ tests/ action/ integrations/
pytest # 200+ tests, runs in <1s
To exercise the full flow end-to-end against a real Postgres + real dbt, see examples/dbt_smoke/ — a runnable mini dbt project.
Code style¶
- Python 3.10+, type hints on public APIs
- Ruff for linting (config in
pyproject.toml); CI fails on warnings - Line length 100
- Prefer explicit imports; avoid wildcard
- Use
pathlib.Pathoveros.path - String literals: define constants for anything used twice (no magic strings)
- No new comments unless they explain why something non-obvious is happening; well-named identifiers carry intent
Testing¶
- All new code paths need tests. Aim for ≥1 happy-path test + ≥1 edge case
- Use mocks for connectors that need cloud creds (see
tests/test_bigquery.py,tests/test_snowflake.py) - Use fixtures for shared test data (see
tests/fixtures/dbt/) - Snapshot tests for the dashboard guard against accidental UI regressions
Commit format¶
Conventional Commits lite — Scherlok uses these prefixes:
feat:— user-visible new functionalityfix:— bug fixchore:— non-user-facing maintenance (build, deps, refactor, release prep)docs:— docs/comments onlytest:— test-only changes
Subject line ≤72 chars. Body explains the why, not the what. Multi-line is fine.
Pull request flow¶
- Branch from
main—feat/<short-description>orfix/<short-description> - One PR per logical change. Don't bundle a feature with unrelated cleanup
- Run
ruff check src/ tests/ action/ integrations/andpytestlocally before pushing — CI mirrors these - Open the PR with a body that covers: what changed, why, how to test, screenshots if UI
- Wait for review — single approval required to merge
- Squash merge is the default
Adding a new connector¶
The fastest path is to read postgres.py and clone its shape. Then:
- Create
src/scherlok/connectors/<name>.pyextendingBaseConnector - Register the scheme(s) in
src/scherlok/connectors/__init__.py; wrap the import intry/except ImportErrorif the driver is heavy - Add the optional dependency in
pyproject.tomlunder[project.optional-dependencies] - Add tests in
tests/test_<name>.pyusing mocks (see existing connector tests) - Document in the README's "Supported adapters" section
If your warehouse is one users wire up via dbt, add the adapter mapping in src/scherlok/dbt/profiles.py too.
Adding a new alerter¶
src/scherlok/alerter/ hosts webhook + email today. To add another channel:
- Create
src/scherlok/alerter/<name>.pywith asend_<name>(target, anomalies)function - Wire it into the dispatch path in
src/scherlok/cli.py(look for_dispatch_alerts) - Add tests with mocked HTTP / SMTP
Release process¶
For maintainers only:
- Bump the version everywhere it is stated:
versioninpyproject.toml,__version__insrc/scherlok/__init__.py,versionindbt_project.yml, bothversionfields inserver.json, therevision: vX.Y.Zdbt install snippet inREADME.mdandwebsite/dbt-package.md, and thescherlok-versiondefault inaction.yml(the version the GitHub Action installs).tests/test_release_versions.pyfails if any of them disagree (a partial bump is how v1.0.0 never reached PyPI) - Bump the Claude Desktop bundle:
versioninmcpb/manifest.json, plusversionand thescherlok[...]>=X.Y.Zfloor inmcpb/pyproject.toml(no lockfile — the bundle is packed during the release that publishes this version) - Move
[Unreleased]to[X.Y.Z] — YYYY-MM-DDinCHANGELOG.md - PR + merge to main —
tests/test_mcpb.pyfails the PR if the bundle manifest or itsscherlokfloor drifted from the package version git tag vX.Y.Z && git push origin vX.Y.Z.github/workflows/release.ymlruns tests, publishes to PyPI via trusted publishing, packsscherlok-X.Y.Z.mcpband creates the GitHub Release with auto-extracted notes and the bundle attached.github/workflows/publish-mcp.ymlthen publishesserver.jsonto the MCP Registry (GitHub OIDC). Check the listing at https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.rbmuller/scherlok; re-run it withgh workflow run publish-mcp.yml --ref mainif needed- Once PyPI shows
X.Y.Z(the action installs it from there), move the GitHub Action's major tag:git tag -f v1 vX.Y.Z && git push -f origin v1. Users onrbmuller/scherlok@v1get the new release on their next run. Release and Docker only run onvX.Y.Ztags, so this publishes nothing. A breaking change to the action's inputs or outputs needs a newv2tag instead - GitHub Marketplace: the first time, open the release on GitHub, edit it, tick Publish this Action to the GitHub Marketplace (two-factor authentication and the Marketplace agreement are required), pick the categories and save. For later releases, check that https://github.com/marketplace/actions/scherlok-verify shows the new version, and tick the box on the release if it does not
Code of conduct¶
Be helpful. Assume good faith. Disagree on technical merits, not on people. Threads should leave both sides smarter than they entered.