graphtage.sequences
Abstract base classes for representing sequences in Graphtage’s intermediate representation.
sequences classes
FixedLengthSequenceEdit
- class graphtage.sequences.FixedLengthSequenceEdit(from_node: SequenceNode, to_node: SequenceNode)
Bases:
SequenceEdit
An edit for sequences that does not consider interleaving.
- __init__(from_node: SequenceNode, to_node: SequenceNode)
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()
.
SequenceEdit
- class graphtage.sequences.SequenceEdit(from_node: SequenceNode, *args, **kwargs)
Bases:
AbstractCompoundEdit
,ABC
An edit type for sequence nodes.
- __init__(from_node: SequenceNode, *args, **kwargs)
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)
- 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()
.
SequenceFormatter
- class graphtage.sequences.SequenceFormatter(*args, **kwargs)
Bases:
GraphtageFormatter
A formatter for sequence nodes and edits.
This class will typically be subclassed so that its methods can be overridden to match the style of its parent formatter. For an example, see the implementation of
graphtage.json.JSONListFormatter
andgraphtage.json.JSONDictFormatter
.- DEFAULT_INSTANCE: Formatter[T] = <graphtage.GraphtageFormatter object>
A default instance of this formatter, automatically instantiated by the
FormatterChecker
metaclass.
- __init__(start_symbol: str, end_symbol: str, delimiter: str, delimiter_callback: Callable[[Printer], Any] | None = None)
Initializes a sequence formatter.
- Parameters:
start_symbol – The symbol to print at the start of the sequence.
end_symbol – The symbol to print at the end of the sequence.
delimiter – A delimiter to print between items.
delimiter_callback –
A callback for when a delimiter is to be printed. If omitted, this defaults to:
lambda p: p.write(delimiter)
- static __new__(cls, *args, **kwargs) Formatter[T]
Instantiates a new formatter.
This automatically instantiates and populates
Formatter.sub_formatters
and sets theirparent
to this new formatter.
- edit_print(printer: Printer, edit: Edit)
Called when the edit for an item is to be printed.
If the
SequenceNode
being printed either is not edited or has no edits, then the edit passed to this function will be aMatch(child, child, 0)
.This implementation simply delegates the print to the Formatting Protocol:
self.print(printer, edit)
- get_formatter(item: T) Callable[[Printer, T], Any] | None
Looks up a formatter for the given item using this formatter as a base.
Equivalent to:
get_formatter(item.__class__, base_formatter=self)
- is_partial: bool = True
This is a partial formatter; it will not be automatically used in the Formatting Protocol.
- item_newline(printer: Printer, is_first: bool = False, is_last: bool = False)
Called before each node is printed.
This is also called one extra time after the last node, if there is at least one node printed.
The default implementation is simply:
printer.newline()
- items_indent(printer: Printer) Printer
Returns a Printer context with an indentation.
This is called as:
with self.items_indent(printer) as p:
immediately after the
self.start_symbol
is printed, but before any of the items have been printed.This default implementation is equivalent to:
return printer.indent()
- parent: Formatter[T] | None = None
The parent formatter for this formatter instance.
This is automatically populated by
Formatter.__new__()
and should never be manually modified.
- print(printer: Printer, node_or_edit: TreeNode | Edit, with_edits: bool = True)
Prints the given node or edit.
- Parameters:
printer – The printer to which to write.
node_or_edit – The node or edit to print.
with_edits – If :keyword:True, print any edits associated with the node.
Note
The protocol for determining how a node or edit should be printed is very complex due to its extensibility. See the Printing Protocol for a detailed description.
- print_SequenceNode(printer: Printer, node: SequenceNode)
Formats a sequence node.
The protocol for this function is as follows:
Print
self.start_symbol
- With the printer returned by
self.items_indent
: - For each
edit
in the sequence (or just a sequence ofgraphtage.Match
for each child, if the node is not edited):
- For each
- With the printer returned by
If at least one edit was printed, then call
self.item_newline(printer, is_last=True)
Print
self.start_symbol
- sub_format_types: Sequence[Type[Formatter[T]]] = ()
A list of formatter types that should be used as sub-formatters in the Formatting Protocol.
SequenceNode
- class graphtage.sequences.SequenceNode(children: T)
Bases:
ContainerNode
,Generic
[T
],ABC
A node representing a sequence, like a list, set, or dict.
- __init__(children: T)
Initializes a sequence node.
- Parameters:
children – A sequence of
TreeNodes
. This is assigned to the protected memberSequenceNode._children
.
- __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)
- abstract property container_type: Type[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[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 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
.
- 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(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.
- 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: