# How Holehe Extracts Module Functions: Dynamic Discovery in holehe/core.py

> Discover how Holehe extracts module functions dynamically in holehe/core.py by recursively importing submodules and converting them to callable check functions for efficient security checks.

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

---

**Holehe extracts module functions by recursively importing all submodules from the `holehe.modules` package using `import_submodules()`, then filtering and converting them into callable check functions via `get_functions()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py).**

The open-source OSINT tool **Holehe** dynamically discovers email-checking routines without maintaining a hard-coded list of services. Understanding how module functions are extracted in Holehe reveals the elegant architecture that powers its extensible plugin system. This process relies on Python’s `pkgutil` and `importlib` to transform directory structures into executable check functions at runtime.

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

Holehe begins module extraction by walking the entire `holehe.modules` package tree. The `import_submodules()` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 37-47) uses `pkgutil.walk_packages()` to discover every module nested within the package directories.

This utility accepts a package name or object and returns a dictionary mapping full module names to imported module objects. When `recursive=True`, it descends into subpackages automatically, ensuring that deeply nested service modules—such as `holehe.modules.social_media.facebook`—are discovered regardless of their depth in the hierarchy.

```python

# holehe/core.py  (lines 37-47)

def import_submodules(package, recursive=True):
    """Get all the holehe submodules"""
    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

```

## Step 2: Converting Modules to Callable Functions with `get_functions()`

Once modules are loaded, `get_functions()` transforms these objects into executable check routines. Located immediately after the import utility in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 50-63), this function filters the module dictionary and extracts specific functions by name.

The filtering logic targets modules with more than three dot-separated components (i.e., `holehe.modules.<category>.<service>`). For each qualifying module, it retrieves the function whose name matches the final segment of the module path from `module.__dict__[site]`.

Additionally, if the user invokes Holehe with the `--no-password-recovery` flag, `get_functions()` explicitly excludes Adobe, Mail.ru, Odnoklassniki, and Samsung checks by inspecting their string representations.

```python

# holehe/core.py  (lines 50-63)

def get_functions(modules, args=None):
    """Transform the modules objects to functions"""
    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:
                if "adobe" not in str(modu.__dict__[site]) \
                   and "mail_ru" not in str(modu.__dict__[site]) \
                   and "odnoklassniki" not in str(modu.__dict__[site]) \
                   and "samsung" not in str(modu.__dict__[site]):
                    websites.append(modu.__dict__[site])
            else:
                websites.append(modu.__dict__[site])
    return websites

```

## Runtime Execution Flow

At startup, the main orchestration routine chains these two utilities to build the complete list of checks. The process executes in two sequential calls:

```python
modules = import_submodules('holehe.modules')
functions = get_functions(modules, args)

```

Each function returned in the list represents an asynchronous check for a specific web service. These functions are subsequently passed to `launch_module()` for concurrent execution against the target email address.

## Practical Implementation Example

You can leverage these same utilities to manually load and execute specific checks. The following example demonstrates dynamic loading of all available modules and manual invocation of a check function:

```python

# Example: Dynamically load all checking functions

from holehe.core import import_submodules, get_functions

# Load every submodule under holehe.modules

all_modules = import_submodules('holehe.modules')

# Convert them into callable async functions

check_functions = get_functions(all_modules)   # → list of async functions

# Run a single check (e.g., Facebook) manually

import asyncio, httpx
async def run_one():
    async with httpx.AsyncClient() as client:
        await check_functions[0]('target@example.com', client, [])
asyncio.run(run_one())

```

## Key Files and Architecture

The module extraction system relies on specific files within the megadose/holehe repository:

- **[`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)**: Implements `import_submodules()`, `get_functions()`, and the main orchestration logic.
- **[`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py)**: Package marker enabling `pkgutil.walk_packages()` to discover subpackages.
- **Service modules** (e.g., [`holehe/modules/social_media/facebook.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/facebook.py)): Individual files containing the async functions extracted by this process.

This architecture ensures that adding a new email-checking service requires only creating a new Python module in the appropriate subdirectory. The dynamic discovery system automatically incorporates new modules without registry updates or configuration changes.

## Summary

- **Dynamic discovery**: `import_submodules()` uses `pkgutil.walk_packages()` to recursively load all modules under `holehe.modules`.
- **Function extraction**: `get_functions()` filters modules by depth and extracts callables matching the module's final name segment.
- **Optional filtering**: The `--no-password-recovery` flag excludes specific services (Adobe, Mail.ru, Odnoklassniki, Samsung) by string inspection.
- **Zero-configuration extensibility**: New services added to the modules directory are automatically available without code changes to the loader.

## Frequently Asked Questions

### How does Holehe discover new modules without hard-coding them?

Holehe discovers modules dynamically using `pkgutil.walk_packages()` in the `import_submodules()` function. This walks the `holehe.modules` package directory at runtime, importing every Python file it finds and returning them as module objects, eliminating the need for a static registry.

### What is the purpose of the --no-password-recovery flag in get_functions()?

The `--no-password-recovery` flag filters out services that specifically perform password recovery checks. When `args.nopasswordrecovery` is true, `get_functions()` excludes Adobe, Mail.ru, Odnoklassniki, and Samsung by checking if these strings appear in the function's representation, preventing potentially disruptive account recovery attempts.

### Why does get_functions() check for more than three components in the module name?

The depth check (`len(module.split(".")) > 3`) ensures that only actual service modules are processed, not category packages. Valid service modules follow the pattern `holehe.modules.<category>.<service>` (four components), while parent packages have fewer segments. This distinction prevents attempts to extract functions from container directories.

### Which file contains the core logic for module extraction in Holehe?

The core extraction logic resides in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). This file defines both `import_submodules()` (lines 37-47) for recursive module loading and `get_functions()` (lines 50-63) for converting loaded modules into callable check functions.