import_contract() returns a ContractImport. It’s the halfway point between an external definition (a JSON Schema, a Frictionless Table Schema, a dbt schema.yml file, and so on) and a Pointblank validation. The definition has been read and translated, but nothing has been run yet, so this is the place to review the translation and decide how to use it.
The object holds what the adapter found: the columns and their types, the rules translated into MappedConstraint objects, the source’s metadata, and a warning for each rule that couldn’t be translated. Its methods turn this into something usable:
to_validate() builds a Validate object for a table, ready to interrogate or to extend with more steps
summary() describes what was imported and what wasn’t
ContractImport is a dataclass, so its fields can be read and changed directly. For instance, an unwanted constraint can be removed from constraints before calling to_validate(). Custom adapters (see ContractAdapter) create these objects by calling the constructor with the parameters below.
Parameters
source_format:str
The name of the adapter that produced the import, such as "json_schema" or "dbt".
source_path:str | None=None
The path of the imported file, or None if the definition was given as a dictionary.
source_version:str | None=None
The version of the source format, when the document states one (e.g., the $schema URL of a JSON Schema). Otherwise None or an empty string.
columns:list[tuple[str, str | None]]=list()
The columns found in the source, as a list of (name, dtype) tuples. The dtype is a Pointblank type name such as "Int64" or "String", or None when the source doesn’t give a type that maps to one.
constraints:list[MappedConstraint]=list()
The translated rules, as a list of MappedConstraint objects, in the order in which they become validation steps.
metadata:dict[str, Any]=dict()
Other information from the source, such as a "title" and a "description".
warnings:list[str]=list()
A message for each rule that was recognized but couldn’t be translated.
coverage:float=1.0
The share of recognized rules that were translated, from 0.0 to 1.0.
Examples
Let’s import a Frictionless Data Package that describes two tables. The resource= option selects the readings table, which has a primary key and a foreign key to the stations table.
The constraints show how the temperature range became a pair of bounds, and how the primary key was expressed as not-null checks plus a rows_distinct() check over both key columns.
The foreign key couldn’t be translated, since checking it would need the stations table. It shows up in warnings, and it’s why the coverage is below 100%.
Because the fields can be changed, the import can be adjusted before it’s used. Here the temperature range is dropped (perhaps the sensors are known to report out-of-range values that are dealt with later) and the remaining checks are run against a table.
readings.constraints = [c for c in readings.constraints if c.kwargs.get("columns") !="temp_c"]readings_tbl = pl.DataFrame( {"station_id": ["S1", "S1", "S2", "S2"],"reading_no": [1, 2, 1, 1],"temp_c": [12.5, 13.1, 75.0, 8.4], })readings.to_validate(readings_tbl, tbl_name="readings").interrogate()
The duplicated ("S2", 1) key is caught by the rows_distinct() step, while the 75.0 reading no longer fails any step because the range check was removed.
This is the most direct way to put an imported definition to work: give it a table and get back a Validate object that checks the table against the definition. The object isn’t interrogated yet, so further validation steps can be chained on before calling interrogate(). That makes it easy to treat the external definition as a baseline and add checks that it can’t express.
The plan starts with a col_schema_match() step built from the columns that have a type, followed by one step for each entry in constraints. The schema check uses the default settings of col_schema_match(), so the table must have the same columns, in the same order, with the same types. Types are compared exactly: a JSON Schema integer becomes Int64, so an Int32 column fails the check. If no column has a type, the schema step is left out.
Parameters
data:Any
The table to validate. Any table type supported by Validate can be used.
**kwargs:Any
Arguments for the Validate constructor, such as tbl_name=, label=, thresholds=, and actions=.
Returns
Validate
A Validate object with the imported validation steps, not yet interrogated.
Examples
Here’s a Frictionless Table Schema for a table of products. Its constraints say that each sku is required and unique, that prices can’t be negative, and that the category must be one of three values.
The arguments after the table go to the Validate constructor. Here we name the table and set a threshold so that any failing test unit counts as a warning.
The report shows the schema check followed by the four imported steps. The duplicated G-002 row, the negative price, and the "film" category each cause a failure.
Since the Validate object isn’t interrogated yet, we can add a rule that the Table Schema doesn’t have (here, that prices stay below 100) before running everything together.
The added rule appears as the last step, and every price passes it. The imported steps fail exactly as before. Mixing imported and hand-written steps like this lets the external definition stay the source of truth for its own rules, while Pointblank covers what the format can’t express.
Where to_validate() checks one table right away, to_contract() produces a Contract: a declarative specification that isn’t tied to any table. A contract can be validated against many tables over time, used at either end of a Pipeline, and saved with to_yaml() so that the translated rules can be reviewed and versioned like any other Pointblank contract.
The contract’s schema is a Schema built from the columns that have a type (no schema is set if none do), and its steps are Step objects made from the constraints. If the source had a description, it becomes the contract’s description unless description= is given. The source’s title isn’t used, so pass it as name= if it should be kept.
Parameters
name:str="imported_contract"
The name of the contract. The default is "imported_contract".
**kwargs:Any
Other arguments for the Contract constructor, such as version=, owner=, description=, direction=, on_violation=, and thresholds=.
Returns
Contract
A contract with the imported schema and steps.
Examples
Let’s import a JSON Schema that has a title and a description, then turn it into a contract. We use the schema’s title as the contract name and add some metadata that the JSON Schema doesn’t have.
import pointblank as pbimport polars as plimported = pb.import_contract( {"title": "sensor_reading","description": "One reading from a temperature sensor.","type": "object","properties": {"sensor_id": {"type": "string", "pattern": "^T-[0-9]{3}$"},"temp_c": {"type": "number", "minimum": -40, "maximum": 85}, },"required": ["sensor_id", "temp_c"], })sensor_contract = imported.to_contract( name=imported.metadata["title"], version="1.0.0", owner="iot-team",)sensor_contract
The short sensor ID "T-03", the missing sensor ID, and the reading of 120.4 (above the maximum of 85) are caught by the steps that came from the JSON Schema.
to_python()
Write the imported rules as Pointblank Python code.
An import doesn’t have to stay an import. Sometimes the external definition is just a starting point, and the plan is to maintain the checks in Python from then on: tightening some rules, adding others, and setting thresholds and actions. to_python() writes the code for a validation plan equivalent to what to_validate() builds, so it can be pasted into a script or notebook and edited from there.
The code chains a col_schema_match() step (when any column has a type) and one validation method per constraint onto Validate, and ends by calling interrogate(). The table is referred to as data, so either define a variable with that name or replace it with the table to validate.
Returns
str
The Python code, as a string.
Examples
Let’s import a small dbt model definition and look at the code for it.
The dbt types became Pointblank types in the schema check, and each dbt test became a validation method call. The code runs as it is once data refers to a table.
YAML is easier to scan than Python for people who don’t write code, so this method is handy for sharing what an import produced, such as in a code review or a ticket. The output has a validation: key holding a steps: list. That list starts with a col_schema_match step (when any column has a type) and then has one entry per constraint, each named after its validation method and holding that method’s arguments.
This is a listing of the steps rather than a complete YAML validation configuration: it has no tbl: entry and its steps are nested under validation:, so it can’t be passed to yaml_interrogate() as is. To keep the imported rules in a YAML file that Pointblank can read back, convert them to a contract with to_contract() and write that with Contract.to_yaml().
Returns
str
The YAML text.
Examples
Here’s the YAML listing for a small Frictionless Table Schema.
Translating a definition from another format is rarely perfect, so it’s worth checking the result before relying on it. summary() gives that check in a few lines: the format, the file (if the definition came from one), the version of the format (if the document states one), the number of columns and constraints found, the coverage, and every warning about a rule that couldn’t be translated. The warnings name the column and the rule, which makes it clear what still needs to be checked some other way.
Returns
str
The summary text. Use print() to display it.
Examples
This JSON Schema uses the date format for one of its properties, which has no Pointblank equivalent among the format checks.
Contract Import Summary
Format: json_schema
Format version: https://json-schema.org/draft/2020-12/schema
Columns detected: 2
Constraints mapped: 2
Coverage: 67%
Warnings (1):
- Column 'signup_date': JSON Schema format 'date' has no Pointblank equivalent — skipped.
Two of the three rules were translated. The warning explains that the date format of signup_date was skipped, so a check on that column’s dates would have to be added by hand (e.g., with col_vals_regex()).