Python modulefinder: Discover Imports

Published on: August 27, 2026
Reading time: 8 minutes
Colorful stacked shipping containers at Hamburg port, showcasing global trade and logistics.

The modulefinder module analyzes a Python script and attempts to discover modules imported directly and transitively. It inspects code objects, follows import instructions, searches configured paths, and builds an inventory that can support packagers, dependency reports, build tools, audits, and missing-import diagnostics.

The analysis is not complete. Python allows dynamic imports, plugin discovery, conditional execution, runtime changes to sys.path, and module names built from configuration. Treat the result as a static approximation based on available code, not proof of everything an application will load.

Create a ModuleFinder

The main class is modulefinder.ModuleFinder.

from modulefinder import ModuleFinder

finder = ModuleFinder()
finder.run_script("app.py")

After analysis, the object contains discovered modules, missing references, and data for reporting.

List discovered modules

The modules attribute maps module names to analyzed module objects.

for name in sorted(finder.modules):
    module = finder.modules[name]
    print(name, module.__file__)

Not every module has a normal file. Builtins, extensions, namespaces, and special loaders can use different representations.

Use report

report() prints a human-readable summary.

finder.report()

It is convenient during manual investigation. Automation should iterate over attributes and generate JSON or another structured format.

Direct and transitive imports

When a script is analyzed, modulefinder follows imports from discovered modules. The inventory therefore includes transitive dependencies, not only lines in the entry script.

One small-looking import can bring a large dependency tree.

Configure the search path

The constructor accepts a path representing directories used to locate modules.

import sys
from modulefinder import ModuleFinder

finder = ModuleFinder(path=["src", *sys.path])
finder.run_script("src/app.py")

Use the same environment and layout as runtime. A different search path can select a different package with the same name.

Virtual environments

Run analysis inside the virtual environment containing the real dependencies. Mixing global paths and a project venv produces misleading results.

Record sys.executable, Python version, and the paths used in the report.

Projects using src layout

Packages stored under src/ need that root in the path or should be installed into the environment.

Analyzing an installed distribution often reproduces namespaces, metadata, and import behavior more accurately.

Relative imports

Relative imports depend on package context. Running an internal module as a standalone script can change their meaning or fail.

Analyze the real entry point and correct package structure whenever possible.

Conditional imports

An import inside a condition may be discovered even when the branch is not used on the current platform.

if sys.platform == "win32":
    import winreg

The report may include optional dependencies for other systems. Classify them instead of deleting them blindly.

Optional imports with try/except

Libraries often attempt an accelerator and fall back to pure Python.

try:
    import accelerator
except ImportError:
    import python_impl

An analysis may list both or mark one missing. That does not automatically mean the application is broken.

Dynamic imports

importlib.import_module(name) can receive a value known only at runtime.

backend = importlib.import_module(config["backend"])

Static tools may not determine the module name. Packagers often require explicit hidden-import declarations.

The __import__ builtin

__import__() can also load calculated names.

Review configuration files, registries, and plugin conventions in addition to the static report.

Plugin systems

Plugins may be discovered through entry points, directories, databases, or configuration, with no direct import in the main source.

Combine modulefinder with importlib.metadata.entry_points(), covered later in this batch.

Namespace packages

A namespace package can span several directories and distributions. The finder must run in the complete installed environment.

Do not assume that one __file__ represents the entire namespace.

Native extensions

Compiled modules can appear as .so, .pyd, or platform equivalents.

Modulefinder can identify the extension module, but it does not automatically enumerate native libraries loaded by it, such as shared libraries and drivers.

Built-in modules

Modules built into the interpreter do not have ordinary Python source files.

A packager should classify them as supplied by the runtime rather than trying to copy a source path.

Frozen modules

Interpreters and frozen executables may bundle modules internally. Their representation varies by runtime and packager.

Test the final artifact because development analysis may not match frozen behavior.

badmodules

The finder tracks module names that could not be found in particular import contexts.

A useful report shows which modules attempted each import. The same missing name may be optional for one package and required for another.

any_missing

Convenience methods can return names considered missing.

missing = finder.any_missing()
print(missing)

Review every name before failing a build, especially platform-specific and optional imports.

Possible versus definite missing modules

Supported versions may provide classifications that separate definite and possible missing imports.

This helps prioritize investigation, but the distinction remains heuristic.

Exclude modules

The constructor accepts an excludes list.

finder = ModuleFinder(
    excludes=["tkinter", "tests"],
)

An exclusion tells the analyzer to ignore a module. It does not prove the application will never request it.

Document exclusion policy

Explain whether each excluded module belongs to another platform, a disabled feature, development tools, or an alternative implementation.

Add runtime tests confirming that the excluded path cannot be reached in that artifact.

replace_paths

replace_paths can replace path prefixes stored in module filenames.

This makes reports more reproducible and prevents temporary CI directories from appearing in published artifacts.

Path privacy

Dependency reports can reveal usernames, home directories, runner structure, and internal package locations.

Normalize paths before sharing reports and restrict access to detailed inventories.

Debug output

The debug argument increases internal logging.

finder = ModuleFinder(debug=2)

