Connectors¶
| Database | Extra | Connection string |
|---|---|---|
| PostgreSQL | built in | postgres://user:pass@host:5432/db |
| BigQuery | scherlok[bigquery] |
bigquery://project-id/dataset-name |
| Snowflake | scherlok[snowflake] |
snowflake://account/database/schema (credentials via SNOWFLAKE_USER, SNOWFLAKE_PASSWORD, SNOWFLAKE_WAREHOUSE) |
| MySQL | scherlok[mysql] |
mysql://user:pass@host:3306/dbname |
| DuckDB | scherlok[duckdb] |
duckdb:///path/to/file.db or duckdb:///:memory: |
Every connector profiles through metadata and aggregate queries only (information_schema, COUNT, AVG, STDDEV, top values). Scherlok never copies rows out of your warehouse.
Want another warehouse? Databricks is tracked in #37; the connector interface is five methods in src/scherlok/connectors/base.py.
BigQuery notes¶
BigQuery¶
Scherlok talks to BigQuery via the official google-cloud-bigquery client. It profiles tables in a dataset, learns their baseline, and flags drift on subsequent runs. This guide is for someone wiring Scherlok against a real GCP project — including the parts most quick-starts skip: what gets queried, what it costs, and where the current rough edges are.
Install¶
pip install scherlok[bigquery]
The [bigquery] extra adds google-cloud-bigquery>=3.0. Authentication is handled by that client — no extra deps.
Authentication¶
v0.7.0 supports Application Default Credentials (ADC) only. Service-account JSON via env var / file is on the roadmap (see Limitations below). For day-to-day:
# Interactive (local dev)
gcloud auth application-default login
# CI / non-interactive: rely on Workload Identity Federation (preferred)
# or a service-account JSON key surfaced as ADC:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json
ADC discovery order is the standard google-auth library order: env var → gcloud config → metadata server. If the principal lacks bigquery.dataViewer on the dataset, scherlok connect returns a clear permission denied — the authenticated identity lacks access to this dataset error.
Connect¶
scherlok connect bigquery://my-gcp-project/my_dataset
The format is bigquery://<project-id>/<dataset>. Scherlok stores the connection in ~/.scherlok/config.json (or in SCHERLOK_CONNECTION for the MCP server flow). Connection is verified at this step — a successful scherlok connect means the credentials can list tables in the dataset.
First run¶
# Profile every table in the dataset
scherlok investigate
# Run again later — Scherlok compares against the baseline
scherlok watch
The first investigate establishes the baseline. The next watch detects:
- Volume drops / spikes (z-score on row count)
- Schema drift (column added / removed / type changed)
- Freshness misses (table last-modified older than tolerance)
- NULL surge on any column
- Distribution shift (z-score on mean of numeric columns)
- Cardinality explosion (distinct count blowing up)
Output is one ✓/✗ line per table plus a summary. CI gating is scherlok ci --fail-on critical (or the scherlok check alias).
What it costs (BigQuery billing transparency)¶
BigQuery charges by bytes scanned. An investigate run is not free — knowing what it costs upfront is part of being honest about production use. Per table, Scherlok issues roughly:
| Query | Source | Billed? |
|---|---|---|
SELECT COUNT(*) AS cnt FROM <fqn> |
full table scan | Yes — scans all rows |
| Schema metadata | INFORMATION_SCHEMA.COLUMNS |
No (metadata) |
| Last-modified | __TABLES__ metadata |
No |
Per column: AVG, STDDEV, MIN, MAX |
scans that column | Yes |
| Per column: top 5 values | scans that column with GROUP BY |
Yes |
For a dataset with T tables averaging C columns each: ~T + 2·T·C billed scans per investigate / watch. A 10-table dataset with 100-column-wide tables ≈ 2,010 queries. Most are tiny because they read a single column, but plan accordingly.
Recommendations for production:
- Prototype on a single small table with
scherlok investigate --select <table>before pointing at the full dataset (the--selectfilter limits scope). - Monitor cost via
region-X.INFORMATION_SCHEMA.JOBS_BY_USERfilteringuser_email = '<scherlok-sa-email>'for the first week. - Schedule
watchon the cadence that matches your data — daily for slow-moving marts, hourly for ingest tables. Each run repeats the same scans.
A maximum_bytes_billed guard is on the roadmap to make per-query budget caps explicit. Until then, control cost via cadence + --select scoping.
dbt + BigQuery¶
If you already use dbt with BigQuery, skip the connect step and let Scherlok read profiles.yml:
# After `dbt run`:
scherlok dbt --project-dir ./my_dbt_project
Scherlok auto-resolves the BQ connection from profiles.yml, discovers every materialized model (table/incremental/view/materialized_view), and profiles them. dbt-style ✓/✗ per model, with the lineage downstream-impact suffix on anomaly messages: Affects 3 downstream models: dim_revenue, mart_finance, dashboard_kpis.
Full dbt integration docs: src/scherlok/dbt/README.md.
MCP + BigQuery (use it from Claude)¶
Since v0.7.0, scherlok-mcp ships built-in. Wire it to BigQuery for an agent that profiles and detects anomalies directly:
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"scherlok": {
"command": "scherlok-mcp",
"env": {
"SCHERLOK_CONNECTION": "bigquery://my-gcp-project/my_dataset",
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/sa-key.json"
}
}
}
}
Restart the client, then prompt: "Use scherlok to profile every table on this connection and report any CRITICAL anomalies." Claude calls investigate and watch directly and reasons over the structured results. The agent never sees the connection string or the SA key — both are resolved by the MCP server from the env block.
CI/CD (GitHub Actions example)¶
# .github/workflows/data-quality.yml
name: data-quality
on:
schedule: [{cron: "0 6 * * *"}]
workflow_dispatch:
jobs:
scherlok-watch:
runs-on: ubuntu-latest
permissions:
id-token: write # Workload Identity Federation
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/scherlok/providers/github
service_account: scherlok-runner@my-gcp-project.iam.gserviceaccount.com
- run: |
pip install scherlok[bigquery]
scherlok config --store s3://my-bucket/scherlok/profiles.db # shared baseline
scherlok ci bigquery://my-gcp-project/my_dataset --fail-on critical \
--webhook ${{ secrets.SLACK_WEBHOOK }}
Notes for prod:
- Profiles baseline lives in
~/.scherlok/profiles.dbby default. For shared CI baselines across runs, persist it viascherlok config --store s3://...(the remote-storage layer is built-in). - Workload Identity Federation beats long-lived SA keys; the workflow above shows the WIF pattern. If you must use a JSON key, mount it via the
authaction'scredentials_jsoninput and Scherlok will pick it up through ADC.
Limitations¶
These are honest gaps in v0.7.0, tracked openly so prospects can plan around them:
- Auth: ADC only. Service-account JSON works via
GOOGLE_APPLICATION_CREDENTIALS(because google-auth treats that as ADC), but there's no native--service-account-jsonflag. A first-class flag is a likely v0.8 addition — track via a new issue if you need it. - Cost cap: not enforced. No
maximum_bytes_billedper query. Currently controllable via cadence +--selectscoping. A--max-bytes-billedflag is the cleanest fix. - Single dataset per connection. A connection string addresses one project + one dataset. Cross-dataset profiling needs multiple
connectinvocations or runningdbtmode where the manifest spans datasets. - Region/location. The connector lets
google-cloud-bigquerydiscover the dataset's region. Multi-region datasets are fine; if you hit a region mismatch, the BQ error surfaces directly.
If any of these block your use case, open an issue describing the constraint — they're tracked under help wanted.