A Python project may initially consist of only one or two files. In this case, you don’t have to worry about a project structure. But the situation is different if the program grows. In addition to the actual Python code files, documentation could also be added at some point. And as a rule, the tests should be stored in separate files and a specially designated directory. Inevitably, the question of a meaningful structure arises.
But what is the best structure for a Python project? This question is not easy to answer. Because on the Internet as well as the relevant specialist literature there are different suggestions. Thus, the following remarks are also to be understood only as a suggestion.
A simple project structure
A typical structure for a project named “my_project” could look like this:
.
├── README.md
├── docs
├── src
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
├── pytest.ini
├── requirements-dev.txt
├── requirements.txt
├── setup.cfg
├── setup.py
└── tests
└── __init__.pyThere are three subfolders within my_project:
src: This directory refers to the source code directory, which is the directory where the files containing the Python code are stored. In this example, this directory contains two files:- This is a package, which is why this folder contains a file called
__init__.py. This file is usually empty (nowadays). It is called when a module of this package is imported. - The
main.pyfile contains the Python code. It serves as the entry point for this program.
- This is a package, which is why this folder contains a file called
docs: This directory contains all the files required for the documentation (e.g. for the documentation generator Sphinx).tests: All files containing tests are located here. This directory also has a file called__init__.py. If pytest is used, a configuration file forpytestcan optionally be added to the project folder:pytest.ini.
A project can also be organized such that a subdirectory is created within the src directory, and this subdirectory has the same name as the project itself. Within this subdirectory, you would find files like __init__.py, main.py, and potentially other modules. Furthermore, tests is a subdirectory of src, while docs remains in the root directory.
my_project/
├── src/
│ ├── my_project/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── module1.py
│ │ └── ...
│ └── tests/
├── docs/
├── pyproject.toml
├── pytest.ini
└── README.mdrequirements.txt
The external libraries required for a project, such as numpy or matplotlib, can be listed in the requirements.txt file. Installation can then be accomplished using pip or pip3:
pip3 install -r requirements.txt # Linux, macOS
pip install -r requirements.txt # WindowsSome developers store the packages that are not immediately required for the execution of this project (e.g. pytest) in a file called requirements-dev.txt. However, this is optional. These packages can also be installed, if necessary, using pip or `pip3:
pip3 install -r requirements-dev.txt # Linux, macOS
pip install -r requirements-dev.txt # WindowsNowadays, it is common practice to specify any required libraries in the pyproject.toml file. Consequently, the requirements.txt and requirements-dev.txt files are no longer necessary.
[project]
name = "my_project"
version = "0.1.0"
dependencies = [
"requests >= 2.28.0",
"pandas >= 3.0.0",
"numpy",
]
[project.optional-dependencies]
dev = [
"pytest",
"black",
]The pyproject.toml file will be discussed in more detail in the next section.
Configuration files
The files pyproject.toml, setup.py, and setup.cfg are configuration files that contain information about the project, such as its dependencies, metadata (e.g., name, version, description), build instructions, and more.
Nowadays, only the pyproject.toml file is needed. It contains all the information about the project.
The files setup.py and setup.cfg are older configuration files. When using pyproject.toml, they are generally no longer needed. However, since they are still frequently found in older Python projects, I will address them for the sake of completeness.
setup.py and setup.cfg
Previously, there was only one file named setup.py. If only this file (and not also setup.cfg) was used, it contained the metadata for the project. For our example project, it might have the following content:
from setuptools import setup, find_packages
VERSION = '0.1.0'
setup(
name='my_project',
version=VERSION,
license='MIT',
description='Just an example.',
author='My Name',
author_email='my_name@xyz.com',
url='https://github.com/<user>/<project>',
python_requires='>=3.8'
package_dir={"": "src"},
packages=find_packages(where="src"),
entry_points={
'console_scripts': [
'my_project = main:main',,
],
},
)The setup.py file could become very complex and difficult to understand. That’s why the setup.cfg configuration file was introduced, which contains the necessary metadata. Often, these configuration files appeared as a pair: the setup.py file contained the build instructions, and the setup.cfg file contained the metadata. This combination was the standard for a long time and was also necessary because older versions of pip and setuptools did not know that they needed to call the setup.cfg file.
When using the combination of setup.py and setup.cfg, the content of setup.py was reduced to just two lines:
import setuptools
# Search for `setup.cfg` in the same directory:
setuptools.setup()And setup.cfg might have looked like this:
[metadata]
name = my_project
author = My Name
author-email = my_name@xyz.com
license = MIT
long_description = file: README.md
url = 'https://github.com/<user>/<project>'
requires-python = >= 3.8
[options]
package_dir =
= src
packages = find:
install_requires =
requests >= 2.45.0
[options.packages.find]
where = src
[options.entry_points]
console_scripts =
my_project = src.main:mainThe pyproject.toml file
Moving from the past to the present, we now come to the pyproject.toml file. Nowadays, it is sufficient as a configuration file. This file, by specifying a build system, enables the creation of an installation package. In the following example, setuptools is used as the build system:
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"Instead of setuptools, you can also use Hatchling, Flip, PDM, or uv-build. If you want to know what information needs to be included in the pyproject.toml file for each build system, the Python Packaging User Guide will be helpful.
By specifying the build system, it is possible to publish a Python program on the platform pypi.org (so that an installation can be performed with the command pip).
Note: It is possible that you will encounter projects that have both the pyproject.toml file and the setup.cfg file. In this case, the pyproject.toml file only contains the information about the build system, while the setup.cfg file contains the remaining information.
After specifying the build system, further project information follows. The pyproject.toml file might look like this:
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my_project"
version = "1.0.0"
description = "Project description"
readme = "README.md"
requires-python = ">=3.12"
license = { text = "MIT" }
authors = [
{ name = "Your Name", email = "name@xyz.com" }
]
urls = {"Homepage" = "https://github.com/username/project_name"}
classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython"
]
dependencies = [
"requests >= 2.28.0",
"pandas >= 3.0.0",
"numpy",
]
[project.optional-dependencies]
dev = [
"pytest",
"black",
]
[project.scripts]
my_project = "src.main:main"Note: In the examples shown for the files setup.py, setup.cfg, and pyproject.toml, it was assumed that the main.py file is located directly in the src directory.
.
├── README.md
├── src
│ ├── __init__.py
│ └── main.pyAdditional notes
The file main.py represents the entry point of the program. The name can be chosen freely, but often this file is called main.py or cli.py (if it’s a console program).
It’s a good idea to add a README.md file to each project. Not least if the project is to be published, a file with this name is required. This is where other developers can find useful information about the project. In addition, projects (on Github or GitLab, etc.) often also contain a CONTRIBUTING.md file that offers other developers tips on how to participate in the project.
Take a look at setup.cfg, setup.py and pyproject.toml files of other projects on Github. This gives you an idea of what kind of metadata others have added.
On Github you can find a shell script that can be used to automatically create a project structure. This script does not claim to be complete. But it may be a good starting point for your own script.
This blog post is also available as a PDF version.
Last updated on January 28th, 2026