Skip to content

Checks

This page contains lists all validation checks available in data-contract-checker.

Attribute paths

Checks are applied to attributes identified by dot-notation paths, e.g. info.title or models.order.fields.id.description.

Use * as a wildcard segment to match every repeated item at that level. Contract standards represent repeatable objects differently, and * handles both:

  • Dicts keyed by name (DCS style): * expands over the dict entries.
  • Lists of named objects (ODCS style): * expands over the list items. Each concrete path is labeled with the item's name when it has one, falling back to the item's index. When two items would get the same label (for example duplicate names), each is labeled label[index], e.g. schema.orders.properties.id[0] and schema.orders.properties.id[2], so every item is checked on its own.
data_contract_checks:
  # DCS: checks every model's fields collection
  - attribute: models.*.fields
    checks:
      - min_items: 1

  # DCS: checks the description of every field in every model
  - attribute: models.*.fields.*.description
    checks:
      - required: true

  # ODCS: checks the logical type of every property in every schema object
  - attribute: schema.*.properties.*.logicalType
    checks:
      - required: true

Wildcards can appear at any level and can be combined freely within the same path.

Resolving name. A name leaf directly after a * works for both styles: in list-style contracts it reads the item's explicit name value, and in dict-style contracts — where the item's name is the key itself — it resolves to the key (an explicit name value inside the entry wins over the key). This only applies to entries that are objects: a null or scalar entry (e.g. fields: {id: null} or info.title) has no name and is reported as missing. So models.*.fields.*.name yields field names regardless of the standard.

Lists without wildcards. A non-wildcard segment applied to a list is transparent: it applies to each item, and the matched values are merged under a single path (e.g. servers.type collects the type of every server). Use * when you want checks to run per item instead.

Missing vs null values

All checks follow the same rule for attributes that are absent or empty:

  • Absent key: skipped by every check except required. The check produces no result, so a field can be present in some contracts and not in others.
  • Key present but null (owner: with no value, owner: null, or owner: ~): fails every check with Attribute '<path>' is null.
  • Null elements inside a value (tags: [a, null], or a null entry in a fields mapping): fail with Attribute '<path>' contains N null element(s). Null elements are not counted by min_items / max_items.
Contract required: true Any other check
info: {title: X} (no owner) FAIL: is missing no result
info: {owner: null} FAIL: is null FAIL: is null
info: {owner: alice} PASS checked normally

To make a field mandatory, add required: true. The other checks never enforce presence:

data_contract_checks:
  # Optional: if tags are present, there must be at least one
  - attribute: info.tags
    checks:
      - min_items: 1

  # Mandatory: tags must be present and have at least one item
  - attribute: info.tags
    checks:
      - required: true
      - min_items: 1

required: false produces no result. You can still write it to document that a field is optional.

A null parent (e.g. info: null) makes every path below it absent, so info.owner is reported as missing by required and skipped by the other checks. Add a check on info itself to report it as null.

Empty values ("", [], {}) are not null. They count as present.

See Pass and fail examples for every check run against real contracts.

Available checks

Initialize validators package and provide convenience exports.

Classes:

Name Description
AcceptedValuesCheck

Check if attribute values are within a list of accepted values.

AtLeastOneCheck

Check that at least one item in a collection satisfies a key-value condition.

ExactlyOneCheck

Check that exactly one item in a collection satisfies a key-value condition.

MaxItemsCheck

Check if a list attribute does not exceed a maximum number of items.

MinItemsCheck

Check if a list attribute has at least a minimum number of items.

PatternCheck

Check if attribute values match a regular expression pattern.

RequiredCheck

Check if a required field exists and is not null.

AcceptedValuesCheck

Check if attribute values are within a list of accepted values.

Both string and list attributes are supported.

  • For string attributes, the value is checked directly against the accepted values.
  • For list attributes, each item in the list is checked against the accepted values, and a validation result is generated for each item.

Values are compared with their type, the same way --lint checks them against the contract schema: a contract value 255 matches an accepted value of 255 (or 255.0) but not "255", and true matches true but not "true" or 1. Unquoted dates are strings on both sides, so 2024-01-01 matches 2024-01-01 and "2024-01-01".

The identifier for this check type ("accepted_values").

