# Fallback Chains for Optional Dependency Probing in book-to-skill

> Discover fallback chains for optional dependency probing in book-to-skill. Learn how the library gracefully degrades from high-quality extractors to stdlib parsers when external packages are unavailable.

- Repository: [Virgilio Junior/book-to-skill](https://github.com/virgiliojr94/book-to-skill)
- Tags: internals
- Published: 2026-08-25

---

**The `book-to-skill` library uses a declarative `DEPENDENCY_GROUPS` table to probe optional dependencies, implementing multi-layered fallback chains that gracefully degrade from high-quality extractors to stdlib-based parsers when external packages are unavailable.**

Understanding how open-source tools handle missing dependencies is critical for building resilient data pipelines. The `book-to-skill` project—available at **virgiliojr94/book-to-skill**—implements an elegant probing system that prioritizes user experience through intelligent fallback chains. This article examines the complete mechanism, from dependency declaration to runtime satisfaction logic.

## The DEPENDENCY_GROUPS Declaration Table

All fallback behavior originates in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py) (lines 13‑70). The **`DEPENDENCY_GROUPS`** list defines every supported format with structured metadata that drives the probing engine.

Each dependency group contains these key fields:

- **`modules`** — Python packages that can handle the format
- **`any_of_modules`** — When `True`, only one module needs to be present; when `False`, all are required
- **`any_tool_suffices`** — When `True`, either a Python module OR a system command satisfies the requirement
- **`system`** — External binaries as tuples of `(command, pretty-name, install-hint)`
- **`required`** — If set, the format has **no fallback** and extraction aborts
- **`note`** — Human-readable description of the fallback chain

## Complete Fallback Chain Reference

The following table maps each format to its probing behavior and degradation path:

| Format | Primary Dependencies | Fallback When Missing |
|--------|---------------------|----------------------|
| **PDF (text-heavy)** | `pdftotext` **or** `pypdf` **or** `pdfminer` | Any installed Python PDF parser; extraction fails if none present |
| **PDF (technical)** | `docling` | Falls back to PDF text-heavy chain |
| **EPUB** | `ebooklib` **and** `bs4` | `zipfile` + `html.parser` from stdlib |
| **DOCX** | `docx` | `zipfile` + `xml.etree.ElementTree` from stdlib |
| **HTML** | `trafilatura` **or** `bs4` **or** `html.parser` | Always degrades to stdlib `html.parser` |
| **RTF** | `striprtf` | Basic regex-based cleanup |
| **MOBI/AZW/AZW3** | `ebook-convert` (Calibre) | **No fallback** — mandatory external tool |

These chains are encoded in the `note` fields at lines 24, 30, 37‑45, 51‑58, and 68‑69 of [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py).

## Source Code Structure: How Probing Works

### Step 1: Availability Checking

The `run_dependency_check` function (lines 73‑115) iterates through `DEPENDENCY_GROUPS` and probes each dependency:

```python
from book_to_skill.dependencies import run_dependency_check

# Returns a structured report of ready/missing/fallback-available formats

status = run_dependency_check()

```

Two helper functions perform the actual probing:

- `python_module_available(module_name)` (lines 73‑75) — tests Python importability
- `shutil.which(command)` — locates system binaries on `PATH`

### Step 2: Satisfaction Determination

The decision block at lines 80‑94 evaluates each group against the declared flags:

```python

# Simplified logic from lines 81-94

if required and not satisfied:
    status = "missing-required"  # Hard failure

elif any_tool_suffices and has_any_module_or_system:
    status = "ready"
elif any_of_modules and has_any_module:
    status = "ready"
elif not any_of_modules and has_all_modules:
    status = "ready"
else:
    status = "fallback-available"  # Degrade gracefully

```

### Step 3: Runtime Fallback Handling

The `prepare_dependencies` function (lines 94‑136) orchestrates the actual fallback during extraction:

```python
from book_to_skill.dependencies import prepare_dependencies

# Example: HTML extraction with automatic fallback

prepare_dependencies(ext=".html", extraction_mode="default", install_mode="ask")

```

Typical console output when falling back:

```

HTML extraction uses beautifulsoup4, if installed, otherwise a stdlib HTML parser.
Non-interactive mode or install disabled; using fallback.
Using fallback: a stdlib HTML parser.

```

## Practical Code Examples

### Running the Full Dependency Audit

Execute the built-in checker to see your system's status across all formats:

```bash
python -m book_to_skill --check

```

This invokes `run_dependency_check` and prints a formatted table with installation suggestions (lines 111‑115).

