Skip to content

Schema Evolution

Check schema compatibility and compute diffs between versions.

Overview

from pycharter.schema_evolution import (
    check_compatibility,
    compute_diff,
    CompatibilityMode,
)

result = check_compatibility(old_schema, new_schema, mode=CompatibilityMode.BACKWARD)

API Reference

check_compatibility

check_compatibility(
    old_schema: dict[str, Any],
    new_schema: dict[str, Any],
    mode: str | CompatibilityMode = BACKWARD,
) -> CompatibilityResult

Check if two schemas are compatible according to the specified mode.

Compatibility Modes
  • BACKWARD: New schema can read data produced by old schema (consumers can be upgraded before producers)
  • FORWARD: Old schema can read data produced by new schema (producers can be upgraded before consumers)
  • FULL: Both backward and forward compatible

A change the diff cannot reason about (oneOf, if/then, dependentRequired, ...) is incompatible in every mode, with a "review manually" message. Adding or removing an optional property of an open object is treated as compatible, assuming producers do not send properties their schema does not declare.

Parameters:

Name Type Description Default
old_schema dict[str, Any]

The existing/original schema

required
new_schema dict[str, Any]

The new schema to check

required
mode str | CompatibilityMode

Compatibility mode to check against

BACKWARD

Returns:

Type Description
CompatibilityResult

CompatibilityResult with compatibility status and details

Example

old = {"type": "object", "properties": {"name": {"type": "string"}}} new = { ... "type": "object", ... "properties": { ... "name": {"type": "string"}, ... "age": {"type": "integer"} # Added optional field ... } ... } result = check_compatibility(old, new, mode="backward") result.compatible True

Source code in src/pycharter/schema_evolution/compatibility.py
def check_compatibility(
    old_schema: dict[str, Any],
    new_schema: dict[str, Any],
    mode: str | CompatibilityMode = CompatibilityMode.BACKWARD,
) -> CompatibilityResult:
    """
    Check if two schemas are compatible according to the specified mode.

    Compatibility Modes:
        - BACKWARD: New schema can read data produced by old schema
                   (consumers can be upgraded before producers)
        - FORWARD: Old schema can read data produced by new schema
                  (producers can be upgraded before consumers)
        - FULL: Both backward and forward compatible

    A change the diff cannot reason about (``oneOf``, ``if``/``then``,
    ``dependentRequired``, ...) is incompatible in every mode, with a
    "review manually" message. Adding or removing an optional property of
    an open object is treated as compatible, assuming producers do not
    send properties their schema does not declare.

    Args:
        old_schema: The existing/original schema
        new_schema: The new schema to check
        mode: Compatibility mode to check against

    Returns:
        CompatibilityResult with compatibility status and details

    Example:
        >>> old = {"type": "object", "properties": {"name": {"type": "string"}}}
        >>> new = {
        ...     "type": "object",
        ...     "properties": {
        ...         "name": {"type": "string"},
        ...         "age": {"type": "integer"}  # Added optional field
        ...     }
        ... }
        >>> result = check_compatibility(old, new, mode="backward")
        >>> result.compatible
        True
    """
    if isinstance(mode, str):
        mode = CompatibilityMode(mode.lower())
    return compatibility_from_diff(compute_diff(old_schema, new_schema), mode)

compute_diff

compute_diff(
    old_schema: dict[str, Any],
    new_schema: dict[str, Any],
    path: str = "",
) -> SchemaDiff

Compute detailed diff between two JSON Schema versions.

Parameters:

Name Type Description Default
old_schema dict[str, Any]

Original schema.

required
new_schema dict[str, Any]

New schema.

required
path str

Current path (for nested comparison).

''

Returns:

Type Description
SchemaDiff

SchemaDiff with all detected changes, in a stable order.

Source code in src/pycharter/schema_evolution/_diff_engine.py
def compute_diff(
    old_schema: dict[str, Any],
    new_schema: dict[str, Any],
    path: str = "",
) -> SchemaDiff:
    """Compute detailed diff between two JSON Schema versions.

    Args:
        old_schema: Original schema.
        new_schema: New schema.
        path: Current path (for nested comparison).

    Returns:
        SchemaDiff with all detected changes, in a stable order.
    """
    if not path:
        # Compare what the schemas accept, not how they spell references.
        old_schema = resolve_refs(old_schema)
        new_schema = resolve_refs(new_schema)
    changes: list[SchemaChange] = []
    changes.extend(_compare_attributes(old_schema, new_schema, path))
    if "properties" in old_schema or "properties" in new_schema:
        changes.extend(
            _compare_properties(
                old_schema,
                new_schema,
                f"{path}.properties" if path else "properties",
            )
        )
    changes.extend(
        _compare_required(
            old_schema.get("required", []),
            new_schema.get("required", []),
            f"{path}.required" if path else "required",
        )
    )
    changes.extend(_compare_additional_properties(old_schema, new_schema, path))
    if "items" in old_schema or "items" in new_schema:
        changes.extend(
            _compare_items(
                old_schema.get("items"),
                new_schema.get("items"),
                f"{path}.items" if path else "items",
            )
        )
    changes.extend(_compare_unanalyzed(old_schema, new_schema, path))
    return SchemaDiff(changes=changes)

CompatibilityMode

Mode Description
BACKWARD New schema can read old data
FORWARD Old schema can read new data
FULL Both backward and forward
NONE No compatibility check

CompatibilityResult

Attribute Type Description
is_compatible bool Compatibility status
issues List[str] List of issues found

SchemaDiff

Attribute Type Description
added_fields List[str] New fields
removed_fields List[str] Removed fields
modified_fields Dict Changed fields

Examples

from pycharter.schema_evolution import (
    check_compatibility,
    compute_diff,
    CompatibilityMode,
)

# Check compatibility
result = check_compatibility(
    old_schema=schema_v1, new_schema=schema_v2, mode=CompatibilityMode.BACKWARD
)

if not result.is_compatible:
    print(f"Issues: {result.issues}")

# Compute diff
diff = compute_diff(schema_v1, schema_v2)
print(f"Added: {diff.added_fields}")
print(f"Removed: {diff.removed_fields}")

See Also