Skip to main content

gx-configure-checkpoint

Turn a working batch definition and expectation suite into a persisted, re-runnable checkpoint with post-run actions — bind them into a validation definition, group into a named checkpoint, verify it by running it once, and hand off a scheduler-agnostic run snippet. Use when a user wants their checks to survive the session ("make this re-runnable", "schedule this", "notify us when this fails", "update our Data Docs after a run"), or when validation results were printed once and never saved.

Jump to install

Source facts

Repository
fivetran/great_expectations
Last source activity
August 19, 2026 at 13:22
Detected SKILL.md language
English
Stars
11,823
Forks
1,851

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
6 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
gx-configure-checkpoint
description
Turn a working batch definition and expectation suite into a persisted, re-runnable checkpoint with post-run actions — bind them into a validation definition, group into a named checkpoint, verify it by running it once, and hand off a scheduler-agnostic run snippet. Use when a user wants their checks to survive the session ("make this re-runnable", "schedule this", "notify us when this fails", "update our Data Docs after a run"), or when validation results were printed once and never saved.
license
Apache-2.0
# Configure a Great Expectations checkpoint This skill takes a user from "here is a suite that already validated my data" to a **persisted, re-runnable checkpoint** — a named, saved grouping of one or more validation definitions plus the actions that should fire after a run — verified by actually running it once. Two objects, each built on work an earlier skill already did: - A **validation definition** binds one batch definition to one expectation suite: the specific slice of data, and the specific checks to run against it. - A **checkpoint** is the named, persisted grouping of validation definitions plus the post-run actions to fire — notifications, a Data Docs update — run as one operation. Everything here goes through Great Expectations' public configuration API and produces ordinary project artifacts. Nothing depends on this skill being present afterwards — the checkpoint this flow builds runs from a plain Python script outside any agent session, which is the point of it. ## The flow 1. **Preflight** — find out which project (or in-memory session) you are operating on, and tell the user. See `references/preflight.md`. 2. **Check the precondition** — at least one expectation suite and one working batch definition must already exist. If either is missing, hand off and stop. 3. **Bind** — pair batch definitions and suites into validation definitions. 4. **Group and add** — collect validation definitions into a named checkpoint with post-run actions, chosen from `references/action-catalog.md`, persisting it explicitly as part of the same step. 5. **Never rely on the run call to persist it for you** — step 4's explicit add is what makes that safe; see why below. 6. **Verify** by running once, and report per-validation-definition and per-expectation outcomes. 7. **Confirm persistence**, and offer write-out when the session is in memory. 8. **Hand off** a run snippet that re-runs the checkpoint from outside this session — see `references/run-and-schedule.md`. Steps 2 and 5, and the fetch-first rule below, are where this flow fails destructively rather than loudly if skipped or reordered. Do not skip step 6 either — a checkpoint that was never run is not a verified result. ## What this skill will not do without being asked <!-- consent-gate: install --> - **Install, upgrade, or remove a package, or otherwise modify the interpreter, virtual environment, environment variables, or shell state.** Not in response to an error, not preemptively while checking whether something is present, and not by announcing an intention to install with a chance to decline attached — none of those is the user asking. When something is missing, name it and hand over the command; running it is a separate act that starts only from the user's own instruction, later. <!-- consent-gate: project --> - **Create a project directory on the user's disk.** Only after the user has agreed to write the session out and named where. <!-- consent-gate: config-file --> - **Edit an existing project's `great_expectations.yml`.** Only after telling the user what the edit does and getting a yes. <!-- consent-gate: saved-file --> - **Write a file to the user's disk that the user did not ask for and locate.** Offer it in the conversation first. ## Step 1 — Preflight Follow `references/preflight.md` in full before touching anything. It establishes whether you are working against a project on disk or an in-memory session, tells you what to announce to the user, and covers the environment problems that silently masquerade as "no project found". The outcome you carry forward is a `context` object and one fact: whether the session is file-backed or in memory. Both are fully supported paths, and the difference matters again at step 7. ## Step 2 — The precondition: a suite and a working batch definition A checkpoint has nothing to run without both a batch definition to read data through and an expectation suite to check it with. **There is no acceptable way to improvise either one here** — do not build a suite from a guess at what the data looks like, and do not construct a batch definition by hand. Enumerate what the session already has: ```python def existing_suites_and_batch_definitions(context): suite_names = [suite.name for suite in context.suites.all()] batch_definitions = [] for datasource in context.data_sources.all().values(): for asset in datasource.assets: for batch_definition in asset.batch_definitions: batch_definitions.append((datasource.name, asset.name, batch_definition.name)) return suite_names, batch_definitions ``` **If either list comes back empty, stop and route:** - No batch definition → hand off to `gx-configure-data-source`. - No expectation suite → hand off to `gx-configure-expectations`. - Neither exists → lead with the data source, since a suite is built against a batch and there is nothing to build one against yet. This is a hard stop, not a suggestion — say plainly what's missing, name the skill that builds it, and end this flow. Do not carry on and do not improvise a workaround. If enumerating what exists, or running the checkpoint in step 6, turns up a missing driver or client library, report it and hand over the install command per the standing rule above. Do not install it yourself — not now, before anything has failed, and not later without the user's own go-ahead. If more than one suite or batch definition is available, name them and let the user choose which pairs to bind — guessing which check should run against which slice of their data is not a decision to make for them. ## Step 3 — Bind: one validation definition per pair A validation definition needs its suite already persisted in the working context. **Building one against a suite the context has never seen raises,** not silently loses work the way an unregistered suite does elsewhere in this skill family: ```python from great_expectations.core import ExpectationSuite, ValidationDefinition unsaved_suite = ExpectationSuite(name="not_saved_yet") # ValidationDefinition(name="x", data=batch_definition, suite=unsaved_suite) # ctx.validation_definitions.add(...) on this raises ResourceFreshnessAggregateError: # "ExpectationSuite 'not_saved_yet' must be added to the DataContext before it # can be updated. Please call `context.suites.add(<SUITE_OBJECT>)`..." ``` So always fetch the suite (and the batch definition) from the context rather than closing over an object from earlier in the conversation that might not be the one actually persisted: ```python from great_expectations.core import ValidationDefinition VALIDATION_DEFINITION_NAME = "orders_quality_check" datasource = context.data_sources.get("warehouse") asset = datasource.get_asset("orders") batch_definition = asset.get_batch_definition("by_month") suite = context.suites.get("orders_quality") existing = {vd.name for vd in context.validation_definitions.all()} if VALIDATION_DEFINITION_NAME in existing: validation_definition = context.validation_definitions.get(VALIDATION_DEFINITION_NAME) else: validation_definition = context.validation_definitions.add( ValidationDefinition( name=VALIDATION_DEFINITION_NAME, data=batch_definition, suite=suite ) ) ``` Membership is checked through `.all()` rather than a fetch-and-except, the same reason `references/write-out.md` uses it for these two object types: `validation_definitions.get()` on a miss raises `DataContextError`, which is not a `LookupError` — the `except LookupError` idiom from the data-source skill does not transfer here. Name it in prose, and never import it. Repeat this once per (batch definition, suite) pair the user wants bound. Reusing an already-bound pair under the same name is safe — the fetch above returns it unchanged. ## Step 4 — Group: build the checkpoint, and add it ```python from great_expectations.checkpoint import Checkpoint from great_expectations.checkpoint.actions import UpdateDataDocsAction CHECKPOINT_NAME = "orders_checkpoint" existing_checkpoints = {c.name for c in context.checkpoints.all()} if CHECKPOINT_NAME in existing_checkpoints: checkpoint = context.checkpoints.get(CHECKPOINT_NAME) else: checkpoint = context.checkpoints.add( Checkpoint( name=CHECKPOINT_NAME, validation_definitions=[validation_definition], actions=[UpdateDataDocsAction(name="update_data_docs")], ) ) ``` **This is the explicit add step 5 below is about — the create branch calls `context.checkpoints.add(...)` directly, so `checkpoint` is a persisted object on both branches before anything runs.** Get the two branches from step 3's pattern above if adapting this by hand: fetch on a name that already exists, build-and-add on one that doesn't. Never construct a bare `Checkpoint(...)` and hold onto it without one of these two calls — that is exactly the unpersisted object step 5 exists to warn about. Ask what should happen after a run — a Slack or Teams message, an email, a Data Docs rebuild, one of the less common actions — before assuming none is wanted. `references/action-catalog.md` covers every attachable action: its fields, which four carry a public-API stability guarantee and which four don't, and how to word each one's credential requirements. <!-- consent-gate: config-file --> **Before attaching a Data Docs update action, check whether the project has a site to write to.** File-backed projects and in-memory sessions both carry a working default site; `references/action-catalog.md` covers both, and one project state that doesn't: `great_expectations.yml` can hold `data_docs_sites: null`, under which every site-management call silently does nothing — no error, no site, no sign anything went wrong. The reference gives the fix, and it edits the user's `great_expectations.yml`, so **ask before running it.** Finding `data_docs_sites: null` is not, by itself, permission to change it — state what you found and what the fix involves, and wait for a yes. If the user declines, name the same fix as the path forward and build the checkpoint without the action. ## The fetch-first rule: never `add_or_update` an existing checkpoint or validation definition to change it `context.checkpoints.add_or_update(...)` and `context.validation_definitions.add_or_update(...)` both persist the **incoming** object with the stored object's id transplanted onto it — a whole-object replacement, not a merge. For a checkpoint, the replacement **cascades**: a fresh validation-definition object passed in as part of the incoming checkpoint is itself `add_or_update`-ed, and that in turn `add_or_update`s its own suite. Verified directly, isolating exactly what that cascade does and does not touch: | | outcome | | --- | --- | | **Always lost** | the checkpoint's validation-definition membership **and its actions** — replaced wholesale by whatever the incoming object listed | | **Conditionally lost** | a bound suite's *contents* — only when a **fresh** suite object is passed under an existing name; upserting with the suite you fetched from the context first leaves it untouched | | **Never lost** | validation definitions already in the store — nothing here deletes one; a validation definition dropped from a checkpoint's membership is still fetchable afterward | Be precise about this when you talk to a user: *"you'll lose this checkpoint's action list and which validation definitions it groups"* is accurate; *"your other validation definitions will be deleted"* is not, and overstating the damage is as much a defect as understating it. **`get` → mutate → `save` avoids the cascade entirely.** To change an existing checkpoint's actions, or which validation definitions it groups, fetch it, mutate the field, and save the same object back — never rebuild it from scratch and hand it to `add_or_update`: ```python checkpoint = context.checkpoints.get(CHECKPOINT_NAME) checkpoint.actions = checkpoint.actions + [UpdateDataDocsAction(name="update_data_docs")] checkpoint.save() ``` `add_or_update` is not banned outright — it is the right call the one time a user explicitly wants a checkpoint's whole contents replaced, and only after you have told them exactly what that discards, per the table above. ## Step 5 — Why step 4 added explicitly, before running **Never construct a bare `Checkpoint(...)` and hand it straight to `.run()` without going through the `context.checkpoints.add(...)` / `context.checkpoints.get(...)` branches in step 4, even though it would work.** `Checkpoint.run()` auto-persists an unsaved checkpoint whose validation definitions and suites are themselves already saved — verified directly: a `Checkpoint` object built and run without ever being added to the context shows up in `context.checkpoints.all()` immediately afterward, with no error and no warning that it happened implicitly. That is exactly why step 4 is written to add explicitly rather than left to be discovered as unnecessary: skipping it still produces a persisted checkpoint, so nothing about the flow fails to teach you that the explicit step was needed. Persistence is something step 4 does on purpose, not a side effect of the verification run in step 6. ## Step 6 — Verify by one run, and report by identity ```python result = checkpoint.run(batch_parameters={"dataframe": df}) ``` Run this inside the duration-tracked wrapper in `references/robustness.md`, exactly as the data-source and expectations skills do — a checkpoint can touch as much data as every validation definition it groups, combined. If the run fails because a driver or client library is missing, hand over the install command and stop — do not install it to get the run passing. `references/run-and-schedule.md` covers what `batch_parameters` actually does across multiple validation definitions, including a real trap when more than one of them is dataframe-backed, and why numeric window parameters are integers on every datasource family. **Pair each outcome to the validation definition it came from by identity, never by position:** ```python for vr_id, vr in result.run_results.items(): print(f"validation definition: batch={vr_id.batch_identifier} suite={vr.suite_name}") for each in vr.results: config = each.expectation_config if each.success: print(f" PASS {config.type} {config.kwargs}") elif not each.result: for _metric_id, info in each.exception_info.items(): print(f" ERROR {config.type} {config.kwargs}: {info['exception_message']}") else: print(f" FAIL {config.type} {config.kwargs}: {each.result}") ``` The same infra-vs-data discriminator from the expectations skill applies at this level too: `success is False` with an **empty** `result` is a metric error — a broken column or query, not a finding about the data — while a populated `result` (even one carrying `observed_value: None`, which empty tables and all-null columns produce routinely) is a real outcome to report as such. `references/robustness.md` has the full rules for translating either case into something a user can act on; do not report a metric error as "your data failed this check." Report the overall `result.success`, then each validation definition's own outcome, then each expectation within it — not just a total pass/fail count. `result.describe()` (the summary method that is `@public_api`) gives the same information as a formatted JSON string, useful for a short written recap, but it does not label its entries by name — use the loop above, not `describe()`, whenever the report needs to say *which* validation definition an outcome belongs to. ## Step 7 — Confirm persistence, offer write-out **In a file-backed project, the checkpoint and its validation definitions are already saved** — the explicit adds in steps 3 and 4 wrote them. Confirm this concretely: name the checkpoint, name the validation definitions it groups, and say that a fresh session picks it up with `context.checkpoints.get("<name>")`. <!-- consent-gate: project --> **In an in-memory session, none of it survives the process.** Say this plainly, and **offer to write the session out** — data sources through checkpoints — to a real project, per `references/write-out.md`. That procedure extends past suites to validation definitions and checkpoints, re-created in the target project only after everything they reference is already there. Offer it; don't do it unprompted, and don't pick the location. The offer ends this flow the same way it does in the earlier skills: write-out is a separate run, starting only after the user has agreed and named a directory — never in the same program as the steps above. ## Step 8 — Hand off the run snippet The checkpoint is only re-runnable outside this conversation if there is a concrete way to re-run it. Give the user a small, self-contained script that loads the project by an absolute path and re-runs the checkpoint by name — see `references/run-and-schedule.md` for the exact template, why each of its choices is deliberate, and the one case (a dataframe-backed validation definition) where it doesn't apply as written. <!-- consent-gate: saved-file --> **Offer to save it to a file at a path the user confirms; never write it unasked.** Present it in the conversation first. Wiring it into an actual schedule — a cron entry, an Airflow DAG, a CI job — is the user's orchestrator's job, not this skill's; say that plainly if asked, and hand over the snippet as the piece their scheduler needs to call. ## Worked example A file-backed project already holds a verified `orders` batch definition, partitioned monthly, and an `orders_quality` suite built and run earlier by the other two skills. ```python import great_expectations as gx from great_expectations.core import ValidationDefinition from great_expectations.checkpoint import Checkpoint from great_expectations.checkpoint.actions import UpdateDataDocsAction context = gx.get_context(cloud_mode=False) # step 1 # step 2: both preconditions already exist -- proceed datasource = context.data_sources.get("warehouse") asset = datasource.get_asset("orders") batch_definition = asset.get_batch_definition("by_month") suite = context.suites.get("orders_quality") VALIDATION_DEFINITION_NAME = "orders_quality_check" # step 3 existing_vds = {vd.name for vd in context.validation_definitions.all()} if VALIDATION_DEFINITION_NAME in existing_vds: validation_definition = context.validation_definitions.get(VALIDATION_DEFINITION_NAME) else: validation_definition = context.validation_definitions.add( ValidationDefinition( name=VALIDATION_DEFINITION_NAME, data=batch_definition, suite=suite ) ) CHECKPOINT_NAME = "orders_checkpoint" # step 4 existing_checkpoints = {c.name for c in context.checkpoints.all()} if CHECKPOINT_NAME in existing_checkpoints: checkpoint = context.checkpoints.get(CHECKPOINT_NAME) else: checkpoint = context.checkpoints.add( # step 4: explicit add Checkpoint( name=CHECKPOINT_NAME, validation_definitions=[validation_definition], actions=[UpdateDataDocsAction(name="update_data_docs")], ) ) result = checkpoint.run(batch_parameters={"year": 2024, "month": 3}) # step 6 for vr_id, vr in result.run_results.items(): print(f"{vr_id.batch_identifier} / {vr.suite_name}: success={vr.success}") for each in vr.results: config = each.expectation_config status = "PASS" if each.success else ("ERROR" if not each.result else "FAIL") print(f" {status} {config.type} {config.kwargs}") ``` Against a month where one order has no customer, that prints: ```text warehouse-orders-year_2024-month_3 / orders_quality: success=False FAIL expect_column_values_to_not_be_null {'batch_id': 'warehouse-orders-year_2024-month_3', 'column': 'customer'} ``` Report to the user: the checkpoint `orders_checkpoint` is saved, groups one validation definition (`orders_quality_check`, over the March 2024 window), ran once, and found one failure — a missing customer on one row. Then move to step 7's persistence confirmation and step 8's run-snippet handoff. ## Where this flow ends The persisted, run, reported checkpoint — with its run snippet handed off —
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub