# How to Dynamically Load All Holehe Modules in Python

> Dynamically load all Holehe modules in Python at runtime using pkgutil walk_packages and importlib.import_module. Discover and load every checker module effortlessly.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: how-to-guide
- Published: 2026-08-27

---

**TLDR:** Holehe discovers and loads every checker module at runtime by walking the `holehe.modules` package using `pkgutil.walk_packages` and `importlib.import_module`.

The open-source OSINT tool **Holehe** (megadose/holehe) searches for accounts linked to email addresses across hundreds of platforms. To maintain its extensible plugin architecture without hard-coding service names or import statements, the project dynamically loads all checker modules at runtime from the `holehe/modules` directory. This design allows developers to add new service checkers simply by dropping a Python file into the appropriate sub-package without modifying the core loading logic.

## How Holehe Discovers Modules Dynamically

Instead of static imports, Holehe implements a **dynamic module discovery** system in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). The mechanism relies on Python's built-in `pkgutil` and `importlib` libraries to traverse the package structure, import every module found, and extract the specific checking functions used to verify email existence on various platforms.

## Step 1: Recursively Import Submodules with `import_submodules()`

In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 37-47, the `import_submodules()` function implements the core discovery logic.

```python
import importlib
import pkgutil

def import_submodules(package, recursive=True):
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
        full_name = package.__name__ + '.' + name
        results[full_name] = importlib.import_module(full_name)
        if recursive and is_pkg:
            results.update(import_submodules(full_name))
    return results

```

**How it works:**

- **Package traversal:** `pkgutil.walk_packages(package.__path__)` iterates over every file in the package directory (e.g., [`amazon.py`](https://github.com/megadose/holehe/blob/main/amazon.py), [`spotify.py`](https://github.com/megadose/holehe/blob/main/spotify.py)).
- **Dynamic import:** Each discovered module is imported using `importlib.import_module(full_name)`, where `full_name` is the fully-qualified name (e.g., `holehe.modules.shopping.amazon`).
- **Recursive loading:** When `recursive=True` and the item is a package (`is_pkg`), the function calls itself to handle nested subdirectories.

The function returns a dictionary mapping fully-qualified module names to their imported module objects.

## Step 2: Extract Checker Functions with `get_functions()`

After importing the modules, Holehe must extract the actual callable functions that perform email checks. The `get_functions()` method in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 50-63, handles this extraction.

```python
def get_functions(modules, args=None):
    websites = []
    for module in modules:
        if len(module.split(".")) > 3:
            modu = modules[module]
            site = module.split(".")[-1]
            if args is not None and args.nopasswordrecovery:
                # filter out modules that require password recovery

                if "adobe" not in str(modu.__dict__[site]) ...:
                    websites.append(modu.__dict__[site])
            else:
                websites.append(modu.__dict__[site])
    return websites

```

**Key details:**

- **Filtering logic:** The condition `len(module.split(".")) > 3` skips the base package itself, targeting only actual checker modules nested within sub-packages.
- **Function retrieval:** The code accesses the checking function via `modu.__dict__[site]`, where `site` is the final component of the module path (e.g., `"amazon"`).
- **Optional filtering:** If `args.nopasswordrecovery` is specified, the loader excludes modules requiring password recovery by checking string representations.

The function returns a list of callable objects ready to be invoked with email addresses.

## Putting It Together: Loading and Executing Modules

In the main execution flow of [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), lines 105-108, these utilities combine to produce a runnable list of modules.

```python

# https://github.com/megadose/holehe/blob/master/holehe/core.py#L105-L108

modules = import_submodules("holehe.modules")
websites = get_functions(modules, args)

```

**Execution flow:**

1. **Discovery:** `import_submodules("holehe.modules")` returns a dictionary of all modules (e.g., `{"holehe.modules.shopping.amazon": <module>, ...}`).
2. **Extraction:** `get_functions(modules, args)` processes this dictionary to extract the specific checking functions.
3. **Invocation:** Each extracted function in `websites` is scheduled for concurrent execution against the target email address.

## Practical Implementation Examples

### Example 1: Listing All Available Modules

This standalone script demonstrates how to dynamically load all Holehe modules and print their names without executing checks.

```python
import importlib
import pkgutil

# Load all sub-modules of holehe.modules

def import_submodules(package, recursive=True):
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    for _, name, is_pkg in pkgutil.walk_packages(package.__path__):
        full_name = f"{package.__name__}.{name}"
        results[full_name] = importlib.import_module(full_name)
        if recursive and is_pkg:
            results.update(import_submodules(full_name))
    return results

def get_functions(modules):
    funcs = []
    for fqname, mod in modules.items():
        if len(fqname.split(".")) > 3:          # skip the package itself

            func_name = fqname.split(".")[-1]   # e.g. "amazon"

            funcs.append((func_name, getattr(mod, func_name)))
    return funcs

if __name__ == "__main__":
    all_modules = import_submodules("holehe.modules")
    checks = get_functions(all_modules)

    for name, fn in checks:
        print(f"Loaded module: {name}  ->  {fn}")

```

### Example 2: Running Checks with Holehe's Helpers

This example uses Holehe's own utilities to run a complete check on a single email address.

```python
import httpx
import trio
from holehe.core import import_submodules, get_functions, launch_module

async def run_one(email):
    client = httpx.AsyncClient()
    modules = import_submodules("holehe.modules")
    checks = get_functions(modules)          # list of checker functions

    results = []

    async with trio.open_nursery() as nursery:
        for check in checks:
            # each `check` is a callable that expects (email, client, out)

            nursery.start_soon(launch_module, check[1], email, client, results)

    await client.aclose()
    return results

# Example usage

if __name__ == "__main__":
    email = "example@example.com"
    trio.run(run_one, email)

```

Both snippets illustrate **how to dynamically discover, import, and invoke every Holehe module** without hard-coding module names or maintaining static import lists.

## Key Source Files and Architecture

| File | Role |
|------|------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Implements `import_submodules()` for discovery and `get_functions()` for callable extraction. |
| [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) | Package marker enabling `pkgutil.walk_packages` traversal. |
| `holehe/modules/*/*.py` | Individual checker modules (e.g., [`shopping/amazon.py`](https://github.com/megadose/holehe/blob/main/shopping/amazon.py)) containing the functions discovered at runtime. |

This architecture creates a true plugin system where adding [`holehe/modules/social/newservice.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social/newservice.py) automatically makes the service available to Holehe scans without touching the loader code.

## Summary

- **Dynamic discovery:** Holehe uses `pkgutil.walk_packages()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) to find all Python files under `holehe.modules` at runtime.
- **Programmatic import:** The `import_submodules()` function leverages `importlib.import_module()` to load discovered modules into memory without hard-coded import statements.
- **Function extraction:** `get_functions()` filters the loaded modules and extracts the specific checking callables by inspecting module dictionaries.
- **Zero-configuration extension:** New service checkers become automatically available simply by placing a Python file in the `holehe/modules` directory structure.

## Frequently Asked Questions

### What is the advantage of dynamically loading Holehe modules?

Dynamic loading eliminates the need to maintain a static list of supported services in the core code. According to the megadose/holehe source code, this allows the tool to support hundreds of platforms while keeping the codebase maintainable—developers add new checkers by creating files rather than modifying import blocks.

### How does Holehe handle nested package structures?

The `import_submodules()` function accepts a `recursive=True` parameter (default enabled) that calls itself recursively when `pkgutil.walk_packages` identifies a subdirectory (`is_pkg=True`). This mechanism discovers modules at any depth within `holehe/modules/` (e.g., [`holehe/modules/shopping/amazon.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/shopping/amazon.py)).

### What is the difference between `import_submodules()` and `get_functions()`?

`import_submodules(package)` returns a dictionary mapping fully-qualified module names to imported module objects (e.g., `{"holehe.modules.music.spotify": <module>}`). `get_functions(modules)` consumes this dictionary and returns a list of the actual callable functions defined within those modules (e.g., `[<function amazon>, <function spotify>]`).

### Can I filter which modules are loaded at runtime?

Yes. The `get_functions()` signature accepts an optional `args` parameter. When provided with `args.nopasswordrecovery=True`, the function filters out modules requiring password recovery by inspecting string representations of the module contents, as implemented in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 57-60.