Example configuration in data-contract-checks.yml:

    data_contract_checks:
      # Single value - ensure version is accepted
      - attribute: info.version
        checks:
          - accepted_values: 1.0.0

      # Multiple accepted values for tags (list attribute)
      - attribute: tags
        checks:
          - accepted_values:
              - production
              - staging
              - development

      # Ensure server types are valid
      - attribute: servers.type
        checks:
          - accepted_values:
              - s3
              - gcs
              - azure_blob
Source code in src/contract_checker/checks/validators/accepted_values_check.py
class AcceptedValuesCheck(Check):
    """Check if attribute values are within a list of accepted values.

    Both string and list attributes are supported.

    - For string attributes, the value is checked directly against the accepted values.
    - For list attributes, each item in the list is checked against the accepted values,
    and a validation result is generated for each item.

    Values are compared with their type, the same way ``--lint`` checks them
    against the contract schema: a contract value ``255`` matches an accepted
    value of ``255`` (or ``255.0``) but not ``"255"``, and ``true`` matches
    ``true`` but not ``"true"`` or ``1``. Unquoted dates are strings on both
    sides, so ``2024-01-01`` matches ``2024-01-01`` and ``"2024-01-01"``.

    The identifier for this check type (``"accepted_values"``).

    Example configuration in ``data-contract-checks.yml``:

    ```yaml

        data_contract_checks:
          # Single value - ensure version is accepted
          - attribute: info.version
            checks:
              - accepted_values: 1.0.0

          # Multiple accepted values for tags (list attribute)
          - attribute: tags
            checks:
              - accepted_values:
                  - production
                  - staging
                  - development

          # Ensure server types are valid
          - attribute: servers.type
            checks:
              - accepted_values:
                  - s3
                  - gcs
                  - azure_blob
    ```
    """

    @property
    def check_type(self) -> str:
        """Return the check type identifier.

        :return: The string ``"accepted_values"``.
        """
        return "accepted_values"

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept a non-empty list of scalar values, or a single scalar value.

        Scalars are strings, numbers and booleans.

        :raises ValueError: If ``check_value`` is a mapping, null or an empty
            list, or a list containing a non-scalar entry (a nested list, a
            mapping or null).
        """
        if isinstance(check_value, _SCALAR_TYPES):
            return
        if not isinstance(check_value, list) or not check_value:
            raise ValueError(f"'accepted_values' expects a non-empty list of values, got {check_value!r}")
        for entry in check_value:
            if not isinstance(entry, _SCALAR_TYPES):
                raise ValueError(f"'accepted_values' entries must be strings, numbers or booleans, got {entry!r}")

    def execute(
        self,
        attribute_path: str,
        value_list: List[Any],
        accepted_values: Union[_Scalar, Sequence[_Scalar]],
    ) -> List[ValidationResult]:
        """Execute the accepted values validation.

        :param attribute_path: The dot-notation path to the attribute being checked.
        :param value_list: List of values found at the attribute path. Each value
            can be a string or a list of strings.
        :param accepted_values: The values that are considered valid: a list of
            scalars, or a single scalar as shorthand for a one-item list.
        :return: A list of :class:`ValidationResult` objects. When the attribute
            contains a list, a result is generated for each item in that list.
            Null elements in a list fail. An absent attribute (empty
            ``value_list``) produces no results. Null values are reported by
            the validator.
        """
        # validate_config allows a single scalar as shorthand for a one-item
        # list. Wrapping it keeps the test a membership test, not a substring
        # test ('act' must not pass against accepted_values: active).
        if isinstance(accepted_values, _SCALAR_TYPES):
            accepted_values = [accepted_values]

        results = []
        for value in value_list:
            if isinstance(value, str):
                items = [value]
            elif isinstance(value, list):
                # Filter out null elements and report them explicitly
                items = [v for v in value if v is not None]
                null_count = len(value) - len(items)
                if null_count:
                    results.append(self.null_elements_result(attribute_path, null_count))
            else:
                items = [value]

            for item in items:
                passed = any(_same_value(item, accepted) for accepted in accepted_values)

                if passed:
                    message = f"Attribute '{attribute_path}' value '{item}' is in the list of accepted values"
                else:
                    message = (
                        f"Attribute '{attribute_path}' value '{item}' "
                        f"is not in the list of accepted values: '{accepted_values}'"
                    ) + self._type_mismatch_hint(item, accepted_values)

                results.append(ValidationResult("", attribute_path, self.check_type, passed, message))

        return results

    @staticmethod
    def _type_mismatch_hint(item: Any, accepted_values: Sequence[_Scalar]) -> str:  # noqa: ANN401
        """Explain a failure where the value is listed, but with another type.

        A contract ``255`` against ``accepted_values: ["255"]``, or ``true``
        against ``["true"]``, reads as the same value but is not: point at the
        quotes in the checks config rather than leave the failure unexplained.
        """
        for accepted in accepted_values:
            if str(accepted).lower() == str(item).lower():
                return (
                    f" (type mismatch: the contract has {_json_type_name(item)} {item!r}, the checks config has "
                    f"{_json_type_name(accepted)} {accepted!r}; add or remove the quotes in the checks config)"
                )
        return ""

AtLeastOneCheck

Check that at least one item in a collection satisfies a key-value condition.

Use this check when a constraint must hold for some (not all) items. Point the attribute path at the collection, not a leaf field, and provide a single attr: val pair under at_least_one as a YAML block mapping to match a nested attribute.

Example — at least one field per model must have unique: true:

data_contract_checks:
  - attribute: models.*.fields
    checks:
      - at_least_one:
          unique: true

Example — at least one field must be a primary key:

data_contract_checks:
  - attribute: models.*.fields
    checks:
      - at_least_one:
          primaryKey: true

Per-expansion semantics. When the attribute path contains a wildcard (e.g. models.*.fields), the check runs once per expanded concrete path — once per model in the examples above. Each expansion must independently satisfy the condition; results are not aggregated across matches. Use a non-wildcarded path if you want a single pooled verdict instead.

Source code in src/contract_checker/checks/validators/at_least_one_check.py
class AtLeastOneCheck(Check):
    """Check that at least one item in a collection satisfies a key-value condition.

    Use this check when a constraint must hold for *some* (not all) items.
    Point the ``attribute`` path at the **collection**, not a leaf field, and
    provide a single ``attr: val`` pair under ``at_least_one`` as a YAML block
    mapping to match a nested attribute.

    Example — at least one field per model must have ``unique: true``:

    ```yaml
    data_contract_checks:
      - attribute: models.*.fields
        checks:
          - at_least_one:
              unique: true
    ```

    Example — at least one field must be a primary key:

    ```yaml
    data_contract_checks:
      - attribute: models.*.fields
        checks:
          - at_least_one:
              primaryKey: true
    ```

    **Per-expansion semantics.** When the attribute path contains a wildcard
    (e.g. ``models.*.fields``), the check runs once per expanded concrete path
    — once per model in the examples above. Each expansion must independently
    satisfy the condition; results are *not* aggregated across matches. Use a
    non-wildcarded path if you want a single pooled verdict instead.
    """

    @property
    def check_type(self) -> str:
        return "at_least_one"

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept only a single-key mapping such as ``{primaryKey: true}``.

        :raises ValueError: If ``check_value`` is not a mapping or does not
            have exactly one key.
        """
        if not isinstance(check_value, dict) or len(check_value) != 1:
            raise ValueError(
                f"'at_least_one' expects a single-key mapping such as {{primaryKey: true}}, got {check_value!r}"
            )

    def execute(
        self,
        attribute_path: str,
        values: List[Any],
        check_value: Any,  # noqa: ANN401
    ) -> List[ValidationResult]:
        """Execute the at-least-one validation.

        :param attribute_path: The dot-notation path to the collection being checked.
        :param values: List of values found at the attribute path. Typically a list
            containing one dict (a mapping of named items, e.g. fields in a model).
        :param check_value: A mapping with exactly one ``attr: val`` entry — at
            least one child object in the collection must have ``attr == val``.
            Multi-key mappings, empty mappings, and non-mapping values are
            rejected with a failing result.
        :return: A list of :class:`ValidationResult` objects: a failing result
            for null items inside the collection, plus a single verdict. An
            absent attribute (empty ``values``) produces no results. Null
            collections are reported by the validator.
        """
        if not values:
            return []

        if not isinstance(check_value, dict) or len(check_value) != 1:
            return [
                ValidationResult(
                    "",
                    attribute_path,
                    self.check_type,
                    False,
                    f"Attribute '{attribute_path}' at_least_one check requires a "
                    f"single-key mapping (e.g. {{unique: true}}); got: {check_value!r}",
                )
            ]

        required_attr, required_val = next(iter(check_value.items()))
        results = []
        found = False
        for value in values:
            items: List[Any]
            # A collection is either a dict keyed by item name (DCS style)
            # or a list of named objects (ODCS style).
            if isinstance(value, dict):
                null_keys = [str(key) for key, item in value.items() if item is None]
                if null_keys:
                    results.append(self.null_elements_result(attribute_path, len(null_keys), null_keys))
                items = list(value.values())
            elif isinstance(value, list):
                null_count = sum(1 for item in value if item is None)
                if null_count:
                    results.append(self.null_elements_result(attribute_path, null_count))
                items = value
            else:
                items = []
            for item in items:
                if isinstance(item, dict) and item.get(required_attr) == required_val:
                    found = True

        if found:
            message = f"Attribute '{attribute_path}' contains at least one item with '{required_attr}: {required_val}'"
        else:
            message = f"Attribute '{attribute_path}' has no items with '{required_attr}: {required_val}'"

        results.append(ValidationResult("", attribute_path, self.check_type, found, message))
        return results

ExactlyOneCheck

Check that exactly one item in a collection satisfies a key-value condition.

Use this check when a constraint must hold for exactly one item in a collection — not zero, not two, not more. The motivating case is primary key enforcement: a model must have exactly one field with primaryKey: true; two is a modelling bug.

Point the attribute path at the collection, not a leaf field, and provide a single attr: val pair under exactly_one as a YAML block mapping to match a nested attribute.

Example — every model must have exactly one primary key field:

data_contract_checks:
  - attribute: models.*.fields
    checks:
      - exactly_one:
          primaryKey: true

Example — every model must have exactly one event-time timestamp field:

data_contract_checks:
  - attribute: models.*.fields
    checks:
      - exactly_one:
          type: timestamp

Per-expansion semantics. When the attribute path contains a wildcard (e.g. models.*.fields), the check runs once per expanded concrete path — once per model in the examples above. Each expansion must independently satisfy the condition; results are not aggregated across matches. Use a non-wildcarded path if you want a single pooled verdict instead.

Check-value shape contract. check_value must be a single-key mapping. Multi-key mappings, empty mappings, and non-mapping values are rejected with a failing result.

check_value Result
{primaryKey: true} OK — checked
{primaryKey: true, unique: true} FAIL — multi-key rejected
{} FAIL — empty mapping rejected
true / "unique" / [...] / null FAIL — non-mapping rejected
Source code in src/contract_checker/checks/validators/exactly_one_check.py
class ExactlyOneCheck(Check):
    """Check that exactly one item in a collection satisfies a key-value condition.

    Use this check when a constraint must hold for **exactly one** item in a
    collection — not zero, not two, not more. The motivating case is primary
    key enforcement: a model must have exactly one field with
    ``primaryKey: true``; two is a modelling bug.

    Point the ``attribute`` path at the **collection**, not a leaf field, and
    provide a single ``attr: val`` pair under ``exactly_one`` as a YAML block
    mapping to match a nested attribute.

    Example — every model must have exactly one primary key field:

    ```yaml
    data_contract_checks:
      - attribute: models.*.fields
        checks:
          - exactly_one:
              primaryKey: true
    ```

    Example — every model must have exactly one event-time timestamp field:

    ```yaml
    data_contract_checks:
      - attribute: models.*.fields
        checks:
          - exactly_one:
              type: timestamp
    ```

    **Per-expansion semantics.** When the attribute path contains a wildcard
    (e.g. ``models.*.fields``), the check runs once per expanded concrete path
    — once per model in the examples above. Each expansion must independently
    satisfy the condition; results are *not* aggregated across matches. Use a
    non-wildcarded path if you want a single pooled verdict instead.

    **Check-value shape contract.** ``check_value`` must be a single-key
    mapping. Multi-key mappings, empty mappings, and non-mapping values are
    rejected with a failing result.

    | ``check_value``                           | Result                         |
    |-------------------------------------------|--------------------------------|
    | ``{primaryKey: true}``                    | OK — checked                   |
    | ``{primaryKey: true, unique: true}``      | FAIL — multi-key rejected      |
    | ``{}``                                    | FAIL — empty mapping rejected  |
    | ``true`` / ``"unique"`` / ``[...]`` / ``null`` | FAIL — non-mapping rejected |
    """

    @property
    def check_type(self) -> str:
        return "exactly_one"

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept only a single-key mapping such as ``{primaryKey: true}``.

        :raises ValueError: If ``check_value`` is not a mapping or does not
            have exactly one key.
        """
        if not isinstance(check_value, dict) or len(check_value) != 1:
            raise ValueError(
                f"'exactly_one' expects a single-key mapping such as {{primaryKey: true}}, got {check_value!r}"
            )

    def execute(
        self,
        attribute_path: str,
        values: List[Any],
        check_value: Any,  # noqa: ANN401
    ) -> List[ValidationResult]:
        """Execute the exactly-one validation.

        :param attribute_path: The dot-notation path to the collection being checked.
        :param values: List of values found at the attribute path. Typically a list
            containing one dict (a mapping of named items, e.g. fields in a model).
        :param check_value: A single-key mapping ``{attr: val}`` — exactly one
            child object in the collection must have ``attr == val``. Any other
            shape (multi-key mapping, empty mapping, non-mapping) is rejected.
        :return: A list of :class:`ValidationResult` objects: a failing result
            for null items inside the collection, plus a single verdict. An
            absent attribute (empty ``values``) produces no results. Null
            collections are reported by the validator.
        """
        if not values:
            return []

        if not isinstance(check_value, dict):
            return [
                ValidationResult(
                    "",
                    attribute_path,
                    self.check_type,
                    False,
                    f"Attribute '{attribute_path}' has invalid exactly_one check_value: "
                    f"expected a single-key mapping, got {type(check_value).__name__}",
                )
            ]

        if len(check_value) != 1:
            return [
                ValidationResult(
                    "",
                    attribute_path,
                    self.check_type,
                    False,
                    f"Attribute '{attribute_path}' has invalid exactly_one check_value: "
                    f"expected a single-key mapping, got {len(check_value)} keys",
                )
            ]

        required_attr, required_val = next(iter(check_value.items()))
        results = []
        count = 0
        for value in values:
            items: List[Any]
            # A collection is either a dict keyed by item name (DCS style)
            # or a list of named objects (ODCS style).
            if isinstance(value, dict):
                null_keys = [str(key) for key, item in value.items() if item is None]
                if null_keys:
                    results.append(self.null_elements_result(attribute_path, len(null_keys), null_keys))
                items = list(value.values())
            elif isinstance(value, list):
                null_count = sum(1 for item in value if item is None)
                if null_count:
                    results.append(self.null_elements_result(attribute_path, null_count))
                items = value
            else:
                items = []
            for item in items:
                if isinstance(item, dict) and item.get(required_attr) == required_val:
                    count += 1

        passed = count == 1
        if passed:
            message = f"Attribute '{attribute_path}' contains exactly one item with '{required_attr}: {required_val}'"
        else:
            message = (
                f"Attribute '{attribute_path}' has {count} items with "
                f"'{required_attr}: {required_val}', expected exactly 1"
            )

        results.append(ValidationResult("", attribute_path, self.check_type, passed, message))
        return results

MaxItemsCheck

Check if a list attribute does not exceed a maximum number of items.

The identifier for this check type ("max_items").

Example configuration in data-contract-checks.yml:

.. code-block:: yaml

data_contract_checks:
  # Limit tags to maximum 5 items
  - attribute: tags
    checks:
      - max_items: 5

  # Limit examples to reasonable number
  - attribute: models.*.fields.*.examples
    checks:
      - max_items: 10
Source code in src/contract_checker/checks/validators/items_check.py
class MaxItemsCheck(Check):
    """Check if a list attribute does not exceed a maximum number of items.

    The identifier for this check type (``"max_items"``).

    Example configuration in ``data-contract-checks.yml``:

    .. code-block:: yaml

        data_contract_checks:
          # Limit tags to maximum 5 items
          - attribute: tags
            checks:
              - max_items: 5

          # Limit examples to reasonable number
          - attribute: models.*.fields.*.examples
            checks:
              - max_items: 10
    """

    @property
    def check_type(self) -> str:
        """Return the check type identifier.

        :return: The string ``"max_items"``.
        """
        return "max_items"

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept only a non-negative integer. See :func:`_validate_count_config`."""
        _validate_count_config(self.check_type, check_value)

    def execute(self, attribute_path: str, values: List[Any], max_count: int) -> List[ValidationResult]:
        """Execute the maximum items validation.

        :param attribute_path: The dot-notation path to the attribute being checked.
        :param values: List of values found at the attribute path. Each value should
            be a list, or a mapping of named items (e.g. DCS ``fields``).
        :param max_count: The maximum number of items allowed in the list.
        :return: A list of :class:`ValidationResult` objects: one failing result
            per list or mapping with null elements, plus a single count result.
            Null elements are not counted. A value that is not a list or
            mapping fails. An absent attribute (empty ``values``)
            produces no results. Null values are reported by the validator.
        """
        if not values:
            return []

        if not all(isinstance(value, (list, dict)) for value in values):
            return [_not_a_collection_result(attribute_path, self.check_type)]

        results = []
        all_passed = True
        for value in values:
            count, null_results = _count_items(self, attribute_path, value)
            results.extend(null_results)
            if count > max_count:
                all_passed = False

        if all_passed:
            message = f"Attribute '{attribute_path}' has at most {max_count} items"
        else:
            message = f"Attribute '{attribute_path}' has more than {max_count} items"
        results.append(ValidationResult("", attribute_path, self.check_type, all_passed, message))

        return results

MinItemsCheck

Check if a list attribute has at least a minimum number of items.

The identifier for this check type ("min_items").

Example configuration in data-contract-checks.yml:

    data_contract_checks:
      # Ensure each model has at least one field
      - attribute: models.*.fields
        checks:
          - min_items: 1

      # Ensure at least 3 examples are provided
      - attribute: models.*.fields.*.examples
        checks:
          - min_items: 3
Source code in src/contract_checker/checks/validators/items_check.py
class MinItemsCheck(Check):
    """Check if a list attribute has at least a minimum number of items.

    The identifier for this check type (``"min_items"``).

    Example configuration in ``data-contract-checks.yml``:

    ``` yaml

        data_contract_checks:
          # Ensure each model has at least one field
          - attribute: models.*.fields
            checks:
              - min_items: 1

          # Ensure at least 3 examples are provided
          - attribute: models.*.fields.*.examples
            checks:
              - min_items: 3
    ```
    """

    @property
    def check_type(self) -> str:
        """Return the check type identifier.

        :return: The string ``"min_items"``.
        """
        return "min_items"

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept only a non-negative integer. See :func:`_validate_count_config`."""
        _validate_count_config(self.check_type, check_value)

    def execute(self, attribute_path: str, values: List[Any], min_count: int) -> List[ValidationResult]:
        """Execute the minimum items validation.

        :param attribute_path: The dot-notation path to the attribute being checked.
        :param values: List of values found at the attribute path. Each value should
            be a list, or a mapping of named items (e.g. DCS ``fields``).
        :param min_count: The minimum number of items required in the list.
        :return: A list of :class:`ValidationResult` objects: one failing result
            per list or mapping with null elements, plus a single count result.
            Null elements are not counted. A value that is not a list or
            mapping fails. An absent attribute (empty ``values``)
            produces no results. Null values are reported by the validator.
        """
        if not values:
            return []

        if not all(isinstance(value, (list, dict)) for value in values):
            return [_not_a_collection_result(attribute_path, self.check_type)]

        results = []
        all_passed = True
        for value in values:
            count, null_results = _count_items(self, attribute_path, value)
            results.extend(null_results)
            if count < min_count:
                all_passed = False

        if all_passed:
            message = f"Attribute '{attribute_path}' has at least {min_count} items"
        else:
            message = f"Attribute '{attribute_path}' has fewer than {min_count} items"
        results.append(ValidationResult("", attribute_path, self.check_type, all_passed, message))

        return results

PatternCheck

Check if attribute values match a regular expression pattern.

The identifier for this check type is ("pattern").

Example configuration in data-contract-checks.yml:

    data_contract_checks:
      # Ensure title starts with "Contract"
      - attribute: info.title
        checks:
          - pattern: ^Contract

      # Ensure version follows semantic versioning
      - attribute: info.version
        checks:
          - pattern: ^\d+\.\d+\.\d+$

      # Ensure field names are snake_case
      - attribute: models.*.fields.*.name
        checks:
          - pattern: ^[a-z][a-z0-9_]*$

      # Ensure every tag is lowercase (list attribute: each item is checked)
      - attribute: tags
        checks:
          - pattern: ^[a-z]+$

To enforce full-string matching implicitly, switch this to re.fullmatch.

Source code in src/contract_checker/checks/validators/pattern_check.py
class PatternCheck(Check):
    """Check if attribute values match a regular expression pattern.

    The identifier for this check type is (``"pattern"``).

    Example configuration in ``data-contract-checks.yml``:

    ``` yaml

        data_contract_checks:
          # Ensure title starts with "Contract"
          - attribute: info.title
            checks:
              - pattern: ^Contract

          # Ensure version follows semantic versioning
          - attribute: info.version
            checks:
              - pattern: ^\\d+\\.\\d+\\.\\d+$

          # Ensure field names are snake_case
          - attribute: models.*.fields.*.name
            checks:
              - pattern: ^[a-z][a-z0-9_]*$

          # Ensure every tag is lowercase (list attribute: each item is checked)
          - attribute: tags
            checks:
              - pattern: ^[a-z]+$
    ```

    To enforce full-string matching implicitly, switch this to ``re.fullmatch``.
    """

    @property
    def check_type(self) -> str:
        """Return the check type identifier.

        :return: The string ``"pattern"``.
        """
        return "pattern"

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept only a string that compiles as a regular expression.

        :raises ValueError: If ``check_value`` is not a string or is not a valid regex.
        """
        if not isinstance(check_value, str):
            raise ValueError(f"'pattern' expects a regular expression string, got {type(check_value).__name__}")
        try:
            re.compile(check_value)
        except re.error as e:
            raise ValueError(f"'pattern' is not a valid regular expression ({check_value!r}): {e}") from e

    def execute(self, attribute_path: str, values: List[Any], pattern: str) -> List[ValidationResult]:
        """Execute the pattern matching validation.

        :param attribute_path: The dot-notation path to the attribute being checked.
        :param values: List of values found at the attribute path. Each value can
            be a scalar or a list of scalars; items are converted to a string
            before pattern matching.
        :param pattern: The regular expression pattern to match against.
        :return: A list of :class:`ValidationResult` objects, one for each scalar
            value. When the attribute contains a list, a result is generated for
            each item in that list. An absent attribute (empty ``values``)
            produces no results, since presence is enforced by the
            ``required`` check. Top-level null values are reported by the
            validator; null items inside a list produce one failed result.
        """
        results = []

        items: List[Any] = []
        for value in values:
            if isinstance(value, list):
                null_count = sum(1 for item in value if item is None)
                if null_count:
                    results.append(self.null_elements_result(attribute_path, null_count))
                items.extend(item for item in value if item is not None)
            else:
                items.append(value)

        for value in items:
            value_str = str(value)
            passed = re.match(pattern, value_str) is not None

            if passed:
                message = f"Attribute '{attribute_path}' value '{value_str}' matches pattern '{pattern}'"
            else:
                message = f"Attribute '{attribute_path}' value '{value_str}' does not match pattern '{pattern}'"

            results.append(ValidationResult("", attribute_path, self.check_type, passed, message))

        return results

RequiredCheck

Check if a required field exists and is not null.

The identifier for this check type is ("required").

Example configuration in data-contract-checks.yml:

    data_contract_checks:
      - attribute: info.title
        checks:
          - required: true

      - attribute: models.*.fields.*.description
        checks:
          - required: true
Source code in src/contract_checker/checks/validators/required_check.py
class RequiredCheck(Check):
    """Check if a required field exists and is not null.

    The identifier for this check type is (``"required"``).

    Example configuration in ``data-contract-checks.yml``:

    ``` yaml

        data_contract_checks:
          - attribute: info.title
            checks:
              - required: true

          - attribute: models.*.fields.*.description
            checks:
              - required: true
    ```
    """

    @property
    def check_type(self) -> str:
        """Return the check type identifier.

        :return: The string ``"required"``.
        """
        return "required"

    # required reports null values itself ("Required attribute ... is null"),
    # and required: false must stay a no-op even when the value is null.
    handles_nulls = True

    def validate_config(self, check_value: Any) -> None:  # noqa: ANN401
        """Accept only ``true`` or ``false``.

        A bare ``required:`` (YAML null) is rejected rather than treated as
        ``false``: it is almost always a forgotten ``true``, and ``required:
        false`` exists for the intentional case.

        :raises ValueError: If ``check_value`` is anything else.
        """
        if check_value is None:
            raise ValueError("'required' has no value (did you mean 'required: true'?)")
        if not isinstance(check_value, bool):
            raise ValueError(f"'required' expects true or false, got {check_value!r}")

    def execute(self, attribute_path: str, values: List[Any], check_value: bool) -> List[ValidationResult]:
        """Execute the required field validation.

        :param attribute_path: The dot-notation path to the attribute being checked
            (e.g., ``"info.title"`` or ``"models.*.fields.*.name"``).
        :param values: List of values found at the attribute path.
        :param check_value: ``True``: the attribute must exist and be non-null.
            ``False``: the check does nothing and returns no results. Anything
            else is rejected (see :meth:`validate_config`).
        :return: A list containing a single :class:`ValidationResult` indicating
            whether the check passed or failed, or an empty list when
            ``check_value`` is ``False``.
        :raises ValueError: If ``check_value`` is not a boolean.
        """
        self.validate_config(check_value)
        if check_value is False:
            # required: false documents that a field is optional; it checks
            # nothing, so it must not add a (misleading) passing result.
            return []

        if not values:
            passed = False
            message = f"Required attribute '{attribute_path}' is missing"
        elif any(v is None for v in values):
            passed = False
            message = f"Required attribute '{attribute_path}' is null"
        else:
            passed = True
            message = f"Attribute '{attribute_path}' is present"

        return [ValidationResult("", attribute_path, self.check_type, passed, message)]

