How to Structure a Python Project

Abstract generated cover for How to Structure a Python Project.

Python lets you start with a single file. That is one of its strengths. But as soon as a project grows, structure matters. Without structure, code ends up scattered across random files, imports become confusing, tests are hard to run, and setup instructions live only in your memory. A good Python project structure does not need to be fancy. It needs to make the important parts obvious:

  • where the application code lives
  • where tests live
  • how dependencies are installed
  • how the project is run
  • where configuration belongs

The Mental Model

The goal is not to follow one perfect folder layout. The goal is to make the project easy to understand from a fresh checkout. Someone should be able to open the repository and answer:

  • What does this project do?
  • Where does the code start?
  • How do I install it?
  • How do I run it?
  • How do I test it? If the structure answers those questions, it is probably good enough.

A Simple Starting Layout

For a small application, this layout works well: ```plain text my-project/ README.md pyproject.toml .gitignore .env.example src/ myproject/ init.py main.py settings.py tests/ testmain.py scripts/ dev-start.sh

The names can change, but the responsibilities are clear.
\`src/my_project/\` contains the application package.
\`tests/\` contains tests.
\`scripts/\` contains helper scripts.
\`pyproject.toml\` describes project metadata and dependencies if your tooling uses it.
\`README.md\` explains how to work with the project.
## Flat Layout vs \`src\` Layout
You will see two common styles.
Flat layout:
```plain text
my_project/
  __init__.py
  main.py
tests/

`src` layout: ```plain text src/ my_project/ init.py main.py tests/

The \`src\` layout helps catch import mistakes because your package is not automatically sitting at the repository root in quite the same way.
For libraries, I like \`src\`.
For small scripts or internal apps, a flat layout can be fine.
The important part is consistency.
## Keep Dependencies Explicit
A Python project should clearly declare what it needs.
You might use:
```plain text
requirements.txt
pyproject.toml
poetry.lock
uv.lock

The exact tool can vary. The rule is the same:

Do not rely on mystery packages installed globally on your machine. If the project needs `requests`, `django`, or `python-dotenv`, that should be visible in dependency files. This matters when you clone the project onto another machine.

Put Configuration in a Predictable Place

Configuration should not be scattered across random modules. For a small project, a `settings.py` file can be enough:

from pathlib import Path
import os

BASE_DIR = Path(__file__).resolve().parent.parent
DEBUG = os.environ.get("DEBUG") == "1"

For secrets, use environment variables. Commit an example file: ```plain text .env.example

Do not commit the real \`.env\` if it contains tokens, passwords, or private values.
## Add a Clear Entry Point
A project should have an obvious way to run.
For a simple script:
```bash
python -m my_project.main

For a web app:

python manage.py runserver

For a static site:

./dev-start.sh

The command itself matters less than documenting it. If a project requires five setup steps, put them in the README or wrap them in a script.

Tests Belong Beside the Project

A common layout: ```plain text tests/ testconfig.py testparser.py test_sync.py

Tests should be easy to discover and easy to run:
```bash
pytest

Even a few focused tests can save time when refactoring. This is especially true for project infrastructure code: parsers, sync scripts, build steps, and config loading.

Common Mistakes

Mistake 1: Starting with too much architecture

Do not create ten folders before the project needs them. Start simple and split code when the boundaries become real.

Mistake 2: Hiding the run command

If only you know how to start the project, the structure is incomplete. Put the command in the README or a script.

Mistake 3: Mixing generated files with source files

Build output, caches, and generated assets should usually be ignored. Keep source files in the repo. Rebuild generated files when needed.

Where This Shows Up in Real Projects

Project structure becomes important when the project moves between machines. If the dependencies are declared, the `.env` expectations are documented, and the start script is obvious, the project is easier to run somewhere else. It also matters when you return to a project after a few weeks. Clear structure reduces the amount of rediscovery you have to do.

Key Takeaways

  • Structure should make the project understandable from a fresh checkout.
  • Keep source code, tests, config, and scripts in predictable places.
  • Declare dependencies explicitly.
  • Do not commit real secrets or generated environment folders.
  • Document the commands people actually need.

    Related Articles

  • Python Virtual Environments Explained

  • Django Project Structure Explained
  • Terminal Tools I Actually Use

← Back to Blog Index