Use it during local investigation. Very verbose CI logs become difficult to search and may expose unnecessary paths.

run_script and load_file

run_script() analyzes an executable script, while related APIs can load files in other contexts.

Choose the operation that correctly represents whether the target is an entry point or a package module.

Bytecode-based analysis

Modulefinder examines code objects and import-related instructions, linking behavior to the Python compiler and bytecode version.

Run it with the same Python version as the target runtime. Compiler changes can affect analysis details.

Inspect import instructions with dis

When an import is classified unexpectedly, the dis module can show generated instructions.

See Python dis.

Unreachable code

The analysis can find imports in functions never called, dead branches, compatibility code, and optional features.

It describes possible syntactic dependencies, not runtime frequency or reachability.

Combine static and dynamic observation

Record imported modules during representative tests and compare them with the static inventory.

Dynamic tests miss unexecuted features, while static analysis misses calculated names. Together they reduce blind spots.

Dependency audits

The inventory can reveal unexpected transitive modules, but it does not automatically provide distribution version, license, or vulnerability information.

Map modules to distributions with importlib.metadata.packages_distributions().

Standard library versus third party

Classify modules by origin: built-in, standard library, project code, and site-packages.

Use sysconfig to identify standard-library locations. See Python sysconfig.

Shadowed modules

Several directories can provide the same module name. Search order determines which one is selected.

Record the selected file and detect shadowing. A local json.py can hide the standard library module.

ZIP imports

Dependencies can come from ZIP files on sys.path. Confirm that the finder and downstream packager support the loader.

Do not assume every module corresponds to an ordinary path.

Several entry points

One project can have a CLI, web server, worker, and scheduled task with different dependency trees.

Analyze each entry point and merge results while preserving which target requires each module.

Optional build profiles

Create separate profiles for minimal, database-specific, GUI, cloud, or data-science features.

A single global list can pull large dependencies into artifacts that never use them.

Structured output

Convert the result into a stable representation.

result = {
    name: {"file": module.__file__}
    for name, module in finder.modules.items()
}

Sort keys for reproducible diffs.

Dependency graphs

The module mapping does not always expose every import edge in a ready-to-visualize form.

Instrument the analysis or combine it with AST processing to build explicit edges. Use Python graphlib when topological ordering is appropriate.

Import cycles

Python allows some cycles, but initialization order can expose partially initialized modules.

Use the inventory as a signal and test imports in a fresh process. Shared code may need to move into a lower-level module.

Custom import behavior

Modulefinder analyzes rather than running the application normally, but custom loaders and import hooks can have special behavior.

Run analysis in an isolated environment for projects you do not control.

Resource limits

Large trees consume time and memory. Limit file count, source size, and analysis duration.

Cache results by environment and entry-point hash when the same analysis runs often.

Uploaded projects

A service that accepts projects should analyze them in a separate worker with a temporary filesystem and reduced permissions.

Validate archives before extraction and reject paths that escape the intended root.

CI comparisons

A pipeline can compare the inventory with a reviewed baseline.

finder = ModuleFinder(path=paths)
finder.run_script(entrypoint)
names = sorted(finder.modules)

Require review for meaningful changes rather than failing on every platform-specific difference.

Testing

Cover absolute, relative, conditional, optional, and dynamic imports; namespace packages; native extensions; ZIP files; missing modules; shadowing; and several entry points.

Run tests on every supported operating system.

Common mistakes

Common failures include treating the inventory as complete, ignoring dynamic plugins, using the wrong virtual environment, analyzing an internal file as a script, failing on optional dependencies, excluding modules without tests, confusing modules with distributions, and skipping final-artifact tests.

Conclusion

modulefinder builds a useful inventory of direct and transitive imports from a script. Configure the correct path, analyze every entry point, review missing modules, and add plugin and runtime information.

Treat the output as an approximation and test the packaged application. Consult the official modulefinder documentation and Python symtable for static name analysis.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Detailed view of code and file structure in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python py_compile: Compile One File

    Learn Python py_compile to compile one file, control .pyc output, logical filenames, optimization, hash invalidation, and build errors.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python compileall: Generate .pyc Bytecode

    Learn Python compileall to generate .pyc files, validate syntax, compile in parallel, control optimization, paths, and reproducible builds.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Vivid close-up of code on a computer screen showcasing programming details.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python codeop: Compile Interactive Input

    Learn Python codeop to detect complete, incomplete, or invalid commands, build REPLs, and preserve __future__ flags safely per session.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python linecache: Read Source Lines

    Learn Python linecache to retrieve source lines, refresh cached files, support tracebacks and loaders, preserve indentation, and secure paths.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python faulthandler: Diagnose Crashes

    Learn Python faulthandler to diagnose crashes, deadlocks, fatal signals, timeouts, and hangs with stack dumps from every thread.

    Ler mais

    Tempo de leitura: 8 minutos
    27/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python symtable: Analyze Name Scopes

    Learn Python symtable to analyze scopes, locals, globals, parameters, imports, nonlocals, closures, and compiler namespaces.

    Ler mais

    Tempo de leitura: 9 minutos
    27/08/2026