all2md.utils.decorators

Utility decorators for all2md parsers and renderers.

This module provides reusable decorators that eliminate code duplication across parsers and renderers, particularly for dependency management.

all2md.utils.decorators.requires_dependencies(converter_name: str, packages: List[Tuple[str, str, str]]) Callable

Check required dependencies and versions before method execution.

This decorator eliminates repeated try/except ImportError blocks across parsers and renderers by centralizing dependency checking logic. It validates both package availability and version requirements, raising a DependencyError with helpful installation instructions if any dependencies are missing or have incompatible versions.

Parameters:
  • converter_name (str) – Name of the converter (e.g., “pdf”, “docx”, “html”). This appears in error messages to help users identify which converter needs dependencies.

  • packages (list of tuple) – Required packages as (install_name, import_name, version_spec) tuples where: - install_name: Package name for pip install (e.g., “pymupdf”) - import_name: Module name for import statement (e.g., “fitz”) - version_spec: Version requirement (e.g., “>=1.26.4” or “” for any version)

Returns:

Decorated method that checks dependencies before execution

Return type:

Callable

Raises:

DependencyError – If any required package is missing or has an incompatible version. The error includes: - List of missing packages - List of version mismatches (package, required version, installed version) - Installation command - Original ImportError for debugging

Examples

Apply to a parser’s parse method:

>>> @requires_dependencies("pdf", [("pymupdf", "fitz", ">=1.26.4")])
... def parse(self, input_data):
...     import fitz
...     # parsing logic here

Apply to a renderer’s render method:

>>> @requires_dependencies("docx", [("python-docx", "docx", ">=1.2.0")])
... def render(self, doc, output):
...     from docx import Document
...     # rendering logic here

Multiple dependencies:

>>> @requires_dependencies("html", [
...     ("beautifulsoup4", "bs4", ">=4.9.0"),
...     ("lxml", "lxml", "")
... ])
... def parse(self, input_data):
...     import bs4
...     import lxml
...     # parsing logic here

Notes

  • The decorator preserves the original ImportError for debugging

  • Uses importlib.import_module for standard, safe importing

  • Validates version requirements using packaging.specifiers

  • Collects all missing packages and version mismatches before raising error

  • Works with both parsers and renderers

  • Compatible with type hints and IDE autocomplete

all2md.utils.decorators.debug_timer(logger: Logger, operation: str) Generator[None, None, None]

Context manager for timing operations with DEBUG-level logging.

This context manager eliminates duplicated timing code patterns where operations are timed only when DEBUG logging is enabled. It automatically measures elapsed time and logs the result.

Parameters:
  • logger (logging.Logger) – Logger instance to use for DEBUG messages

  • operation (str) – Description of the operation being timed (e.g., “Parsing (pdf)”)

Yields:

None – Control flow to the code block being timed

Examples

Basic usage with automatic timing:

>>> logger = logging.getLogger(__name__)
>>> with debug_timer(logger, "Parsing (pdf)"):
...     result = parser.parse(document)
... # Logs: "Parsing (pdf) completed in 1.23s" at DEBUG level

Replacing duplicated if/else blocks:

>>> # Before:
>>> if logger.isEnabledFor(logging.DEBUG):
...     start_time = time.perf_counter()
...     result = expensive_operation()
...     elapsed = time.perf_counter() - start_time
...     logger.debug(f"Operation completed in {elapsed:.2f}s")
... else:
...     result = expensive_operation()
>>>
>>> # After:
>>> with debug_timer(logger, "Operation"):
...     result = expensive_operation()

Notes

  • Only measures time when logger has DEBUG level enabled

  • Uses perf_counter for high-resolution timing

  • Formats elapsed time to 2 decimal places

  • Zero overhead when DEBUG logging is disabled