import pointblank as pb
from pointblank.adapters import MappedConstraint
@pb.register_adapter("required_columns")
class RequiredColumnsAdapter(pb.ContractAdapter):
format_name = "required_columns"
supports_export = False
@staticmethod
def detect(source):
return isinstance(source, dict) and "required_columns" in source
def import_contract(self, source, **kwargs):
columns = source["required_columns"]
return pb.ContractImport(
source_format="required_columns",
columns=[(col, None) for col in columns],
constraints=[
MappedConstraint("col_vals_not_null", {"columns": col}, f"{col} is required")
for col in columns
],
)register_adapter()
Register a contract adapter so that it can be used for import and export.
Usage
register_adapter(format_name=None)Pointblank ships with adapters for JSON Schema, Frictionless Table Schema, dbt, and the Open Data Contract Standard, but many organizations describe their data in other ways: an in-house data dictionary, a spreadsheet export, or a schema format from another tool. Writing a ContractAdapter subclass for such a format and registering it with register_adapter() makes the format a first-class citizen. import_contract() and export_contract() can then use it by name with format=, and list_adapters() includes it.
Registration creates one instance of the class (by calling it with no arguments) and stores it under the format name for the rest of the session. Registering a second adapter under a name that’s already taken replaces the first one, and that includes the built-in adapters. When a format has to be detected, adapters are tried in the order they were registered, so the built-in adapters are always tried before custom ones.
Parameters
format_name: str | None = None-
The name to register the adapter under, which becomes the value to pass to
format=. IfNone(the default), the class’s ownformat_nameattribute is used.
Returns
Callable or type-
When called with a name (or with no arguments), a decorator that registers the class it’s applied to and returns that class unchanged. When applied directly to a class (as a bare
@register_adapter), the class itself, unchanged.
Raises
ValueError-
If no name is given and the class has an empty
format_nameattribute.
Ways To Register
There are three equivalent ways to register an adapter class:
@register_adapter("name")above the class definition registers it under"name"- a bare
@register_adapterabove the class definition uses the class’sformat_name register_adapter("name")(AdapterClass)registers a class that’s already been defined (the name can be left out here too, as inregister_adapter()(AdapterClass))
Examples
Suppose a team keeps a simple list of required columns in a dictionary with a required_columns key. A minimal adapter for this format only needs to say how to recognize it and how to turn it into a ContractImport. Since this adapter can’t export, it sets supports_export = False.
The new format now appears next to the built-in ones in list_adapters().
for name, info in pb.list_adapters().items():
print(f"{name:<18} import={info['supports_import']} export={info['supports_export']}")dbt import=True export=True
frictionless import=True export=True
json_schema import=True export=True
odcs import=True export=True
required_columns import=True export=False
And it can be used with import_contract(), either by name or (since it implements detect()) by letting the format be detected from the content.
imported = pb.import_contract({"required_columns": ["id", "email"]})
imported.constraints[MappedConstraint('col_vals_not_null', columns='id'),
MappedConstraint('col_vals_not_null', columns='email')]
Exporting to this format isn’t possible, so export_contract() raises an error that says so.
contract = pb.Contract(name="users", steps=[pb.Step("col_vals_not_null", columns="id")])
try:
pb.export_contract(contract, format="required_columns")
except ValueError as e:
print(e)Adapter 'required_columns' does not support export.
The error is about the adapter, not the contract. To make the format work in both directions, the adapter class would also implement export_contract() and drop the supports_export = False line.
See Also
- ContractAdapter
-
The base class to subclass when writing an adapter.
- ContractImport
-
What an adapter’s
import_contract()method returns. - list_adapters()
-
List the registered adapters.
- import_contract()
-
Import a definition using a registered adapter.
- export_contract()
-
Export a contract using a registered adapter.