Validus Group Inc. | Technical Library
Practical engineering notes • Controls & automation • Applied software
White Paper
Python 3.10+
PyCharm / CLI / CI
January 25, 2026

Professional Python Project Structure That Works Everywhere

A practical guide to the src/ layout, editable installs (pip install -e .), and unittest discovery so your code runs the same in PyCharm, the terminal, and CI.
Author: Fred Fisher, President & Principal Engineer
Organization: Validus Group Inc.
Scope: Packaging & imports, test discovery, repeatable development workflow

Executive Summary

Python projects often start as a few scripts and quickly grow into something that needs repeatable structure. The most common pain point is imports: code runs in an IDE but fails in the terminal (or in CI).

The approach in this paper solves that by making Python treat your project as a real installable package:

  • Use a standard src/ layout so your package is clearly separated from test code and project tooling.
  • Use an editable install (pip install -e .) so imports work without environment hacks.
  • Use unittest discover in a way that is deterministic and reproducible.
Outcome
Your code becomes import-stable: it runs consistently in PyCharm, from the CLI, and in CI—without relying on IDE-only behaviors or PYTHONPATH tricks.

What This Solves

Symptom
Works in IDE, fails in terminal
Cause
Import paths differ across runners
Fix
src/ + editable install
Benefit
Terminal and CI behave like PyCharm

Reference Commands

# Install your package into the active venv (editable)
python -m pip install -e .

# Run unittest discovery from project root
python -m unittest discover -s tests -t . -v
Note
PYTHONPATH=src can make things “work”, but it’s a manual override. A professional setup installs the package so imports work naturally without extra environment variables.

1) How Python Imports Actually Work

Python does not “know what a project is.” It only knows how to import modules from locations on sys.path—a list of directories searched during import.

Typical entries in sys.path include: the current working directory, standard library paths, and your environment’s site-packages directory (where installed packages live).

Key Idea
If your package is installed into the environment, it becomes importable from site-packages automatically. That is why pip install -e . is such a reliable approach.
# Quick proof: show what Python will search during imports
python -c "import sys; print('\n'.join(sys.path[:6]))"

2) The Professional src/ Layout

In a professional Python package, your importable code lives under src/. Tests and tooling live alongside it but are not mixed into the import path by accident.

test_codex/
  pyproject.toml
  README.md
  src/
    test_codex/
      __init__.py
      app.py
  tests/
    __init__.py
    test_smoke.py

Why this layout is valuable

3) Editable Installs: What pip install -e . Actually Does

An editable install registers your project package with the environment, but links it back to your working directory. That means:

# From the project root, with your venv activated:
python -m pip install -e .

# Verify the package is importable:
python -c "import test_codex; print(test_codex.__file__)"
Professional Rule of Thumb
If you want stable imports everywhere, install the package. If you only rely on PYTHONPATH, your results depend on how each tool sets the environment.

4) Unittest Discovery: The Parameters That Matter

This is the discovery command used in a reproducible setup:

python -m unittest discover -s tests -t . -v

What each option means

Why tests/__init__.py was included
When -t is used, unittest expects the start directory (tests) to be importable. Adding tests/__init__.py makes tests a package, so unittest can import tests.test_smoke reliably.

5) Reference Implementation (Minimal, Clean)

Package code: src/test_codex/app.py

def add(a: int, b: int) -> int:
    return a + b

Test code: tests/test_smoke.py

import unittest
from test_codex.app import add

class TestSmoke(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)

if __name__ == "__main__":
    unittest.main()

Run tests: ./run_tests.sh

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"

PY=".venv/bin/python"
if [[ ! -x "$PY" ]]; then
  echo "ERROR: .venv missing. Create it with: python -m venv .venv"
  exit 1
fi

# Ensure editable install so imports work without PYTHONPATH.
"$PY" -m pip install -e . >/dev/null

"$PY" -m unittest discover -s tests -t . -v

6) Setup Checklist (Copy/Paste)

  1. Create and activate venv
    Keeps dependencies isolated and repeatable.
    cd /home/fred/PycharmProjects/test_codex
    python -m venv .venv
    source .venv/bin/activate
  2. Install the package (editable)
    This makes test_codex importable everywhere.
    python -m pip install -U pip
    python -m pip install -e .
  3. Verify imports
    Confirms the environment sees your package.
    python -c "import test_codex; import test_codex.app; print('ok')" 
  4. Run tests
    This should behave the same in terminal, PyCharm, and CI.
    python -m unittest discover -s tests -t . -v

7) Practical Notes for PyCharm

The Big Win
Once the package is installed editable, you are no longer depending on IDE-specific run behavior. That eliminates an entire class of “import path” surprises.

8) Summary

A professional Python workflow is mostly about making your environment deterministic: the same commands behave the same everywhere. The combination of:

gives you a foundation that scales from a quick test project to production tooling.