Python modulefinder: Analyze Imports

Published on: August 13, 2026
Reading time: 5 minutes
Binary code network representing the import graph analyzed with Python modulefinder

Python modulefinder analyzes a script and attempts to determine which modules it imports. The result can support dependency audits, packaging preparation, missing-import diagnostics, and internal build reports.

It follows imports discovered while inspecting code. That is not the same as exercising every runtime path. Dynamically constructed imports, plugins discovered from configuration, calls to importlib.import_module(), and custom import hooks may not appear. Treat the report as useful evidence rather than a perfect inventory.

Create a first ModuleFinder report

The main class is modulefinder.ModuleFinder. Call run_script() with a Python file, then use report() for a human-readable summary.

from modulefinder import ModuleFinder

finder = ModuleFinder()
finder.run_script('application.py')
finder.report()

The report includes discovered modules, paths, and modules that are missing or appear to be missing. For automated systems, access the internal mappings and emit structured JSON instead.

Read the modules mapping

finder.modules maps module names to analysis objects.

from modulefinder import ModuleFinder

finder = ModuleFinder()
finder.run_script('application.py')

for name, module in sorted(finder.modules.items()):
    print(name, module.__file__)

Built-in and internal modules may not have a physical file. Handle None as a valid value. To understand installation paths, see Python sysconfig.

Inspect missing modules

Potential problems appear in badmodules. They may be real failures, optional imports protected by try/except ImportError, platform-specific modules, or false positives.

for name in sorted(finder.badmodules):
    print('Possibly missing:', name)

Do not fail a build for every entry automatically. Classify required and optional dependencies, consider the target platform, and verify behavior in a clean environment.

Optional imports

Libraries often try to load accelerators, database backends, or operating-system integrations.

try:
    import uvloop
except ImportError:
    uvloop = None

The analyzer can report uvloop as missing even when the fallback is intentional. Maintain a documented allowlist of accepted optional modules rather than suppressing all warnings.

Customize search paths

The constructor accepts a path list. If omitted, it uses sys.path.

finder = ModuleFinder(path=[
    '/project/src',
    '/project/vendor',
])
finder.run_script('/project/src/app.py')

Use absolute controlled paths. Blindly adding the current directory may cause local files to shadow legitimate dependencies.

Exclude known modules

The excludes argument skips selected names.

finder = ModuleFinder(
    excludes=['tkinter', 'tests', 'devtools'],
)

Exclusions can improve speed and remove known optional trees, but an oversized list hides real dependencies. Store a reason for each exclusion.

Normalize paths in reports

replace_paths accepts (old, new) pairs.

finder = ModuleFinder(
    replace_paths=[
        ('/home/alex/project', '<PROJECT>'),
        ('/opt/build/venv', '<VENV>'),
    ]
)

Normalization improves comparison between machines and prevents usernames or internal directory layouts from leaking into shared artifacts.

Add a package path

AddPackagePath() records an additional location for a package.

import modulefinder

modulefinder.AddPackagePath(
    'my_plugin',
    '/opt/plugins/my_plugin',
)

Use this only for known layouts. If a path comes from configuration, resolve it and restrict it to an approved root.

Replace a module with a package

ReplacePackage(oldname, newname) tells the analyzer that one name should be treated as another package. This specialized compatibility feature can support unusual layouts.

Document every replacement. Reports become confusing when readers do not know that names were rewritten. Prefer fixing the package layout when you control the code.

Run modulefinder from the command line

The module file can also be executed as a script and given a Python filename. The API is usually better for pipelines because it allows filtering, normalization, and structured output.

Store the report as a build artifact and compare it between releases. An unexpected dependency may indicate architectural growth, an accidental import, or a packaging change.

Generate structured JSON

import json
from modulefinder import ModuleFinder

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

report = {
    'modules': {
        name: getattr(module, '__file__', None)
        for name, module in sorted(finder.modules.items())
    },
    'missing': sorted(finder.badmodules),
}

with open('imports.json', 'w', encoding='utf-8') as stream:
    json.dump(report, stream, indent=2)

Normalize sensitive paths before writing. Cap output size for very large dependency graphs.

Compare releases

before = set(old_report['modules'])
after = set(new_report['modules'])

print('Added:', sorted(after - before))
print('Removed:', sorted(before - after))

The difference reflects static reachability from the analyzed entry point. It does not prove that every module is used in every execution.

Dynamic imports are a blind spot

from importlib import import_module

name = settings['backend']
backend = import_module(f'my_app.backends.{name}')

The final module name is not a literal import. Maintain an explicit plugin manifest or combine modulefinder with tests covering supported configurations. For packaged data, importlib.resources solves a different concern.

Entry points and plugin systems

Installed plugins can be discovered through distribution metadata without being imported in the main source. Modulefinder may never see them.

Query installed entry points and add expected packages to the dependency inventory. The standard importlib.metadata module is appropriate for this job.

Platform-conditional imports

import sys

if sys.platform == 'win32':
    import winreg
else:
    import pwd

The report depends on the environment used for analysis. Run it on each supported platform or maintain a documented matrix of conditional dependencies.

Virtual environments matter

The analyzer sees paths available to the current process. Run it in the same virtual environment used for testing or packaging.

Record the Python version, normalized sys.path, and installed dependency set. Without this context, reports from two machines may differ for reasons unrelated to source changes.

Use it before zipapp builds

Before creating a Python zipapp artifact, the report can suggest pure-Python dependencies that need to be copied into the staging directory.

Do not automatically copy every discovered file. Filter standard-library modules, native extensions, external paths, package data, and license requirements. The report is a starting point, not a complete packager.

Combine with compileall

Python compileall verifies syntax and builds bytecode; modulefinder maps imports. They address different failure modes.

A project can compile successfully while a required module is missing. It can also import correctly while an unreachable source file contains a syntax error.

Debug output

The debug argument increases diagnostic messages.

finder = ModuleFinder(debug=2)
finder.run_script('app.py')

High debug levels may produce large logs containing local paths. Store them in protected locations and use short retention.

Performance and isolation

Large import graphs consume time and memory. Apply an external timeout, cap repository size, and run analysis in an isolated worker when processing third-party projects.

Although modulefinder does not normally run the full application, unknown source can still stress parsers or analysis logic. Avoid elevated privileges.

Test your reporting layer

Create small fixtures containing required, optional, relative, conditional, and dynamic imports.

def test_basic_dependency(tmp_path):
    script = tmp_path / 'app.py'
    script.write_text('import json\n', encoding='utf-8')

    finder = ModuleFinder()
    finder.run_script(str(script))
    assert 'json' in finder.modules

Focus tests on project-relevant modules instead of asserting the entire standard-library graph, which can vary by Python build.

Common mistakes

  • Treating the report as a perfect inventory.
  • Ignoring dynamic imports and entry points.
  • Failing for every optional missing module.
  • Analyzing in a different environment than the build.
  • Leaking local paths in reports.
  • Copying every discovered module automatically.
  • Testing only one platform.

Best practices

  • Use reproducible environments.
  • Normalize paths before storing results.
  • Classify required and optional dependencies.
  • Combine static analysis with runtime tests.
  • Maintain manifests for dynamic plugins.
  • Compare reports between releases.
  • Isolate analysis of third-party code.

Conclusion

Python modulefinder reveals the import graph reachable from a script, including discovered paths and potentially missing modules. It is useful for audits, diagnostics, and packaging preparation.

Interpret the output with context. Dynamic imports, plugins, and platform conditions require additional checks. Consult the official modulefinder documentation and the Python import system reference.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Organized binders representing applications packaged as executable .pyz files with Python zipapp
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipapp: Build Executable .pyz Files

    Learn Python zipapp to package applications as executable .pyz files, define entry points, bundle dependencies, and distribute safely.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Code editor representing REPL completion with Python rlcompleter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python rlcompleter: REPL Completion

    Learn Python rlcompleter to add completion to REPLs, consoles, and editors, control namespaces, filter results, and avoid side effects.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Terminal window representing an interactive console built with Python cmd
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python cmd: Build Interactive Consoles

    Learn Python cmd to build interactive consoles with commands, help, history, completion, testing, streams, and secure action control.

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026
    Interactive terminal representing a custom REPL built with the Python code module
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python code: Build a Custom REPL

    Learn the Python code module to build custom REPLs, control namespaces, prompts, output, incomplete blocks, errors, and local exit behavior.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Web application code representing WSGI with Python wsgiref
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python wsgiref: WSGI Applications

    Learn Python wsgiref to build and validate WSGI applications, test environ and headers, route requests, and run a local reference

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026
    Secure Internet protocol representing Unicode preparation with Python stringprep
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python stringprep: Prepare Unicode

    Learn Python stringprep to apply RFC 3454 tables, map Unicode, reject prohibited characters, and validate bidirectional protocol rules.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026