Modern Python Project Initialization
Welcome to 2026. The ecosystem has matured. Thanks to a new wave of tooling primarily from Astral we can now spin up a robust…
Modern Python Project Initialization
Welcome to 2026. The ecosystem has matured. Thanks to a new wave of tooling primarily from Astral we can now spin up a robust, standardized, and incredibly fast Python project in minutes.
This guide is opinionated. We are ditching the old ways for a stack that prioritizes speed, determinism, and developer experience.

The Modern Stack:
**uv:** The unified Python package and project manager. It replaces pip, poetry, virtualenv, and pyenv entirely. It is shockingly fast.**ruff:** An extremely fast Python linter and formatter written in Rust.**mypy:** Static type checking to catch logic errors before runtime.**pre-commit:** The gatekeeper that ensures bad code never enters your repository.
Let’s build a professional-grade scaffold from scratch.
0. Prerequisites
You only need one tool installed globally on your machine: uv.
# On macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# On Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
Verify it worked: uv --version
Step 1: The Foundation (Git and Python Version)
We start empty. Create your directory and initialize git.
Bash
mkdir my-modern-app
cd my-modern-app
git init
# Good practice: name main branch 'main'
git branch -M main
The Critical .python-version File
Before we write code, we must agree on the Python version. If I use 3.12 and you use 3.10, things will break.
We solve this with a single file that uv respects automatically.
Bash
echo "3.12" > .python-version
Why this matters: When any developer (or your CI server) enters this directory and runs a uv command, uv will automatically fetch and use Python 3.12. No manual downloading required.
Step 2: Initialize the Project Structure
We will use uv to scaffold the project. We are aiming for the "src layout"—the industry standard for robust packaging, where project code lives in a distinct src/ subdirectory.
Bash
# Initialize a library structure named 'my_app'
uv init --lib --name my_app
Your folder now looks like this:
my-modern-app/
├── .git/
├── .python-version
├── README.md
├── pyproject.toml <-- The heart of modern Python
└── src/
└── my_app/
├── __init__.py
└── py.typed <-- Marker for type checker support
uv has automatically created a virtual environment for you in a hidden .venv folder. You never need to manually create or activate environments again.
Step 3: The Toolchain (Dependencies)
Now we add our development tools. Notice the --dev flag. These tools are needed to build the project, but not to run the final application.
Bash
# The holy trinity of modern Python quality
uv add --dev ruff mypy pytest pre-commit
What just happened?
uvresolved compatible versions of these tools lightning-fast.- It installed them into the isolated project environment.
- It added them to
pyproject.tomlunder dependency groups. - Crucially: It created
uv.lock. This file must be committed to git. It ensures every developer on your team is using the exact same version of every sub-dependency down to the hash.
Step 4: Configuration (The “Secret Sauce”)
Tools are useless without configuration. In modern Python, nearly everything goes into pyproject.toml.
Open pyproject.toml. It already has the basics. We need to add strict configurations for Ruff and Mypy to ensure high quality.
Append the following to your pyproject.toml:
Ini, TOML
# --- RUFF CONFIGURATION ---
[tool.ruff]
# Target Python 3.12 specifically
target-version = "py312"
# A reasonable line length for modern screens
line-length = 100
[tool.ruff.lint]
# Enable Pyflakes (`F`), pycodestyle (`E`, `W`), isort (`I`),
# and Bugbear (`B`) for catching common pitfalls.
select = ["E", "F", "I", "B", "UP"]
ignore = []
# Allow fix for all enabled rules (when `--fix` is passed).
fixable = ["ALL"]
[tool.ruff.format]
# Use double quotes for strings.
quote-style = "double"
# Use spaces instead of tabs.
indent-style = "space"
# --- MYPY CONFIGURATION (TYPE CHECKING) ---
[tool.mypy]
# Be strict. This is the way.
strict = true
# Don't complain if a third-party library doesn't have type hints
ignore_missing_imports = true
# Don't check the tests directory for strict typing
exclude = ["tests"]
Step 5: The Gatekeeper (pre-commit)
We don’t want to rely on humans remembering to run linters. We use pre-commit to run checks automatically whenever someone types git commit. If the checks fail, the commit is blocked.
Create a new file named .pre-commit-config.yaml in the root directory:
YAML
repos:
# Standard file fixers
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
# Ruff - Linting and Formatting in one go
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
# Run the linter and automatically fix simple issues
- id: ruff
args: [ --fix ]
# Run the formatter
- id: ruff-format
# Mypy - Static Type Checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
additional_dependencies: [] # Add types-requests etc here if needed
Finally, install these hooks into your local .git directory:
Bash
uv run pre-commit install
Tip: Run uv run pre-commit run --all-files right now to see it in action. It might modify your yaml file slightly to fix formatting.
Step 6: Final Polish & First Commit
Let’s add a basic test file to ensure pytest is working.
Create tests/test_basic.py:
Python
def test_smoke():
"""A simple smoke test to verify the test harness works."""
assert True
Let’s verify our entire harness works using uv run. This command executes tools within the project's isolated environment.
- Lint:
uv run ruff check . - Format:
uv run ruff format . - Type Check:
uv run mypy . - Test:
uv run pytest
If those all pass, you are ready.
Bash
git add .
git commit -m "chore: initial project scaffold with uv, ruff, and mypy"
# git remote add origin <your-repo-url>
# git push -u origin main
The “Day 2” Workflow
Congratulations, you have a state-of-the-art Python environment. But how do you use it daily?
1. Adding a production library (e.g., FastAPI): Do not use pip. Use uv.
Bash
uv add fastapi
# This adds it to pyproject.toml AND updates uv.lock automatically
2. Running a script: You don’t need to activate the virtual environment.
Bash
uv run python src/my_app/main.py
3. The Daily Routine: Write code. Before you commit, you might run tests manually:
Bash
uv run pytest
When you commit, pre-commit will automatically run Ruff and Mypy to ensure you didn't break anything.
Summary
We have replaced a half-dozen brittle tools with a cohesive, lightning-fast workflow.
- uv manages Python versions, environments, and dependencies deterministically
- Ruff keeps code readable and standard.
- Mypy catches bugs before they run.
- pre-commit ensures no one bypasses the rules.
This setup takes 5 minutes to implement but saves hundreds of hours of debugging and configuration headaches down the road. Welcome to modern Python.
메타데이터
- post_id
- d8a31a6e9bf5
- slug
- modern-python-project-initialization-d8a31a6e9bf5
- url
- https://medium.com/@hasithvikasitha/modern-python-project-initialization-d8a31a6e9bf5
- canonical_url
- https://medium.com/@hasithvikasitha/modern-python-project-initialization-d8a31a6e9bf5
- author_url
- https://medium.com/@hasithvikasitha
- status
- ok
- fetched_at
- 2026-06-16 19:09:56