2025, Nov 12 13:00

How to Resolve Python ModuleNotFoundError (myLib.main): Align Imports with Flat or Package Layouts in a Monorepo

Learn why Python raises ModuleNotFoundError for myLib.main in monorepos and how to fix it by aligning imports with flat modules or proper package layouts.

Fixing ModuleNotFoundError for internal workspace packages often comes down to how Python resolves modules versus how the project is laid out. If your app tries to import myLib.main but the library is structured as a flat module rather than a package, Python cannot find myLib at runtime and fails with ModuleNotFoundError.

Reproducing the issue

Consider this workspace layout where the application imports a function from an internal library:

myProject
├── pyproject.toml
├── apps
│   └── myApp
│       ├── pyproject.toml
│       └── main.py
└── lib
    └── myLib
        ├── pyproject.toml
        ├── main.py
        └── __init__.py

The library provides a simple function:

# lib/myLib/main.py
def say_hi():
    return "Hello!"

The application tries to import it as a submodule of myLib:

# apps/myApp/main.py
from myLib.main import say_hi
print(say_hi())

Running the app results in:

ModuleNotFoundError: No module named 'myLib'

What is actually happening

The path that gets added to Python’s import search path corresponds to the project root of the installed distribution, not automatically to a nested package name. In the current layout, the myLib project contains a top-level file main.py. That file is importable as a top-level module named main, not as myLib.main. In other words, Python sees main, but there is no package named myLib that provides a submodule main, so from myLib.main import ... cannot work in this structure.

Two ways to resolve it

The fix depends on which import style you want to keep.

If you’re fine with a flat layout, import the module directly as main. In that case, keep the library structure as is and change the app’s import to:

# apps/myApp/main.py
from main import say_hi
print(say_hi())

If you specifically want to import via myLib.main, turn the library into a proper package by nesting the source under a myLib directory inside the project. After restructuring, the library should look like this:

lib
└── myLib              <- project root added to search path
    ├── pyproject.toml
    └── myLib          <- package directory
        ├── __init__.py
        └── main.py    <- import path is myLib.main

With this layout, the original app code works as written:

# apps/myApp/main.py
from myLib.main import say_hi
print(say_hi())

If you’re deciding between keeping a flat module or organizing code under a package directory, it’s worth reviewing the discussion of src layout versus flat layout: https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/

Why this matters

Understanding how import paths map to your on-disk layout prevents elusive ModuleNotFoundError at runtime. Consistency between the intended import path and the project’s file structure removes ambiguity for both tooling and developers who interact with the codebase.

Takeaways

Match your imports to your layout. If the code lives as a single module file at the project root, import it directly as main. If you want a namespaced import like myLib.main, create a package directory named myLib inside the project and place the module there. Aligning these two eliminates the import error and keeps your workspace predictable.