graphtage.pydiff

Functions to diff in-memory Python objects.

See the documentation on using Graphtage programmatically for some examples.

pydiff classes

ASTBuilder

class graphtage.pydiff.ASTBuilder(options: BuildOptions | None = None)

Bases: BasicBuilder

Builds a Graphtage tree from a Python Abstract Syntax Tree

BUILDERS: Dict[Type[Any], Callable[[Builder, Any, List[TreeNode]], TreeNode]] = {<class 'int'>: <function BasicBuilder.build_int>, <class 'bytes'>: <function BasicBuilder.build_str>, <class 'str'>: <function BasicBuilder.build_str>, <class 'NoneType'>: <function BasicBuilder.build_none>, <class 'float'>: <function BasicBuilder.build_float>, <class 'bool'>: <function BasicBuilder.build_bool>, <class 'tuple'>: <function BasicBuilder.build_list>, <class 'list'>: <function BasicBuilder.build_list>, <class 'frozenset'>: <function BasicBuilder.build_set>, <class 'set'>: <function BasicBuilder.build_set>, <class 'dict'>: <function BasicBuilder.build_dict>, <class '_ast.Module'>: <function ASTBuilder.build_module>, <class '_ast.Set'>: <function ASTBuilder.build_set>, <class '_ast.Tuple'>: <function ASTBuilder.build_ast_list>, <class '_ast.List'>: <function ASTBuilder.build_ast_list>, <class '_ast.Assign'>: <function ASTBuilder.build_assign>, <class '_ast.Name'>: <function ASTBuilder.build_name>, <class '_ast.Expr'>: <function ASTBuilder.build_constant>, <class '_ast.Constant'>: <function ASTBuilder.build_constant>, <class '_ast.Call'>: <function ASTBuilder.build_call>, <class '_ast.ImportFrom'>: <function ASTBuilder.build_import_from>, <class '_ast.alias'>: <function ASTBuilder.build_alias>, <class '_ast.Attribute'>: <function ASTBuilder.build_attribute>, <class '_ast.Dict'>: <function ASTBuilder.build_ast_dict>, <class '_ast.Subscript'>: <function ASTBuilder.build_subscript>}
EXPANDERS: Dict[Type[Any], Callable[[Builder, Any], Iterable[Any] | None]] = {<class 'frozenset'>: <function BasicBuilder.expand_list>, <class 'set'>: <function BasicBuilder.expand_list>, <class 'tuple'>: <function BasicBuilder.expand_list>, <class 'list'>: <function BasicBuilder.expand_list>, <class 'dict'>: <function BasicBuilder.expand_dict>, <class '_ast.Module'>: <function ASTBuilder.expand_module>, <class '_ast.Set'>: <function ASTBuilder.expand_collection>, <class '_ast.Tuple'>: <function ASTBuilder.expand_collection>, <class '_ast.List'>: <function ASTBuilder.expand_collection>, <class '_ast.Assign'>: <function ASTBuilder.expand_assign>, <class '_ast.Attribute'>: <function ASTBuilder.expand_constant>, <class '_ast.Expr'>: <function ASTBuilder.expand_constant>, <class '_ast.Constant'>: <function ASTBuilder.expand_constant>, <class '_ast.Call'>: <function ASTBuilder.expand_call>, <class '_ast.ImportFrom'>: <function ASTBuilder.expand_import_from>, <class '_ast.Dict'>: <function ASTBuilder.expand_ast_dict>, <class '_ast.Subscript'>: <function ASTBuilder.expand_subscript>}
__init__(options: BuildOptions | None = None)
classmethod _resolve(obj_type: Type[Any], choices: Dict[Type[Any], T]) T | None

Resolves the most specialized expander or builder for obj_type

build(node: Any, children: List[TreeNode]) TreeNode
build_alias(node: alias, _)
build_assign(_, children)
build_ast_dict(node: Dict, children: List[TreeNode])
build_ast_list(node: List, children)
build_attribute(node: Attribute, children: List[TreeNode])
build_bool(obj: bool, _) BoolNode
build_call(_, children: List[TreeNode])
build_constant(_, children: List[TreeNode])
build_dict(_, children: List[TreeNode])
build_float(obj: float, _) FloatNode
build_import_from(node: ImportFrom, children: List[TreeNode])
build_int(obj: int, _) IntegerNode
build_list(obj, children: List[TreeNode]) ListNode
build_module(_, children: List[TreeNode])
build_name(node: Name, _)
build_none(obj, _) NullNode
build_set(_, children: List[TreeNode])
build_str(obj: str, _) StringNode
build_subscript(_, children: List[TreeNode])
build_tree(root_obj) TreeNode
static builder(node_type: Type[T])
default_builder(node: Any, children: List[TreeNode]) TreeNode
default_expander(node: Any) Iterable[Any]
expand(node: Any) Iterable[Any]
expand_assign(node: Assign)
expand_ast_dict(node: Dict)
expand_call(node: Call)
expand_collection(node: List | Tuple | Set)
expand_constant(node: Constant)
expand_dict(obj: dict)
expand_import_from(node: ImportFrom)
expand_list(obj: list)
expand_module(node: Module)
expand_subscript(node: Subscript)
static expander(node_type: Type[T])
options: BuildOptions
classmethod resolve_builder(obj_type: Type[Any]) Callable[[Any, List[TreeNode]], TreeNode] | None

Resolves the most specialized builder for obj_type

classmethod resolve_expander(obj_type: Type[Any]) Callable[[Any], Iterable[Any] | None] | None

Resolves the most specialized expander for obj_type

PyAlias

class graphtage.pydiff.PyAlias(*args, **kwargs)

Bases: DataClassNode

__init__(*args, **kwargs)

Be careful extending __init__; consider using DataClassNode.post_init() instead.

add_edit_modifier(modifier: Callable[[TreeNode, TreeNode], Edit | None])
all_children_are_leaves() bool

Tests whether all of the children of this container are leaves.

Equivalent to:

all(c.is_leaf for c in self)
Returns:

True if all children are leaves.

Return type:

bool

as_name: StringNode
calculate_total_size() int

Calculates the size of this node. This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

children() Sequence[TreeNode]

The children of this node.

Equivalent to:

list(self)
copy() T

Creates a deep copy of this node

copy_from(children: Iterable[TreeNode]) T

Constructs a new instance of this tree node from a list of its children

dfs() Iterator[TreeNode]

Performs a depth-first traversal over all of this node’s descendants.

self is always included and yielded first.

This implementation is equivalent to:

stack = [self]
while stack:
    node = stack.pop()
    yield node
    stack.extend(reversed(node.children()))
diff(node: TreeNode) EditedTreeNode | T

Performs a diff against the provided node.

Parameters:

node – The node against which to perform the diff.

Returns:

An edited version of this node with all edits being completed.

