sibling imports
package management
Python
software development
coding practices

Sibling package imports

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Sibling package imports are imports between modules that share the same parent package. They are reliable when package structure and execution mode are consistent, but they break easily when scripts are run from arbitrary paths. Good import hygiene is mainly about project layout, entry-point discipline, and avoiding sys.path hacks.

Use a Clear Package Layout

A stable source tree is the foundation of reliable imports.

text
1repo/
2  pyproject.toml
3  src/
4    myapp/
5      __init__.py
6      services/
7        __init__.py
8        billing.py
9      utils/
10        __init__.py
11        currency.py

The src layout reduces accidental imports from repository root and catches packaging issues earlier.

Prefer Absolute Imports by Default

Absolute imports are usually easier to read and refactor in larger codebases.

python
1# src/myapp/services/billing.py
2from myapp.utils.currency import format_cents
3
4
5def render_total(cents: int) -> str:
6    return f"Total: {format_cents(cents)}"
python
1# src/myapp/utils/currency.py
2
3def format_cents(cents: int) -> str:
4    return f"$ {cents / 100:.2f}"

This style makes module origins explicit and avoids fragile dot-counting in deep relative imports.

Run Modules with Package Context

A common failure pattern is running a module file directly.

Bad pattern:

bash
python src/myapp/services/billing.py

Better pattern:

bash
python -m myapp.services.billing

Using -m preserves package context so sibling imports resolve consistently.

Use a Proper Application Entry Point

Define a package entry module for local and CI execution.

python
1# src/myapp/__main__.py
2from myapp.services.billing import render_total
3
4
5def main() -> None:
6    print(render_total(2599))
7
8
9if __name__ == "__main__":
10    main()

Then run:

bash
python -m myapp

One entry path reduces environment-specific import bugs.

Handle Circular Sibling Dependencies

Sibling imports can fail because two modules import each other at import time. Solve this structurally by moving shared contracts into a neutral module.

python
1# src/myapp/contracts.py
2from dataclasses import dataclass
3
4@dataclass
5class Invoice:
6    id: str
7    total_cents: int
python
1# src/myapp/services/billing.py
2from myapp.contracts import Invoice
3
4
5def summarize(invoice: Invoice) -> str:
6    return f"Invoice {invoice.id}: {invoice.total_cents} cents"

This removes cycles and improves architecture clarity.

Align Tooling Configuration

Import behavior should match across runtime and tooling.

Example pyproject.toml snippets:

toml
1[tool.pytest.ini_options]
2pythonpath = ["src"]
3
4[tool.ruff]
5src = ["src"]
6
7[tool.mypy]
8mypy_path = "src"

Add an import smoke test in CI to catch broken package moves.

python
1# tests/test_imports.py
2
3def test_imports() -> None:
4    import myapp
5    import myapp.services.billing
6    import myapp.utils.currency

Editable Installs for Local Development

In multi-package repositories, editable installs are safer than path mutation.

bash
pip install -e .

For monorepos with multiple packages, install each package explicitly in development environments. This keeps dependency resolution close to production behavior.

Avoid sys.path Mutation in Application Code

Temporary sys.path.append can unblock local experiments, but it hides packaging errors and creates brittle runtime behavior. If path customization is needed, keep it in tooling scripts, not library modules.

Packaging Metadata Matters

Keep package names and entry points defined clearly in pyproject.toml so local and CI installs resolve the same import roots. Consistent packaging metadata prevents hidden environment-specific import behavior.

Common Pitfalls

  • Running module files directly and bypassing package context.
  • Mixing absolute and relative import styles without conventions.
  • Using sys.path mutations in source modules.
  • Allowing circular sibling imports to grow unchecked.
  • Mismatching import roots between test tools and runtime.

Summary

  • Sibling imports depend on clean package structure and consistent execution mode.
  • Absolute imports are generally the best default in larger projects.
  • Use python -m and explicit entry modules for predictable behavior.
  • Break cycles by extracting shared contracts into neutral modules.
  • Keep tooling configuration aligned with runtime package layout.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.