šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Professional Python Project Layout

Structure a Python project the way professional teams do — the src layout, why it exists, and where tests, config, and docs belong.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

In a flat layout, why can pytest accidentally pass even if the package has a broken pyproject.toml?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

There's a specific, well-tested way professional Python projects are laid out on disk, and it isn't the first structure most people reach for. This lesson covers the 'src layout', why a flat layout causes subtle import bugs, and where tests, configuration, and docs fit around it.

1Flat Layout vs src Layout: A Subtle but Consequential Choice

A 'flat layout' places your package directory directly at the project root, next to pyproject.toml: my_project/mypackage/__init__.py. It reads naturally and is what most tutorials show first. The problem surfaces specifically when you run tests: if you cd my_project && pytest, Python's import system can find and import mypackage directly from the current working directory, completely independent of whether the package was ever actually pip install-ed — because the current directory is implicitly importable.

That might sound harmless, but it means your test suite is silently testing 'the code as it sits on disk in this exact directory', not 'the code as it would actually be installed and imported by a real user via pip install my-package'. A broken pyproject.toml, a missing file that should have been included in the built package, or an accidentally-excluded submodule can all pass a full flat-layout test suite locally and still fail the moment a real user installs the published package — a gap discovered far too late, often after a broken release has already shipped.

The 'src layout' — nesting the package one level deeper, under src/mypackage/ — closes that gap by construction. src/ is not on Python's default import path, so mypackage is *not* importable just by being in the current directory; the only way to import it is to actually install it (even in editable mode), which forces your test suite to exercise the real installation and import-resolution path every single time.

āœ•
—
+
my_project/
  pyproject.toml
  mypackage/
    __init__.py
    core.py
  tests/
    test_core.py
localhost:3000
Import Behavior
Flat layout: importable without install
src layout: import requires real install

2Editable Installs: Fast Iteration Without Losing the Guarantee

The obvious objection to the src layout is friction: if the package must be installed to be importable, does that mean reinstalling after every code change during development? pip install -e . (an 'editable install') solves exactly this — it registers your package as installed, pointing back at your live source tree in src/, so edits to source files take effect immediately without a reinstall step, while still going through the genuine package-resolution machinery Python uses for any installed package.

Modern editable installs (via pyproject.toml-based builds, using setuptools' PEP 660 support or similar from other build backends like Hatchling) are implemented with an import hook or a small .pth-style redirect rather than copying files, so they stay lightweight and fast, and — importantly — they still validate that your package's declared structure (what's in [tool.setuptools.packages.find] or equivalent) actually resolves correctly, catching packaging misconfigurations the flat layout would have hidden.

The workflow this enables in practice: pip install -e ".[dev]" once, at the start of working on a project, installs the package editable plus its development dependencies (test runners, linters), and from then on pytest, running from anywhere, exercises the real installed-and-importable package while still reflecting every source edit instantly.

āœ•
—
+
# With a flat layout, this can succeed even if your package
# has a broken pyproject.toml that would fail for real users:
$ cd my_project
$ pytest   # imports ./mypackage directly, bypassing installation
localhost:3000
Development Workflow
pip install -e .
Editable, real install — live source, real import resolution

3Where Tests, Config, and Docs Fit Around the Package

tests/ lives as a sibling to src/, not inside the package itself — this keeps test code out of what actually gets shipped to users when the package is built and published, and it's the layout pytest's default discovery and most CI templates expect without extra configuration. Test files typically mirror the package's internal module structure (tests/test_core.py testing src/mypackage/core.py), which keeps navigation between a module and its tests predictable as the codebase grows.

Configuration lives almost entirely in pyproject.toml at the project root in modern Python projects — build system metadata, dependencies, tool configuration for ruff, mypy, and pytest can all coexist in different [tool.X] sections of that single file, replacing what used to be a scattered collection of setup.py, setup.cfg, requirements.txt, pytest.ini, and .flake8 files. The next lesson covers pyproject.toml's structure directly.

A typical complete professional layout looks like: pyproject.toml, README.md, LICENSE, src/mypackage/ (the actual package), tests/ (mirroring the package structure), and optionally docs/ for Sphinx or MkDocs documentation sources — with a .gitignore covering __pycache__/, *.egg-info/, .venv/, and build artifacts, none of which belong in version control.

āœ•
—
+
my_project/
  pyproject.toml
  src/
    mypackage/
      __init__.py
      core.py
  tests/
    test_core.py
localhost:3000
Complete Layout
pyproject.toml, src/, tests/, docs/, README.md
The professional standard

4Step-by-Step Breakdown

Two projects can have identical code and wildly different reliability, just from how the files are arranged on disk. Let's build the layout professional teams standardize on.

A 'flat layout' puts your package directly in the project root, alongside pyproject.toml. It looks simple, but has a subtle trap.

The trap: running pytest from the project root can import mypackage directly from the source tree, even if it isn't properly installed — masking packaging bugs.

Checkpoint: In a flat layout, why can pytest accidentally pass even if the package has a broken pyproject.toml?

  • →Python can import the package directly from the current directory, bypassing installation entirely
  • →This is a known bug in pytest that will be fixed in a future version

The 'src layout' nests your package under src/, so it can ONLY be imported if properly installed — tests then verify what users actually get.

pip install -e . installs the src-layout package in editable mode, making it importable everywhere while still reading live from source.

Checkpoint: What does pip install -e . do?

  • →Installs the package in "editable" mode, linking to the source so changes are reflected immediately
  • →Copies the entire project into site-packages, disconnected from the source

With a solid project skeleton in place, the next question is what goes inside it — packages and modules, next.

Detect a Real src Layout. Finish is_src_layout(): the src layout nests the package one directory deeper.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Default to the src layout for any package you intend to publish or distribute

It forces your test suite to exercise the real, installed import path, catching packaging bugs before a user ever encounters them — worth the minor extra directory nesting.

Keep tests/ as a sibling of src/, never nested inside the package itself

This keeps test code out of the built distribution, matches default pytest discovery, and is what virtually every CI template and tutorial in the ecosystem now assumes.

Frequent Bugs

THE BUG

Using a flat layout for a published package, where local tests pass because the package is importable directly from the working directory, but a fresh pip install of the published package fails for real users due to a packaging misconfiguration.

THE FIX

Switch to the src layout so tests can only run against a genuinely installed (even if editable) version of the package, surfacing packaging bugs locally instead of after publishing.

Real-World Examples

Setting Up a New Publishable Library

A team is starting a new internal Python library meant to be pip-installed by several other internal services, and wants to avoid packaging bugs reaching those consumers.

my_library/
  pyproject.toml
  README.md
  src/
    my_library/
      __init__.py
      client.py
  tests/
    test_client.py

# Setup: pip install -e ".[dev]"
# Then: pytest — exercises the real installed package

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Running pytest in a src-layout project without first running pip install -e ., getting ModuleNotFoundError: No module named 'mypackage'.

# Fails: mypackage was never installed $ pytest ModuleNotFoundError: No module named 'mypackage' # Correct: install editable first $ pip install -e . $ pytest

The Solution //

Run pip install -e . (or pip install -e ".[dev]") once before running tests — the src layout intentionally makes the package unimportable until it's actually installed.

Lesson Glossary

[01]Flat layout

A project structure where the package directory sits directly at the project root, alongside pyproject.toml.

Code Preview
// Flat layout context

[02]src layout

A project structure nesting the package one level deeper, under src/, so it is not importable without a real installation.

Code Preview
// src layout context

[03]Editable install

An installation mode (pip install -e .) that links an installed package back to its live source tree, reflecting edits immediately.

Code Preview
// Editable install context

[04]Package discovery

The build backend's process of determining which directories/files under src/ constitute the installable package, configured in pyproject.toml.

Code Preview
// Package discovery context

Continue Learning