How Holehe Dynamically Imports All Website Modules at Runtime

Holehe automatically discovers and loads every site-specific module using a recursive utility called import_submodules in 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 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). This function walks the package tree and recursively imports every module it finds.

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:

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) invokes the importer and prepares modules for execution:

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:


# 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 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 (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 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, while Amazon's is in holehe/modules/shopping/amazon.py. The dynamic import system makes all of them available to the concurrent execution loop without explicit enumeration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →