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].
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.
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 planvalidation = ( 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 instructionedited = 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 changeplan = 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:
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.
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 pbsmall_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.
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.
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.
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.
--- 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.
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.
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.
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.
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.
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.
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.
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()).
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:
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.