Project Structure and Dependencies
Explore how to organize professional Python projects by setting up a clean workspace, using the standardized src directory layout, and managing dependencies with pyproject.toml. Understand how to install packages in editable mode and verify your setup to ensure consistent builds and runs across different environments.
So far, the examples in this course have used standalone scripts or small groups of modules. This approach works well for learning and experimentation, but does not scale well for professional software development. A loosely organized collection of scripts leaves important questions unresolved, such as which file serves as the entry point, how dependencies should be installed, and why imports behave differently across environments.
Python projects typically address these issues through a standardized structure and explicit configuration. A clear project layout defines where source code resides, where tests are located, and how the application is executed. Configuration files specify dependencies and build metadata, allowing tools to install, validate, and run the project consistently across environments.
By adopting a formal layout and packaging conventions, we convert a collection of personal scripts into a reproducible and distributable application that other developers can understand quickly and that automation tooling can build and execute reliably.
Step 1: Setting up the workspace
When starting a professional project, we never clutter the root directory. We need a clean workspace. We will use the mkdir command with the -p flag.
mkdir: Make a directory.-p: Theparentsoption instructs the command to create any missing parent directories automatically, such as creatingsrcandsimple_toolin a single step.
Run the following commands to create a fresh directory for our project and the standard src layout structure:
# 1. Create the project root and enter itmkdir my_projectcd my_project# 2. Create the source code directories# The -p flag creates 'src' AND 'simple_tool' at the same timemkdir -p src/simple_tool# 3. Create a placeholder for testsmkdir tests# 4. View the structuretree .
Lines 2–3: We create
my_projectto keep our work separate from other files in/app.Line 7: This creates the folder
src/simple_tool. This is where our actual Python code will live.Line 13: The
treecommand confirms we have the correct hierarchy.
Step 2: Configuration with pyproject.toml
For years, Python used multiple configuration files (setup.py, requirements.txt). Modern Python (PEP 621) unifies these into a single file, called pyproject.toml. This file defines our project's metadata, build system, and dependencies.
We will create this file using cat and a "heredoc" (<<EOF). This allows us to write multiple lines of text into a file at once. Run this command to create the configuration file:
cat <<EOF > pyproject.toml[build-system]requires = ["setuptools>=61.0"]build-backend = "setuptools.build_meta"[project]name = "simple_tool"version = "0.1.0"description = "A demonstration package"readme = "README.md"requires-python = ">=3.11"authors = [{name = "Developer", email = "dev@example.com"}]dependencies = ["requests>=2.28.0"][project.scripts]simple-tool = "simple_tool.main:entry_point"[project.optional-dependencies]dev = ["pytest","black"]EOF
[project]: Defines the package name (simple_tool) and version.dependencies: Lists external libraries (likerequests) that our project needs.[project.scripts]: Creates a command-line shortcut. When we typesimple-toollater, it will run theentry_pointfunction in our code.[project.optional-dependencies]: Defines extra tools (likepytest) that are only needed for development, not for running the app.
Step 3: Creating the source code
Now we need the actual Python logic. We will create two files:
__init__.py: Marks the directory as an importable package.main.py: Contains the code we want to run.
Run these commands to generate the code files:
# Create the __init__.py fileecho "__version__ = \"0.1.0\"" > src/simple_tool/__init__.py# Create the main.py filecat <<EOF > src/simple_tool/main.pyimport sysimport requestsdef entry_point():print("-" * 50)print(f"SUCCESS: 'simple_tool' is installed and running!")print(f"Python Location: {sys.executable}")print(f"Requests Version: {requests.__version__}")print("-" * 50)if __name__ == "__main__":entry_point()EOF
echo: A simple command to write a single line of text to a file.import requests: This works because we listedrequestsin ourpyproject.tomldependencies.
Step 4: The editable install
Currently, the code exists only as files in a directory. Python does not yet recognize it as an installable package. For development, the package is typically installed in editable mode.
We use pip with the -e flag.
install: The standard command to install packages.-e(Editable): Tells Python to look at oursrcfolder directly. Changes we make to the code appear immediately without reinstalling..(Dot): Tells pip to look forpyproject.tomlin the current directory.
Run the install command:
# Install the project in editable modepip install -e .
You will see pip installing dependencies (like requests) and then "Successfully installed simple_tool".
Step 5: Verification and optional dependencies
Now that the package is installed, we can run it from anywhere in the terminal using the command name we defined in [project.scripts]. We can also install the development tools we defined earlier (like pytest) by targeting the [dev] group.
Run these commands to verify the tool and install extra dependencies:
# 1. Run the tool we just builtsimple-tool# 2. Install the optional 'dev' dependencies# Quotes are required around ".[dev]" in some terminalspip install -e ".[dev]"
simple-tool: This command works because pip read our config and created a wrapper for us.".[dev]": This tells pip to install the main package plus the extra tools listed under thedevgroup in our TOML file.
Now feel free to try the commands one by one in the following terminal:
In the end, don’t forget to give the final command tree to see the complete hierarchy of the files. The output will look like this:
├── pyproject.toml├── src│ ├── simple_tool│ │ ├── __init__.py│ │ └── main.py│ └── simple_tool.egg-info│ ├── PKG-INFO│ ├── SOURCES.txt│ ├── dependency_links.txt│ ├── entry_points.txt│ ├── requires.txt│ └── top_level.txt└── tests5 directories, 9 files
We have moved beyond writing isolated scripts and established a professional engineering workflow. By using the src layout, we prevent import errors and isolate our code. By using pyproject.toml, we explicitly declare our project's identity and requirements. This structure serves as the foundation for collaboration; anyone who clones our repository can run a single command (pip install -e .) to recreate the exact environment needed to run, test, and develop our application.