graphtage
graphtage classes
AbstractCompoundEdit
- class graphtage.AbstractCompoundEdit(from_node: TreeNode, to_node: TreeNode | None = None, constant_cost: int | None = 0, cost_upper_bound: int | None = None)
Bases:
AbstractEdit
,CompoundEdit
,ABC
Abstract base class implementing the
CompoundEdit
protocol.- __init__(from_node: TreeNode, to_node: TreeNode | None = None, constant_cost: int | None = 0, cost_upper_bound: int | None = None)
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
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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)
- abstract 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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
AbstractEdit
- class graphtage.AbstractEdit(from_node: TreeNode, to_node: TreeNode | None = None, constant_cost: int | None = 0, cost_upper_bound: int | None = None)
Bases:
Debuggable
,Edit
,ABC
Abstract base class for the
Edit
protocol.- __init__(from_node: TreeNode, to_node: TreeNode | None = None, constant_cost: int | None = 0, cost_upper_bound: int | None = None)
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.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode | TreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
- abstract 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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
BoolNode
- class graphtage.BoolNode(bool_like: bool)
Bases:
LeafNode
A node containing either
True
orFalse
.- __init__(bool_like: bool)
Creates a node with the given object.
- Parameters:
obj – The underlying Python object wrapped by the node.
- calculate_total_size() int
By default, leaf nodes’ sizes are equal to the length of their wrapped object’s string representation.
This is equivalent to:
return len(str(self.object))
However, subclasses may override this function to return whatever size is required.
- Returns:
The length of the string representation of
self.object
.- Return type:
- children() Collection[TreeNode]
Leaf nodes have no children, so this always returns an empty tuple.
- Returns:
An empty tuple.
- Return type:
- copy() T
Creates a deep copy of this node
- copy_from(children: Iterable[TreeNode]) 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this leaf node.
By default, leaf nodes print the
repr()
of their wrapped object:printer.write(repr(self.object))
- Parameters:
printer – The printer to which to write 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 the object wrapped by this node.
This is equivalent to:
return self.object
- 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:
BuildOptions
- class graphtage.BuildOptions(*, allow_key_edits=True, auto_match_keys=True, allow_list_edits=True, allow_list_edits_when_same_length=True, check_for_cyces=True, ignore_cycles=False, printer=<graphtage.printer.Printer object>, **kwargs)
Bases:
object
A class for passing options to tree building functions in
Filetype
- __getattr__(item)
Default all undefined options to
False
- __init__(*, allow_key_edits=True, auto_match_keys=True, allow_list_edits=True, allow_list_edits_when_same_length=True, check_for_cyces=True, ignore_cycles=False, printer=<graphtage.printer.Printer object>, **kwargs)
Initializes the options. All keyword values will be set as attributes of this class.
Options not specified will default to
False
.
- copy() BuildOptions
CompoundEdit
- class graphtage.CompoundEdit(*args, **kwargs)
Bases:
Edit
,Protocol
A protocol for edits that are composed of sub-edits
- __init__(*args, **kwargs)
- abstract bounds() Range
The bounds on the cost of this edit.
The lower bound must always be finite and non-negative.
- Returns:
The bounds on the cost of this edit.
- Return type:
- 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.
- abstract is_complete() bool
Returns
True
if all of the final edits are available.Note
This should return
True
if the edit can determine that its representation will no longer change, regardless of whether our bounds have been fully tightened.
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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.
- abstract 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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
ConstantCostEdit
- class graphtage.ConstantCostEdit(from_node: TreeNode, to_node: TreeNode | None = None, cost: int = 0)
Bases:
AbstractEdit
,ABC
An edit whose definitive cost is known at the time of construction.
- __init__(from_node: TreeNode, to_node: TreeNode | None = None, cost: int = 0)
Constructs a new edit.
- Parameters:
from_node – The node being edited.
to_node – The node into which
from_node
is being transformed.cost – The constant cost of the edit.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode | TreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
ContainerNode
- class graphtage.ContainerNode(*args, **kwds)
-
A tree node that has children.
- __init__()
- 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:
- abstract 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:
- 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- abstract 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- 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.
- abstract to_obj()
Returns a pure Python representation of this node.
For example, a node representing a list, like
graphtage.ListNode
, should return a Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
DictNode
- class graphtage.DictNode(items: Iterable[T], auto_match_keys: bool = True)
Bases:
MappingNode
,MultiSetNode
[KeyValuePairNode
]A dictionary node implemented as a multiset of key/value pairs.
This is the default dictionary type used by Graphtage. Unlike its more efficient alternative,
FixedKeyDictNode
, this class supports matching dictionaries with duplicate keys.- __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:
- __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:
- Raises:
KeyError – If the key is not found.
- 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:
- 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:
- 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__
, callingTreeNode.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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- classmethod from_dict(source_dict: Dict[LeafNode, TreeNode]) T
Constructs a
DictNode
from a mapping ofLeafNode
toTreeNode
.
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 overgraphtage.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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(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 Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
Edit
- class graphtage.Edit(*args, **kwargs)
Bases:
Bounded
,Protocol
A protocol for defining an edit.
- initial_bounds
The initial bounds of this edit. Classes implementing this protocol can, for example, set this by calling
self.bounds()
during initialization.- Type:
- __init__(*args, **kwargs)
- abstract bounds() Range
The bounds on the cost of this edit.
The lower bound must always be finite and non-negative.
- Returns:
The bounds on the cost of this edit.
- Return type:
- 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.
- abstract is_complete() bool
Returns
True
if all of the final edits are available.Note
This should return
True
if the edit can determine that its representation will no longer change, regardless of whether our bounds have been fully tightened.
- on_diff(from_node: EditedTreeNode | TreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
- abstract 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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
EditCollection
- class graphtage.EditCollection(from_node: TreeNode, to_node: TreeNode | None, collection: Type[C], add_to_collection: Callable[[C, Edit], Any], edits: Iterator[Edit], explode_edits: bool = True)
Bases:
AbstractCompoundEdit
,Generic
[C
]An edit comprised of one or more sub-edits.
- __init__(from_node: TreeNode, to_node: TreeNode | None, collection: Type[C], add_to_collection: Callable[[C, Edit], Any], edits: Iterator[Edit], explode_edits: bool = True)
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
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
EditSequence
- class graphtage.EditSequence(from_node: TreeNode, to_node: TreeNode | None, edits: Iterator[Edit], explode_edits: bool = True)
Bases:
EditCollection
[List
]An
EditCollection
using alist
as the underlying container.- __init__(from_node: TreeNode, to_node: TreeNode | None, edits: Iterator[Edit], explode_edits: bool = True)
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
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
EditedTreeNode
- class graphtage.EditedTreeNode
Bases:
object
A mixin for a
TreeNode
that has been edited.In practice, an object that is an instance of
EditedTreeNode
will always also be an instance ofTreeNode
.This class should almost never be instantiated directly; it is used by
TreeNode.diff()
.- __init__()
- edited_cost() int
The cost of the edit applied to this node.
This will first fully tighten all of the bounds of
self.edit_list
, and then return the sum of their upper bounds:while any(e.tighten_bounds() for e in self.edit_list): pass return sum(e.bounds().upper_bound for e in self.edit_list)
Since all of the edits are fully tightened, this function returns a
int
instead of agraphtage.bounds.Bounds
.- Returns:
The sum of the costs of the edits applied to this node.
- Return type:
Filetype
- class graphtage.Filetype(type_name: str, default_mimetype: str, *mimetypes: str)
Bases:
object
Abstract base class from which all Graphtage file formats should extend.
When this class is subclassed, the subclass will automatically be added to Graphtage’s filetype registry. This includes automatic inclusion in
graphtage
’s command line arguments and mime type auto-detection inget_filetype()
.- __init__(type_name: str, default_mimetype: str, *mimetypes: str)
Initializes a new Graphtage file format type
- Parameters:
- Raises:
ValueError – The
type_name
and/or one of the mimetypes of thisFiletype
conflicts with that of a preexistingFiletype
.
- abstract build_tree(path: str, options: BuildOptions | None = None) TreeNode
Builds an intermediate representation tree from a file of this
Filetype
.- Parameters:
path – Path to the file to parse
options – An optional set of options for building the tree
- Returns:
The root tree node of the provided file
- Return type:
- abstract build_tree_handling_errors(path: str, options: BuildOptions | None = None) str | TreeNode
Same as
Filetype.build_tree()
, but it should return a human-readable error string on failure.This function should never throw an exception.
- abstract get_default_formatter() GraphtageFormatter
Returns the default formatter for printing files of this type.
FiletypeWatcher
- class graphtage.FiletypeWatcher(name, bases, namespace, **kwargs)
Bases:
ABCMeta
Abstract metaclass for the
Filetype
class.This registers any subclasses of
Filetype
, automatically adding them to the graphtage command line arguments and mimetype lookup inget_filetype()
.- __init__(name, bases, clsdict)
- __instancecheck__(instance)
Override for isinstance(instance, cls).
- __subclasscheck__(subclass)
Override for issubclass(subclass, cls).
- _abc_caches_clear()
Clear the caches (for debugging or testing).
- _abc_registry_clear()
Clear the registry (for debugging or testing).
- _dump_registry(file=None)
Debug helper to print the ABC registry.
- mro()
Return a type’s method resolution order.
- register(subclass)
Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
FixedKeyDictNode
- class graphtage.FixedKeyDictNode(children: T)
Bases:
MappingNode
,SequenceNode
[Dict
[LeafNode
,KeyValuePairNode
]]A dictionary that only attempts to match two
KeyValuePairNode
objects if they share the same key.This is the most efficient dictionary matching node type, and is what is used with the
--no-key-edits
/-k
command line argument.Note
This implementation does not currently support duplicate keys.
- __init__(children: T)
Initializes a sequence node.
- Parameters:
children – A sequence of
TreeNodes
. This is assigned to the protected memberSequenceNode._children
.
- __len__() int
The number of children of this sequence.
This is equivalent to:
return len(self._children)
- 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:
- calculate_total_size()
Calculates the total size of this sequence.
This is equivalent to:
return sum(c.total_size for c in self)
- property container_type: Type[Dict[LeafNode, KeyValuePairNode]]
The container type required by
graphtage.sequences.SequenceNode
- Returns:
- 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- classmethod from_dict(source_dict: Dict[LeafNode, TreeNode]) T
Constructs a
FixedKeyDictNode
from a mapping ofLeafNode
toTreeNode
.- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 overgraphtage.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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(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 Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
FixedKeyDictNodeEdit
- class graphtage.FixedKeyDictNodeEdit(from_node: FixedKeyDictNode, to_node: TreeNode, edits: Iterator[Edit])
Bases:
SequenceEdit
,EditCollection
[List
]The edit type returned by
FixedKeyDictNode
.- __init__(from_node: FixedKeyDictNode, to_node: TreeNode, edits: Iterator[Edit])
Initializes a sequence edit.
- Parameters:
from_node – The node being edited.
*args – The remainder of the arguments to be passed to
AbstractCompoundEdit.__init__()
.**kwargs – The remainder of the keyword arguments to be passed to
AbstractCompoundEdit.__init__()
.
- Raises:
ValueError – If
from_node
is not an instance ofSequenceNode
.
- __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
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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)
Prints this edit.
This is equivalent to:
formatter.get_formatter(self.sequence)(printer, self.sequence)
- property sequence: SequenceNode
Returns the sequence being edited.
This is a convenience function solely to aid in automated type checking. It is equivalent to:
typing.cast(SequenceNode, self.from_node)
- 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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
FloatNode
- class graphtage.FloatNode(float_like: float)
Bases:
LeafNode
A node containing a float
- __init__(float_like: float)
Creates a node with the given object.
- Parameters:
obj – The underlying Python object wrapped by the node.
- calculate_total_size() int
By default, leaf nodes’ sizes are equal to the length of their wrapped object’s string representation.
This is equivalent to:
return len(str(self.object))
However, subclasses may override this function to return whatever size is required.
- Returns:
The length of the string representation of
self.object
.- Return type:
- children() Collection[TreeNode]
Leaf nodes have no children, so this always returns an empty tuple.
- Returns:
An empty tuple.
- Return type:
- copy() T
Creates a deep copy of this node
- copy_from(children: Iterable[TreeNode]) 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this leaf node.
By default, leaf nodes print the
repr()
of their wrapped object:printer.write(repr(self.object))
- Parameters:
printer – The printer to which to write 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 the object wrapped by this node.
This is equivalent to:
return self.object
- 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:
GraphtageFormatter
- class graphtage.GraphtageFormatter(*args, **kwargs)
Bases:
Formatter
[Union
[TreeNode
,Edit
]]A base class for defining formatters that operate on
TreeNode
andEdit
.- DEFAULT_INSTANCE: Formatter[T] = <graphtage.GraphtageFormatter 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 theirparent
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)
- 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.
- sub_format_types: Sequence[Type[Formatter[T]]] = ()
A list of formatter types that should be used as sub-formatters in the Formatting Protocol.
Insert
- class graphtage.Insert(to_insert: TreeNode, insert_into: TreeNode, penalty: int = 1)
Bases:
ConstantCostEdit
A constant cost edit specifying that a node should be added to a container.
- __init__(to_insert: TreeNode, insert_into: TreeNode, penalty: int = 1)
Constructs a new edit.
- Parameters:
from_node – The node being edited.
to_node – The node into which
from_node
is being transformed.cost – The constant cost of the edit.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
IntegerNode
- class graphtage.IntegerNode(int_like: int)
Bases:
LeafNode
A node containing an int
- __init__(int_like: int)
Creates a node with the given object.
- Parameters:
obj – The underlying Python object wrapped by the node.
- calculate_total_size() int
By default, leaf nodes’ sizes are equal to the length of their wrapped object’s string representation.
This is equivalent to:
return len(str(self.object))
However, subclasses may override this function to return whatever size is required.
- Returns:
The length of the string representation of
self.object
.- Return type:
- children() Collection[TreeNode]
Leaf nodes have no children, so this always returns an empty tuple.
- Returns:
An empty tuple.
- Return type:
- copy() T
Creates a deep copy of this node
- copy_from(children: Iterable[TreeNode]) 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this leaf node.
By default, leaf nodes print the
repr()
of their wrapped object:printer.write(repr(self.object))
- Parameters:
printer – The printer to which to write 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 the object wrapped by this node.
This is equivalent to:
return self.object
- 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:
KeyValuePairEdit
- class graphtage.KeyValuePairEdit(from_kvp: KeyValuePairNode, to_kvp: KeyValuePairNode)
Bases:
AbstractCompoundEdit
An edit type for two key/value pairs
- __init__(from_kvp: KeyValuePairNode, to_kvp: KeyValuePairNode)
Creates a key/value pair edit.
- Parameters:
from_kvp – The key/value pair from which to match.
to_kvp – The key/value pair to which to match.
- Raises:
ValueError – If
from_kvp.allow_key_edits
and the keys do not match.
- __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
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
KeyValuePairNode
- class graphtage.KeyValuePairNode(key: LeafNode, value: TreeNode, allow_key_edits: bool = True)
Bases:
ContainerNode
A node containing a key/value pair.
This is used by nodes subclassing
MappingNode
.- __eq__(other)
Tests whether this key/value pair equals another.
Equivalent to:
isinstance(other, KeyValuePair) and self.key == other.key and self.value == other.value
- Parameters:
other – The object to test.
- Returns:
True
if this key/value pair is equal toother
.- Return type:
- __init__(key: LeafNode, value: TreeNode, allow_key_edits: bool = True)
Creates a new key/value pair node.
- Parameters:
key – The key of the pair.
value – The value of the pair.
allow_key_edits – If
False
, only consider matching against another key/value pair node if it has the same key.
- __lt__(other)
Compares this key/value pair to another.
If
other
is also an instance ofKeyValuePairNode
, return:(self.key < other.key) or (self.key == other.key and self.value < other.value)
otherwise, return:
self.key < other
- Parameters:
other – The object to which to compare.
- Returns:
True
if this key/value pair is smaller thanother
.- Return type:
- 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:
- 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:
- 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this node.
This default implementation prints the key in blue, followed by a bright white “: “, followed by the value.
- 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 Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
LeafNode
- class graphtage.LeafNode(obj)
Bases:
TreeNode
Abstract class for nodes that have no children.
- __init__(obj)
Creates a node with the given object.
- Parameters:
obj – The underlying Python object wrapped by the node.
- calculate_total_size() int
By default, leaf nodes’ sizes are equal to the length of their wrapped object’s string representation.
This is equivalent to:
return len(str(self.object))
However, subclasses may override this function to return whatever size is required.
- Returns:
The length of the string representation of
self.object
.- Return type:
- children() Collection[TreeNode]
Leaf nodes have no children, so this always returns an empty tuple.
- Returns:
An empty tuple.
- Return type:
- copy() T
Creates a deep copy of this node
- copy_from(children: Iterable[TreeNode]) 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this leaf node.
By default, leaf nodes print the
repr()
of their wrapped object:printer.write(repr(self.object))
- Parameters:
printer – The printer to which to write 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 the object wrapped by this node.
This is equivalent to:
return self.object
- 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:
ListNode
- class graphtage.ListNode(nodes: Iterable[T], allow_list_edits: bool = True, allow_list_edits_when_same_length: bool = True)
Bases:
SequenceNode
[Tuple
[T
, …]],Generic
[T
]A node containing an ordered sequence of nodes.
- __init__(nodes: Iterable[T], allow_list_edits: bool = True, allow_list_edits_when_same_length: bool = True)
Initializes a List node.
- Parameters:
nodes – The set of nodes in this list.
allow_list_edits – Whether to consider removal and insertion when editing this list.
allow_list_edits_when_same_length – Whether to consider removal and insertion when comparing this list to another list of the same length.
- __iter__() Iterator[TreeNode]
Iterates over this sequence’s child nodes.
This is equivalent to:
return iter(self._children)
- __len__() int
The number of children of this sequence.
This is equivalent to:
return len(self._children)
- 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:
- calculate_total_size()
Calculates the total size of this sequence.
This is equivalent to:
return sum(c.total_size for c in self)
- property container_type: Type[Tuple[T, ...]]
The container type required by
graphtage.sequences.SequenceNode
- Returns:
- Return type:
Type[Tuple[T, …]]
- copy() T
Creates a deep copy of this node
- copy_from(children: Iterable[TreeNode]) 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__
, callingTreeNode.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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(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()
Returns a pure Python representation of this node.
For example, a node representing a list, like
graphtage.ListNode
, should return a Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
MappingNode
- class graphtage.MappingNode(*args, **kwds)
Bases:
ContainerNode
,ABC
An abstract base class for nodes that represent mappings.
- __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:
- __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:
- Raises:
KeyError – If the key is not found.
- __init__()
- abstract __iter__() Iterator[KeyValuePairNode]
Mappings should return an iterator over
graphtage.KeyValuePairNode
.
- 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:
- abstract 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:
- 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- abstract 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:
- abstract classmethod from_dict(source_dict: Dict[LeafNode, TreeNode]) T
Constructs a
MappingNode
from a mapping ofLeafNode
toTreeNode
.- Parameters:
source_dict – The source mapping.
- Returns:
The resulting
MappingNode
.- Return type:
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 overgraphtage.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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- 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 Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
Match
- class graphtage.Match(match_from: TreeNode, match_to: TreeNode, cost: int)
Bases:
ConstantCostEdit
A constant cost edit specifying that one node should be matched to another.
- __init__(match_from: TreeNode, match_to: TreeNode, cost: int)
Constructs a new edit.
- Parameters:
from_node – The node being edited.
to_node – The node into which
from_node
is being transformed.cost – The constant cost of the edit.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
MultiSetNode
- class graphtage.MultiSetNode(items: Iterable[T], auto_match_keys: bool = True)
Bases:
SequenceNode
[HashableCounter
[T
]],Generic
[T
]A node representing a set that can contain duplicate items.
- __init__(items: Iterable[T], auto_match_keys: bool = True)
Initializes a sequence node.
- Parameters:
children – A sequence of
TreeNodes
. This is assigned to the protected memberSequenceNode._children
.
- 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:
- calculate_total_size()
Calculates the total size of this sequence.
This is equivalent to:
return sum(c.total_size for c in 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__
, callingTreeNode.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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
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:
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(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()
Returns a pure Python representation of this node.
For example, a node representing a list, like
graphtage.ListNode
, should return a Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
NullNode
- class graphtage.NullNode
Bases:
LeafNode
A node representing a null or
None
type.- __init__()
Creates a node with the given object.
- Parameters:
obj – The underlying Python object wrapped by the node.
- calculate_total_size() int
By default, leaf nodes’ sizes are equal to the length of their wrapped object’s string representation.
This is equivalent to:
return len(str(self.object))
However, subclasses may override this function to return whatever size is required.
- Returns:
The length of the string representation of
self.object
.- Return type:
- children() Collection[TreeNode]
Leaf nodes have no children, so this always returns an empty tuple.
- Returns:
An empty tuple.
- Return type:
- 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this leaf node.
By default, leaf nodes print the
repr()
of their wrapped object:printer.write(repr(self.object))
- Parameters:
printer – The printer to which to write 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 the object wrapped by this node.
This is equivalent to:
return self.object
- 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:
PossibleEdits
- class graphtage.PossibleEdits(from_node: TreeNode, to_node: TreeNode, edits: Iterator[Edit] = (), initial_cost: Range | None = None)
Bases:
AbstractCompoundEdit
A compound edit that chooses the best option among one or more competing alternatives.
The best option is chosen by performing
graphtage.search.IterativeTighteningSearch
on the alternatives.- __init__(from_node: TreeNode, to_node: TreeNode, edits: Iterator[Edit] = (), initial_cost: Range | None = None)
Constructs a new Possible Edits object.
- Parameters:
from_node – The node being edited.
to_node – The node into which
from_node
is being transformed.edits – One or more edits from which to choose.
initial_cost – Initial bounds on the cost of the best choice, if known.
- __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
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
Remove
- class graphtage.Remove(to_remove: TreeNode, remove_from: TreeNode, penalty: int = 1)
Bases:
ConstantCostEdit
A constant cost edit specifying that a node should be removed from a container.
- __init__(to_remove: TreeNode, remove_from: TreeNode, penalty: int = 1)
Constructs a new edit.
- Parameters:
from_node – The node being edited.
to_node – The node into which
from_node
is being transformed.cost – The constant cost of the edit.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
Replace
- class graphtage.Replace(to_replace: TreeNode, replace_with: TreeNode)
Bases:
ConstantCostEdit
A constant cost edit specifying that one node should be replaced with another.
- __init__(to_replace: TreeNode, replace_with: TreeNode)
Constructs a new edit.
- Parameters:
from_node – The node being edited.
to_node – The node into which
from_node
is being transformed.cost – The constant cost of the edit.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode | TreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- 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.
StringEdit
- class graphtage.StringEdit(from_node: StringNode, to_node: StringNode)
Bases:
AbstractEdit
An edit returned from a
StringNode
- __init__(from_node: StringNode, to_node: StringNode)
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.
- __lt__(other)
Tests whether the bounds of this edit are less than the bounds of
other
.
- 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:
- 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.
- 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 returnTrue
if no future calls toEdit.tighten_bounds()
will affect the result ofCompoundEdit.edits()
.- Returns:
True
if subsequent calls toEdit.tighten_bounds()
will only serve to tighten the bounds of this edit and will not affect the semantics of the edit.- Return type:
- on_diff(from_node: EditedTreeNode | TreeNode)
A callback for when an edit is assigned to an
EditedTreeNode
inTreeNode.diff()
.This default implementation adds the edit to the node:
from_node.edit = self from_node.edit_list.append(self)
Implementations of the
Edit
protocol that have sub-edits (likeCompoundEdit
) should recursively callEdit.on_diff()
on its sub-edits.- Parameters:
from_node – The edited node that was added to the diff
- print(formatter: GraphtageFormatter, printer: Printer)
StringEdit does not implement
graphtage.tree.Edit.print()
.Instead, it raises
NotImplementedError
so that the formatting protocol will default to using a formatter likeStringFormatter
.
- 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:
Note
Implementations of this function should return
False
if and only ifself.bounds().definitive()
.
StringFormatter
- class graphtage.StringFormatter(*args, **kwargs)
Bases:
GraphtageFormatter
A default string formatter
- DEFAULT_INSTANCE: Formatter[T] = <graphtage.StringFormatter 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 theirparent
to this new formatter.
- escape(c: str) str
String escape.
This function is called once for each character in the string.
- Returns:
The escaped version of c, or c itself if no escaping is required.
- Return type:
- 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)
- 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_StringEdit(printer: Printer, edit: StringEdit)
- print_StringNode(printer: Printer, node: StringNode)
- 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.
- write_char(printer: Printer, c: str | int, index: int, num_edits: int, removed=False, inserted=False)
Writes a character to the printer.
Note
This function calls
graphtage.StringFormatter.escape()
; classes extendinggraphtage.StringFormatter
should also callgraphtage.StringFormatter.escape()
when reimplementing this function.Note
There is no need to specially format characters that have been removed or inserted; the printer will have already automatically been configured to format them prior to the call to
StringFormatter.write_char()
.- Parameters:
printer – The printer to which to write the character.
c – The character to write.
index – The index of the character in the string.
num_edits – The total number of characters that will be printed.
removed – Whether this character was removed from the source string.
inserted – Whether this character is inserted into the source string.
- write_end_quote(printer: Printer, edit: StringEdit)
Prints an ending quote for the string, if necessary
- write_start_quote(printer: Printer, edit: StringEdit)
Prints a starting quote for the string, if necessary
StringNode
- class graphtage.StringNode(string_like: str | bytes, quoted=True)
Bases:
LeafNode
A node containing a string
- __init__(string_like: str | bytes, quoted=True)
Initializes a string node.
- Parameters:
string_like – the string contained by the node
quoted – whether or not the string should be quoted when being formatted
- calculate_total_size() int
By default, leaf nodes’ sizes are equal to the length of their wrapped object’s string representation.
This is equivalent to:
return len(str(self.object))
However, subclasses may override this function to return whatever size is required.
- Returns:
The length of the string representation of
self.object
.- Return type:
- children() Collection[TreeNode]
Leaf nodes have no children, so this always returns an empty tuple.
- Returns:
An empty tuple.
- Return type:
- copy() T
Creates a deep copy of this node
- copy_from(children: Iterable[TreeNode]) 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- print(printer: Printer)
Prints this leaf node.
By default, leaf nodes print the
repr()
of their wrapped object:printer.write(repr(self.object))
- Parameters:
printer – The printer to which to write 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 the object wrapped by this node.
This is equivalent to:
return self.object
- 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:
TreeNode
- class graphtage.TreeNode
Bases:
object
Abstract base class for nodes in Graphtage’s intermediate representation.
Tree nodes are intended to be immutable.
EditedTreeNode
, on the other hand, can be mutable. SeeTreeNode.make_edited()
.- __init__()
- abstract 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:
- abstract children() Sequence[TreeNode]
Returns a sequence of this node’s children, if any.
It is the responsibility of any node that has children must set the .parent member of each child.
- 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__
, callingTreeNode.editable_dict()
on anyTreeNode
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
, whereasEditedTreeNode.edited()
returnsTrue
.
- abstract 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:
- 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
anyCompoundEdit
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
anyCompoundEdit
in the sequence.- Return type:
Iterator[Edit]
- property is_leaf: bool
Returns whether this node is a leaf.
This implementation is equivalent to:
return len(self.children()) == 0
- 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 ofTreeNode
does not extend off ofContainerNode
and for whichlen(self.children()) > 0
, then each child’s parent must be set.
- 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.
- abstract to_obj()
Returns a pure Python representation of this node.
For example, a node representing a list, like
graphtage.ListNode
, should return a Pythonlist
. A node representing a mapping, likegraphtage.MappingNode
, should return a Pythondict
. Container nodes should recursively callTreeNode.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:
TreeNodeMeta
- class graphtage.TreeNodeMeta(name, bases, namespace, **kwargs)
Bases:
ABCMeta
- __init__(name, *args, **kwargs)
- __instancecheck__(instance)
Override for isinstance(instance, cls).
- __subclasscheck__(subclass)
Override for issubclass(subclass, cls).
- _abc_caches_clear()
Clear the caches (for debugging or testing).
- _abc_registry_clear()
Clear the registry (for debugging or testing).
- _dump_registry(file=None)
Debug helper to print the ABC registry.
- edited_type() Type[EditedTreeNode | T]
Dynamically constructs a new class that is both a
TreeNode
and anEditedTreeNode
.The edited type’s member variables are populated by the result of
TreeNode.editable_dict()
of theTreeNode
it wraps:new_node.__dict__ = dict(wrapped_tree_node.editable_dict())
- Returns:
A class that is both a
TreeNode
and anEditedTreeNode
. Its constructor accepts aTreeNode
that it will wrap.- Return type:
Type[Union[EditedTreeNode, T]]
- mro()
Return a type’s method resolution order.
- register(subclass)
Register a virtual subclass of an ABC.
Returns the subclass, to allow usage as a class decorator.
graphtage functions
explode_edits
- graphtage.explode_edits(edit: Edit) Iterator[Edit]
Performs a depth-first traversal over a potentially compound edit.
If an edit implements the
CompoundEdit
protocol, its sub-edits are recursively included in the output.This implementation is equivalent to:
if isinstance(edit, CompoundEdit): return itertools.chain(*map(explode_edits, edit.edits())) else: return iter((edit,))
- Parameters:
edit – The edit that is to be exploded
- Returns:
An iterator over the edits.
- Return type:
Iterator[Edit]
get_filetype
- graphtage.get_filetype(path: str | None = None, mime_type: str | None = None) Filetype
Looks up the filetype for the given path.
At least one of
path
ormime_type
must be notNone
. If both are provided, onlymime_type
will be used. If onlypath
is provided, its mimetype will be guessed usingmimetypes.guess_type()
.- Parameters:
path – An optional path to a file.
mime_type – An optional MIME type string.
- Returns:
The filetype object associated with the given path and/or MIME type.
- Return type:
- Raises:
ValueError – If both
path
andmime_type
areNone
.ValueError – If
mime_type
was not provided andmimetypes.guess_type()
could not identify the file atpath
.ValueError – If either the provided or guessed mimetype is not supported by any registered
Filetype
.
string_edit_distance
- graphtage.string_edit_distance(s1: str, s2: str) EditDistance
A convenience function for computing the edit distance between two strings.
This is equivalent to:
list1 = ListNode([StringNode(c) for c in s1]) list2 = ListNode([StringNode(c) for c in s2]) return EditDistance(list1, list2, list1.children(), list2.children(), insert_remove_penalty=0)
- Parameters:
s1 – the string to compare from
s2 – the string to compare to
- Returns:
The
graphtage.levenshtein.EditDistance
edit object for the strings.- Return type: