# How to Import and Use Holehe Modules in Custom Python Scripts

> Learn to import and use Holehe modules in custom Python scripts. Dynamically load service probes and extract async functions for concurrent execution with Trio and httpx.

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

---

**You can import and use Holehe modules by leveraging two helper utilities from `holehe.core` — `import_submodules()` to dynamically load all service probes, and `get_functions()` to extract the callable async functions — then run them concurrently with Trio and an `httpx.AsyncClient`.**

Holehe is an OSINT tool for checking email address registrations across hundreds of online services. While it ships with a command-line interface, its modular architecture makes it easy to embed directly into your own Python applications. According to the megadose/holehe source code, each service probe lives under `holehe.modules` and follows a consistent async signature, enabling programmatic reuse without CLI overhead.

## Holehe's Module Architecture

The repository organizes email verification logic into independent **modules**, each targeting a specific platform (Twitter, Instagram, Amazon, etc.). Understanding this structure is key to importing and using Holehe modules effectively.

### Module Location and Signature

All modules reside under the `holehe.modules` package. Each exposes a single asynchronous function with this signature:

```python
async def <service_name>(email: str, client: httpx.AsyncClient, out: list) -> None

```

The function receives an email string, an HTTP client instance, and a shared results list. It appends discovery results directly to `out` rather than returning values.

### Core Helper Utilities

In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), two functions handle module discovery and extraction:

| Utility | Purpose | Source Location |
|---------|---------|-----------------|
| `import_submodules` | Dynamically imports every submodule under a package path using `pkgutil.walk_packages` | [`holehe/core.py:37-48`](https://github.com/megadose/holehe/blob/master/holehe/core.py#L37-L48) |
| `get_functions` | Converts imported modules into a flat list of callable probe functions; respects `--no-password-recovery` flags | [`holehe/core.py:50-64`](https://github.com/megadose/holehe/blob/master/holehe/core.py#L50-L64) |

These utilities separate **discovery** from **execution**, making the codebase extensible and testable.

## How the CLI Uses These Modules

The `maincore` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) demonstrates the standard workflow for importing and using Holehe modules:

1. Parse command-line arguments
2. Call `import_submodules("holehe.modules")` to load all service modules
3. Call `get_functions(modules, args)` to extract probe functions
4. Create an `httpx.AsyncClient` with user-specified timeout
5. Launch probes concurrently via Trio's `nursery.start_soon()`
6. Collect results from the shared `out` list

You can replicate this exact pattern in custom scripts, substituting your own logic for steps 1 and 6.

## Example 1: Basic Email Check with All Modules

This script mirrors the CLI workflow but gives you direct access to raw results:

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

async def check_email(email: str):
    # 1. Import every module under holehe.modules

    modules = import_submodules("holehe.modules")
    
    # 2. Extract probe functions (default behavior, no filtering)

    functions = get_functions(modules)
    
    # 3. Create async HTTP client

    client = httpx.AsyncClient(timeout=10)
    
    # 4. Prepare results collection list

    results = []
    
    # 5. Launch all probes concurrently with Trio

    async with trio.open_nursery() as nursery:
        for probe in functions:
            nursery.start_soon(probe, email, client, results)
    
    await client.aclose()
    return results

if __name__ == "__main__":
    email = "target@example.com"
    data = trio.run(check_email, email)
    
    for entry in data:
        print(f"Service: {entry.get('name')}, Exists: {entry.get('exists')}")

```

**Key points:** The `get_functions()` call returns all available probes. Each probe runs independently; failures in one don't affect others due to `launch_module`'s internal try/except wrapping in [`core.py`](https://github.com/megadose/holehe/blob/main/core.py).

## Example 2: Selective Module Usage

For targeted checks, bypass `get_functions()` and import specific modules directly:

```python
import httpx
import trio
from holehe.core import import_submodules

async def check_twitter_instagram(email: str):
    # Import full module tree

    modules = import_submodules("holehe.modules")
    
    # Select specific modules by fully-qualified name

    target_modules = {
        "holehe.modules.social_media.twitter",
        "holehe.modules.social_media.instagram"
    }
    
    # Extract probe functions manually

    selected_funcs = [
        modules[name].__dict__[name.split(".")[-1]]
        for name in target_modules
        if name in modules
    ]
    
    client = httpx.AsyncClient(timeout=8)
    out = []
    
    async with trio.open_nursery() as nursery:
        for fn in selected_funcs:
            nursery.start_soon(fn, email, client, out)
    
    await client.aclose()
    return out

if __name__ == "__main__":
    results = trio.run(check_twitter_instagram, "user@domain.com")
    print(results)

```

**Key points:** Direct module access reduces overhead when you only need specific platforms. The `__dict__` lookup uses the final segment of the module path as the function name.

## Example 3: Integration with Existing Async Workflows

Embed Holehe checks into larger Trio applications using queues or other communication patterns:

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

async def holehe_background_task(email: str, result_queue: trio.MemorySendChannel):
    """Run Holehe probes and send results through a Trio channel."""
    modules = import_submodules("holehe.modules")
    probes = get_functions(modules)
    
    async with httpx.AsyncClient(timeout=12) as client:
        results = []
        async with trio.open_nursery() as nursery:
            for probe in probes:
                nursery.start_soon(probe, email, client, results)
    
    # Send completed results to parent task

    await result_queue.send(results)

async def main_application():
    """Example of Holehe integrated into a larger program."""
    send_channel, receive_channel = trio.open_memory_channel(0)
    
    async with trio.open_nursery() as nursery:
        # Start Holehe check in background

        nursery.start_soon(
            holehe_background_task, 
            "alice@example.org", 
            send_channel
        )
        
        # Concurrently run other tasks...

        # Then collect Holehe results when ready

        report = await receive_channel.receive()
        
        print(f"Found {len(report)} services with registrations")
        for item in report:
            if item.get("exists"):
                print(f"  - {item['name']}: account exists")

if __name__ == "__main__":
    trio.run(main_application)

```

**Key points:** The `results` list behaves like standard Python data — serializable, storable, and transformable. Use Trio channels, `asyncio` queues, or direct return values depending on your architecture.

## Key Source Files for Reference

| File | Role |
|------|------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Central orchestration: `import_submodules()`, `get_functions()`, `maincore()` |
| [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) | Package marker enabling module discovery |
| [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) | Example probe showing expected function signature |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | Random User-Agent generator used by probes |
| [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) | Trio progress bar implementation for CLI |

## Summary

- **Import modules dynamically** with `import_submodules("holehe.modules")` from [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)
- **Extract probe functions** using `get_functions()` for automatic discovery, or reference modules directly by their fully-qualified names
- **Execute probes concurrently** via Trio's nursery pattern with a shared `httpx.AsyncClient`
- **Handle results flexibly** — the `out` list receives standard dictionaries with `name`, `exists`, and other service-specific fields
- **Reuse any probe individually** by importing its module and calling its async function directly

## Frequently Asked Questions

### Can I use Holehe modules without the Trio concurrency library?

No. According to the megadose/holehe source code, all probe functions are designed for Trio and use `trio.open_nursery()` for concurrent execution. The probes themselves are standard `async def` functions, but the execution pattern expects Trio. Using `asyncio` would require wrapping or rewriting the concurrency logic.

### How do I filter out password-recovery probes when importing modules?

Pass a configuration object to `get_functions()` with the `nopasswordrecovery` attribute set. The function signature is `get_functions(modules, args=None)`, and when `args` has `nopasswordrecovery=True`, it filters out probes that only check password recovery endpoints. Check `holehe/core.py:50-64` for the exact implementation.

### What data structure does each probe return to the results list?

Each probe appends a dictionary with consistent keys: `name` (service name), `domain` (service domain), `method` (detection method), `frequent_rate_limit` (boolean), `rateLimit` (boolean), `exists` (boolean), and optionally `emailrecovery`, `phoneNumber`, or `others` with leaked account metadata. The exact fields vary by service.

### Can I modify the HTTP timeout for individual probes?

No timeouts are set per-probe. Create the `httpx.AsyncClient` with your desired timeout before launching probes — this single client instance is shared across all concurrent checks. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the CLI passes `--timeout` directly to `httpx.AsyncClient(timeout=...)`.