Skip to content

ModelDiff class

Here's the reference information for the ModelDiff class, with all its parameters, attributes and methods.

You can import the ModelDiff class directly from pyrmute:

from pyrmute import ModelDiff

pyrmute.ModelDiff dataclass

ModelDiff(
    model_name,
    from_version,
    to_version,
    added_fields,
    removed_fields,
    modified_fields,
    added_field_info,
    unchanged_fields,
)

Contains the difference between two models.

model_name instance-attribute

model_name

from_version instance-attribute

from_version

to_version instance-attribute

to_version

added_fields instance-attribute

added_fields

removed_fields instance-attribute

removed_fields

modified_fields instance-attribute

modified_fields

added_field_info instance-attribute

added_field_info

unchanged_fields instance-attribute

unchanged_fields

to_markdown

to_markdown(header_depth=1)

Generate a markdown representation of the diff.

PARAMETER DESCRIPTION
header_depth

Base header level (1-6). All headers are relative to this. For example, header_depth=2 makes the title "##" and subsections "###".

TYPE: int DEFAULT: 1

RETURNS DESCRIPTION
str

Formatted markdown string showing the differences.

Example
diff.to_markdown(header_depth=1)  # Default: # Title, ## Sections
diff.to_markdown(header_depth=2)  # ## Title, ### Sections
diff.to_markdown(header_depth=3)  # ### Title, #### Sections
Source code in src/pyrmute/model_diff.py
def to_markdown(self: Self, header_depth: int = 1) -> str:
    """Generate a markdown representation of the diff.

    Args:
        header_depth: Base header level (1-6). All headers are relative to this.
            For example, header_depth=2 makes the title "##" and subsections "###".

    Returns:
        Formatted markdown string showing the differences.

    Example:
        ```python
        diff.to_markdown(header_depth=1)  # Default: # Title, ## Sections
        diff.to_markdown(header_depth=2)  # ## Title, ### Sections
        diff.to_markdown(header_depth=3)  # ### Title, #### Sections
        ```
    """
    header_depth = max(1, min(6, header_depth))

    h1 = "#" * header_depth
    h2 = "#" * (header_depth + 1)

    lines = [
        f"{h1} {self.model_name}: {self.from_version}{self.to_version}",
        "",
    ]

    lines.append(f"{h2} Added Fields")
    lines.append("")
    if self.added_fields:
        for field_name in sorted(self.added_fields):
            field_desc = self._format_field_description(field_name, "added")
            lines.append(f"- {field_desc}")
    else:
        lines.append("None")
    lines.append("")

    lines.append(f"{h2} Removed Fields")
    lines.append("")
    if self.removed_fields:
        for field_name in sorted(self.removed_fields):
            field_desc = self._format_field_description(field_name, "removed")
            lines.append(f"- {field_desc}")
    else:
        lines.append("None")
    lines.append("")

    lines.append(f"{h2} Modified Fields")
    lines.append("")
    if self.modified_fields:
        for field_name in sorted(self.modified_fields.keys()):
            changes = self.modified_fields[field_name]
            field_desc = self._format_modified_field(field_name, changes)
            lines.append(f"- {field_desc}")
    else:
        lines.append("None")
    lines.append("")

    breaking_changes = self._identify_breaking_changes()
    if breaking_changes:
        lines.append(f"{h2} Breaking Changes")
        lines.append("")
        lines.extend(f"-⚠️  {warning}" for warning in breaking_changes)
        lines.append("")

    return "\n".join(lines)

to_dict

to_dict()

Convert to a JSON-serializable dictionary.

Converts type objects to their string representation for JSON compatibility.

RETURNS DESCRIPTION
dict[str, Any]

Dictionary with all type objects converted to strings.

Example
diff_dict = diff.to_dict()
json.dumps(diff_dict, indent=2)
Source code in src/pyrmute/model_diff.py
def to_dict(self: Self) -> dict[str, Any]:
    """Convert to a JSON-serializable dictionary.

    Converts type objects to their string representation for JSON compatibility.

    Returns:
        Dictionary with all type objects converted to strings.

    Example:
        ```python
        diff_dict = diff.to_dict()
        json.dumps(diff_dict, indent=2)
        ```
    """
    diff_dict = asdict(self)
    return cast("dict[str, Any]", self._serialize_for_json(diff_dict))

from_models classmethod

from_models(
    name, from_model, to_model, from_version, to_version
)

Create a ModelDiff by comparing two Pydantic models.

PARAMETER DESCRIPTION
name

Name of the model.

TYPE: str

from_model

Source model class.

TYPE: type[BaseModel]

to_model

Target model class.

TYPE: type[BaseModel]

from_version

Source version string.

TYPE: str

to_version

Target version string.

TYPE: str

RETURNS DESCRIPTION
Self

ModelDiff instance with computed differences.

Source code in src/pyrmute/model_diff.py
@classmethod
def from_models(
    cls,
    name: str,
    from_model: type[BaseModel],
    to_model: type[BaseModel],
    from_version: str,
    to_version: str,
) -> Self:
    """Create a ModelDiff by comparing two Pydantic models.

    Args:
        name: Name of the model.
        from_model: Source model class.
        to_model: Target model class.
        from_version: Source version string.
        to_version: Target version string.

    Returns:
        ModelDiff instance with computed differences.
    """
    from_fields = from_model.model_fields
    to_fields = to_model.model_fields

    from_keys = set(from_fields.keys())
    to_keys = set(to_fields.keys())

    added = list(to_keys - from_keys)
    removed = list(from_keys - to_keys)
    common = from_keys & to_keys

    modified = {}
    unchanged = []

    for field_name in common:
        from_field = from_fields[field_name]
        to_field = to_fields[field_name]

        changes: dict[str, Any] = {}

        if from_field.annotation != to_field.annotation:
            changes["type_changed"] = {
                "from": from_field.annotation,
                "to": to_field.annotation,
            }

        from_required = from_field.is_required()
        to_required = to_field.is_required()
        if from_required != to_required:
            changes["required_changed"] = {
                "from": from_required,
                "to": to_required,
            }

        from_default = from_field.default
        to_default = to_field.default

        if from_default != to_default and not (
            from_default is PydanticUndefined and to_default is PydanticUndefined
        ):
            if (
                from_default is not PydanticUndefined
                and to_default is not PydanticUndefined
            ):
                changes["default_changed"] = {
                    "from": from_default,
                    "to": to_default,
                }
            elif from_default is PydanticUndefined:
                changes["default_added"] = to_default
            else:
                changes["default_removed"] = from_default

        if changes:
            modified[field_name] = changes
        else:
            unchanged.append(field_name)

    added_field_info = {}
    for field_name in added:
        to_field = to_fields[field_name]
        added_field_info[field_name] = {
            "type": to_field.annotation,
            "required": to_field.is_required(),
            "default": to_field.default
            if to_field.default is not PydanticUndefined
            else None,
        }

    return cls(
        model_name=name,
        from_version=from_version,
        to_version=to_version,
        added_fields=added,
        removed_fields=removed,
        modified_fields=modified,
        unchanged_fields=unchanged,
        added_field_info=added_field_info,
    )