onnx_light.compatibility.schema_diff#

Structured diff between two OpSchema versions.

This module provides compare_schemas() and the accompanying dataclasses (SchemaDiff, ParameterDiff, AttributeDiff, ConstraintDiff, DocDiff) for detecting differences — including breaking changes — between two versions of the same ONNX operator schema.

Documentation strings are diffed at the line level (via DocDiff), using difflib (line-based edit distance and unified diff) rather than character-level comparison, so the result reads naturally both as plain text and inside Sphinx code-block:: diff directives.

Typical usage with a full OpSchema:

<<<

from onnx_light.onnx import defs
from onnx_light.compatibility.schema_diff import compare_schemas

defs.register_onnx_operator_set_schema()
old = defs.get_schema("Relu", 6)
new = defs.get_schema("Relu", 14)
diff = compare_schemas(old, new)
print(diff)

>>>

    SchemaDiff: Relu (domain='')
      old version : 6
      new version : 14
      breaking    : False
      Type constraints:
        changed 'T': added types: ['tensor(bfloat16)', 'tensor(int16)', 'tensor(int32)', 'tensor(int64)', 'tensor(int8)']

The same function also accepts the lightweight LightOpSchema objects exposed by onnx_light (those have no attributes nor input/output arity, so those sections of the diff are simply omitted):

<<<

from collections import defaultdict
from onnx_light.onnx_proto._onnxpy import onnx_op
from onnx_light.compatibility.schema_diff import compare_schemas

by_name = defaultdict(list)
for s in onnx_op.GetAllOnnxOpSchemasWithHistory(True):
    by_name[(s.domain, s.name)].append(s)
versions = sorted(by_name[("ai.onnx", "Add")], key=lambda s: s.since_version)
old, new = versions[0], versions[-1]
diff = compare_schemas(old, new)
print(diff)

>>>

    SchemaDiff: Add (domain='ai.onnx')
      old version : 1
      new version : 14
      breaking    : False
      Type constraints:
        changed 'T': added types: ['tensor(bfloat16)', 'tensor(int16)', 'tensor(int32)', 'tensor(int64)', 'tensor(int8)', 'tensor(uint16)', 'tensor(uint32)', 'tensor(uint64)', 'tensor(uint8)']
      Documentation:
        line similarity: 0.09 (+3/-18 lines)
        --- Add v1
        +++ Add v14
        @@ -1,19 +1,4 @@
        -Performs element-wise binary addition (with limited broadcast support).
        -If necessary the right-hand-side argument will be broadcasted to match the
        -shape of left-hand-side argument. When broadcasting is specified, the second
        -tensor can either be of element size 1 (including a scalar tensor and any
        -tensor with rank equal to or smaller than the first tensor), or having its
        -shape as a contiguous subset of the first tensor's shape. The starting of the
        -mutually equal shape is specified by the argument "axis", and if it is not set,
        -suffix matching is assumed. 1-dim expansion doesn't work yet.
        +Performs element-wise binary addition (with Numpy-style broadcasting support).
         
        -For example, the following tensor shapes are supported (with broadcast=1):
        -
        -  shape(A) = (2, 3, 4, 5), shape(B) = (,), i.e. B is a scalar tensor
        -  shape(A) = (2, 3, 4, 5), shape(B) = (1, 1), i.e. B is an 1-element tensor
        -  shape(A) = (2, 3, 4, 5), shape(B) = (5,)
        -  shape(A) = (2, 3, 4, 5), shape(B) = (4, 5)
        -  shape(A) = (2, 3, 4, 5), shape(B) = (3, 4), with axis=1
        -  shape(A) = (2, 3, 4, 5), shape(B) = (2), with axis=0
        -
        -Attribute `broadcast=1` needs to be passed to enable broadcasting.
        +This operator supports multidirectional (i.e., Numpy-style) broadcasting;
        +for more details please check the broadcasting behavior in ONNX.

A change is considered breaking when upgrading existing ONNX models from the old schema version to the new one could alter observable behaviour without any other modification. Examples of breaking changes:

  • Removing a previously required input or output.

  • Adding a new required input (existing models do not supply it).

  • Changing an attribute’s type or making an optional attribute required.

  • Narrowing a type constraint (removing previously allowed types).

Non-breaking examples:

  • Adding a new optional input or output.

  • Widening a type constraint (adding new allowed types).

  • Adding an optional attribute with a sensible default.

class onnx_light.compatibility.schema_diff.AttributeDiff(name: str, kind: str, details: list[str] = <factory>, is_breaking: bool = False)#

Bases: object

Records a difference in a single operator attribute.

Parameters:
  • name – Name of the attribute.

  • kind – Type of difference: 'added', 'removed', or 'changed'.

  • details – Human-readable description of what changed.

  • is_breakingTrue if the difference is a breaking change.

classmethod compare(old_attrs: dict[str, Any], new_attrs: dict[str, Any]) list[AttributeDiff]#

Compares two attribute dictionaries from OpSchema objects.

Parameters:
  • old_attrs – Attribute mapping from the old schema.

  • new_attrs – Attribute mapping from the new schema.

Returns:

A list of AttributeDiff items describing additions, removals, and changes.

Return type:

list[AttributeDiff]

details: list[str]#
is_breaking: bool = False#
kind: str#
name: str#
class onnx_light.compatibility.schema_diff.ConstraintDiff(name: str, kind: str, added_types: list[str] = <factory>, removed_types: list[str] = <factory>, details: list[str] = <factory>, is_breaking: bool = False)#

Bases: object

Records a difference in a type constraint.

Parameters:
  • name – Type parameter string (e.g. 'T', 'T1').

  • kind – Type of difference: 'added', 'removed', or 'changed'.

  • added_types – Types present in the new schema but not in the old one.

  • removed_types – Types present in the old schema but not in the new one.

  • details – Human-readable description of what changed.

  • is_breakingTrue if the difference is a breaking change.

added_types: list[str]#
classmethod compare(old_constraints: list[Any], new_constraints: list[Any]) list[ConstraintDiff]#

Compares two lists of type constraint parameters.

Parameters:
  • old_constraints – Type constraints from the old schema.

  • new_constraints – Type constraints from the new schema.

Returns:

A list of ConstraintDiff items describing additions, removals, and changes.

Return type:

list[ConstraintDiff]

details: list[str]#
is_breaking: bool = False#
kind: str#
name: str#
removed_types: list[str]#
class onnx_light.compatibility.schema_diff.DocDiff(old_doc: str = '', new_doc: str = '', similarity: float = 1.0, unified_diff: list[str] = <factory>, added_lines: int = 0, removed_lines: int = 0)#

Bases: object

Records a difference between two operator documentation strings.

Differences are computed at the line level using difflib.SequenceMatcher() and difflib.unified_diff(), rather than at the character level. This matches how humans typically edit docstrings (line by line) and produces a much more readable diff.

A documentation change is never considered breaking on its own, but it is still useful information when reviewing a new operator schema version.

Parameters:
  • old_doc – Documentation string of the old schema (may be empty).

  • new_doc – Documentation string of the new schema (may be empty).

  • similarity – Line-level similarity ratio in [0.0, 1.0] as returned by difflib.SequenceMatcher.ratio() applied to the list of lines. A value of 1.0 means the two docs are identical.

  • unified_diff – A list of lines produced by difflib.unified_diff() describing the line-level edits to transform old_doc into new_doc. Empty when the two docs are identical.

  • added_lines – Number of inserted lines (+ lines in the unified diff, excluding the file header).

  • removed_lines – Number of removed lines (- lines in the unified diff, excluding the file header).

added_lines: int = 0#
property changed: bool#

Returns True if the two documentation strings differ.

classmethod compare(old_doc: str | None, new_doc: str | None, old_label: str = 'old', new_label: str = 'new', context_lines: int = 3) DocDiff#

Compares two documentation strings at the line level.

The two strings are split into lines (preserving relative blank lines). Similarity is computed with difflib.SequenceMatcher.ratio() on the resulting lists, which is equivalent to a normalised line-level edit distance. A unified diff is produced with difflib.unified_diff() so the result renders nicely both as plain text and inside RST code-block:: diff directives.

Parameters:
  • old_doc – Old documentation string (None is treated as an empty string).

  • new_doc – New documentation string (None is treated as an empty string).

  • old_label – Label used for the old side of the unified diff header.

  • new_label – Label used for the new side of the unified diff header.

  • context_lines – Number of context lines around each hunk in the unified diff.

Returns:

A DocDiff summarising the differences.

Return type:

DocDiff

new_doc: str = ''#
old_doc: str = ''#
removed_lines: int = 0#
similarity: float = 1.0#
unified_diff: list[str]#
class onnx_light.compatibility.schema_diff.ParameterDiff(name: str, kind: str, details: list[str] = <factory>, is_breaking: bool = False)#

Bases: object

Records a difference in a single input or output formal parameter.

Parameters:
  • name – Name of the input or output parameter.

  • kind – Type of difference: 'added', 'removed', or 'changed'.

  • details – Human-readable description of what changed.

  • is_breakingTrue if the difference is a breaking change.

classmethod compare(old_params: list[Any], new_params: list[Any], kind_label: str) list[ParameterDiff]#

Compares two lists of formal parameters (inputs or outputs).

Parameters:
  • old_params – Formal parameters from the old schema.

  • new_params – Formal parameters from the new schema.

  • kind_label – Label used for context ('input' or 'output').

Returns:

A list of ParameterDiff items describing additions, removals, and changes.

Return type:

list[ParameterDiff]

details: list[str]#
is_breaking: bool = False#
kind: str#
name: str#
class onnx_light.compatibility.schema_diff.SchemaDiff(op_name: str, domain: str, old_version: int, new_version: int, inputs: list[ParameterDiff] = <factory>, outputs: list[ParameterDiff] = <factory>, attributes: list[AttributeDiff] = <factory>, constraints: list[ConstraintDiff] = <factory>, doc: DocDiff = <factory>, is_breaking: bool = False, breaking_reasons: list[str] = <factory>)#

Bases: object

Summarizes the differences between two versions of an operator schema.

Use compare_schemas() to create instances of this class.

Parameters:
  • op_name – Operator name.

  • domain – Operator domain.

  • old_versionsince_version of the old (reference) schema.

  • new_versionsince_version of the new schema.

  • inputs – Differences in formal input parameters.

  • outputs – Differences in formal output parameters.

  • attributes – Differences in attributes.

  • constraints – Differences in type constraints.

  • doc – Line-level diff of the operator documentation strings.

  • is_breakingTrue if any individual change is breaking.

  • breaking_reasons – Human-readable list of reasons why the change is breaking.

attributes: list[AttributeDiff]#
breaking_reasons: list[str]#
constraints: list[ConstraintDiff]#
doc: DocDiff#
domain: str#
inputs: list[ParameterDiff]#
is_breaking: bool = False#
new_version: int#
old_version: int#
op_name: str#
outputs: list[ParameterDiff]#
to_rst() str#

Returns an RST-formatted summary of the schema diff.

Produces reStructuredText markup suitable for inclusion in Sphinx documentation (e.g. via a .. runpython:: directive with the :rst: flag, provided by the sphinx-runpython extension).

Returns:

An RST string describing all differences.

Return type:

str

onnx_light.compatibility.schema_diff.compare_schemas(schema_old: Any, schema_new: Any) SchemaDiff#

Compares two OpSchema objects for the same operator.

