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

    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Python code showing warnings controlled with catch_warnings
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: Capture Python Warnings in Tests

    Learn Python catch_warnings to capture, test, and control warnings with focused filters and safe temporary scopes.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Python code representing persistent pickle references
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: Serialize External References

    Learn Python pickle persistent_id for stable external references, validation, security, performance, and long-term compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Binary code representing Python buffers and memory views
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: Count Values Without Buffer Copies

    Learn Python memoryview.count to count bytes and values in buffers without copies, with formats, limits, and practical safety.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code and type annotations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: Avoid Circular Imports in Annotations

    Learn Python annotationlib to inspect deferred annotations, avoid circular imports, and build safer runtime tooling.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code on screen representing module and package inspection
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    inspect.ispackage: Identify Python Packages

    Learn Python inspect.ispackage to identify packages, explore module trees, and build safer introspection and plugin tools.

    Ler mais

    Tempo de leitura: 5 minutos
    05/10/2026