on_node
eventon_module_node
event"}}
create_mod{{create module instance}}
event_mod_instance{{"on_instance
eventon_module_instance
event"}}
visit_mod_members{{visit module members}}
visit_cls{{enter class node}}
event_cls_node{{"on_node
eventon_class_node
event"}}
create_cls{{create class instance}}
event_cls_instance{{"on_instance
eventon_class_instance
event"}}
visit_cls_members{{visit class members}}
visit_func{{enter func node}}
event_func_node{{"on_node
eventon_function_node
event"}}
create_func{{create function instance}}
event_func_instance{{"on_instance
eventon_function_instance
event"}}
event_cls_members{{"on_members
eventon_class_members
event"}}
event_mod_members{{"on_members
eventon_module_members
event"}}
start{start} --> visit_mod
visit_mod --> event_mod_node
event_mod_node --> create_mod
create_mod --> event_mod_instance
event_mod_instance --> visit_mod_members
visit_mod_members --1--> visit_cls
visit_cls --> event_cls_node
event_cls_node --> create_cls
create_cls --> event_cls_instance
event_cls_instance --> visit_cls_members
visit_cls_members --1--> visit_func
visit_func --> event_func_node
event_func_node --> create_func
create_func --> event_func_instance
event_func_instance --> visit_cls_members
visit_cls_members --2--> event_cls_members
event_cls_members --> visit_mod_members
visit_mod_members --2--> event_mod_members
event_mod_members --> finish{finish}
class event_mod_node event
class event_mod_instance event
class event_cls_node event
class event_cls_instance event
class event_func_node event
class event_func_instance event
class event_cls_members event
class event_mod_members event
classDef event stroke:#3cc,stroke-width:2
```
Hopefully this flowchart gives you a pretty good idea of what happens when Griffe collects data from a Python module. The next section will explain in more details the different events that are triggered, and how to hook onto them in your extensions.
### Events and hooks
There are two kinds of events in Griffe: **load events** and **analysis events**. Load events are scoped to the Griffe loader. Analysis events are scoped to the visitor and inspector agents (triggered during static and dynamic analysis).
#### Load events
There are two **load events**:
- [`on_package_loaded`][griffe.Extension.on_package_loaded]: The "on package loaded" event is triggered when the loader has finished loading a package entirely, i.e. when all its submodules were scanned and loaded. This event can be hooked by extensions which require the whole package to be loaded, to be able to navigate the object tree without raising lookup errors or alias resolution errors.
- [`on_wildcard_expansion`][griffe.Extension.on_wildcard_expansion]: The "on wildcard expansion" event is triggered for each alias that is created by expanding wildcard imports (`from ... import *`).
#### Analysis events
There are 3 generic **analysis events**:
- [`on_node`][griffe.Extension.on_node]: The "on node" events are triggered when the agent (visitor or inspector) starts handling a node in the tree (AST or object tree).
- [`on_instance`][griffe.Extension.on_instance]: The "on instance" events are triggered when the agent just created an instance of [Module][griffe.Module], [Class][griffe.Class], [Function][griffe.Function], or [Attribute][griffe.Attribute], and added it as a member of its parent. The "on instance" event is **not** triggered when an [Alias][griffe.Alias] is created.
- [`on_members`][griffe.Extension.on_members]: The "on members" events are triggered when the agent just finished handling all the members of an object. Functions and attributes do not have members, so there are no "on members" events for these two kinds.
There are also specific **analysis events** for each object kind:
- [`on_module_node`][griffe.Extension.on_module_node]
- [`on_module_instance`][griffe.Extension.on_module_instance]
- [`on_module_members`][griffe.Extension.on_module_members]
- [`on_class_node`][griffe.Extension.on_class_node]
- [`on_class_instance`][griffe.Extension.on_class_instance]
- [`on_class_members`][griffe.Extension.on_class_members]
- [`on_function_node`][griffe.Extension.on_function_node]
- [`on_function_instance`][griffe.Extension.on_function_instance]
- [`on_attribute_node`][griffe.Extension.on_attribute_node]
- [`on_attribute_instance`][griffe.Extension.on_attribute_instance]
And a special event for aliases:
- [`on_alias`][griffe.Extension.on_alias]: The "on alias" event is triggered when an [Alias][griffe.Alias] was just created and added as a member of its parent object.
---
**Hooks** are methods that are called when a particular event is triggered. To target a specific event, the hook must be named after it.
**Extensions** are classes that inherit from [Griffe's Extension base class][griffe.Extension] and define some hooks as methods:
```python
import ast
import griffe
class MyExtension(griffe.Extension):
def on_instance(
self,
node: ast.AST | griffe.ObjectNode,
obj: griffe.Object,
agent: griffe.Visitor | griffe.Inspector,
**kwargs,
) -> None:
"""Do something with `node` and/or `obj`."""
```
Hooks are always defined as methods of a class inheriting from [Extension][griffe.Extension], never as standalone functions. IDEs should autocomplete the signature when you start typing `def` followed by a hook name.
Since hooks are declared in a class, feel free to also declare state variables (or any other variable) in the `__init__` method:
```python
import ast
from griffe import Extension, Object, ObjectNode
class MyExtension(Extension):
def __init__(self) -> None:
super().__init__()
self.state_thingy = "initial stuff"
self.list_of_things = []
def on_instance(
self,
node: ast.AST | griffe.ObjectNode,
obj: griffe.Object,
agent: griffe.Visitor | griffe.Inspector,
**kwargs,
) -> None:
"""Do something with `node` and/or `obj`."""
```
### Static/dynamic support
Extensions can support both static and dynamic analysis of modules. If a module is scanned statically, your extension hooks will receive AST nodes (from the [ast][] module of the standard library). If the module is scanned dynamically, your extension hooks will receive [object nodes][griffe.ObjectNode]. Similarly, your hooks will receive a reference to the analysis agent that calls them, either a [Visitor][griffe.Visitor] or an [Inspector][griffe.Inspector].
To support static analysis, dynamic analysis, or both, you can therefore check the type of the received node or agent:
```python
import ast
import griffe
class MyExtension(griffe.Extension):
def on_instance(
self,
node: ast.AST | griffe.ObjectNode,
obj: griffe.Object,
agent: griffe.Visitor | griffe.Inspector,
**kwargs,
) -> None:
"""Do something with `node` and/or `obj`."""
if isinstance(node, ast.AST):
... # Apply logic for static analysis.
else:
... # Apply logic for dynamic analysis.
```
```python
import ast
import griffe
class MyExtension(Extension):
def on_instance(
self,
node: ast.AST | griffe.ObjectNode,
obj: griffe.Object,
agent: griffe.Visitor | griffe.Inspector,
**kwargs,
) -> None:
"""Do something with `node` and/or `obj`."""
if isinstance(agent, griffe.Visitor):
... # Apply logic for static analysis.
else:
... # Apply logic for dynamic analysis.
```
The preferred method is to check the type of the received node rather than the agent.
Since hooks also receive instantiated modules, classes, functions and attributes, most of the time you will not need to use the `node` argument other than for checking its type and deciding what to do based on the result. And since we always add `**kwargs` to the hooks' signatures, you can drop any parameter you don't use from the signature:
```python
import griffe
class MyExtension(Extension):
def on_instance(self, obj: griffe.Object, **kwargs) -> None:
"""Do something with `obj`."""
...
```
### Visiting trees
Extensions provide basic functionality to help you visit trees:
- [`visit`][griffe.Extension.visit]: call `self.visit(node)` to start visiting an abstract syntax tree.
- [`generic_visit`][griffe.Extension.generic_visit]: call `self.generic_visit(node)` to visit each subnode of a given node.
- [`inspect`][griffe.Extension.inspect]: call `self.inspect(node)` to start visiting an object tree. Nodes contain references to the runtime objects, see [`ObjectNode`][griffe.ObjectNode].
- [`generic_inspect`][griffe.Extension.generic_inspect]: call `self.generic_inspect(node)` to visit each subnode of a given node.
Calling `self.visit(node)` or `self.inspect(node)` will do nothing unless you actually implement methods that handle specific types of nodes:
- for ASTs, methods must be named `visit_> > - [`Add`][ast.Add] > - [`alias`][ast.alias] > - [`And`][ast.And] > - [`AnnAssign`][ast.AnnAssign] > - [`arg`][ast.arg] > - [`arguments`][ast.arguments] > - [`Assert`][ast.Assert] > - [`Assign`][ast.Assign] > - [`AsyncFor`][ast.AsyncFor] > - [`AsyncFunctionDef`][ast.AsyncFunctionDef] > - [`AsyncWith`][ast.AsyncWith] > - [`Attribute`][ast.Attribute] > - [`AugAssign`][ast.AugAssign] > - [`Await`][ast.Await] > - [`BinOp`][ast.BinOp] > - [`BitAnd`][ast.BitAnd] > - [`BitOr`][ast.BitOr] > - [`BitXor`][ast.BitXor] > - [`BoolOp`][ast.BoolOp] > - [`Break`][ast.Break] > - `Bytes`[^1] > - [`Call`][ast.Call] > - [`ClassDef`][ast.ClassDef] > - [`Compare`][ast.Compare] > - [`comprehension`][ast.comprehension] > - [`Constant`][ast.Constant] > - [`Continue`][ast.Continue] > - [`Del`][ast.Del] > - [`Delete`][ast.Delete] > > | > > - [`Dict`][ast.Dict] > - [`DictComp`][ast.DictComp] > - [`Div`][ast.Div] > - `Ellipsis`[^1] > - [`Eq`][ast.Eq] > - [`ExceptHandler`][ast.ExceptHandler] > - [`Expr`][ast.Expr] > - `Expression`[^1] > - `ExtSlice`[^2] > - [`FloorDiv`][ast.FloorDiv] > - [`For`][ast.For] > - [`FormattedValue`][ast.FormattedValue] > - [`FunctionDef`][ast.FunctionDef] > - [`GeneratorExp`][ast.GeneratorExp] > - [`Global`][ast.Global] > - [`Gt`][ast.Gt] > - [`GtE`][ast.GtE] > - [`If`][ast.If] > - [`IfExp`][ast.IfExp] > - [`Import`][ast.Import] > - [`ImportFrom`][ast.ImportFrom] > - [`In`][ast.In] > - `Index`[^2] > - `Interactive`[^3] > - [`Invert`][ast.Invert] > - [`Is`][ast.Is] > - [`IsNot`][ast.IsNot] > - [`JoinedStr`][ast.JoinedStr] > - [`keyword`][ast.keyword] > > | > > - [`Lambda`][ast.Lambda] > - [`List`][ast.List] > - [`ListComp`][ast.ListComp] > - [`Load`][ast.Load] > - [`LShift`][ast.LShift] > - [`Lt`][ast.Lt] > - [`LtE`][ast.LtE] > - [`Match`][ast.Match] > - [`MatchAs`][ast.MatchAs] > - [`match_case`][ast.match_case] > - [`MatchClass`][ast.MatchClass] > - [`MatchMapping`][ast.MatchMapping] > - [`MatchOr`][ast.MatchOr] > - [`MatchSequence`][ast.MatchSequence] > - [`MatchSingleton`][ast.MatchSingleton] > - [`MatchStar`][ast.MatchStar] > - [`MatchValue`][ast.MatchValue] > - [`MatMult`][ast.MatMult] > - [`Mod`][ast.Mod] > - `Module`[^3] > - [`Mult`][ast.Mult] > - [`Name`][ast.Name] > - `NameConstant`[^1] > - [`NamedExpr`][ast.NamedExpr] > - [`Nonlocal`][ast.Nonlocal] > - [`Not`][ast.Not] > - [`NotEq`][ast.NotEq] > - [`NotIn`][ast.NotIn] > - `Num`[^1] > > | > > - [`Or`][ast.Or] > - [`Pass`][ast.Pass] > - `pattern`[^3] > - [`Pow`][ast.Pow] > - `Print`[^4] > - [`Raise`][ast.Raise] > - [`Return`][ast.Return] > - [`RShift`][ast.RShift] > - [`Set`][ast.Set] > - [`SetComp`][ast.SetComp] > - [`Slice`][ast.Slice] > - [`Starred`][ast.Starred] > - [`Store`][ast.Store] > - `Str`[^1] > - [`Sub`][ast.Sub] > - [`Subscript`][ast.Subscript] > - [`Try`][ast.Try] > - `TryExcept`[^5] > - `TryFinally`[^6] > - [`Tuple`][ast.Tuple] > - [`UAdd`][ast.UAdd] > - [`UnaryOp`][ast.UnaryOp] > - [`USub`][ast.USub] > - [`While`][ast.While] > - [`With`][ast.With] > - [`withitem`][ast.withitem] > - [`Yield`][ast.Yield] > - [`YieldFrom`][ast.YieldFrom] > > |