This page contains information about the validation checks available in data-contract-checker.

at_least_one

Passes when at least one item in a collection satisfies a key-value condition. Use this when a constraint must hold for some items rather than all items — for example, enforcing that every model has at least one unique field.

Point the attribute path at the collection (not the leaf attribute), and specify a single key-value pair as the check value using a YAML block mapping:

data_contract_checks:
  # Every model must have at least one field with unique: true
  - attribute: models.*.fields
    checks:
      - at_least_one:
          unique: true

  # Every model must have at least one primary key field
  - attribute: models.*.fields
    checks:
      - at_least_one:
          primaryKey: true

Why point at the collection? Wildcard paths like models.*.fields.*.unique are expanded and checked per field. Stopping one level up at models.*.fields passes the entire fields dict to the check, so it can inspect all fields together.

Per-model, not aggregated. When the attribute path contains a wildcard (e.g. models.*.fields), the check runs once per expanded path — once for each model in this example. Each model must independently satisfy the condition; results are not pooled across models. If two of three models have a unique: true field and one does not, you'll get two pass results and one fail, not a single combined verdict. To require that at least one unique field exists somewhere across all models, point attribute at a non-wildcarded path instead.

Check value shape. at_least_one requires a single-key mapping, for example:

checks:
  - at_least_one:
      unique: true

