How to Dynamically Load All Holehe Modules in Python
TLDR: Holehe discovers and loads every checker module at runtime by walking the holehe.modules package using pkgutil.walk_packages and importlib.import_module.
The open-source OSINT tool Holehe (megadose/holehe) searches for accounts linked to email addresses across hundreds of platforms. To maintain its extensible plugin architecture without hard-coding service names or import statements, the project dynamically loads all checker modules at runtime from the holehe/modules directory. This design allows developers to add new service checkers simply by dropping a Python file into the appropriate sub-package without modifying the core loading logic.
How Holehe Discovers Modules Dynamically
Instead of static imports, Holehe implements a dynamic module discovery system in holehe/core.py. The mechanism relies on Python's built-in pkgutil and importlib libraries to traverse the package structure, import every module found, and extract the specific checking functions used to verify email existence on various platforms.
Step 1: Recursively Import Submodules with import_submodules()
In holehe/core.py, lines 37-47, the import_submodules() function implements the core discovery logic.
import importlib
import pkgutil
def import_submodules(package, recursive=True):
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
How it works:
- Package traversal:
pkgutil.walk_packages(package.__path__)iterates over every file in the package directory (e.g.,amazon.py,spotify.py). - Dynamic import: Each discovered module is imported using
importlib.import_module(full_name), wherefull_nameis the fully-qualified name (e.g.,holehe.modules.shopping.amazon). - Recursive loading: When
recursive=Trueand the item is a package (is_pkg), the function calls itself to handle nested subdirectories.
The function returns a dictionary mapping fully-qualified module names to their imported module objects.
Step 2: Extract Checker Functions with get_functions()
After importing the modules, Holehe must extract the actual callable functions that perform email checks. The get_functions() method in holehe/core.py, lines 50-63, handles this extraction.
def get_functions(modules, args=None):
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:
# filter out modules that require password recovery
if "adobe" not in str(modu.__dict__[site]) ...:
websites.append(modu.__dict__[site])
else:
websites.append(modu.__dict__[site])
return websites
Key details:
- Filtering logic: The condition
len(module.split(".")) > 3skips the base package itself, targeting only actual checker modules nested within sub-packages. - Function retrieval: The code accesses the checking function via
modu.__dict__[site], wheresiteis the final component of the module path (e.g.,"amazon"). - Optional filtering: If
args.nopasswordrecoveryis specified, the loader excludes modules requiring password recovery by checking string representations.
The function returns a list of callable objects ready to be invoked with email addresses.
Putting It Together: Loading and Executing Modules
In the main execution flow of holehe/core.py, lines 105-108, these utilities combine to produce a runnable list of modules.
# https://github.com/megadose/holehe/blob/master/holehe/core.py#L105-L108
modules = import_submodules("holehe.modules")
websites = get_functions(modules, args)
Execution flow:
- Discovery:
import_submodules("holehe.modules")returns a dictionary of all modules (e.g.,{"holehe.modules.shopping.amazon": <module>, ...}). - Extraction:
get_functions(modules, args)processes this dictionary to extract the specific checking functions. - Invocation: Each extracted function in
websitesis scheduled for concurrent execution against the target email address.
Practical Implementation Examples
Example 1: Listing All Available Modules
This standalone script demonstrates how to dynamically load all Holehe modules and print their names without executing checks.
import importlib
import pkgutil
# Load all sub-modules of holehe.modules
def import_submodules(package, recursive=True):
if isinstance(package, str):
package = importlib.import_module(package)
results = {}
for _, 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
def get_functions(modules):
funcs = []
for fqname, mod in modules.items():
if len(fqname.split(".")) > 3: # skip the package itself
func_name = fqname.split(".")[-1] # e.g. "amazon"
funcs.append((func_name, getattr(mod, func_name)))
return funcs
if __name__ == "__main__":
all_modules = import_submodules("holehe.modules")
checks = get_functions(all_modules)
for name, fn in checks:
print(f"Loaded module: {name} -> {fn}")
Example 2: Running Checks with Holehe's Helpers
This example uses Holehe's own utilities to run a complete check on a single email address.
import httpx
import trio
from holehe.core import import_submodules, get_functions, launch_module
async def run_one(email):
client = httpx.AsyncClient()
modules = import_submodules("holehe.modules")
checks = get_functions(modules) # list of checker functions
results = []
async with trio.open_nursery() as nursery:
for check in checks:
# each `check` is a callable that expects (email, client, out)
nursery.start_soon(launch_module, check[1], email, client, results)
await client.aclose()
return results
# Example usage
if __name__ == "__main__":
email = "example@example.com"
trio.run(run_one, email)
Both snippets illustrate how to dynamically discover, import, and invoke every Holehe module without hard-coding module names or maintaining static import lists.
Key Source Files and Architecture
| File | Role |
|---|---|
holehe/core.py |
Implements import_submodules() for discovery and get_functions() for callable extraction. |
holehe/modules/__init__.py |
Package marker enabling pkgutil.walk_packages traversal. |
holehe/modules/*/*.py |
Individual checker modules (e.g., shopping/amazon.py) containing the functions discovered at runtime. |
This architecture creates a true plugin system where adding holehe/modules/social/newservice.py automatically makes the service available to Holehe scans without touching the loader code.
Summary
- Dynamic discovery: Holehe uses
pkgutil.walk_packages()inholehe/core.pyto find all Python files underholehe.modulesat runtime. - Programmatic import: The
import_submodules()function leveragesimportlib.import_module()to load discovered modules into memory without hard-coded import statements. - Function extraction:
get_functions()filters the loaded modules and extracts the specific checking callables by inspecting module dictionaries. - Zero-configuration extension: New service checkers become automatically available simply by placing a Python file in the
holehe/modulesdirectory structure.
Frequently Asked Questions
What is the advantage of dynamically loading Holehe modules?
Dynamic loading eliminates the need to maintain a static list of supported services in the core code. According to the megadose/holehe source code, this allows the tool to support hundreds of platforms while keeping the codebase maintainable—developers add new checkers by creating files rather than modifying import blocks.
How does Holehe handle nested package structures?
The import_submodules() function accepts a recursive=True parameter (default enabled) that calls itself recursively when pkgutil.walk_packages identifies a subdirectory (is_pkg=True). This mechanism discovers modules at any depth within holehe/modules/ (e.g., holehe/modules/shopping/amazon.py).
What is the difference between import_submodules() and get_functions()?
import_submodules(package) returns a dictionary mapping fully-qualified module names to imported module objects (e.g., {"holehe.modules.music.spotify": <module>}). get_functions(modules) consumes this dictionary and returns a list of the actual callable functions defined within those modules (e.g., [<function amazon>, <function spotify>]).
Can I filter which modules are loaded at runtime?
Yes. The get_functions() signature accepts an optional args parameter. When provided with args.nopasswordrecovery=True, the function filters out modules requiring password recovery by inspecting string representations of the module contents, as implemented in holehe/core.py lines 57-60.
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 →