# How Holehe Dynamically Imports All Website Modules at Runtime

> Holehe dynamically imports website modules automatically using recursive import. Discover plug-and-play OSINT checks without manual registration for efficient data gathering.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: internals
- Published: 2026-08-20

---

**Holehe automatically discovers and loads every site-specific module using a recursive utility called `import_submodules` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), enabling plug-and-play OSINT checks without manual registration.**

The **dynamic import system** is the backbone of Holehe's plugin architecture. As implemented in the [megadose/holehe](https://github.com/megadose/holehe) repository, this mechanism allows the tool to scale to hundreds of supported websites simply by adding Python files to the `holehe/modules` directory—no code changes required.

## The import_submodules Utility

Holehe's dynamic import logic lives in **`import_submodules`** (lines 37-47 of [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)). This function walks the package tree and recursively imports every module it finds.

```python
import importlib
import pkgutil

def import_submodules(package, recursive=True):
    """Import every submodule of *package* and return a dict."""
    if isinstance(package, str):
        package = importlib.import_module(package)
    
    results = {}
    for loader, 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

```

The function accepts either a string package name or module object, uses **`pkgutil.walk_packages`** to iterate subdirectories, and builds full dotted names before importing with **`importlib.import_module`**. Nested packages trigger recursive calls when `is_pkg` is true.

## How Dynamic Import Works at Runtime

The process unfolds in three stages when you run Holehe.

### 1. Package Discovery

`pkgutil.walk_packages(package.__path__)` yields a tuple for every entry in `holehe/modules`:

- `loader` – the module loader object
- `name` – the module or package name
- `is_pkg` – boolean indicating if it's a sub-package

### 2. Module Import and Storage

For each discovered name, Holehe constructs the full dotted path and imports it:

```python
full_name = f"{package.__name__}.{name}"  # e.g., "holehe.modules.social_media.twitter"

results[full_name] = importlib.import_module(full_name)

```

This stores imported module objects in a dictionary keyed by their full names, enabling later lookup by any part of the system.

### 3. Recursive Expansion

When `is_pkg` is true and recursion is enabled, `import_submodules` calls itself on the sub-package, merging results upward. This captures deeply nested modules like `holehe.modules.social_media.twitter` regardless of directory depth.

## Loading Modules in maincore

The **`maincore`** function (around line 206 in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)) invokes the importer and prepares modules for execution:

```python
modules = import_submodules("holehe.modules")   # loads all site modules

websites = get_functions(modules, args)          # extracts callable coroutines

```

The `modules` dictionary now contains every site-specific module—**`holehe.modules.social_media.twitter`**, **`holehe.modules.shopping.amazon`**, and any others present in the filesystem.

### From Modules to Callable Checks

The **`get_functions`** helper transforms the imported module objects into actual coroutine functions. Each module exports an async function matching its filename:

```python

# holehe/modules/social_media/twitter.py

async def twitter(email, client, out):
    # Password recovery probe against twitter.com

    out.append({
        "name": "twitter",
        "domain": "twitter.com",
        "rateLimit": False,
        "error": False,
        "exists": True,
        "emailrecovery": "t****@gmail.com",
        "phoneNumber": None,
        "others": None,
    })

```

These coroutines are then launched concurrently via **Trio** to perform OSINT checks across all supported platforms.

## Why This Architecture Matters

The dynamic import approach provides three key advantages:

- **Zero configuration** – New site modules work immediately when dropped into `holehe/modules`
- **Clean separation** – Each platform lives in its own file with no central registry to maintain
- **Scalable maintenance** – Contributors add support for new sites without touching core code

According to the Holehe source code, this pattern is intentionally minimal. The [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) file is empty—its sole purpose is marking the directory as a Python package so `importlib` can traverse it.

## Summary

- **`import_submodules`** in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 37-47) implements recursive dynamic import using `pkgutil.walk_packages` and `importlib.import_module`
- **Runtime invocation** occurs in `maincore` around line 206, loading all modules under `holehe.modules` into a dictionary
- **Automatic discovery** eliminates manual registration—new Python files in `holehe/modules` become available immediately
- **`get_functions`** converts imported modules into callable coroutines for concurrent OSINT execution

## Frequently Asked Questions

### What Python standard library modules does Holehe use for dynamic importing?

Holehe uses **`pkgutil`** for package tree walking and **`importlib`** for programmatic module importing. These are both part of Python's standard library and require no external dependencies.

### Can I disable recursive importing in Holehe's import_submodules?

Yes. The `import_submodules` function accepts a `recursive` parameter that defaults to `True`. Passing `recursive=False` would limit imports to the top-level package only, though Holehe's `maincore` calls it with the default behavior to ensure nested site modules load.

### How does Holehe handle new website modules I add manually?

Any Python file placed under `holehe/modules` (or its subdirectories) is automatically imported on the next run. The file should contain an async function matching its basename—[`pinterest.py`](https://github.com/megadose/holehe/blob/main/pinterest.py) defines `async def pinterest(...)`—which `get_functions` will discover and execute.

### Where does the actual email checking logic live?

Each site-specific coroutine lives in its own module under `holehe/modules/`, organized by category. For example, Twitter's implementation is in [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py), while Amazon's is in [`holehe/modules/shopping/amazon.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/shopping/amazon.py). The dynamic import system makes all of them available to the concurrent execution loop without explicit enumeration.