How holehe Dynamically Discovers and Loads Modules at Runtime
holehe dynamically discovers its service checkers by recursively scanning the holehe.modules package using pkgutil.walk_packages and importlib, then converts the imported modules into callable functions via get_functions.
The holehe email exposure testing tool supports hundreds of services without hard-coding each integration. Instead, it leverages Python's dynamic import capabilities to load modules at runtime. This architecture allows developers to add new service checkers simply by placing Python files in the appropriate directory, eliminating the need for manual registration.
The Core Discovery Mechanism
The dynamic loading process centers on the import_submodules function defined in holehe/core.py. This function serves as the entry point for discovering all available service modules under the holehe.modules namespace.
Recursive Package Scanning with pkgutil
The function utilizes pkgutil.walk_packages to iterate over every directory and Python file within the target package's __path__. This standard library utility yields a tuple containing the loader, module name, and a boolean indicating whether the entry is a subpackage.
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
Dynamic Module Import with importlib
For each entry discovered by walk_packages, the function constructs the fully qualified module name (e.g., holehe.modules.social_media.twitter) and imports it using importlib.import_module. When recursive=True and the entry is a package (directory), the function calls itself to descend into subdirectories, ensuring nested modules like holehe.modules.shopping.amazon are loaded alongside top-level modules.
Converting Modules to Callable Checkers
Discovery alone does not make the modules usable. Immediately after calling import_submodules, the code passes the results dictionary to get_functions, which filters and extracts the actual checker functions.
The transformation logic inspects each imported module and retains only objects whose dotted name depth exceeds three levels (e.g., holehe.modules.social_media.twitter.twitter). This filtering ensures only the specific service checkers are retained, excluding intermediate package markers.
modules = import_submodules("holehe.modules")
websites = get_functions(modules, args)
The resulting websites list contains callable functions that accept an email address, an HTTP client, and an output list—the standard signature for all holehe service checkers.
Practical Usage Examples
Listing All Discovered Modules
You can programmatically inspect which modules holehe has loaded by invoking import_submodules directly:
from holehe.core import import_submodules
all_modules = import_submodules("holehe.modules")
print(f"Loaded {len(all_modules)} modules")
print(list(all_modules.keys())[:5])
# Output includes: ['holehe.modules.social_media.twitter', ...]
Manually Executing a Specific Checker
Once imported, individual checkers can be accessed by their fully qualified path and executed with an async HTTP client:
import httpx
import trio
from holehe.core import import_submodules
async def check_twitter(email):
modules = import_submodules("holehe.modules")
twitter_func = modules['holehe.modules.social_media.twitter'].twitter
async with httpx.AsyncClient(timeout=10) as client:
out = []
await twitter_func(email, client, out)
return out
results = trio.run(check_twitter, "test@example.com")
Adding a Custom Service Module
The dynamic architecture enables zero-configuration extension. To add support for a new service:
- Create a new Python file at
holehe/modules/category/mynewservice.py - Define an async function matching the signature:
async def mynewservice(email, client, out): - Restart holehe—the
import_submodulescall will automatically discover and load the new module on the next execution cycle
No changes to core.py or any central registry are required.
Key Source Files
The module discovery system is implemented across three primary locations:
-
[
holehe/core.py](https://github.com/megadose/holehe/blob/master/holehe/core.py) – Contains theimport_submodulesfunction andget_functionslogic that orchestrates runtime loading. -
[
holehe/modules/__init__.py](https://github.com/megadose/holehe/blob/master/holehe/modules/__init__.py) – Marks themodulesdirectory as a Python package, establishing the root namespace for all service checkers. -
[
holehe/modules/social_media/twitter.py](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/twitter.py) – Representative example of a service module that gets dynamically imported. Each similar file underholehe/modules/follows this pattern.
Summary
- Dynamic Discovery: holehe uses
import_submodulesto scan theholehe.modulespackage tree at runtime. - Standard Library Tools: The implementation relies on
pkgutil.walk_packagesfor directory traversal andimportlib.import_modulefor actual imports. - Recursive Loading: Subpackages are handled recursively, ensuring all nested service categories are discovered regardless of directory depth.
- Function Extraction:
get_functionstransforms the raw module dictionary into a list of callable checker functions by filtering for specific naming patterns. - Zero-Config Extensibility: Adding new services requires only placing a Python file in the correct directory; the discovery mechanism handles registration automatically.
Frequently Asked Questions
How does holehe find new modules without updating a configuration file?
holehe treats the holehe/modules/ directory as a live package tree. The import_submodules function scans this tree on every execution using pkgutil.walk_packages, meaning any new .py file added to the directory structure is automatically imported on the next run without requiring manual registration.
What Python standard library modules does holehe use for dynamic loading?
The discovery mechanism relies on two specific standard library modules: pkgutil (specifically walk_packages) to iterate over package contents, and importlib (specifically import_module) to programmatically import discovered modules by their fully qualified names.
Can I disable recursive module loading in holehe?
The import_submodules function accepts a recursive parameter that defaults to True. Setting this to False would restrict loading to only the immediate children of the specified package, though the standard holehe execution always uses the recursive default to ensure all subcategories (like social_media, shopping, etc.) are loaded.
Why does holehe use dotted name depth filtering in get_functions?
The get_functions utility filters imported objects to include only those with a name depth greater than three (e.g., holehe.modules.category.service). This ensures that intermediate package directories and the root __init__.py files are excluded from the final callable list, retaining only the actual service checker functions that implement the email verification logic.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →