Use Measured Cloud with Dagster
Goal
You'll model Measured Cloud validations as Dagster asset checks so a failed validation shows up in the asset's reliability sidebar — the shift-left, asset-based story — instead of in a separate dashboard. Instead of hand-rolling an op that POSTs to the API and polls for a result, you install the official provider package and register checks with one line each.
The provider package source lives at providers/dagster/ in this repo and ships under the name dagster-dq-cloud (#86). Publishing to PyPI is on the roadmap; until then, install directly from the repo (see below).
Coming from checkpoints? Checkpoints were retired (see Checkpoints (retired)).
dq_cloud_check(checkpoint_id=...),DQCloudResource.run_checkpointandcheckpoints_to_asset_checksstill work for one release as deprecated shims: they run the validation the checkpoint maps to and emit aDeprecationWarning. Per-runrule_parametersoverrides are no longer applied. Switch tovalidation_id=— the shims are removed in the next major release.
Prereqs
- Dagster
>=1.7running somewhere — Dagster OSS / self-managed, Dagster+, or a localdagster dev. - Python 3.9 or newer in the Dagster environment.
- A Measured Cloud organisation + workspace + at least one validation to run. Every data asset with rules has a default validation, so to check one table, run its default validation.
- Workspace editor role or higher in Measured Cloud (so your personal API key can trigger runs).
Steps
-
Install the provider in the Dagster environment.
While the package is not yet on PyPI, install it straight from the repo:
pip install "git+https://github.com/tomplace/DQ-Cloud.git#subdirectory=providers/dagster"For a stable install, pin to a tag or commit SHA in the URL.
-
Generate a personal API key in Measured Cloud.
In the UI, open Settings → API keys → New API key (see Create an API token). Copy the
dqk_…value somewhere safe — you only see it once. Export it (and your org / workspace ids) to the environment so they stay out of code:export DQ_CLOUD_API_KEY="dqk_…"export DQ_CLOUD_ORG_ID="00000000-0000-0000-0000-000000000001"export DQ_CLOUD_WORKSPACE_ID="00000000-0000-0000-0000-000000000002" -
Wire the resource into your
Definitions.DQCloudResourceis a DagsterConfigurableResource. Set it up once and useEnvVarso the API key never lands in code or the Definitions snapshot.from dagster import Definitions, EnvVarfrom dagster_dq_cloud import DQCloudResourcedq = DQCloudResource(base_url="https://dq.example.com", # no trailing slashapi_key=EnvVar("DQ_CLOUD_API_KEY"), # dqk_… personal API keyorg_id=EnvVar("DQ_CLOUD_ORG_ID"),workspace_id=EnvVar("DQ_CLOUD_WORKSPACE_ID"),) -
Register an asset check with
dq_cloud_check.The factory collapses the "start a validation run → poll → emit
AssetCheckResult" pattern into one line.from dagster import AssetKey, Definitions, assetfrom dagster_dq_cloud import DQCloudResource, dq_cloud_checkVALIDATION_ORDERS = "00000000-0000-0000-0000-0000000000aa"@assetdef orders() -> int:...orders_dq = dq_cloud_check(validation_id=VALIDATION_ORDERS,asset=AssetKey(["orders"]),name="orders_dq", # optional — defaults to dq_cloud_<short_id>)defs = Definitions(assets=[orders],asset_checks=[orders_dq],resources={"dq": dq},)When Dagster materialises the check, the provider starts an execution run of the validation (
POST /api/v2/.../validations/{id}/run), polls it until it finishes, and returns anAssetCheckResultwithpassedset when the runsucceeded, plus metadata forrules_evaluated,rules_failed,status,health_score,execution_run_id,validation_id, and a deep-linkvalidation_url. Passrule_ids=[...]to run only some of the validation's rules. A failed validation surfaces on the asset's check sidebar; passblocking=Trueif you also want it to fail the upstream materialisation. -
(Deprecated) Auto-discover checks from the checkpoint catalog.
checkpoints_to_asset_checksreads the retired checkpoint catalog and emits one check per checkpoint that targets a configured asset; each check runs the validation its checkpoint maps to. It is kept for one release. Prefer explicitdq_cloud_check(validation_id=...)lines for new code. The caller supplies the bridge between Dagster asset keys and clouddata_asset_ids — the most ergonomic form stashes the cloud id in the asset's metadata:from dagster import Definitions, assetfrom dagster_dq_cloud import checkpoints_to_asset_checks@asset(metadata={"dq_cloud_asset_id": "44444444-4444-4444-4444-444444444444"})def orders() -> int:...checks = checkpoints_to_asset_checks(dq, assets=[orders])defs = Definitions(assets=[orders],asset_checks=checks,resources={"dq": dq},)A bare asset with no
dq_cloud_asset_id(in a tuple or in metadata) raisesValueErrorat definition-load time, so a forgotten mapping fails loudly instead of silently dropping checks.
Verify
- In the Dagster UI, the asset's Checks tab lists
orders_dq. Run it — the check turns green when the validation run succeeds, red when it fails, with the failed-rule count in the metadata. - The check metadata includes a
validation_urlthat deep-links to the run in Measured Cloud. - In Measured Cloud, a new run appears on the validation's Execution runs card (trigger
api).
Troubleshooting
DQCloudClientError: … returned 401— thedqk_…key was rejected. Confirm it hasn't been revoked in Settings → API keys, and that the workspace it's scoped to matches the resource'sworkspace_id.DQCloudClientError: run_validation response missing execution_run_id— the API accepted the request but returned an unexpected ack shape. Confirm thebase_urlpoints at the API host (not the UI) and that the validation id exists in the configured workspace.TimeoutError: … did not reach a terminal state within Ns— Measured Cloud accepted the run but it didn't finish in time. Bumprun_timeout_secondson the resource, or check the worker logs. If the validation includes an agent-mode datasource, confirm the on-prem agent is connected: the run staysrunninguntil the agent reports.ValueError: … without a dq_cloud_asset_id— an asset passed tocheckpoints_to_asset_checkscarries no cloud asset id. Add a(asset, dq_cloud_asset_id)tuple or adq_cloud_asset_idmetadata entry.
Out of scope
- The Dagster+ / Dagster Cloud OAuth flow — the recipe above assumes a self-managed Dagster with a personal API key.