Skip to main content

EditValidation

Edit an existing validation plan with a plain-English instruction using an LLM.

Usage

Source

EditValidation(
    validation,
    instruction,
    model,
    data=None,
    api_key=None,
    verify_ssl=True,
    max_reprompts=1,
)

While DraftValidation generates a validation plan from scratch by profiling a table, EditValidation takes an existing plan plus a natural-language instruction (e.g., “add a freshness check on updated_at, tighten the email regex, and drop the price > 0 check”) and produces a revised plan. You review the change as a diff and explicitly accept() it before anything runs, keeping a human in control.

EditValidation reuses the same provider abstraction as DraftValidation (via the chatlas package), supporting the "anthropic", "openai", "ollama", "bedrock", and "azure-openai" providers. Install the optional requirements with pip install pointblank[generate].

Warning

The EditValidation class is experimental. Please report any issues you encounter in the Pointblank issue tracker.

Parameters

validation: Any

The existing validation plan to edit. This can be a Validate object, a Python code string (such as the output of Validate.to_code()), a YAML configuration string, or a path to a .py or .yaml/.yml file. All inputs are normalized to Pointblank code before being sent to the model.

instruction: str

A plain-English description of the changes to make to the plan.

model: str

The model to be used. This should be in the form of provider:model (e.g., "anthropic:claude-opus-4-8"). Supported providers are "anthropic", "openai", "ollama", "bedrock", and "azure-openai".

data: Any = None

Optional data table. When provided, a DataScan summary of the table is included in the prompt so the model can make informed edits (e.g., tightening a range using observed min/max values). It is also used as the default data source when building a Validate via accept().

api_key: str | None = None

The API key to be used for the model.

verify_ssl: bool = True

Whether to verify SSL certificates when making requests to the LLM provider. Defaults to True. Use with caution as disabling SSL verification can pose security risks.

max_reprompts: int = 1
The maximum number of times to automatically re-prompt the model when the returned code fails the syntax/lint check, before surfacing the result to the user. Defaults to 1.

Returns

EditValidation
An EditValidation object exposing the revised plan through to_code(), diff(), changed_steps(), and accept().

Notes On Data Sent To The Model Provider

As with DraftValidation, only a JSON summary of the table (generated by DataScan) is ever sent to the model, never the underlying rows. If data= is omitted, no table information is sent at all; the model edits the plan using only the plan code and your instruction.

Examples

Editing a plan with a model needs the chatlas package and an API key, so this first example isn’t run here. We start with a plan for the small_table dataset and ask for two changes:

import pointblank as pb

# An existing plan
validation = (
    pb.Validate(data=pb.load_dataset("small_table"))
    .col_vals_gt(columns="d", value=100)
    .col_vals_regex(columns="b", pattern="[0-9]-[a-z]{3}-[0-9]{3}")
)

# Edit it with a plain-English instruction
edited = pb.EditValidation(
    validation=validation,
    instruction="Loosen the `d` check to value=50 and add a not-null check on column `a`.",
    model="anthropic:claude-opus-4-8",
    data=pb.load_dataset("small_table"),
)

print(edited.diff())        # review the change
plan = edited.accept()      # a Validate you can .interrogate()

Nothing runs when the EditValidation object is created, apart from the call to the model. The diff shows the revised plan line by line against the original, and only accept() turns the revised code into a Validate object, using the data= given here because none is passed to accept(). Because data= was given, the model also saw a summary of the table, which helps with edits like choosing a new bound.

The review methods don’t depend on the model, and from_plans() builds an EditValidation from two plans you already have. Here the revised plan makes the same changes that the instruction above asked for:

small_table = pb.load_dataset("small_table")

original = (
    pb.Validate(data=small_table)
    .col_vals_gt(columns="d", value=100)
    .col_vals_regex(columns="b", pattern="[0-9]-[a-z]{3}-[0-9]{3}")
)
revised = (
    pb.Validate(data=small_table)
    .col_vals_gt(columns="d", value=50)
    .col_vals_regex(columns="b", pattern="[0-9]-[a-z]{3}-[0-9]{3}")
    .col_vals_not_null(columns="a")
)

edit = pb.EditValidation.from_plans(original, revised, instruction="Loosen d; require a")

edit.review()
Proposed plan changes
Loosen d; require a
Change Method Before After
~ modified col_vals_gt col_vals_gt(columns='d', value=100) col_vals_gt(columns='d', value=50)
+ added col_vals_not_null col_vals_not_null(columns='a')

The review table lists one row per changed step: the col_vals_gt() step is modified (its value= went from 100 to 50), the col_vals_not_null() step is added, and the unchanged col_vals_regex() step isn’t shown. Once the changes look right, accept() gives the revised plan as a Validate object that’s ready to interrogate.

See Also

DraftValidation

Draft a new validation plan for a table with an LLM.

to_code()

Get a validation plan as Python code.

Methods

Name Description
review() Return a great_tables GT object summarizing the step-level changes.
accept() Execute the revised plan and return the resulting Validate object.
diff() Return a unified textual diff of the original plan versus the revised plan.
changed_steps() Return a structured, step-level list of the changes between the plans.
to_code() Return the revised validation plan as Python code.
validate_syntax() Check whether the revised plan parses and uses only known validation methods.
from_plans() Build an EditValidation from two known plans, without calling a model.

review()

Return a great_tables GT object summarizing the step-level changes.

Usage

Source

review()

This renders the added, removed, and modified steps side by side for quick human review before accepting the edit. It pairs with accept(): review the change, then accept it. Each row is one record from changed_steps(), colored by the kind of change, and the instruction is shown as the subtitle. When nothing changed, the table has a single “(no changes)” row.

Returns

GT
A great_tables table with one row per changed step.

Examples

import pointblank as pb

small_table = pb.load_dataset("small_table")

edit = pb.EditValidation.from_plans(
    pb.Validate(data=small_table).col_vals_gt(columns="d", value=100).rows_distinct(),
    pb.Validate(data=small_table).col_vals_gt(columns="d", value=50),
    instruction="Loosen the d check and drop the duplicate-row check",
)

edit.review()
Proposed plan changes
Loosen the d check and drop the duplicate-row check
Change Method Before After
~ modified col_vals_gt col_vals_gt(columns='d', value=100) col_vals_gt(columns='d', value=50)
− removed rows_distinct rows_distinct()

The modified col_vals_gt() step shows its old and new text in the Before and After columns, and the removed rows_distinct() step has only a Before entry. Because the result is a GT object, it can be styled further or saved with the great_tables API.

accept()

Execute the revised plan and return the resulting Validate object.

Usage

Source

accept(data=None)

The returned Validate has NOT been interrogated; call .interrogate() on it to run the validation. Before running the code, accept() applies the same check as validate_syntax() and raises a ValueError if it fails. It then runs the code with the table bound to your_data and returns the Validate object that the code assigns to validation (or the last Validate object it assigns). Any .interrogate() call in the revised code is removed first.

Warning

accept() runs the revised code with Python’s exec(). Read the code (with to_code() or review()) before accepting an edit, as you would any other code you didn’t write.

Parameters

data: Any = None
The data table to bind to the plan’s your_data placeholder. If omitted, the data= provided to EditValidation is used. One of the two must be present.

Returns

Validate
The revised validation plan as a Validate object.

Examples

import pointblank as pb

small_table = pb.load_dataset("small_table")

edit = pb.EditValidation.from_plans(
    pb.Validate(data=small_table).col_vals_gt(columns="d", value=100),
    (
        pb.Validate(data=small_table)
        .col_vals_gt(columns="d", value=50)
        .col_vals_not_null(columns="a")
    ),
)

validation = edit.accept(data=small_table).interrogate()

validation
STEP COLUMNS VALUES TBL EVAL UNITS PASS FAIL W E C EXT
#4CA64C 1
col_vals_gt
col_vals_gt()
d 50 ✓ 13 13
1.00
0
0.00
— — — —
#4CA64C 2
col_vals_not_null
col_vals_not_null()
a — ✓ 13 13
1.00
0
0.00
— — — —

The revised plan has both steps, and it ran against small_table because that’s the table passed to accept(). An EditValidation built by from_plans() has no data= of its own, so the table has to be given here. Passing a different table is also how to run the same revised plan on new data.

diff()

Return a unified textual diff of the original plan versus the revised plan.

Usage

Source

diff()

The diff compares the two plans’ code line by line, in the format used by git diff and code review tools (lines starting with - were removed and lines starting with + were added). It’s the quickest way to see an edit in context, but it’s sensitive to formatting: a step that was only reformatted shows up as changed. For a list of the changed steps that ignores formatting, use changed_steps().

Returns

str
The diff as a single string, which is empty when the two plans have the same code.

Examples

import pointblank as pb

small_table = pb.load_dataset("small_table")

edit = pb.EditValidation.from_plans(
    pb.Validate(data=small_table).col_vals_gt(columns="d", value=100),
    (
        pb.Validate(data=small_table)
        .col_vals_gt(columns="d", value=50)
        .col_vals_not_null(columns="a")
    ),
)

print(edit.diff())
--- original_plan.py
+++ edited_plan.py
@@ -4,7 +4,8 @@
     pb.Validate(
         data=your_data,  # Replace your_data with the actual data variable
     )
-    .col_vals_gt(columns="d", value=100)
+    .col_vals_gt(columns="d", value=50)
+    .col_vals_not_null(columns="a")
 )
 
 validation

The old col_vals_gt() line is replaced by one with value=50, and a col_vals_not_null() line is added. The two files in the header (original_plan.py and edited_plan.py) are only labels, and nothing is written to disk.

changed_steps()

Return a structured, step-level list of the changes between the plans.

Usage

Source

changed_steps()

Unlike diff(), which is a textual diff sensitive to formatting, this compares the two plans at the level of validation steps and is robust to reformatting. Each entry is a dict with an "action" of "add", "remove", or "modify", a "method" name, and the relevant "old"/"new" step text. A step whose method stays the same but whose arguments change is reported as "modify", and a step replaced by one with a different method is reported as a "remove" and an "add". Unchanged steps aren’t listed.

Returns

list[dict]
One record per changed step, in plan order. The list is empty when the plans have the same steps.

Examples

import pointblank as pb

small_table = pb.load_dataset("small_table")

edit = pb.EditValidation.from_plans(
    (
        pb.Validate(data=small_table)
        .col_vals_gt(columns="d", value=100)
        .rows_distinct()
    ),
    (
        pb.Validate(data=small_table)
        .col_vals_gt(columns="d", value=50)
        .col_vals_not_null(columns="a")
    ),
)

for change in edit.changed_steps():
    print(change)
{'action': 'modify', 'method': 'col_vals_gt', 'old': "col_vals_gt(columns='d', value=100)", 'new': "col_vals_gt(columns='d', value=50)"}
{'action': 'remove', 'method': 'rows_distinct', 'old': 'rows_distinct()'}
{'action': 'add', 'method': 'col_vals_not_null', 'new': "col_vals_not_null(columns='a')"}

The col_vals_gt() step is modified, so its record has both the old and the new step text. The rows_distinct() step was removed and has only "old", and the new col_vals_not_null() step has only "new". The same records are behind the table that review() shows.

to_code()

Return the revised validation plan as Python code.

Usage

Source

to_code()

The code is in the same form as Validate.to_code() produces, with the table given as the placeholder your_data. Use it to save the revised plan, to read it in full rather than as a diff, or to edit it by hand when the revision isn’t quite right. Printing the EditValidation object shows the same code.

Returns

str
The revised plan as a string of Python code.