The function inspects inputs, outputs, attributes, and type constraints and highlights what was added, removed, or changed. It also determines whether the transition from schema_old to schema_new constitutes a breaking change, defined as a change that would alter the observable behaviour of an existing model when its operator is upgraded to the new schema version without any other modification.

Parameters:
  • schema_old – The reference (typically older) schema.

  • schema_new – The schema compared against (typically newer).

Returns:

A SchemaDiff instance summarising all detected differences.

Return type:

SchemaDiff

<<<

from onnx_light.onnx import defs
from onnx_light.compatibility.schema_diff import compare_schemas

defs.register_onnx_operator_set_schema()
old = defs.get_schema("Relu", 6)
new = defs.get_schema("Relu", 14)
diff = compare_schemas(old, new)
print(diff)

>>>

    SchemaDiff: Relu (domain='')
      old version : 6
      new version : 14
      breaking    : False
      Type constraints:
        changed 'T': added types: ['tensor(bfloat16)', 'tensor(int16)', 'tensor(int32)', 'tensor(int64)', 'tensor(int8)']

The function also accepts lightweight schemas exposed by onnx_light (onnx_proto._onnxpy.onnx_op.LightOpSchema). Those schemas do not expose attributes nor input/output arity, so those parts of the diff are simply omitted when both schemas lack them.

<<<

from collections import defaultdict
from onnx_light.onnx_proto._onnxpy import onnx_op
from onnx_light.compatibility.schema_diff import compare_schemas

by_name = defaultdict(list)
for s in onnx_op.GetAllOnnxOpSchemasWithHistory(True):
    by_name[(s.domain, s.name)].append(s)
versions = sorted(by_name[("ai.onnx", "Add")], key=lambda s: s.since_version)
old, new = versions[0], versions[-1]
diff = compare_schemas(old, new)
print(diff)

>>>

    SchemaDiff: Add (domain='ai.onnx')
      old version : 1
      new version : 14
      breaking    : False
      Type constraints:
        changed 'T': added types: ['tensor(bfloat16)', 'tensor(int16)', 'tensor(int32)', 'tensor(int64)', 'tensor(int8)', 'tensor(uint16)', 'tensor(uint32)', 'tensor(uint64)', 'tensor(uint8)']
      Documentation:
        line similarity: 0.09 (+3/-18 lines)
        --- Add v1
        +++ Add v14
        @@ -1,19 +1,4 @@
        -Performs element-wise binary addition (with limited broadcast support).
        -If necessary the right-hand-side argument will be broadcasted to match the
        -shape of left-hand-side argument. When broadcasting is specified, the second
        -tensor can either be of element size 1 (including a scalar tensor and any
        -tensor with rank equal to or smaller than the first tensor), or having its
        -shape as a contiguous subset of the first tensor's shape. The starting of the
        -mutually equal shape is specified by the argument "axis", and if it is not set,
        -suffix matching is assumed. 1-dim expansion doesn't work yet.
        +Performs element-wise binary addition (with Numpy-style broadcasting support).
         
        -For example, the following tensor shapes are supported (with broadcast=1):
        -
        -  shape(A) = (2, 3, 4, 5), shape(B) = (,), i.e. B is a scalar tensor
        -  shape(A) = (2, 3, 4, 5), shape(B) = (1, 1), i.e. B is an 1-element tensor
        -  shape(A) = (2, 3, 4, 5), shape(B) = (5,)
        -  shape(A) = (2, 3, 4, 5), shape(B) = (4, 5)
        -  shape(A) = (2, 3, 4, 5), shape(B) = (3, 4), with axis=1
        -  shape(A) = (2, 3, 4, 5), shape(B) = (2), with axis=0
        -
        -Attribute `broadcast=1` needs to be passed to enable broadcasting.
        +This operator supports multidirectional (i.e., Numpy-style) broadcasting;
        +for more details please check the broadcasting behavior in ONNX.