Return type:

Union[EditedTreeNode, T]

editable_dict() Dict[str, Any]

Copies self.__dict__, calling TreeNode.editable_dict() on any TreeNode objects therein.

This is equivalent to:

ret = dict(self.__dict__)
if not self.is_leaf:
    for key, value in ret.items():
        if isinstance(value, TreeNode):
            ret[key] = value.make_edited()
return ret

This is used by TreeNode.make_edited().

property edited: bool

Returns whether this node has been edited.

The default implementation returns False, whereas EditedTreeNode.edited() returns True.

edits(node: TreeNode) Edit

Calculates the best edit to transform this node into the provided node.

Parameters:

node – The node to which to transform.

Returns:

The best possible edit.

Return type:

Edit

get_all_edit_contexts(node: TreeNode) Iterator[Tuple[Tuple[TreeNode, ...], Edit]]

Returns an iterator over all edit contexts that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over pairs of paths from node to the edited node, as well as its edit. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Tuple[Tuple[“TreeNode”, …], Edit]

get_all_edits(node: TreeNode) Iterator[Edit]

Returns an iterator over all edits that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over edits. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Edit]

property is_leaf: bool

Container nodes are never leaves, even if they have no children.

Returns:

False

Return type:

bool

items() Iterator[Tuple[str, TreeNode]]
make_edited() EditedTreeNode | T

Returns a new, copied instance of this node that is also an instance of EditedTreeNode.

This is equivalent to:

return self.__class__.edited_type()(self)
Returns:

A copied version of this node that is also an instance of EditedTreeNode and thereby mutable.

Return type:

Union[EditedTreeNode, T]

name: StringNode
property parent: TreeNode | None

The parent node of this node, or None if it has no parent.

The setter for this property should only be called by the parent node setting itself as the parent of its child.

ContainerNode subclasses automatically set this property for all of their children. However, if you define a subclass of TreeNode does not extend off of ContainerNode and for which len(self.children()) > 0, then each child’s parent must be set.

post_init()

Callback called after this class’s members have been initialized.

This callback should not call super().post_init(). Each superclass’s post_init() will be automatically called in order of the __mro__.

print(printer: Printer)

Prints this node.

print_parent_context(printer: Printer, for_child: TreeNode)

Prints the context for the given child node.

For example, if this node represents a list and the child is the element at index 3, then “[3]” might be printed.

The child is expected to be one of this node’s children, but this is not validated.

The default implementation prints nothing.

to_obj()

Returns a pure Python representation of this node.

For example, a node representing a list, like graphtage.ListNode, should return a Python list. A node representing a mapping, like graphtage.MappingNode, should return a Python dict. Container nodes should recursively call TreeNode.to_obj() on all of their children.

This is used solely for the providing objects to operate on in the commandline expressions evaluation, for options like –match-if and –match-unless.

property total_size: int

The size of this node.

This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

The first time this property is called, its value will be set and memoized by calling TreeNode.calculate_total_size().

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

PyDictFormatter

class graphtage.pydiff.PyDictFormatter(*args, **kwargs)

Bases: JSONDictFormatter

DEFAULT_INSTANCE: Formatter[T] = <graphtage.pydiff.PyDictFormatter object>

A default instance of this formatter, automatically instantiated by the FormatterChecker metaclass.

__init__()

Initializes a sequence formatter.

Parameters:
  • start_symbol – The symbol to print at the start of the sequence.

  • end_symbol – The symbol to print at the end of the sequence.

  • delimiter – A delimiter to print between items.

  • delimiter_callback

    A callback for when a delimiter is to be printed. If omitted, this defaults to:

    lambda p: p.write(delimiter)
    

static __new__(cls, *args, **kwargs) Formatter[T]

Instantiates a new formatter.

This automatically instantiates and populates Formatter.sub_formatters and sets their parent to this new formatter.

delimiter_callback: Callable[[Printer], Any]
edit_print(printer: Printer, edit: Edit)

Called when the edit for an item is to be printed.

If the SequenceNode being printed either is not edited or has no edits, then the edit passed to this function will be a Match(child, child, 0).

This implementation simply delegates the print to the Formatting Protocol:

self.print(printer, edit)
get_formatter(item: T) Callable[[Printer, T], Any] | None

Looks up a formatter for the given item using this formatter as a base.

Equivalent to:

get_formatter(item.__class__, base_formatter=self)
is_partial: bool = True

This is a partial formatter; it will not be automatically used in the Formatting Protocol.

item_newline(printer: Printer, is_first: bool = False, is_last: bool = False)

Called before each node is printed.

This is also called one extra time after the last node, if there is at least one node printed.

The default implementation is simply:

printer.newline()
items_indent(printer: Printer) Printer

Returns a Printer context with an indentation.

This is called as:

with self.items_indent(printer) as p:

immediately after the self.start_symbol is printed, but before any of the items have been printed.

This default implementation is equivalent to:

return printer.indent()
parent: Formatter[T] | None = None

The parent formatter for this formatter instance.

This is automatically populated by Formatter.__new__() and should never be manually modified.

print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)

Prints the given node or edit.

Parameters:
  • printer – The printer to which to write.

  • node_or_edit – The node or edit to print.

  • with_edits – If :keyword:True, print any edits associated with the node.

Note

The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.

print_MappingNode(*args, **kwargs)

Prints a graphtage.MappingNode.

Equivalent to:

super().print_SequenceNode(*args, **kwargs)
print_MultiSetNode(*args, **kwargs)

Prints a graphtage.MultiSetNode.

Equivalent to:

super().print_SequenceNode(*args, **kwargs)
print_SequenceNode(*args, **kwargs)

Prints a non-Dict sequence.

This delegates to the parent formatter’s implementation:

self.parent.print(*args, **kwargs)

which should invoke JSONFormatter.print(), thereby delegating to the JSONListFormatter in instances where a dict contains a list.

property root: Formatter[T]

Returns the root formatter.

sub_format_types: Sequence[Type[Formatter[T]]] = ()

A list of formatter types that should be used as sub-formatters in the Formatting Protocol.

sub_formatters: List[Formatter[T]] = []

The list of instantiated formatters corresponding to Formatter.sub_format_types.

This list is automatically populated by Formatter.__new__() and should never be manually modified.

PyDiffFormatter

class graphtage.pydiff.PyDiffFormatter(*args, **kwargs)

Bases: GraphtageFormatter

DEFAULT_INSTANCE: Formatter[T] = <graphtage.pydiff.PyDiffFormatter object>

A default instance of this formatter, automatically instantiated by the FormatterChecker metaclass.

__init__()
static __new__(cls, *args, **kwargs) Formatter[T]

Instantiates a new formatter.

This automatically instantiates and populates Formatter.sub_formatters and sets their parent to this new formatter.

get_formatter(item: T) Callable[[Printer, T], Any] | None

