all2md.transforms.hooks
Hook system for AST transformation pipeline.
This module provides a flexible hook system for intercepting and modifying the transformation pipeline at various stages. Hooks can be registered for: - Pipeline stages (pre/post AST, render, etc.) - Specific node types (headings, images, links, etc.)
Examples
Register a hook for all images:
>>> from all2md.transforms import HookManager
>>> manager = HookManager()
>>>
>>> def log_image(node, context):
... print(f"Found image: {node.url}")
... return node
>>>
>>> manager.register_hook('image', log_image)
Register a pipeline hook:
>>> def validate_document(doc, context):
... print(f"Processing document with {len(doc.children)} nodes")
... return doc
>>>
>>> manager.register_hook('pre_render', validate_document)
- class all2md.transforms.hooks.HookContext
Bases:
objectContext passed to hook functions.
This class provides hooks with access to document state, metadata, and a shared data dictionary for passing information between hooks and transforms.
- Parameters:
document (Document) – The current document being processed
metadata (dict, default = empty dict) – Document metadata from the source format
shared (dict, default = empty dict) – Shared mutable dictionary for passing data between hooks/transforms
transform_name (str, optional) – Name of the current transform (for transform hooks)
node_path (list[Node], default = empty list) – Path from document root to current node (for node hooks). WARNING: This list is mutated during tree traversal. Not thread-safe.
Examples
Access context in a hook:
>>> def my_hook(node: Image, context: HookContext) -> Image: ... # Store image count in shared state ... context.shared['image_count'] = context.shared.get('image_count', 0) + 1 ... ... # Access document metadata ... if 'author' in context.metadata: ... print(f"Document by: {context.metadata['author']}") ... ... return node
- metadata: dict[str, Any]
- transform_name: str | None = None
Get a value from shared state.
- Parameters:
key (str) – Key to retrieve
default (Any, optional) – Default value if key not found
- Returns:
Value from shared state or default
- Return type:
Any
Set a value in shared state.
- Parameters:
key (str) – Key to set
value (Any) – Value to store
- class all2md.transforms.hooks.HookManager
Bases:
objectManager for registering and executing hooks.
This class provides a central registry for hooks at various pipeline stages and for specific node types.
- Parameters:
strict (bool, default = False) – If True, hook exceptions are re-raised and abort the pipeline. If False (default), exceptions are logged and execution continues.
Examples
- Create a hook manager:
>>> manager = HookManager()
- Create a strict hook manager:
>>> manager = HookManager(strict=True)
- Register a pipeline hook:
>>> def pre_render_hook(doc, context): ... print("About to render") ... return doc >>> manager.register_hook('pre_render', pre_render_hook)
- Register a node hook:
>>> def image_hook(node, context): ... print(f"Processing image: {node.url}") ... return node >>> manager.register_hook('image', image_hook)
- Execute hooks:
>>> context = HookContext(document=my_doc) >>> result = manager.execute_hooks('pre_render', my_doc, context)
Notes
In strict mode (strict=True), any exception raised by a hook will be re-raised and abort the pipeline. This is useful for debugging or when hook failures should be treated as critical errors.
In non-strict mode (strict=False, the default), exceptions are logged with full traceback but execution continues with subsequent hooks. This provides a fail-safe default that prevents a single problematic hook from breaking the entire pipeline.
Thread Safety
WARNING: HookManager instances are NOT thread-safe. Hook registration and execution use shared mutable state without synchronization.
For safe concurrent usage: - Create a separate HookManager instance per thread/pipeline (recommended) - Each Pipeline instance creates its own HookManager (default behavior) - If sharing across threads, wrap access with external locks (e.g., threading.Lock)
Initialize the hook manager.
- param strict:
Enable strict mode for hook exception handling
- type strict:
bool, default = False
- __init__(strict: bool = False) None
Initialize the hook manager.
- Parameters:
strict (bool, default = False) – Enable strict mode for hook exception handling
- register_hook(target: Literal['post_ast', 'pre_transform', 'post_transform', 'pre_render', 'post_render', 'document', 'heading', 'paragraph', 'code_block', 'block_quote', 'list', 'list_item', 'table', 'table_row', 'table_cell', 'thematic_break', 'html_block', 'text', 'emphasis', 'strong', 'code', 'link', 'image', 'line_break', 'strikethrough', 'underline', 'superscript', 'subscript', 'html_inline', 'footnote_reference', 'footnote_definition', 'math_inline', 'math_block', 'definition_list', 'definition_term', 'definition_description'], hook: Callable[[Any, HookContext], Any], priority: int = 100) None
Register a hook for a target.
- Parameters:
target (HookTarget) – Hook point or node type to hook into
hook (callable) – Hook function with signature: (obj, context) -> obj
priority (int, default = 100) – Execution priority (lower runs first)
Notes
Hooks for the same target are executed in priority order (lower first). If priorities are equal, hooks run in registration order.
Sorting is deferred until execution time for better performance when registering many hooks.
Examples
>>> manager = HookManager() >>> manager.register_hook('image', my_image_hook, priority=50)
- unregister_hook(target: Literal['post_ast', 'pre_transform', 'post_transform', 'pre_render', 'post_render', 'document', 'heading', 'paragraph', 'code_block', 'block_quote', 'list', 'list_item', 'table', 'table_row', 'table_cell', 'thematic_break', 'html_block', 'text', 'emphasis', 'strong', 'code', 'link', 'image', 'line_break', 'strikethrough', 'underline', 'superscript', 'subscript', 'html_inline', 'footnote_reference', 'footnote_definition', 'math_inline', 'math_block', 'definition_list', 'definition_term', 'definition_description'], hook: Callable[[Any, HookContext], Any]) bool
Unregister a hook.
- Parameters:
target (HookTarget) – Hook point or node type
hook (callable) – Hook function to remove
- Returns:
True if hook was found and removed
- Return type:
bool
- execute_hooks(target: Literal['post_ast', 'pre_transform', 'post_transform', 'pre_render', 'post_render', 'document', 'heading', 'paragraph', 'code_block', 'block_quote', 'list', 'list_item', 'table', 'table_row', 'table_cell', 'thematic_break', 'html_block', 'text', 'emphasis', 'strong', 'code', 'link', 'image', 'line_break', 'strikethrough', 'underline', 'superscript', 'subscript', 'html_inline', 'footnote_reference', 'footnote_definition', 'math_inline', 'math_block', 'definition_list', 'definition_term', 'definition_description'], obj: Any, context: HookContext) Any
Execute all hooks for a target.
Hooks are executed in priority order. Each hook receives the result from the previous hook. If a hook returns None, the object is removed (for node hooks).
- Parameters:
target (HookTarget) – Hook point or node type
obj (Any) – Object to process (Document or Node)
context (HookContext) – Hook context
- Returns:
Processed object (or None if removed by a hook)
- Return type:
Any
- Raises:
Exception – Any exception from hooks if strict mode is enabled
Examples
>>> context = HookContext(document=doc) >>> result = manager.execute_hooks('image', image_node, context)
Notes
In strict mode, exceptions from hooks are re-raised and abort execution. In non-strict mode (default), exceptions are logged and execution continues.
Hooks are sorted by priority at execution time for better registration performance when many hooks are registered.
- has_hooks(target: Literal['post_ast', 'pre_transform', 'post_transform', 'pre_render', 'post_render', 'document', 'heading', 'paragraph', 'code_block', 'block_quote', 'list', 'list_item', 'table', 'table_row', 'table_cell', 'thematic_break', 'html_block', 'text', 'emphasis', 'strong', 'code', 'link', 'image', 'line_break', 'strikethrough', 'underline', 'superscript', 'subscript', 'html_inline', 'footnote_reference', 'footnote_definition', 'math_inline', 'math_block', 'definition_list', 'definition_term', 'definition_description']) bool
Check if any hooks are registered for a target.
- Parameters:
target (HookTarget) – Hook point or node type
- Returns:
True if hooks are registered
- Return type:
bool
- static get_node_type(node: Node) Literal['document', 'heading', 'paragraph', 'code_block', 'block_quote', 'list', 'list_item', 'table', 'table_row', 'table_cell', 'thematic_break', 'html_block', 'text', 'emphasis', 'strong', 'code', 'link', 'image', 'line_break', 'strikethrough', 'underline', 'superscript', 'subscript', 'html_inline', 'footnote_reference', 'footnote_definition', 'math_inline', 'math_block', 'definition_list', 'definition_term', 'definition_description'] | None
Get the node type string for a node instance.
This static method supports subclasses by using isinstance checks rather than exact type matching. If a node is a subclass of a known type, it will be identified by its parent type.
- Parameters:
node (Node) – AST node
- Returns:
Node type string (e.g., ‘heading’, ‘image’), or None if unknown
- Return type:
NodeType or None
Notes
The method iterates through known node types and returns the first match using isinstance checks. This allows custom subclasses to be recognized by their base type. For example, a custom MyImage(Image) subclass will be identified as type ‘image’.
Performance: Uses module-level _NODE_TYPE_MAP constant to avoid reconstructing the mapping on every call (hot path optimization).
This is a static method because it doesn’t depend on instance state, only on the module-level _NODE_TYPE_MAP constant. This allows it to be called without instantiating HookManager.
Examples
>>> from all2md.ast.nodes import Image >>> img = Image(url="test.png", alt_text="Test") >>> node_type = HookManager.get_node_type(img) >>> print(node_type) 'image'
- list_hooks() dict[Literal['post_ast', 'pre_transform', 'post_transform', 'pre_render', 'post_render', 'document', 'heading', 'paragraph', 'code_block', 'block_quote', 'list', 'list_item', 'table', 'table_row', 'table_cell', 'thematic_break', 'html_block', 'text', 'emphasis', 'strong', 'code', 'link', 'image', 'line_break', 'strikethrough', 'underline', 'superscript', 'subscript', 'html_inline', 'footnote_reference', 'footnote_definition', 'math_inline', 'math_block', 'definition_list', 'definition_term', 'definition_description'], list[tuple[int, Callable[[Any, HookContext], Any]]]]
List all registered hooks with their priorities.
This method provides a public API for enumerating hooks without exposing the internal _hooks dictionary structure.
- Returns:
Dictionary mapping hook targets to lists of (priority, hook) tuples. The returned dictionary is a shallow copy to prevent external modifications to internal state.
- Return type:
dict[HookTarget, list[tuple[int, HookCallable]]]
Examples
>>> manager = HookManager() >>> manager.register_hook('image', my_hook, priority=50) >>> hooks = manager.list_hooks() >>> print(hooks) {'image': [(50, <function my_hook>)]}
- clear() None
Clear all registered hooks.
This is primarily useful for testing.