Validation

SLOTH provides a layered validation system built on the plugin system. Two ready-to-use validator classes and a library of composable rule factories cover everything from mmCIF dictionary conformance to wwPDB deposition business rules.

All validation code lives in sloth.mmcif.validator.

Quick Start

The simplest way to validate is with MMCIFValidator:

from sloth import MMCIFHandler, MMCIFValidator

handler = MMCIFHandler()
mmcif = handler.read("model.cif")

# Full validation (dictionary schema + wwPDB rules)
vp = MMCIFValidator()
report = vp.validate(mmcif)

print(report.is_valid)      # True / False
print(len(report.errors))   # number of ERROR-level issues
print(len(report.warnings)) # number of WARNING-level issues

# Raise on first error
report.raise_on_error()

validate() is polymorphic β€” it works on a single Category, a DataBlock, or an entire MMCIFDataContainer.

Per-Category Validation

Register a validator on a model to get dot-notation access:

from sloth import MMCIFValidator

vp = MMCIFValidator()
block = mmcif.data_1ABC

# Register on a category
block._refine.register("validate", vp)
block._refine.validate()

# Cross-category validation
block._entity.register("validate", vp)
block._entity.validate().against(block._atom_site)

Validator Classes

SLOTH ships two validator classes in sloth.mmcif.validator, both subclasses of ValidatorPlugin:

SchemaValidator

Auto-generated from the bundled mmcif_pdbx_v50.dic (or any mmCIF dictionary). Covers mandatory items, enumerations, type-regex patterns, foreign keys, composite keys, and parent/child category presence β€” all extracted via DictionaryParser.

MMCIFValidator

Extends SchemaValidator with wwPDB deposition business rules expressed as declarative class-level data tables. Adding a new rule is as simple as appending a tuple.

Use them directly:

from sloth.mmcif.validator import SchemaValidator, MMCIFValidator

# Schema-only (no wwPDB rules)
schema_vp = SchemaValidator()
report = schema_vp.validate(mmcif)

# Full wwPDB + schema
full_vp = MMCIFValidator()
report = full_vp.validate(mmcif)

Multi-Level Validators

For validating entire blocks or containers via the plugin interface (dot-notation):

DataBlockValidator

Wraps a ValidatorPlugin and runs all per-category validators + cross- category checkers across every category in a DataBlock. Returns a ValidationReport.

ContainerValidator

Delegates to DataBlockValidator for each block in an MMCIFDataContainer.

Register them on models for dot-notation access:

from sloth.mmcif.validator import (
    MMCIFValidator, DataBlockValidator, ContainerValidator,
)

vp = MMCIFValidator()
bv = DataBlockValidator(vp)
cv = ContainerValidator(bv)

# Register on a container for one-call validation
mmcif.register("validate", cv)
wrapper = mmcif.validate()
wrapper.report.raise_on_error()

# Or register on a block
block.register("validate", bv)
wrapper = block.validate()
print(wrapper.report.is_valid)

Validation Report

ValidationReport collects all ValidationError instances:

report = vp.validate(container)

report.is_valid          # True if no ERROR-level issues
report.errors            # list of ERROR-level ValidationError
report.warnings          # list of WARNING-level ValidationError
report.all_issues        # everything (ERROR + WARNING + INFO)
report.raise_on_error()  # raises the first ERROR, or does nothing

Single-Category Validation

# Register validator, then use dot-notation
block._atom_site.register("validate", vp)
block._atom_site.validate()

Cross-Category Validation

The built-in validators register cross-checkers automatically for FK, parent/child, and ordering constraints. Chain .against() to run them:

block._entity_src_nat.register("validate", vp)
block._entity_src_nat.validate().against(block._entity)

You can also register custom cross-checkers on a ValidatorPlugin:

vp = ValidatorPlugin()
vp.register_cross_checker(
    ("_entity", "_atom_site"),
    lambda e, a: check_entity_coverage(e, a),
)

Custom Rules with Factories

The sloth.mmcif.validator module exports 18 composable factory functions that return validator callables. Use them to build a custom ValidatorPlugin:

from sloth.mmcif.validator import ValidatorPlugin
from sloth.mmcif.validator import (
    mandatory_items,
    value_length,
    ordering_check,
    foreign_key,
)

vp = ValidatorPlugin()

# Category-level rules
vp.register_validator("_struct", mandatory_items(["title"]))
vp.register_validator("_struct", value_length("title", min_len=10))

# Cross-category rule
vp.register_cross_checker(
    ("_atom_site", "_entity"),
    foreign_key("label_entity_id", "id"),
)

# Validate directly
report = vp.validate(mmcif)

# Or register on a model for dot-notation
block._struct.register("validate", vp)
block._struct.validate()

Single-category factories:

Cross-category factories:

Validation Severity

Every rule factory accepts a severity parameter:

from sloth import ValidationError, ValidationSeverity
from sloth.mmcif.validator import value_range

# ERROR β€” prevents processing (default for most factories)
# WARNING β€” flags potential issues
# INFO β€” informational notices

check = value_range("defocus", min_val=0, max_val=200,
                     severity=ValidationSeverity.WARNING)

Extending MMCIFValidator

To add wwPDB rules, subclass MMCIFValidator and extend the declarative tables:

from sloth.mmcif.validator import MMCIFValidator

class MyValidator(MMCIFValidator):
    # Add mandatory items for a custom category
    _MANDATORY = MMCIFValidator._MANDATORY + [
        ("_my_category", ["required_field_a", "required_field_b"]),
    ]

Or add rules at runtime after instantiation:

from sloth.mmcif.validator import MMCIFValidator, regex_check

v = MMCIFValidator()
v.register_validator("_my_category", regex_check("code", r"^[A-Z]{3}$"))