### Programmatic Dependency Probing

Check a specific format before extraction:

```python
from book_to_skill.dependencies import (
    DEPENDENCY_GROUPS,
    python_module_available
)
import shutil

def check_pdf_readiness():
    """Probe PDF extraction capabilities with full fallback chain."""
    pdf_group = next(g for g in DEPENDENCY_GROUPS if "PDF (text-heavy)" in g["label"])
    
    # Check Python modules

    modules_ok = any(
        python_module_available(m) 
        for m in pdf_group["modules"]
    )
    
    # Check system tools

    system_ok = any(
        shutil.which(cmd) for cmd, _, _ in pdf_group["system"]
    )
    
    return {
        "ready": modules_ok or system_ok,
        "has_pypdf": python_module_available("pypdf"),
        "has_pdfminer": python_module_available("pdfminer"),
        "has_pdftotext": shutil.which("pdftotext") is not None
    }

```

### Handling Isolated Tool Installations

For tools installed via **pipx** or similar isolation mechanisms:

```python
from book_to_skill.dependencies import isolated_install_hint

print(isolated_install_hint("docling"))

# → a `docling` command exists at /home/user/.local/pipx/venvs/docling/bin/docling,

#   so it is installed in an isolated environment ...

```

## Optional Dependency Installation Flow

When `install_mode` is set to `"ask"` or `"auto"`, the `offer_dependency_install` function (invoked from `prepare_dependencies`) can resolve missing Python packages:

```python
from book_to_skill.dependencies import install_python_packages

# Automatic resolution (lines 111-127)

install_python_packages(["pypdf", "beautifulsoup4"], mode="auto")

```

The installation respects the mapping in [`book_to_skill/config.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/config.py) (`PYTHON_DEPENDENCIES`), which translates import names to pip package identifiers.

## Key Implementation Files

| File | Responsibility |
|------|---------------|
| [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py) | Core probing logic, `DEPENDENCY_GROUPS` definition, satisfaction evaluation (lines 13‑70, 73‑95, 94‑136) |
| [`book_to_skill/config.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/config.py) | Module-to-package name mapping (`PYTHON_DEPENDENCIES`) for installation |
| [`scripts/extract.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/scripts/extract.py) | CLI entry point, `--check` reporter invocation |
| [`book_to_skill/cli.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/cli.py) | Argument parsing for `--install-missing` and related flags |
| [`tests/test_book_to_skill.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tests/test_book_to_skill.py) | Verified fallback behavior across all format chains |

## Summary

- **`DEPENDENCY_GROUPS`** in [`book_to_skill/dependencies.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/dependencies.py) (lines 13‑70) provides the single source of truth for all optional dependency probing and fallback chains.
- **Three-tier satisfaction logic** (ready / fallback-available / missing-required) enables graceful degradation without hard failures for most formats.
- **MOBI/AZW/AZW3** is the sole exception with no fallback—it requires Calibre's `ebook-convert` as a mandatory external dependency.
- **Automatic installation** can resolve missing Python packages when configured, while system tools always require manual installation.
- **Stdlib fallbacks** for EPUB, DOCX, and HTML ensure basic functionality even in minimal environments.

## Frequently Asked Questions

### How does book-to-skill handle missing PDF libraries?

The PDF text-heavy format accepts **any one of** `pdftotext` (system), `pypdf`, or `pdfminer`. Probing occurs via `shutil.which` for system tools and `python_module_available` for Python packages. If none are found, extraction fails with a diagnostic message. Technical PDFs using `docling` automatically fall back to the text-heavy chain.

### Can I force book-to-skill to use stdlib parsers only?

Not directly through a global flag, but you can uninstall or exclude the optional packages. For HTML specifically, the tool always prefers `trafilatura`, then `beautifulsoup4`, then `html.parser`—so absence of the first two forces stdlib usage. For EPUB and DOCX, removing `ebooklib`/`docx` triggers `zipfile`-based extraction.

### What happens if ebook-convert is not installed for MOBI files?

The MOBI/AZW/AZW3 group has `required=True`, meaning **no fallback exists**. The `prepare_dependencies` function returns a hard failure status, and extraction aborts with instructions to install Calibre. This is the only format in `DEPENDENCY_GROUPS` with mandatory external dependencies.

### How do I check my system's dependency status before running extraction?

Run `python -m book_to_skill --check` or programmatically call `run_dependency_check()`. This prints a formatted table showing ready formats, available fallbacks, and missing required tools—with specific installation commands for your platform.