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**](#load-events) and [**analysis events**](#analysis-events). Load events are scoped to the Griffe loader (triggered once a package is fully loaded). Analysis events are scoped to the visitor and inspector agents (triggered during static and dynamic analysis).
**Hooks** are methods that are called when a particular event is triggered. To target a specific event, the hook must be named after it. See [Extensions and hooks](#extensions-and-hooks).
#### Load events
**Load events** are triggered once the tree for a given package has been fully constructed.
There is 1 generic **load event**:
- [`on_object`][griffe.Extension.on_object]: The "on object" event is triggered on any kind of object (except for aliases and packages, so modules, classes, functions, attributes and type aliases), once the tree for the object's package has been fully constructed.
There are also specific **load events** for each object kind:
- [`on_module`][griffe.Extension.on_module]: The "on module" event is triggered on modules.
- [`on_class`][griffe.Extension.on_class]: The "on class" event is triggered on classes.
- [`on_function`][griffe.Extension.on_function]: The "on function" event is triggered on functions.
- [`on_attribute`][griffe.Extension.on_attribute]: The "on attribute" event is triggered on attributes.
- [`on_type_alias`][griffe.Extension.on_type_alias]: The "on type alias" event is triggered on type aliases.
- [`on_alias`][griffe.Extension.on_alias]: The "on alias" event is triggered on aliases (imported/inherited objects).
- [`on_package`][griffe.Extension.on_package]: The "on package" event is triggered on top-level modules (packages) only.
#### Analysis events
**Analysis events** are triggered while modules are being scanned (with static or dynamic analysis). Data is incomplete when these events are triggered, so we recommend only hooking onto these events if you know what you are doing. In doubt, prefer using **load events** above.
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], [Attribute][griffe.Attribute], or [Type Alias][griffe.TypeAlias], 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, attributes and type aliases do not have members, so there are no "on members" events for these 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]
- [`on_type_alias_node`][griffe.Extension.on_type_alias_node]
- [`on_type_alias_instance`][griffe.Extension.on_type_alias_instance]
- [`on_alias_instance`][griffe.Extension.on_alias_instance]
#### Extensions and hooks
**Extensions** are classes that inherit from [Griffe's Extension base class][griffe.Extension] and define some hooks as methods:
```python
import griffe
class MyExtension(griffe.Extension):
def on_object(
self,
*,
obj: griffe.Object,
loader: griffe.GriffeLoader,
**kwargs,
) -> None:
"""Do something with `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_object(
self,
*,
obj: griffe.Object,
loader: griffe.GriffeLoader,
**kwargs,
) -> None:
"""Do something with `obj`."""
```
### Static/dynamic support
Extensions can support both static and dynamic analysis of modules.
Objects have an `analysis` attribute whose value will be `"static"` if they were loaded using static analysis, or `"dynamic"` if they were loaded using dynamic analysis. If the value is `None`, it means the object was created manually (for example by another extension).
To support static analysis, dynamic analysis, or both in your load events, you can therefore check the value of the `analysis` attribute:
```python
import griffe
class MyExtension(griffe.Extension):
def on_object(self, *, obj: griffe.Object, **kwargs) -> None:
"""Do something with `obj`."""
if obj.analysis == "static":
... # Apply logic for static analysis.
elif obj.analysis == "dynamic":
... # Apply logic for dynamic analysis.
else:
... # Apply logic for manually built objects.
```
### Visiting trees
Extensions provide basic functionality to help you visit trees during analysis of the code:
- [`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] > - [`ParamSpec`][ast.ParamSpec] > - [`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] > - [`TypeAlias`][ast.TypeAlias] > - [`TypeVar`][ast.TypeVar] > - [`TypeVarTuple`][ast.TypeVarTuple] > - [`UAdd`][ast.UAdd] > - [`UnaryOp`][ast.UnaryOp] > - [`USub`][ast.USub] > - [`While`][ast.While] > - [`With`][ast.With] > - [`withitem`][ast.withitem] > - [`Yield`][ast.Yield] > - [`YieldFrom`][ast.YieldFrom] > > |