# How to Embed Holehe in a Python Application and Call Individual Modules Like `snapchat`

> Embed holehe in your Python app by importing module functions directly from holehe.modules. Call individual modules like snapchat with httpx.AsyncClient and bypass the CLI.

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

---

**You can embed holehe in a Python application by importing async module functions directly from `holehe.modules` and calling them with an `httpx.AsyncClient` and a mutable results list, bypassing the CLI entirely.**

Holehe is an email reconnaissance tool that checks whether an address exists on hundreds of platforms. While the command-line interface runs all checks by default, you can embed specific modules—such as `snapchat`—into your own Python codebase for targeted, asynchronous lookups.

## How Holehe's Module System Works

The `megadose/holehe` codebase is architected around **dynamic module discovery** and **uniform async function signatures**. Understanding this structure lets you call individual services programmatically.

### Dynamic Loading Architecture

Holehe discovers services through two key functions in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py):

- **`import_submodules()`** (lines 37–48): Uses `pkgutil.walk_packages` to traverse `holehe.modules` and returns a dictionary of all loaded submodules.
- **`get_functions()`** (lines 50–60): Extracts callable service functions from those modules, producing the list that the CLI iterates over.

Each service module contains an async function matching this signature:

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

```

The function performs HTTP requests and appends a standardized result dictionary to `out`. No return value—the result is always appended to the mutable list.

## Embedding Individual Modules

### Import and Call a Single Service

The simplest approach imports a specific module—like `snapchat`—and invokes it directly:

```python
import asyncio
import httpx
from holehe.modules.social_media.snapchat import snapchat

async def check_snapchat(email: str):
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        await snapchat(email, client, out)
        return out[0]

result = asyncio.run(check_snapchat("example@example.com"))
print(result)

```

**Key requirements:**
- Import the concrete module from its path (e.g., `holehe.modules.social_media.snapchat`).
- Pass an `httpx.AsyncClient`—synchronous clients will fail.
- Provide a list (`out`) for the result; the module always appends exactly one dictionary.

### Constraining Multiple Services with Shared Client

Reuse the same `httpx.AsyncClient` across multiple modules for efficient connection pooling:

```python
import asyncio
import httpx
from holehe.modules.social_media.snapchat import snapchat
from holehe.modules.social_media.instagram import instagram
from holehe.modules.social_media.twitter import twitter

async def check_multiple(email: str, targets: set):
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        
        # Map target names to imported functions

        module_map = {
            "snapchat": snapchat,
            "instagram": instagram,
            "twitter": twitter,
        }
        
        selected = [module_map[t] for t in targets if t in module_map]
        await asyncio.gather(*(
            fn(email, client, out) for fn in selected
        ))
        
        return {r["name"]: r for r in out}

results = asyncio.run(check_multiple(
    "example@example.com",
    {"snapchat", "instagram"}
))

```

This pattern enables **concurrent execution** with `asyncio.gather`, reducing total latency versus sequential calls.

## Bridging to Synchronous Code

### Wrapper for Blocking Contexts

When your application isn't async-native, use `asyncio.run()` as a bridge:

```python
import asyncio
import httpx
from holehe.modules.social_media.snapchat import snapchat

def check_snapchat_sync(email: str, timeout: float = 10.0):
    """Synchronous wrapper for the snapchat module."""
    async def _run():
        async with httpx.AsyncClient(timeout=timeout) as client:
            out = []
            await snapchat(email, client, out)
            return out[0] if out else None
    
    return asyncio.run(_run())

# Use from ordinary Python code

result = check_snapchat_sync("example@example.com")
print(f"Snapchat: {result.get('exists') if result else 'error'}")

```

**Performance note:** Creating a new event loop per call adds overhead. For high-throughput scenarios, consider restructuring to async-native code or using an executor pool.

## Leveraging Holehe's Discovery for Filtered Scans

If you want holehe's automatic module discovery but need to restrict execution, combine `import_submodules` and `get_functions`:

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

async def filtered_scan(email: str, allowlist: set[str]):
    """
    Run only specified modules using holehe's built-in discovery.
    """
    modules = import_submodules("holehe.modules")
    all_functions = get_functions(modules)
    
    selected = [
        fn for fn in all_functions 
        if fn.__name__ in allowlist
    ]
    
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        await asyncio.gather(*(
            fn(email, client, out) for fn in selected
        ))
        return out

# Check only snapchat through discovery

results = asyncio.run(filtered_scan(
    "example@example.com",
    {"snapchat"}
))

```

This approach future-proofs your code: new modules appear automatically, but you control which execute.

## Understanding the Result Format

Every holehe module returns a dictionary with consistent fields:

| Field | Type | Description |
|-------|------|-------------|
| `name` | `str` | Service identifier (e.g., `"snapchat"`) |
| `domain` | `str` | Service domain |
| `method` | `str` | HTTP method used for verification |
| `frequent_rate_limit` | `bool` | Whether the service aggressively rate-limits |
| `rateLimit` | `bool` | Whether this call hit a rate limit |
| `exists` | `bool` | Whether the email is registered |
| `emailrecovery` | `str \| None` | Partial recovery email if exposed |
| `phoneNumber` | `str \| None` | Partial phone if exposed |
| `others` | `dict \| None` | Additional metadata |

Access fields directly: `result["exists"]` indicates registration status.

## Source Code References

Critical files in `megadose/holehe` for embedded usage:

- **[`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)** — Dynamic loading (`import_submodules`, `get_functions`), CLI argument parsing, and result formatting.
- **[`holehe/modules/social_media/snapchat.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/snapchat.py)** — Concrete implementation of the Snapchat check, showing the expected async signature.
- **[`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py)** — User-agent rotation used by modules including `snapchat`.

## Summary

- **Import directly** from `holehe.modules.<category>.<service>` to bypass CLI overhead.
- **Use `httpx.AsyncClient`**—modules expect async HTTP clients exclusively.
- **Pass a mutable list** as `out`; results are appended, not returned.
- **Share clients** across multiple module calls for connection efficiency.
- **Bridge with `asyncio.run()`** when embedding in synchronous codebases.
- **Reuse discovery logic** via `import_submodules` and `get_functions` for dynamic but filtered execution.

## Frequently Asked Questions

### Can I use `requests` instead of `httpx` with holehe modules?

No. All holehe modules are implemented as `async def` functions expecting `httpx.AsyncClient`. The source code in files like [`holehe/modules/social_media/snapchat.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/snapchat.py) uses `client.get()` and `client.post()` methods specific to httpx. To use requests, you would need to rewrite the module or run it in a separate process.

### Why does the module append to a list instead of returning a value?

This design enables **concurrent execution** with `asyncio.gather`. Since `gather` collects return values in order, but modules run at unpredictable speeds, the shared `out` list allows thread-safe result collection without complex synchronization. The CLI in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) relies on this same pattern.

### How do I handle rate limits when embedding holehe?

Check the `rateLimit` and `frequent_rate_limit` fields in the result dictionary. Services marked `frequent_rate_limit=True` (visible in each module's source) require careful throttling. Implement delays between calls or use `asyncio.Semaphore` to limit concurrency: `semaphore = asyncio.Semaphore(5)` then `async with semaphore:` around each module call.

### Can I modify holehe modules to add proxy support?

Yes. The `httpx.AsyncClient` accepts a `proxies` parameter. When embedding, instantiate your client with `httpx.AsyncClient(proxies={"https://": "http://proxy:8080"})` and pass it to the module. The module will use your configured client for all requests without source modification.