sourcery_analytics.visitors

Classes for handling and recursing into tree structures.

class sourcery_analytics.visitors.Visitor

Bases: ABC, Generic[P]

Abstract visitor class.

Visitors implement two methods: _touch and _enter. _touch should return a “fact” about a node, for instance its name or its depth. It should not directly recurse into the node’s children, except where this represents part of the “fact” being calculated (see TreeVisitor). “Facts” which require context, such as depth, can be derived from custom (mutable) attributes manipulated in the _enter method, which, as a context manager, should yield after pre-node calculations and before post-node calculations.

With both these methods implemented, the visitor provides the public .visit method to at once enter and calculate over a node.

Examples

>>> class DepthVisitor(Visitor[int]):
...     def __init__(self):
...         self.depth = 0
...     @contextlib.contextmanager
...     def _enter(self, node: astroid.nodes.NodeNG):
...         self.depth += 1
...         yield
...         self.depth -= 1
...     def visit(self, node: astroid.nodes.NodeNG) -> int:
...         return self.depth - 1
abstract touch(node: NodeNG) P

Returns a “fact” about a node.

enter(_node: NodeNG)

Updates visitor context then yields.

visit(node: NodeNG) P

Enters the node and returns a fact about it.

class sourcery_analytics.visitors.IdentityVisitor

Bases: Visitor[NodeNG]

No-op visitor, returning the node itself.

Useful as a default sub-visitor for other visitors.

touch(node: NodeNG) NodeNG

Returns a “fact” about a node.

class sourcery_analytics.visitors.FunctionVisitor(function: Callable[[NodeNG], P])

Bases: Visitor[P], Generic[P]

Generic visitor returning the result of its function calculated on the node.

Examples

>>> name_visitor = FunctionVisitor(lambda node: node.__class__.__name__)
>>> indentation_visitor = FunctionVisitor(lambda node: node.col_offset)
touch(node: NodeNG) P

Returns a “fact” about a node.

class sourcery_analytics.visitors.ConditionalVisitor(sub_visitor: Visitor[P] = IdentityVisitor(), condition: Callable[[NodeNG], bool] = always)

Bases: Visitor[Optional[P]], Generic[P]

Returns the result of its sub-visitor when condition is True, and None otherwise.

By default, this visitor will unconditionally return the node itself.

sub_visitor

a Visitor to optionally return the result from.

condition

a Condition describing when to return the result.

Examples

>>> from sourcery_analytics.conditions import is_type
>>> from astroid.nodes import FunctionDef
>>> method_visitor = ConditionalVisitor(condition=is_type(FunctionDef))
>>> method = astroid.extract_node("def foo(): pass")
>>> method_visitor.visit(method).name
'foo'
>>> binop = astroid.extract_node("a + b")
>>> method_visitor.visit(binop) is None
True

See also

enter(node: NodeNG)

Updates visitor context then yields.

touch(node: NodeNG) Optional[P]

Returns a “fact” about a node.

class sourcery_analytics.visitors.CompoundVisitor(*visitors: Visitor[P], collector: Callable[[Iterable[P]], Q] = tuple)

Bases: Visitor[Q], Generic[P, Q]

Combines a collection of other visitors into a single visitor

Handles context and collection of the results.

This is most useful when you need a single sub-visitor for some other visitor.

Examples

Collect the name and indentation of every sub-node in a tree:

>>> name_visitor = FunctionVisitor(lambda node: node.__class__.__name__)
>>> line_visitor = FunctionVisitor(lambda node: node.lineno)
>>> name_line_visitor = CompoundVisitor(name_visitor, line_visitor)
>>> every_node_visitor = TreeVisitor(name_line_visitor, collector=list)
>>> source = '''
...     def one():
...         return 1
... '''
>>> from sourcery_analytics.utils import clean_source
>>> node = astroid.parse(clean_source(source))
>>> every_node_visitor.visit(node)
[('Module', 0), ('FunctionDef', 1), ('Arguments', None), ('Return', 2)...
enter(node: NodeNG)

Updates visitor context then yields.

touch(node: NodeNG) Q

Returns a “fact” about a node.

class sourcery_analytics.visitors.TreeVisitor(sub_visitor: Visitor[P] = IdentityVisitor(), collector: Callable[[Iterator[P]], Q] = iter)

Bases: Visitor, Generic[P, Q]

Collects the result of the sub-visitor applied to every sub-node of a node.

By default, returns an iterable of every sub-node of a node. The type parameters indicate the expected output of the sub-visitor and the output of the tree visitor respectively (as handled by the collector).

Examples

>>> name_visitor = FunctionVisitor(lambda node: node.__class__.__name__)
>>> every_name_visitor = TreeVisitor(name_visitor, list)
>>> source = "x + y"
>>> node = astroid.extract_node(source)
>>> every_name_visitor.visit(node)
['BinOp', 'Name', 'Name']
enter(node: NodeNG)

Updates visitor context then yields.

touch(node: NodeNG) Q

Returns a “fact” about a node.