Looks up a formatter for the given item using this formatter as a base.

Equivalent to:

get_formatter(item.__class__, base_formatter=self)
is_partial: bool = False
parent: Formatter[T] | None = None

The parent formatter for this formatter instance.

This is automatically populated by Formatter.__new__() and should never be manually modified.

print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)

Prints the given node or edit.

Parameters:
  • printer – The printer to which to write.

  • node_or_edit – The node or edit to print.

  • with_edits – If :keyword:True, print any edits associated with the node.

Note

The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.

print_PyAlias(printer: Printer, node: PyAlias)
print_Subscript(printer: Printer, node: Subscript)
property root: Formatter[T]

Returns the root formatter.

sub_format_types: Sequence[Type[Formatter[T]]] = [<class 'graphtage.pydiff.PyObjFormatter'>, <class 'graphtage.pydiff.PyImportFormatter'>, <class 'graphtage.pydiff.PyModuleFormatter'>, <class 'graphtage.pydiff.PyListFormatter'>, <class 'graphtage.pydiff.PyDictFormatter'>]

A list of formatter types that should be used as sub-formatters in the Formatting Protocol.

sub_formatters: List[Formatter[T]] = []

The list of instantiated formatters corresponding to Formatter.sub_format_types.

This list is automatically populated by Formatter.__new__() and should never be manually modified.

PyImportFormatter

class graphtage.pydiff.PyImportFormatter(*args, **kwargs)

Bases: SequenceFormatter

DEFAULT_INSTANCE: Formatter[T] = <graphtage.pydiff.PyImportFormatter object>

A default instance of this formatter, automatically instantiated by the FormatterChecker metaclass.

__init__()

Initializes a sequence formatter.

Parameters:
  • start_symbol – The symbol to print at the start of the sequence.

  • end_symbol – The symbol to print at the end of the sequence.

  • delimiter – A delimiter to print between items.

  • delimiter_callback

    A callback for when a delimiter is to be printed. If omitted, this defaults to:

    lambda p: p.write(delimiter)
    

static __new__(cls, *args, **kwargs) Formatter[T]

Instantiates a new formatter.

This automatically instantiates and populates Formatter.sub_formatters and sets their parent to this new formatter.

edit_print(printer: Printer, edit: Edit)

Called when the edit for an item is to be printed.

If the SequenceNode being printed either is not edited or has no edits, then the edit passed to this function will be a Match(child, child, 0).

This implementation simply delegates the print to the Formatting Protocol:

self.print(printer, edit)
get_formatter(item: T) Callable[[Printer, T], Any] | None

Looks up a formatter for the given item using this formatter as a base.

Equivalent to:

get_formatter(item.__class__, base_formatter=self)
is_partial: bool = True

This is a partial formatter; it will not be automatically used in the Formatting Protocol.

item_newline(printer: Printer, is_first: bool = False, is_last: bool = False)

Called before each node is printed.

This is also called one extra time after the last node, if there is at least one node printed.

The default implementation is simply:

printer.newline()
items_indent(printer: Printer) Printer

Returns a Printer context with an indentation.

This is called as:

with self.items_indent(printer) as p:

immediately after the self.start_symbol is printed, but before any of the items have been printed.

This default implementation is equivalent to:

return printer.indent()
parent: Formatter[T] | None = None

The parent formatter for this formatter instance.

This is automatically populated by Formatter.__new__() and should never be manually modified.

print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)

Prints the given node or edit.

Parameters:
  • printer – The printer to which to write.

  • node_or_edit – The node or edit to print.

  • with_edits – If :keyword:True, print any edits associated with the node.

Note

The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.

print_Assignment(printer: Printer, node: Assignment)
print_Import(printer: Printer, node: Import)
print_SequenceNode(printer: Printer, node: SequenceNode)

Formats a sequence node.

The protocol for this function is as follows:

  • Print self.start_symbol

  • With the printer returned by self.items_indent:
    • For each edit in the sequence (or just a sequence of graphtage.Match for each child, if the node is not edited):
      • Call self.item_newline(printer, is_first=index == 0)

      • Call self.edit_print(printer, edit)

  • If at least one edit was printed, then call self.item_newline(printer, is_last=True)

  • Print self.start_symbol

property root: Formatter[T]

Returns the root formatter.

sub_format_types: Sequence[Type[Formatter[T]]] = [<class 'graphtage.pydiff.PyListFormatter'>]

A list of formatter types that should be used as sub-formatters in the Formatting Protocol.

sub_formatters: List[Formatter[T]] = []

The list of instantiated formatters corresponding to Formatter.sub_format_types.

This list is automatically populated by Formatter.__new__() and should never be manually modified.

PyListFormatter

class graphtage.pydiff.PyListFormatter(*args, **kwargs)

Bases: JSONListFormatter

DEFAULT_INSTANCE: Formatter[T] = <graphtage.pydiff.PyListFormatter object>

A default instance of this formatter, automatically instantiated by the FormatterChecker metaclass.

__init__()

Initializes the JSON list formatter.

Equivalent to:

super().__init__('[', ']', ',')
static __new__(cls, *args, **kwargs) Formatter[T]

Instantiates a new formatter.

This automatically instantiates and populates Formatter.sub_formatters and sets their parent to this new formatter.

delimiter_callback: Callable[[Printer], Any]
edit_print(printer: Printer, edit: Edit)

Called when the edit for an item is to be printed.

If the SequenceNode being printed either is not edited or has no edits, then the edit passed to this function will be a Match(child, child, 0).

This implementation simply delegates the print to the Formatting Protocol:

self.print(printer, edit)
get_formatter(item: T) Callable[[Printer, T], Any] | None

Looks up a formatter for the given item using this formatter as a base.

Equivalent to:

get_formatter(item.__class__, base_formatter=self)
is_partial: bool = True

This is a partial formatter; it will not be automatically used in the Formatting Protocol.

item_newline(printer: Printer, is_first: bool = False, is_last: bool = False)

Called before each node is printed.

This is also called one extra time after the last node, if there is at least one node printed.

The default implementation is simply:

printer.newline()
items_indent(printer: Printer) Printer

Returns a Printer context with an indentation.

This is called as:

with self.items_indent(printer) as p:

immediately after the self.start_symbol is printed, but before any of the items have been printed.

This default implementation is equivalent to:

return printer.indent()
parent: Formatter[T] | None = None

The parent formatter for this formatter instance.

This is automatically populated by Formatter.__new__() and should never be manually modified.

print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)

Prints the given node or edit.

Parameters:
  • printer – The printer to which to write.

  • node_or_edit – The node or edit to print.

  • with_edits – If :keyword:True, print any edits associated with the node.

Note

The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.

print_ListNode(*args, **kwargs)

Prints a graphtage.ListNode.

Equivalent to:

super().print_SequenceNode(*args, **kwargs)
print_SequenceNode(*args, **kwargs)

Prints a non-List sequence.

This delegates to the parent formatter’s implementation:

self.parent.print(*args, **kwargs)

which should invoke JSONFormatter.print(), thereby delegating to the JSONDictFormatter in instances where a list contains a dict.

property root: Formatter[T]

Returns the root formatter.

sub_format_types: Sequence[Type[Formatter[T]]] = ()

A list of formatter types that should be used as sub-formatters in the Formatting Protocol.

sub_formatters: List[Formatter[T]] = []

The list of instantiated formatters corresponding to Formatter.sub_format_types.

This list is automatically populated by Formatter.__new__() and should never be manually modified.

PyModuleFormatter

class graphtage.pydiff.PyModuleFormatter(*args, **kwargs)

Bases: SequenceFormatter

DEFAULT_INSTANCE: Formatter[T] = <graphtage.pydiff.PyModuleFormatter object>

A default instance of this formatter, automatically instantiated by the FormatterChecker metaclass.

__init__()

Initializes a sequence formatter.

Parameters:
  • start_symbol – The symbol to print at the start of the sequence.

  • end_symbol – The symbol to print at the end of the sequence.

  • delimiter – A delimiter to print between items.

  • delimiter_callback

    A callback for when a delimiter is to be printed. If omitted, this defaults to:

    lambda p: p.write(delimiter)
    

static __new__(cls, *args, **kwargs) Formatter[T]

Instantiates a new formatter.

This automatically instantiates and populates Formatter.sub_formatters and sets their parent to this new formatter.

edit_print(printer: Printer, edit: Edit)

Called when the edit for an item is to be printed.

If the SequenceNode being printed either is not edited or has no edits, then the edit passed to this function will be a Match(child, child, 0).

This implementation simply delegates the print to the Formatting Protocol:

self.print(printer, edit)
get_formatter(item: T) Callable[[Printer, T], Any] | None

Looks up a formatter for the given item using this formatter as a base.

Equivalent to:

get_formatter(item.__class__, base_formatter=self)
is_partial: bool = True

This is a partial formatter; it will not be automatically used in the Formatting Protocol.

item_newline(printer: Printer, is_first: bool = False, is_last: bool = False)

Called before each node is printed.

This is also called one extra time after the last node, if there is at least one node printed.

The default implementation is simply:

printer.newline()
items_indent(printer: Printer) Printer

Returns a Printer context with an indentation.

This is called as:

with self.items_indent(printer) as p:

immediately after the self.start_symbol is printed, but before any of the items have been printed.

This default implementation is equivalent to:

return printer.indent()
parent: Formatter[T] | None = None

The parent formatter for this formatter instance.

This is automatically populated by Formatter.__new__() and should never be manually modified.

print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)

Prints the given node or edit.

Parameters:
  • printer – The printer to which to write.

  • node_or_edit – The node or edit to print.

  • with_edits – If :keyword:True, print any edits associated with the node.

Note

The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.

print_Module(printer: Printer, node: Module)
print_SequenceNode(printer: Printer, node: SequenceNode)

Formats a sequence node.

The protocol for this function is as follows:

  • Print self.start_symbol

  • With the printer returned by self.items_indent:
    • For each edit in the sequence (or just a sequence of graphtage.Match for each child, if the node is not edited):
      • Call self.item_newline(printer, is_first=index == 0)

      • Call self.edit_print(printer, edit)

  • If at least one edit was printed, then call self.item_newline(printer, is_last=True)

  • Print self.start_symbol

property root: Formatter[T]

Returns the root formatter.

sub_format_types: Sequence[Type[Formatter[T]]] = [<class 'graphtage.pydiff.PyListFormatter'>]

A list of formatter types that should be used as sub-formatters in the Formatting Protocol.

sub_formatters: List[Formatter[T]] = []

The list of instantiated formatters corresponding to Formatter.sub_format_types.

This list is automatically populated by Formatter.__new__() and should never be manually modified.

PyObj

class graphtage.pydiff.PyObj(class_name: StringNode, attrs: PyObjAttributes | PyObjFixedAttributes | None)

Bases: ContainerNode

__init__(class_name: StringNode, attrs: PyObjAttributes | PyObjFixedAttributes | None)
add_edit_modifier(modifier: Callable[[TreeNode, TreeNode], Edit | None])
all_children_are_leaves() bool

Tests whether all of the children of this container are leaves.

Equivalent to:

all(c.is_leaf for c in self)
Returns:

True if all children are leaves.

Return type:

bool

calculate_total_size() int

Calculates the size of this node. This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

children() Sequence[TreeNode]

The children of this node.

Equivalent to:

list(self)
copy() T

Creates a deep copy of this node

copy_from(children: Iterable[TreeNode]) T

Constructs a new instance of this tree node from a list of its children

dfs() Iterator[TreeNode]

Performs a depth-first traversal over all of this node’s descendants.

self is always included and yielded first.

This implementation is equivalent to:

stack = [self]
while stack:
    node = stack.pop()
    yield node
    stack.extend(reversed(node.children()))
diff(node: TreeNode) EditedTreeNode | T

Performs a diff against the provided node.

Parameters:

node – The node against which to perform the diff.

Returns:

An edited version of this node with all edits being completed.

Return type:

Union[EditedTreeNode, T]

editable_dict() Dict[str, Any]

Copies self.__dict__, calling TreeNode.editable_dict() on any TreeNode objects therein.

This is equivalent to:

ret = dict(self.__dict__)
if not self.is_leaf:
    for key, value in ret.items():
        if isinstance(value, TreeNode):
            ret[key] = value.make_edited()
return ret

This is used by TreeNode.make_edited().

property edited: bool

Returns whether this node has been edited.

The default implementation returns False, whereas EditedTreeNode.edited() returns True.

edits(node: TreeNode) Edit

Calculates the best edit to transform this node into the provided node.

Parameters:

node – The node to which to transform.

Returns:

The best possible edit.

Return type:

Edit

get_all_edit_contexts(node: TreeNode) Iterator[Tuple[Tuple[TreeNode, ...], Edit]]

Returns an iterator over all edit contexts that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over pairs of paths from node to the edited node, as well as its edit. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Tuple[Tuple[“TreeNode”, …], Edit]

get_all_edits(node: TreeNode) Iterator[Edit]

Returns an iterator over all edits that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over edits. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Edit]

property is_leaf: bool

Container nodes are never leaves, even if they have no children.

Returns:

False

Return type:

bool

make_edited() EditedTreeNode | T

Returns a new, copied instance of this node that is also an instance of EditedTreeNode.

This is equivalent to:

return self.__class__.edited_type()(self)
Returns:

A copied version of this node that is also an instance of EditedTreeNode and thereby mutable.

Return type:

Union[EditedTreeNode, T]

property parent: TreeNode | None

The parent node of this node, or None if it has no parent.

The setter for this property should only be called by the parent node setting itself as the parent of its child.

ContainerNode subclasses automatically set this property for all of their children. However, if you define a subclass of TreeNode does not extend off of ContainerNode and for which len(self.children()) > 0, then each child’s parent must be set.

print(printer: Printer)

Prints this node.

print_parent_context(printer: Printer, for_child: TreeNode)

Prints the context for the given child node.

For example, if this node represents a list and the child is the element at index 3, then “[3]” might be printed.

The child is expected to be one of this node’s children, but this is not validated.

The default implementation prints nothing.

to_obj()

Returns a pure Python representation of this node.

For example, a node representing a list, like graphtage.ListNode, should return a Python list. A node representing a mapping, like graphtage.MappingNode, should return a Python dict. Container nodes should recursively call TreeNode.to_obj() on all of their children.

This is used solely for the providing objects to operate on in the commandline expressions evaluation, for options like –match-if and –match-unless.

property total_size: int

The size of this node.

This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

The first time this property is called, its value will be set and memoized by calling TreeNode.calculate_total_size().

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

PyObjAttribute

class graphtage.pydiff.PyObjAttribute(*args, **kwargs)

Bases: DataClassNode

__init__(*args, **kwargs)

Be careful extending __init__; consider using DataClassNode.post_init() instead.

add_edit_modifier(modifier: Callable[[TreeNode, TreeNode], Edit | None])
all_children_are_leaves() bool

Tests whether all of the children of this container are leaves.

Equivalent to:

all(c.is_leaf for c in self)
Returns:

True if all children are leaves.

Return type:

bool

attr: StringNode
calculate_total_size() int

Calculates the size of this node. This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

children() Sequence[TreeNode]

The children of this node.

Equivalent to:

list(self)
copy() T

Creates a deep copy of this node

copy_from(children: Iterable[TreeNode]) T

Constructs a new instance of this tree node from a list of its children

dfs() Iterator[TreeNode]

Performs a depth-first traversal over all of this node’s descendants.

self is always included and yielded first.

This implementation is equivalent to:

stack = [self]
while stack:
    node = stack.pop()
    yield node
    stack.extend(reversed(node.children()))
diff(node: TreeNode) EditedTreeNode | T

Performs a diff against the provided node.

Parameters:

node – The node against which to perform the diff.

Returns:

An edited version of this node with all edits being completed.

Return type:

Union[EditedTreeNode, T]

editable_dict() Dict[str, Any]

Copies self.__dict__, calling TreeNode.editable_dict() on any TreeNode objects therein.

This is equivalent to:

ret = dict(self.__dict__)
if not self.is_leaf:
    for key, value in ret.items():
        if isinstance(value, TreeNode):
            ret[key] = value.make_edited()
return ret

This is used by TreeNode.make_edited().

property edited: bool

Returns whether this node has been edited.

The default implementation returns False, whereas EditedTreeNode.edited() returns True.

edits(node: TreeNode) Edit

Calculates the best edit to transform this node into the provided node.

Parameters:

node – The node to which to transform.

Returns:

The best possible edit.

Return type:

Edit

get_all_edit_contexts(node: TreeNode) Iterator[Tuple[Tuple[TreeNode, ...], Edit]]

Returns an iterator over all edit contexts that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over pairs of paths from node to the edited node, as well as its edit. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Tuple[Tuple[“TreeNode”, …], Edit]

get_all_edits(node: TreeNode) Iterator[Edit]

Returns an iterator over all edits that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over edits. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Edit]

property is_leaf: bool

Container nodes are never leaves, even if they have no children.

Returns:

False

Return type:

bool

items() Iterator[Tuple[str, TreeNode]]
make_edited() EditedTreeNode | T

Returns a new, copied instance of this node that is also an instance of EditedTreeNode.

This is equivalent to:

return self.__class__.edited_type()(self)
Returns:

A copied version of this node that is also an instance of EditedTreeNode and thereby mutable.

Return type:

Union[EditedTreeNode, T]

object: TreeNode
property parent: TreeNode | None

The parent node of this node, or None if it has no parent.

The setter for this property should only be called by the parent node setting itself as the parent of its child.

ContainerNode subclasses automatically set this property for all of their children. However, if you define a subclass of TreeNode does not extend off of ContainerNode and for which len(self.children()) > 0, then each child’s parent must be set.

post_init()

Callback called after this class’s members have been initialized.

This callback should not call super().post_init(). Each superclass’s post_init() will be automatically called in order of the __mro__.

print(printer: Printer)

Prints this node.

print_parent_context(printer: Printer, for_child: TreeNode)

Prints the context for the given child node.

For example, if this node represents a list and the child is the element at index 3, then “[3]” might be printed.

The child is expected to be one of this node’s children, but this is not validated.

The default implementation prints nothing.

to_obj()

Returns a pure Python representation of this node.

For example, a node representing a list, like graphtage.ListNode, should return a Python list. A node representing a mapping, like graphtage.MappingNode, should return a Python dict. Container nodes should recursively call TreeNode.to_obj() on all of their children.

This is used solely for the providing objects to operate on in the commandline expressions evaluation, for options like –match-if and –match-unless.

property total_size: int

The size of this node.

This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

The first time this property is called, its value will be set and memoized by calling TreeNode.calculate_total_size().

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

PyObjAttributes

class graphtage.pydiff.PyObjAttributes(items: Iterable[T], auto_match_keys: bool = True)

Bases: DictNode

__contains__(item: TreeNode)

Tests whether the given item is a key in this mapping.

The implementation is equivalent to:

return any(k == item for k, _ in self.items())

Note

This implementation runs in worst-case linear time in the size of the mapping.

Parameters:

item – The key of the item sought.

Returns:

True if the key exists in this mapping.

Return type:

bool

__getitem__(item: TreeNode) KeyValuePairNode

Looks up a key/value pair item from this mapping by its key.

The implementation is equivalent to:

for kvp in self:
    if kvp.key == item:
        return kvp
raise KeyError(item)

Note

This implementation runs in worst-case linear time in the size of the mapping.

Parameters:

item – The key of the key/value pair that is sought.

Returns:

The first key/value pair found with key item.

Return type:

KeyValuePairNode

Raises:

KeyError – If the key is not found.

__init__(items: Iterable[T], auto_match_keys: bool = True)
add_edit_modifier(modifier: Callable[[TreeNode, TreeNode], Edit | None])
all_children_are_leaves() bool

Tests whether all of the children of this container are leaves.

Equivalent to:

all(c.is_leaf for c in self)
Returns:

True if all children are leaves.

Return type:

bool

calculate_total_size()

Calculates the size of this node. This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

children() Sequence[TreeNode]

The children of this node.

Equivalent to:

list(self)
property container_type: Type[HashableCounter[T]]

Returns the container type used to store SequenceNode._children.

This is used for performing a deep copy of this node in the SequenceNode.editable_dict() function.

copy() T

Creates a deep copy of this node

copy_from(children: Iterable[T]) C

Constructs a new instance of this tree node from a list of its children

dfs() Iterator[TreeNode]

Performs a depth-first traversal over all of this node’s descendants.

self is always included and yielded first.

This implementation is equivalent to:

stack = [self]
while stack:
    node = stack.pop()
    yield node
    stack.extend(reversed(node.children()))
diff(node: TreeNode) EditedTreeNode | T

Performs a diff against the provided node.

Parameters:

node – The node against which to perform the diff.

Returns:

An edited version of this node with all edits being completed.

Return type:

Union[EditedTreeNode, T]

editable_dict() Dict[str, Any]

Copies self.__dict__, calling TreeNode.editable_dict() on all children.

This is equivalent to:

ret = dict(self.__dict__)
ret['_children'] = self.container_type(n.make_edited() for n in self)
return ret

This is used by SequenceNode.make_edited().

property edited: bool

Returns whether this node has been edited.

The default implementation returns False, whereas EditedTreeNode.edited() returns True.

edits(node: TreeNode) Edit

Calculates the best edit to transform this node into the provided node.

Parameters:

node – The node to which to transform.

Returns:

The best possible edit.

Return type:

Edit

classmethod from_dict(source_dict: Dict[LeafNode, TreeNode]) T

Constructs a DictNode from a mapping of LeafNode to TreeNode.

Parameters:

source_dict – The source mapping.

Returns:

The resulting DictNode.

Return type:

DictNode

get_all_edit_contexts(node: TreeNode) Iterator[Tuple[Tuple[TreeNode, ...], Edit]]

Returns an iterator over all edit contexts that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over pairs of paths from node to the edited node, as well as its edit. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Tuple[Tuple[“TreeNode”, …], Edit]

get_all_edits(node: TreeNode) Iterator[Edit]

Returns an iterator over all edits that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over edits. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Edit]

property is_leaf: bool

Container nodes are never leaves, even if they have no children.

Returns:

False

Return type:

bool

items() Iterator[Tuple[TreeNode, TreeNode]]

Iterates over the key/value pairs in this mapping, similar to dict.items().

The implementation is equivalent to:

for kvp in self:
    yield kvp.key, kvp.value

since MappingNode.__iter__() returns an iterator over graphtage.KeyValuePairNode.

make_edited() EditedTreeNode | T

Returns a new, copied instance of this node that is also an instance of EditedTreeNode.

This is equivalent to:

return self.__class__.edited_type()(self)
Returns:

A copied version of this node that is also an instance of EditedTreeNode and thereby mutable.

Return type:

Union[EditedTreeNode, T]

classmethod make_key_value_pair_node(key: LeafNode, value: TreeNode, allow_key_edits: bool = True) KeyValuePairNode
property parent: TreeNode | None

The parent node of this node, or None if it has no parent.

The setter for this property should only be called by the parent node setting itself as the parent of its child.

ContainerNode subclasses automatically set this property for all of their children. However, if you define a subclass of TreeNode does not extend off of ContainerNode and for which len(self.children()) > 0, then each child’s parent must be set.

print(printer: Printer)

Prints a sequence node.

By default, sequence nodes are printed like lists:

SequenceFormatter('[', ']', ',').print(printer, self)
print_parent_context(printer: Printer, for_child: TreeNode)

Prints the context for the given child node.

For example, if this node represents a list and the child is the element at index 3, then “[3]” might be printed.

The child is expected to be one of this node’s children, but this is not validated.

The default implementation prints nothing.

to_obj() Dict[Any, Any]

Returns a pure Python representation of this node.

For example, a node representing a list, like graphtage.ListNode, should return a Python list. A node representing a mapping, like graphtage.MappingNode, should return a Python dict. Container nodes should recursively call TreeNode.to_obj() on all of their children.

This is used solely for the providing objects to operate on in the commandline expressions evaluation, for options like –match-if and –match-unless.

property total_size: int

The size of this node.

This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

The first time this property is called, its value will be set and memoized by calling TreeNode.calculate_total_size().

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

PyObjBuilder

class graphtage.pydiff.PyObjBuilder(options: BuildOptions | None = None)

Bases: BasicBuilder

BUILDERS: Dict[Type[Any], Callable[[Builder, Any, List[TreeNode]], TreeNode]] = {<class 'int'>: <function BasicBuilder.build_int>, <class 'bytes'>: <function BasicBuilder.build_str>, <class 'str'>: <function BasicBuilder.build_str>, <class 'NoneType'>: <function BasicBuilder.build_none>, <class 'float'>: <function BasicBuilder.build_float>, <class 'bool'>: <function BasicBuilder.build_bool>, <class 'tuple'>: <function BasicBuilder.build_list>, <class 'list'>: <function BasicBuilder.build_list>, <class 'frozenset'>: <function BasicBuilder.build_set>, <class 'set'>: <function BasicBuilder.build_set>, <class 'dict'>: <function BasicBuilder.build_dict>}
EXPANDERS: Dict[Type[Any], Callable[[Builder, Any], Iterable[Any] | None]] = {<class 'frozenset'>: <function BasicBuilder.expand_list>, <class 'set'>: <function BasicBuilder.expand_list>, <class 'tuple'>: <function BasicBuilder.expand_list>, <class 'list'>: <function BasicBuilder.expand_list>, <class 'dict'>: <function BasicBuilder.expand_dict>}
__init__(options: BuildOptions | None = None)
classmethod _resolve(obj_type: Type[Any], choices: Dict[Type[Any], T]) T | None

Resolves the most specialized expander or builder for obj_type

build(node: Any, children: List[TreeNode]) TreeNode
build_bool(obj: bool, _) BoolNode
build_dict(_, children: List[TreeNode])
build_float(obj: float, _) FloatNode
build_int(obj: int, _) IntegerNode
build_list(obj, children: List[TreeNode]) ListNode
build_none(obj, _) NullNode
build_set(obj, children: List[TreeNode]) MultiSetNode
build_str(obj: str, _) StringNode
build_tree(root_obj) TreeNode
static builder(node_type: Type[T])
default_builder(node: Any, children: List[TreeNode])
default_expander(node: Any) Iterable[Any]
expand(node: Any) Iterable[Any]
expand_dict(obj: dict)
expand_list(obj: list)
static expander(node_type: Type[T])
options: BuildOptions
classmethod resolve_builder(obj_type: Type[Any]) Callable[[Any, List[TreeNode]], TreeNode] | None

Resolves the most specialized builder for obj_type

classmethod resolve_expander(obj_type: Type[Any]) Callable[[Any], Iterable[Any] | None] | None