Multi-key mappings, empty mappings, and non-mapping values (scalars, lists) are rejected with a failing result.

exactly_one

Passes when exactly one item in a collection satisfies a key-value condition — not zero, not two, not more. Use this when a constraint must hold for a single designated item. The motivating case is primary-key enforcement: a model with two primaryKey: true fields is a modelling bug that at_least_one silently accepts.

Point the attribute path at the collection (not the leaf attribute), and specify a single key-value pair as the check value using a YAML block mapping:

data_contract_checks:
  # Every model must have exactly one primary key field
  - attribute: models.*.fields
    checks:
      - exactly_one:
          primaryKey: true

  # Every model must have exactly one event-time timestamp field
  - attribute: models.*.fields
    checks:
      - exactly_one:
          type: timestamp

Failure messages include the actual count, e.g. has 2 items with 'primaryKey: true', expected exactly 1.

Why point at the collection? Wildcard paths like models.*.fields.*.primaryKey are expanded and checked per field. Stopping one level up at models.*.fields passes the entire fields dict to the check, so it can count all matching fields together.

Per-model, not aggregated. When the attribute path contains a wildcard (e.g. models.*.fields), the check runs once per expanded path — once for each model in this example. Each model must independently have exactly one match; results are not pooled across models. To require a single designated item across all models, point attribute at a non-wildcarded path instead.

Check-value shape contract. exactly_one requires a single-key mapping. Anything else fails loudly:

check_value Result
{primaryKey: true} OK — checked
{primaryKey: true, unique: true} FAIL — multi-key rejected
{} FAIL — empty mapping rejected
true / "unique" / [primaryKey] / null FAIL — non-mapping rejected