Examples

import pointblank as pb

small_table = pb.load_dataset("small_table")

edit = pb.EditValidation.from_plans(
    pb.Validate(data=small_table).col_vals_gt(columns="d", value=100),
    pb.Validate(data=small_table).col_vals_gt(columns="d", value=50),
)

print(edit.to_code())
import pointblank as pb

validation = (
    pb.Validate(
        data=your_data,  # Replace your_data with the actual data variable
    )
    .col_vals_gt(columns="d", value=50)
)

validation

The revised plan reads its table from your_data, so replace that with the table to use before running the code yourself, or let accept() fill it in.

validate_syntax()

Check whether the revised plan parses and uses only known validation methods.

Usage

Source

validate_syntax()

The check parses the revised code and looks at each method whose name starts with col_, row_, or rows_. It fails if the code isn’t valid Python or if one of those methods isn’t a Pointblank validation method, which catches a model inventing a method or misspelling one. The code isn’t run, so a wrong argument name or value isn’t caught until accept() or interrogation. When EditValidation calls a model, the same check decides whether to re-prompt it (see max_reprompts=), and accept() runs it too.

Returns

bool
True if the revised plan passes the check, False otherwise.

Examples

import pointblank as pb

original = "validation = pb.Validate(data=your_data).col_vals_gt(columns='d', value=100)"
misspelled = "validation = pb.Validate(data=your_data).col_vals_gtt(columns='d', value=50)"

print(pb.EditValidation.from_plans(original, original).validate_syntax())
print(pb.EditValidation.from_plans(original, misspelled).validate_syntax())
True
False

The second revision calls col_vals_gtt(), which doesn’t exist, so it fails the check. Calling accept() on it would raise a ValueError naming the unknown method.

from_plans()

Build an EditValidation from two known plans, without calling a model.

Usage

Source

from_plans(
    original,
    revised,
    instruction="Manual plan comparison",
)

This is a lightweight way to compare an original plan against a revised one and review the difference with diff(), changed_steps(), and review(). It is useful for reviewing the change between two saved versions of a plan (for example, in code review) and does not require an LLM provider, an API key, or the chatlas package.

Parameters

original: Any

The original plan. Accepts anything the validation= argument accepts: a Validate object, a code string, a YAML string, or a file path.

revised: Any

The revised plan, in any of the same forms as original.

instruction: str = "Manual plan comparison"
An optional label describing the comparison (shown as the subtitle in review()).

Returns

EditValidation
An EditValidation holding the code of the two plans, ready for diff(), changed_steps(), review(), and accept(). It has no data=, so pass the table to accept().

Examples

Two versions of a plan can be compared directly, here as Validate objects:

import pointblank as pb

small_table = pb.load_dataset("small_table")

v1 = pb.Validate(data=small_table).col_vals_gt(columns="d", value=100)
v2 = pb.Validate(data=small_table).col_vals_gt(columns="d", value=50)

comparison = pb.EditValidation.from_plans(v1, v2)

comparison.changed_steps()
[{'action': 'modify',
  'method': 'col_vals_gt',
  'old': "col_vals_gt(columns='d', value=100)",
  'new': "col_vals_gt(columns='d', value=50)"}]

The only change is the value= of the col_vals_gt() step, reported as a single modification. The plans can also be YAML configurations, which are converted to Python code before they’re compared:

v1_yaml = '''
tbl: small_table
steps:
  - col_vals_gt:
      columns: d
      value: 100
'''
v2_yaml = '''
tbl: small_table
steps:
  - col_vals_gt:
      columns: d
      value: 100
  - rows_distinct
'''

comparison = pb.EditValidation.from_plans(v1_yaml, v2_yaml)

comparison.changed_steps()
[{'action': 'add', 'method': 'rows_distinct', 'new': 'rows_distinct()'}]

The second version adds a rows_distinct() step. Since the inputs can be file paths too, this works well for checking a change to a saved plan before it’s merged.