Resolves the most specialized expander for obj_type

PyObjEdit

class graphtage.pydiff.PyObjEdit(from_obj: PyObj, to_obj: PyObj)

Bases: AbstractCompoundEdit

__init__(from_obj: PyObj, to_obj: PyObj)

Constructs a new Edit.

Parameters:
  • from_node – The node that this edit transforms.

  • to_node – The node that this edit transforms from_node into.

  • constant_cost – A optional lower bound on the cost of this edit.

  • cost_upper_bound – An optional upper bound on the cost of this edit.

__iter__() Iterator[Edit]

Returns an iterator over this edit’s sub-edits.

Returns:

The result of AbstractCompoundEdit.edits()

Return type:

Iterator[Edit]

__lt__(other)

Tests whether the bounds of this edit are less than the bounds of other.

_debug_tighten_bounds() bool

Adds debugging assertions when tightening bounds; for debugging only

bounds() Range

Returns the bounds of this edit.

This defaults to the bounds provided when this AbstractEdit was constructed. If an upper bound was not provided to the constructor, the upper bound defaults to:

self.from_node.total_size + self.to_node.total_size + 1
Returns:

A range bounding the cost of this edit.

Return type:

Range

edits() Iterator[Edit]

Returns an iterator over this edit’s sub-edits

from_node: TreeNode
has_non_zero_cost() bool

Returns whether this edit has a non-zero cost.

This will tighten the edit’s bounds until either its lower bound is greater than zero or its bounds are definitive.

initial_bounds: Range
is_complete() bool

An edit is complete when no further calls to Edit.tighten_bounds() will change the nature of the edit.

This implementation considers an edit complete if it is valid and its bounds are definitive:

return not self.valid or self.bounds().definitive()

If an edit is able to discern that it has a unique solution even if its final bounds are unknown, it should reimplement this method to define that check.

For example, in the case of a CompoundEdit, this method should only return True if no future calls to Edit.tighten_bounds() will affect the result of CompoundEdit.edits().

Returns:

True if subsequent calls to Edit.tighten_bounds() will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.

Return type:

bool

on_diff(from_node: EditedTreeNode)

A callback for when an edit is assigned to an EditedTreeNode in TreeNode.diff().

This default implementation adds the edit to the node, and recursively calls Edit.on_diff() on all of the sub-edits:

from_node.edit = self
from_node.edit_list.append(self)
for edit in self.edits():
    edit.on_diff(edit.from_node)
Parameters:

from_node – The edited node that was added to the diff

print(formatter: GraphtageFormatter, printer: Printer)

Edits can optionally implement a printing method

This function is called automatically from the formatter in the Printing Protocol and should never be called directly unless you really know what you’re doing! Raising NotImplementedError will cause the formatter to fall back on its own printing implementations.

This implementation is equivalent to:

for edit in self.edits():
    edit.print(formatter, printer)
tighten_bounds() bool

Tightens the Edit.bounds() on the cost of this edit, if possible.

Returns:

True if the bounds have been tightened.

Return type:

bool

Note

Implementations of this function should return False if and only if self.bounds().definitive().

property valid: bool

Returns whether this edit is valid

PyObjFixedAttributes

class graphtage.pydiff.PyObjFixedAttributes(children: T)

Bases: FixedKeyDictNode

__init__(children: T)

Initializes a sequence node.

Parameters:

children – A sequence of TreeNodes. This is assigned to the protected member SequenceNode._children.

__len__() int

The number of children of this sequence.

This is equivalent to:

return len(self._children)
add_edit_modifier(modifier: Callable[[TreeNode, TreeNode], Edit | None])
all_children_are_leaves() bool

Tests whether all of the children of this container are leaves.

Equivalent to:

all(c.is_leaf for c in self)
Returns:

True if all children are leaves.

Return type:

bool

calculate_total_size()

Calculates the total size of this sequence.

This is equivalent to:

return sum(c.total_size for c in self)
children() Sequence[TreeNode]

The children of this node.

Equivalent to:

list(self)
property container_type: Type[Dict[LeafNode, KeyValuePairNode]]

The container type required by graphtage.sequences.SequenceNode

Returns:

dict

Return type:

Type[Dict[LeafNode, KeyValuePairNode]]

copy() T

Creates a deep copy of this node

copy_from(children: Iterable[TreeNode]) T

Constructs a new instance of this tree node from a list of its children

dfs() Iterator[TreeNode]

Performs a depth-first traversal over all of this node’s descendants.

self is always included and yielded first.

This implementation is equivalent to:

stack = [self]
while stack:
    node = stack.pop()
    yield node
    stack.extend(reversed(node.children()))
diff(node: TreeNode) EditedTreeNode | T

Performs a diff against the provided node.

Parameters:

node – The node against which to perform the diff.

Returns:

An edited version of this node with all edits being completed.

Return type:

Union[EditedTreeNode, T]

editable_dict() Dict[str, Any]

Copies self.__dict__, calling TreeNode.editable_dict() on any TreeNode objects therein.

This is equivalent to:

ret = dict(self.__dict__)
if not self.is_leaf:
    for key, value in ret.items():
        if isinstance(value, TreeNode):
            ret[key] = value.make_edited()
return ret

This is used by TreeNode.make_edited().

property edited: bool

Returns whether this node has been edited.

The default implementation returns False, whereas EditedTreeNode.edited() returns True.

edits(node: TreeNode) Edit

Calculates the best edit to transform this node into the provided node.

Parameters:

node – The node to which to transform.

Returns:

The best possible edit.

Return type:

Edit

classmethod from_dict(source_dict: Dict[LeafNode, TreeNode]) T

Constructs a FixedKeyDictNode from a mapping of LeafNode to TreeNode.

Parameters:

source_dict – The source mapping.

Note

This implementation does not currently check for duplicate keys. Only the first key returned from source_dict.items() will be included in the output.

Returns:

The resulting FixedKeyDictNode

Return type:

FixedKeyDictNode

get_all_edit_contexts(node: TreeNode) Iterator[Tuple[Tuple[TreeNode, ...], Edit]]

Returns an iterator over all edit contexts that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over pairs of paths from node to the edited node, as well as its edit. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Tuple[Tuple[“TreeNode”, …], Edit]

get_all_edits(node: TreeNode) Iterator[Edit]

Returns an iterator over all edits that will transform this node into the provided node.

Parameters:

node – The node to which to transform this one.

Returns:

An iterator over edits. Note that this iterator will automatically explode any CompoundEdit in the sequence.

Return type:

Iterator[Edit]

property is_leaf: bool

Container nodes are never leaves, even if they have no children.

Returns:

False

Return type:

bool

items() Iterator[Tuple[LeafNode, TreeNode]]

Iterates over the key/value pairs in this mapping, similar to dict.items().

The implementation is equivalent to:

for kvp in self:
    yield kvp.key, kvp.value

since MappingNode.__iter__() returns an iterator over graphtage.KeyValuePairNode.

make_edited() EditedTreeNode | T

Returns a new, copied instance of this node that is also an instance of EditedTreeNode.

This is equivalent to:

return self.__class__.edited_type()(self)
Returns:

A copied version of this node that is also an instance of EditedTreeNode and thereby mutable.

Return type:

Union[EditedTreeNode, T]

classmethod make_key_value_pair_node(key: LeafNode, value: TreeNode, allow_key_edits: bool = True) KeyValuePairNode
property parent: TreeNode | None

The parent node of this node, or None if it has no parent.

The setter for this property should only be called by the parent node setting itself as the parent of its child.

ContainerNode subclasses automatically set this property for all of their children. However, if you define a subclass of TreeNode does not extend off of ContainerNode and for which len(self.children()) > 0, then each child’s parent must be set.

print(printer: Printer)

Prints a sequence node.

By default, sequence nodes are printed like lists:

SequenceFormatter('[', ']', ',').print(printer, self)
print_parent_context(printer: Printer, for_child: TreeNode)

Prints the context for the given child node.

For example, if this node represents a list and the child is the element at index 3, then “[3]” might be printed.

The child is expected to be one of this node’s children, but this is not validated.

The default implementation prints nothing.

to_obj() Dict[Any, Any]

Returns a pure Python representation of this node.

For example, a node representing a list, like graphtage.ListNode, should return a Python list. A node representing a mapping, like graphtage.MappingNode, should return a Python dict. Container nodes should recursively call TreeNode.to_obj() on all of their children.

This is used solely for the providing objects to operate on in the commandline expressions evaluation, for options like –match-if and –match-unless.

property total_size: int

The size of this node.

This is an arbitrary, immutable value that is used to calculate the bounded costs of edits on this node.

The first time this property is called, its value will be set and memoized by calling TreeNode.calculate_total_size().

Returns:

An arbitrary integer representing the size of this node.

Return type:

int

PyObjFormatter

class graphtage.pydiff.PyObjFormatter(*args, **kwargs)

Bases: SequenceFormatter

DEFAULT_INSTANCE: Formatter[T] = <graphtage.pydiff.PyObjFormatter object>

A default instance of this formatter, automatically instantiated by the FormatterChecker metaclass.

__init__()

Initializes a sequence formatter.

Parameters:
  • start_symbol – The symbol to print at the start of the sequence.

  • end_symbol – The symbol to print at the end of the sequence.

  • delimiter – A delimiter to print between items.

  • delimiter_callback

    A callback for when a delimiter is to be printed. If omitted, this defaults to:

    lambda p: p.write(delimiter)
    

static __new__(cls, *args, **kwargs) Formatter[T]

Instantiates a new formatter.

This automatically instantiates and populates Formatter.sub_formatters and sets their parent to this new formatter.

edit_print(printer: Printer, edit: Edit)

Called when the edit for an item is to be printed.

If the SequenceNode being printed either is not edited or has no edits, then the edit passed to this function will be a Match(child, child, 0).

This implementation simply delegates the print to the Formatting Protocol:

self.print(printer, edit)
get_formatter(item: T) Callable[[Printer, T], Any] | None

Looks up a formatter for the given item using this formatter as a base.

Equivalent to:

get_formatter(item.__class__, base_formatter=self)
is_partial: bool = True

This is a partial formatter; it will not be automatically used in the Formatting Protocol.

item_newline(printer: Printer, is_first: bool = False, is_last: bool = False)

Called before each node is printed.

This is also called one extra time after the last node, if there is at least one node printed.

The default implementation is simply:

printer.newline()
items_indent(printer: Printer) Printer

Returns a Printer context with an indentation.

This is called as:

with self.items_indent(printer) as p:

immediately after the self.start_symbol is printed, but before any of the items have been printed.

This default implementation is equivalent to:

return printer.indent()
parent: Formatter[T] | None = None

The parent formatter for this formatter instance.

This is automatically populated by Formatter.__new__() and should never be manually modified.

print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)

Prints the given node or edit.

Parameters:
  • printer – The printer to which to write.

  • node_or_edit – The node or edit to print.

  • with_edits – If :keyword:True, print any edits associated with the node.

Note

The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.

print_Call(printer: Printer, node: Call)
print_CallArguments(*args, **kwargs)
print_KeywordArgument(printer: Printer, node: KeyValuePairNode)

Prints a graphtage.PyKeywordArgument key/value pair.

By default, the key is printed in red, followed by “=”, followed by the value in light blue.

print_PyObj(printer: Printer, node: PyObj)
print_PyObjAttribute(printer: Printer, node: PyObjAttribute)
print_PyObjAttributes(*args, **kwargs)
print_PyObjFixedAttributes(*args, **kwargs)
print_SequenceNode(printer: Printer, node: SequenceNode)

Formats a sequence node.

The protocol for this function is as follows:

  • Print self.start_symbol

  • With the printer returned by self.items_indent:
    • For each edit in the sequence (or just a sequence of graphtage.Match for each child, if the node is not edited):
      • Call self.item_newline(printer, is_first=index == 0)

      • Call self.edit_print(printer, edit)

  • If at least one edit was printed, then call self.item_newline(printer, is_last=True)

  • Print self.start_symbol

property root: Formatter[T]

Returns the root formatter.

sub_format_types: Sequence[Type[Formatter[T]]] = [<class 'graphtage.pydiff.PyListFormatter'>, <class 'graphtage.pydiff.PyDictFormatter'>]

A list of formatter types that should be used as sub-formatters in the Formatting Protocol.

sub_formatters: List[Formatter[T]] = []

The list of instantiated formatters corresponding to Formatter.sub_format_types.

This list is automatically populated by Formatter.__new__() and should never be manually modified.

pydiff functions

ast_to_tree

graphtage.pydiff.ast_to_tree(tree: AST, options: BuildOptions | None = None) TreeNode

Builds a Graphtage tree from a Python Abstract Syntax Tree.

Parameters:
  • tree – The abstract syntax tree from which to build the Graphtage tree.

  • options – An optional set of options for building the tree.

Returns:

The resulting tree.

Return type:

TreeNode

build_tree

graphtage.pydiff.build_tree(python_obj: Any, options: BuildOptions | None = None) TreeNode

Builds a Graphtage tree from an arbitrary Python object, even complex custom classes.

Parameters:
  • python_obj – The object from which to build the tree.

  • options – An optional set of options for building the tree.

Returns:

The resulting tree.

Return type:

TreeNode

Raises:

ValueError – If the object is of an unsupported type, or if a cycle is detected and not ignored.

diff

graphtage.pydiff.diff(from_py_obj, to_py_obj, options: BuildOptions | None = None)