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 handler.validate():

from sloth import MMCIFHandler

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

# Full validation (dictionary schema + wwPDB rules)
report = handler.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.

Relaxed Mode

Pass relaxed=True to skip the built-in MmcifValidator and run only user-registered custom rules:

report = handler.validate(mmcif, relaxed=True)

Per-Category Validation

You can also validate individual categories via the plugin interface:

# Per-category validation (raises ValidationError on failure)
mmcif.data_1ABC._refine.validate()

# Cross-category validation
mmcif.data_1ABC._entity.validate.against(mmcif.data_1ABC._atom_site)

Validator Classes

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

DictionaryValidator

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 DictionaryValidator 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 when you want explicit control:

from sloth import MMCIFHandler, PluginScope
from sloth.mmcif.validator import DictionaryValidator, MmcifValidator

handler = MMCIFHandler()

# Schema-only (no wwPDB rules)
handler.register("validate", DictionaryValidator(), scope=PluginScope.CATEGORY)

# Full wwPDB + schema
handler.register("validate", MmcifValidator(), scope=PluginScope.CATEGORY)

Multi-Level Validators

For validating entire blocks or containers in one call:

BlockValidator

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

ContainerValidator

Delegates to BlockValidator for each block in an MMCIFDataContainer.

These are used internally by handler.validate() but can also be used directly:

from sloth.mmcif.validator import BlockValidator, MmcifValidator

bv = BlockValidator(MmcifValidator())
report = bv.execute(block)
report.raise_on_error()

Validation Report

ValidationReport collects all ValidationError instances:

report = handler.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

mmcif.data_1ABC._atom_site.validate()

Cross-Category Validation

Register a cross-checker by passing a tuple of category names, or use the built-in validators which register cross-checkers automatically:

# The built-in validators already register FK / parent-child / ordering
# cross-checkers.  Just chain .against():
mmcif.data_1ABC._entity_src_nat.validate().against(
    mmcif.data_1ABC._entity
)

You can also register custom cross-checkers:

from sloth import PluginScope

handler.register(
    ("_entity", "_atom_site"),
    lambda e, a: check_entity_coverage(e, a),
    scope=PluginScope.CATEGORY,
)

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"),
)

handler.register("validate", vp, scope=PluginScope.CATEGORY)

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}$"))