02 Code Quality in Practice: From Working Code to Professional Software
October 2025 (13550 Words, 76 Minutes)
1. Introduction: When Working Code Isn’t Enough
You’ve built your Road Profile Viewer application. The visualization is beautiful, the camera ray intersects perfectly with the road profile, and when you click “Run,” everything works. Congratulations! 🎉
But then… you push your code to GitHub, open a pull request, and automated checks fail. ❌
$ git push origin feature/road-profile-viewer
Running automated checks...
❌ Ruff check: 14 errors found
❌ Format check: 8 files need formatting
❌ Type check: 6 type errors detected
Pull request blocked - fix issues before merging
What happened? Your code works, but it doesn’t meet professional quality standards. This is the gap between hobbyist code and production-ready software.
This lecture is about understanding that gap and closing it—not with tedious manual reviews, but with automated tools that make quality enforcement effortless and consistent.
2. Learning Objectives
By the end of this lecture, you will:
- Understand the difference between code that works and code that meets professional standards
- Recognize common code quality issues through real examples from the Road Profile Viewer project
- Distinguish between overlapping quality concepts: style guides, linting, formatting, type hints, and type checking—understanding what each one does and how they complement each other
- Learn about Python’s style guide (PEP 8) and why it matters for professional development
- Master modern quality tools:
- Ruff: The all-in-one linter and formatter that enforces PEP 8 and catches bugs
- Pyright: The type checker that validates type annotations and prevents type-related errors
- Understand why we need both Ruff AND Pyright: They work on different layers (style/bugs vs. type safety)
- Apply automated checks to catch issues before human review
- Build quality into your workflow from day one, not as an afterthought
- Appreciate the business case for code quality: reduced costs, faster debugging, and better team collaboration
3. Part 1: The Reality Check - Our Code Under Scrutiny
Let’s look at real code from the Road Profile Viewer project and see what automated tools find.
3.1 The Code We Wrote
Here’s a snippet from main.py:
"""
Road Profile Viewer - Interactive 2D Visualization
"""
import numpy as np
from dash import Dash, html, dcc, Input, Output
import plotly.graph_objects as go
import sys,os # Multiple imports on one line
def generate_road_profile(num_points=100,x_max=80):
"""Generate a road profile using a clothoid-like approximation."""
x = np.linspace(0, x_max, num_points)
x_norm = x / x_max
# Clothoid approximation
y=0.015 * x_norm**3 * x_max + 0.3 * np.sin(2 * np.pi * x_norm)
y = y - y[0]
return x,y
def HelperFunction(val):
"""Unused helper function that violates naming convention"""
result=val*2
return result
# Later in the code...
camera_x,camera_y = 0,2.0
marker=dict(size=12,color='red',symbol='circle')
# Add camera ray - This is a very long comment that exceeds the recommended 79 character line length limit specified in PEP8 style guide
First impression? Looks reasonable. It works. But let’s see what the tools say…
3.2 Running Automated Checks
$ uv run ruff check .
src/road_profile_viewer/main.py:16:1: E401 Multiple imports on one line
src/road_profile_viewer/main.py:16:8: F401 `sys` imported but unused
src/road_profile_viewer/main.py:16:12: F401 `os` imported but unused
src/road_profile_viewer/main.py:24:5: E701 Multiple statements on one line (colon)
src/road_profile_viewer/main.py:29:5: E225 Missing whitespace around operator
src/road_profile_viewer/main.py:32:12: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:36:1: N802 Function name should be lowercase
src/road_profile_viewer/main.py:36:1: F841 Local variable `HelperFunction` is assigned but never used
src/road_profile_viewer/main.py:38:11: E225 Missing whitespace around operator
src/road_profile_viewer/main.py:43:14: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:43:24: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:45:8: E225 Missing whitespace around operator
src/road_profile_viewer/main.py:45:13: E231 Missing whitespace after ','
src/road_profile_viewer/main.py:47:1: E501 Line too long (149 > 88 characters)
Found 14 errors.
14 errors in code that works perfectly! This is the difference between “runs on my machine” and “meets professional standards.”
These automated tools are checking against Python’s established style guidelines and best practices. We’ll explore the foundation of these standards—Python’s official style guide—later in this lecture. For now, let’s understand what each issue means and why it matters.
Let’s break down each issue and understand why it matters.
4. Part 2: Understanding Code Quality Issues
4.1 Issue Category 1: Import Violations
4.1.1 The Problem
import sys,os # BAD: Multiple imports on one line
What’s wrong?
- Multiple imports on one line (should be separate)
- Both
sysandosare imported but never used
Ruff reports:
E401: Multiple imports on one lineF401: ‘sys’ imported but unusedF401: ‘os’ imported but unused
4.1.2 Why This Matters
For Computers: Importing unused modules wastes memory and startup time. In this small example it’s negligible, but in large applications with dozens of unused imports, it accumulates.
For Humans:
When you see import sys, you expect the code to use sys somewhere. Unused imports create confusion:
- “Why do we need
syshere?” - “Am I missing something?”
- “Can I safely remove this?”
For Maintainability: Unused imports accumulate over time. Someone adds an import for a quick test, forgets to remove it, and it stays forever. After a few years, your file has 20 imports but only uses 10. Good luck figuring out which ones are safe to remove!
4.1.3 The Fix
# GOOD: Separate imports, only import what you use
import numpy as np
from dash import Dash, html, dcc, Input, Output
import plotly.graph_objects as go
Python Best Practice: Imports should usually be on separate lines for better readability and maintainability.
# Good
import os
import sys
# Bad
import sys, os
4.2 Issue Category 2: Missing Whitespace
4.2.1 The Problem
# BAD: Missing spaces everywhere
def generate_road_profile(num_points=100,x_max=80):
y=0.015 * x_norm**3 * x_max
return x,y
camera_x,camera_y = 0,2.0
result=val*2
marker=dict(size=12,color='red',symbol='circle')
Ruff reports:
E231: Missing whitespace after ‘,’E225: Missing whitespace around operator
4.2.2 Why This Matters
Readability is King
Compare these two versions:
# Hard to read - everything runs together
marker=dict(size=12,color='red',symbol='circle')
# Easy to read - clear separation
marker = dict(size=12, color='red', symbol='circle')
Which one can you parse faster? The second one, obviously.
The Science Behind It
Research in cognitive psychology shows that humans parse visual information in “chunks.” Whitespace creates natural boundaries between chunks, making code easier to scan and understand.
Your brain processes the second version ~30% faster because it doesn’t have to work as hard to segment the information.
Real-World Impact
- Code review time: Developers spend 60-80% of their time reading code, not writing it
- Bug detection: Dense, cramped code hides bugs more effectively than well-spaced code
- Onboarding: New team members understand well-spaced code 30-40% faster
Version Control Benefits
Consistent whitespace placement helps avoid unwanted diffs in future commits. Consider this scenario:
# Inconsistent spacing - changes create noisy diffs
marker=dict(size=12,color='red') # First commit
marker = dict(size=12, color='red', symbol='circle') # Later: added parameter
The diff shows the entire line changed, making it hard to see that only symbol='circle' was added. Compare to:
# Consistent spacing from the start - clean diffs
marker = dict(size=12, color='red') # First commit
marker = dict(size=12, color='red', symbol='circle') # Later: only new param visible
Key Principle: When everyone on the team follows the same whitespace rules, git diffs show actual logic changes instead of style changes. This makes code reviews faster and more effective.
4.2.3 The Fix
# GOOD: Proper spacing
def generate_road_profile(num_points=100, x_max=80):
y = 0.015 * x_norm**3 * x_max
return x, y
camera_x, camera_y = 0, 2.0
result = val * 2
marker = dict(size=12, color='red', symbol='circle')
Professional Python Style:
- Always surround these binary operators with a single space on either side: assignment (
=), augmented assignment (+=,-=, etc.), comparisons (==,<,>,!=,<=,>=,in,not in,is,is not), Booleans (and,or,not) - Use spaces after commas in argument lists, function calls, and data structures
4.3 Issue Category 3: Naming Conventions
4.3.1 The Problem
def HelperFunction(val): # BAD: PascalCase for function
"""Unused helper function"""
result = val * 2
return result
Ruff reports:
N802: Function name should be lowercaseF841: Local variableHelperFunctionis assigned but never used
4.3.2 Why This Matters
Naming Conventions are a Shared Language
In Python, there are established naming patterns that signal different types of code elements:
PascalCasemeans “this is a class” (e.g.,RoadProfile,CameraRay)snake_casemeans “this is a function or variable” (e.g.,calculate_total,camera_x)UPPER_CASEmeans “this is a constant” (e.g.,MAX_SPEED,PI)
These conventions are formalized in Python’s official style guide, which we’ll explore in detail later in this lecture.
When you see HelperFunction(), your brain thinks “class.” When you try to use it and discover it’s a function, you experience cognitive dissonance. This tiny moment of confusion happens hundreds of times while reading code.
The Principle: Least Astonishment
Code should behave as readers expect. Breaking conventions creates astonishment (and frustration).
Real Example from Production
# Someone wrote this in a real codebase
DatabaseConnection = connect_to_db() # Looks like a class!
# Later, another developer tried:
conn = DatabaseConnection() # TypeError: 'Connection' object is not callable
This cost 2 hours of debugging because the naming violated expectations.
4.3.3 The Fix
# GOOD: Clear naming convention
def helper_function(val):
"""Calculate double of input value."""
result = val * 2
return result
But wait! This function is also never used…
4.4 Issue Category 4: Dead Code
4.4.1 The Problem
def HelperFunction(val):
"""Unused helper function"""
result = val * 2
return result
# This function is never called anywhere in the code
Ruff reports:
F841: Local variableHelperFunctionis assigned but never used
4.4.2 Why This Matters
Dead Code is Technical Debt
Every line of code has a cost:
1. Maintenance Cost Someone has to maintain it, even if it’s not used:
- Updates for API changes
- Security patches
- Python version compatibility
2. Cognitive Cost Future developers must read and understand it:
- “Is this function important?”
- “Why was it written if it’s not used?”
- “Can I safely delete it?”
3. Testing Cost Dead code often gets tested anyway:
- Wasted test coverage
- False sense of security
Real-World Scenario
# You need to change how coordinates work
# You update all the coordinate functions...
def calculate_position(x, y, z): # ✅ Updated!
return Vector3D(x, y, z)
def transform_point(point): # ✅ Updated!
return point.to_3d()
def HelperFunction(val): # ❌ Forgot to update this!
# Still using old 2D logic
return val * 2
The dead code doesn’t break anything (it’s not called), but it sits there like a ticking time bomb. If someone discovers it later and tries to use it, it will use outdated logic.
4.4.3 The Fix
# GOOD: Delete dead code
# If you need it later, that's what version control (Git) is for!
Key Principle: If code isn’t being used, remove it. Git remembers everything.
4.5 Issue Category 5: Line Length
4.5.1 The Problem
# Add camera ray - This is a very long comment that exceeds the recommended 79 character line length limit specified in PEP8 style guide
Ruff reports:
E501: Line too long (149 > 88 characters)
4.5.2 Why This Matters
Screen Real Estate is Limited
Consider these scenarios:
Scenario 1: Side-by-side Code Review
[Your Code File ] | [Reviewer's Comments ]
[ ] | [ ]
With 150-character lines, the reviewer must scroll horizontally constantly. This is exhausting and error-prone.
Scenario 2: Split-Screen Development
[Implementation File] | [Test File ]
[Old Code Version ] | [New Code Version ]
Long lines force you to work full-screen, losing productivity benefits of split views.
Scenario 3: Code Diffs
- marker=dict(size=12,color='red',symbol='circle',shape='diamond',opacity=0.8,line=dict(width=2,color='black'))
+ marker=dict(size=12,color='blue',symbol='circle',shape='diamond',opacity=0.8,line=dict(width=2,color='black'))
Can you spot the change? It’s color='red' → color='blue', but it’s buried in a 120-character line.
Compare to:
marker = dict(
size=12,
- color='red',
+ color='blue',
symbol='circle',
shape='diamond',
)
The change is immediately obvious.
The Research
Studies show comprehension drops significantly when reading lines longer than 60-80 characters. Your eyes must make longer saccades (jumps), and you lose your place more easily.
4.5.3 The Fix
# GOOD: Break long lines logically
marker = dict(
size=12,
color='red',
symbol='circle'
)
# GOOD: Break long comments
# Add camera ray - This comment is broken into multiple lines
# to respect recommended line length limits for better readability
Professional Style Guideline:
- Limit all lines to a maximum of 79 characters
- For docstrings or comments, limit to 72 characters
- Modern interpretation: 88-100 characters is acceptable (Black/Ruff default)
5. Part 3: The Bigger Picture - Why Code Quality Matters
Now that we’ve seen specific issues, let’s zoom out and understand the principles.
5.1 Principle 1: Code is Read More Than Written
The 10:1 Rule
For every hour spent writing code, approximately 10 hours are spent reading it.
Think about your development process:
- You write a feature (1 hour)
- You debug it (read your code) (30 min)
- Your colleague reviews it (reads your code) (20 min)
- Future you adds a feature (reads old code) (1 hour)
- New team member learns the codebase (reads your code) (2 hours)
- Someone fixes a bug (reads your code) (1 hour)
Total: 5.5 hours reading, 1 hour writing.
Code quality optimizes for reading, not writing.
5.2 Principle 2: Consistency Reduces Cognitive Load
Your brain is a pattern-matching machine. When code is consistent:
- You recognize patterns instantly
- You spot anomalies quickly
- You understand intent faster
Example:
# Inconsistent spacing - your brain must parse each line individually
x_road,y_road = generate_road_profile(num_points=100, x_max=80)
camera_x, camera_y = 0, 2.0
result= calculate_value(a,b)
# Consistent spacing - your brain recognizes the pattern
x_road, y_road = generate_road_profile(num_points=100, x_max=80)
camera_x, camera_y = 0, 2.0
result = calculate_value(a, b)
The second version feels “smoother” because your brain doesn’t context-switch for each line.
5.3 Principle 3: Early Detection Prevents Expensive Bugs
The Cost of Bugs Grows Exponentially
| When Found | Relative Cost | Example |
|---|---|---|
| While writing | 1x | "Oops, typo!" (5 seconds to fix) |
| During code review | 10x | "Let me push a fix..." (5 minutes) |
| During QA testing | 100x | "Need to debug, fix, retest" (1 hour) |
| In production | 1000x+ | "Emergency hotfix, customer compensation, reputation damage" (days/weeks) |
Real Example from Our Code
def find_intersection(x_road, y_road, angle_degrees, camera_x=0, camera_y=1.5):
# ... code ...
return x_intersect, y_intersect, distance
# Later:
x_intersect, y_intersect, distance = find_intersection(...)
This works fine. But imagine if someone changed it without type hints:
def find_intersection(x_road, y_road, angle_degrees, camera_x=0, camera_y=1.5):
# ... code ...
return distance # Oops! Forgot to return x and y
# Later:
x_intersect, y_intersect, distance = find_intersection(...) # BOOM! Runtime error
Without type checking: This bug reaches production, crashes when a user adjusts the angle.
With type checking:
def find_intersection(
x_road: np.NDArray,
y_road: np.NDArray,
angle_degrees: float,
camera_x: float = 0,
camera_y: float = 1.5
) -> tuple[float, float, float] | tuple[None, None, None]:
# ... code ...
return distance # Type checker: Error! Expected tuple[float, float, float]
Caught in 2 seconds, not 2 weeks.
5.4 Principle 4: Automation Scales, Humans Don’t
The Math
Imagine you’re a team lead with 10 developers. Without automation:
Manual code review per pull request: 30 minutes
Pull requests per week: 50
Total review time: 25 hours/week
You need to hire someone just for code reviews!
With automation:
Automated checks per pull request: 2 minutes
Pull requests per week: 50
Total automated time: 100 minutes/week
Human review time per PR: 10 minutes (focusing on logic, not style)
Total human time: 8.3 hours/week
Time saved: 16.7 hours/week = 867 hours/year per team
At $100/hour (typical developer cost), that’s $86,700 saved per year.
6. Part 4: PEP 8 - The Foundation of Python Code Quality
Throughout this lecture, we’ve seen automated tools catch issues like import formatting, spacing, naming conventions, and line length. You might be wondering: Where do these rules come from? Who decides what’s “correct”?
The answer is PEP 8—Python’s official style guide that forms the foundation of all these quality standards.
6.1 What is PEP 8?
PEP 8 is Python’s official style guide. PEP stands for “Python Enhancement Proposal”—it’s how Python evolves as a language.
- Written by: Guido van Rossum (Python’s creator), Barry Warsaw, and Nick Coghlan
- First published: 2001
- Status: Active (updated regularly)
- Official document: python.org/dev/peps/pep-0008
PEP 8 is not suggestions—it’s the community-agreed standard.
6.2 Why Follow PEP 8?
6.2.1 Reason 1: It’s a Common Language
When you follow PEP 8:
- Your code looks like Python code
- Other Python developers instantly understand your style
- You can read any PEP 8-compliant code easily
It’s like using standard English spelling. You could spell “color” as “colur”, but nobody would understand you.
6.2.2 Reason 2: Tooling Expects It
All major Python tools assume PEP 8:
- IDEs provide auto-complete based on PEP 8 naming
- Linters check against PEP 8 rules
- Formatters enforce PEP 8 style
- Documentation generators expect PEP 8 conventions
Fighting PEP 8 means fighting the entire ecosystem.
6.2.3 Reason 3: Hiring and Career
When companies hire Python developers, they expect PEP 8 knowledge. Code that doesn’t follow PEP 8 is a red flag in:
- Technical interviews
- GitHub portfolios
- Open-source contributions
- Code samples for job applications
6.3 Key PEP 8 Rules (Quick Reference)
Indentation
# Use 4 spaces per indentation level (not tabs)
def example_function():
if True:
print("Four spaces")
Line Length
# Limit lines to 79 characters (or 88 with modern tools)
# Break long lines at logical points
result = some_function(
argument1,
argument2,
argument3
)
Imports
# Imports at top of file, grouped in order:
# 1. Standard library
# 2. Third-party
# 3. Local application
import os
import sys
import numpy as np
from dash import Dash
from myapp import utils
Naming Conventions
# Functions and variables: snake_case
def calculate_average(numbers):
total_sum = sum(numbers)
return total_sum / len(numbers)
# Classes: PascalCase
class RoadProfile:
pass
# Constants: UPPER_CASE
MAX_SPEED = 100
PI = 3.14159
Whitespace
# Yes: Spaces around operators and after commas
result = x + y
my_list = [1, 2, 3]
func(a, b, c)
# No: Missing spaces
result=x+y
my_list=[1,2,3]
func(a,b,c)
Comments
# Use inline comments sparingly
x = x + 1 # Compensate for border
# Use docstrings for functions
def calculate_average(numbers):
"""
Calculate the arithmetic mean of a list of numbers.
Args:
numbers: List of numeric values
Returns:
float: The average value
"""
return sum(numbers) / len(numbers)
7. Part 5: Modern Tools - The Ruff Revolution
7.1 The Evolution of Python Code Quality Tools
Traditional Approach (Pre-2023):
- Flake8 for linting
- Black for formatting
- isort for import sorting
- mypy for type checking
- pylint for additional checks
Each tool had its own configuration, ran separately, and took significant time.
Modern Approach (2024+):
- Ruff for linting, formatting, and import sorting
- Pyright for type checking
Two tools. Blazingly fast. One configuration file.
7.2 What is Ruff?
Ruff is a modern Python linter and formatter written in Rust. It’s developed by Astral, the same company behind uv.
Key Features:
- 10-100x faster than traditional Python tools
- Replaces Flake8, Black, isort, and many Flake8 plugins
- 800+ rules from multiple sources
- Auto-fix for many issues
- Zero configuration to get started
- Compatible with existing Black/Flake8 configs
7.3 Installing Ruff - Understanding Dependency Management
Before we can use Ruff, we need to understand where and how to install it. This is crucial for making our commands work!
7.3.1 The Problem: uv run ruff Won’t Work (Yet)
If you try to run Ruff right now:
$ uv run ruff check .
error: program not found: ruff
Why? Because uv run looks for ruff in your project’s .venv directory, and we haven’t installed it there yet!
This leads us to an important question: Where should we install Ruff?
7.3.2 Three Ways to Use Python Packages
Understanding how to properly manage dependencies is crucial for:
- Reproducibility: Ensuring your project works the same way on different machines
- Collaboration: Team members can instantly get the exact same development environment
- Maintenance: Clear separation between production code and development tools
- Efficiency: Avoiding confusion about where packages are installed and how to run them
Let’s explore the three contexts where Python packages can be installed:
1. Project Dependencies ([project.dependencies])
What: Packages your application NEEDS to run in production.
For our Road Profile Viewer, these are the packages that must be installed for the application to work:
[project]
dependencies = [
"dash>=2.14.0",
"plotly>=5.18.0",
"numpy>=1.26.0",
]
Add with:
uv add dash plotly numpy
When to use: When the package is required for the application to run.
2. Development Dependencies ([dependency-groups.dev])
What: Tools you need for DEVELOPMENT but not for running the app (linters, formatters, test frameworks).
This is where Ruff belongs! Ruff is a development tool—we use it to check and format our code, but the Road Profile Viewer application doesn’t need Ruff to run.
[dependency-groups]
dev = [
"ruff>=0.8.0",
"pytest>=7.0.0",
]
Add with:
uv add --dev ruff pytest
When to use: When you need the tool for development, testing, or code quality but not for running the application.
3. Global Tools (uv tool install)
What: CLI tools you want available system-wide across ALL projects.
uv tool install ruff
When to use: When you want a tool available everywhere without adding it to each project’s dependencies.
Trade-off:
- ✅ Available in all projects instantly
- ❌ Not documented in
pyproject.toml(team members won’t know to install it) - ❌ Version may differ across team members
7.3.3 Decision Tree: Where Should Ruff Go?
Is Ruff needed to RUN the Road Profile Viewer application?
└─ NO → It's a development tool
Is Ruff specific to THIS project?
└─ YES → We want consistent versions across the team
Do we want it documented in version control?
└─ YES → Team members should see it in pyproject.toml
✅ Decision: Install as development dependency
7.3.4 Installing Ruff for Our Project
Step 1: Add Ruff as a development dependency
uv add --dev ruff
This modifies your pyproject.toml:
[project]
name = "road-profile-viewer"
version = "0.1.0"
dependencies = [
"dash>=2.14.0",
"plotly>=5.18.0",
"numpy>=1.26.0",
]
[dependency-groups]
dev = [
"ruff>=0.8.0", # ← Added here!
]
Step 2: Install all dependencies (including dev)
uv sync --group dev
This installs:
- All production dependencies (dash, plotly, numpy)
- All development dependencies (ruff)
Step 3: Verify Ruff is installed
uv run ruff --version
# Output: ruff 0.8.0
Now uv run ruff will work! 🎉
7.3.5 Understanding uv run Behavior
When you run uv run <command>, here’s what happens:
1. uv looks for pyproject.toml (walking up the directory tree)
2. uv checks if <command> exists in the project's .venv
3. If found: runs it with the project's Python environment
4. If not found: error "program not found"
Common Confusion:
❌ This DOESN’T work (Ruff not in .venv yet):
uv run ruff check .
# Error: program not found
✅ This WORKS (after uv add --dev ruff):
uv run ruff check .
# Runs successfully!
✅ This WORKS (temporary installation, doesn’t modify project):
uv run --with ruff ruff check .
# Creates temporary environment with ruff and runs it
✅ This WORKS (if installed globally):
uv tool install ruff
ruff check . # Run directly, no uv run needed
7.3.6 Best Practice: Document Development Setup
Add a section to your README.md:
## 8. Development Setup {#development-setup}
### 8.1 Installing Dependencies {#installing-dependencies}
```bash
# Install all dependencies (including development tools)
uv sync --group dev
8.2 Available Development Commands
# Run linter
uv run ruff check .
# Auto-fix linting issues
uv run ruff check --fix .
# Format code
uv run ruff format .
# Run application
uv run road-profile-viewer
This ensures new team members know exactly how to set up their environment!
8.2.1 Summary: Dependency Management with uv
| Command | Purpose | Modifies pyproject.toml? |
|---|---|---|
uv add <package> |
Add production dependency | ✅ Yes → [project.dependencies] |
uv add --dev <package> |
Add development dependency | ✅ Yes → [dependency-groups.dev] |
uv sync |
Install production dependencies only | ❌ No |
uv sync --group dev |
Install all dependencies (prod + dev) | ❌ No |
uv run <command> |
Run command in project environment | ❌ No |
uv run --with <pkg> <cmd> |
Temporary package usage | ❌ No |
uv tool install <package> |
Install global CLI tool | ❌ No |
Key Takeaway: Development tools like Ruff should be installed with uv add --dev to:
- ✅ Document them in
pyproject.toml - ✅ Ensure consistent versions across team
- ✅ Make setup reproducible
- ✅ Enable
uv runto find them
8.3 Ruff Check - The Linter
Now that Ruff is properly installed as a development dependency, let’s explore what it can do!
What it does: Analyzes code for errors, bugs, style violations, and code smells.
Running Ruff Check:
# Check all files in current directory
uv run ruff check .
# Check specific file
uv run ruff check src/main.py
# Auto-fix issues where possible
uv run ruff check --fix .
# Show what would be fixed without changing files
uv run ruff check --fix --diff .
Example Output:
$ uv run ruff check .
src/main.py:16:1: E401 [*] Multiple imports on one line
src/main.py:16:8: F401 [*] `sys` imported but unused
src/main.py:29:5: E225 [*] Missing whitespace around operator
src/main.py:36:1: N802 Function name `HelperFunction` should be lowercase
src/main.py:47:1: E501 Line too long (149 > 88 characters)
Found 5 errors.
[*] 3 fixable with the `--fix` option.
The [*] indicates auto-fixable issues.
8.4 Ruff Format - The Code Formatter
What it does: Automatically reformats code to follow consistent style (compatible with Black).
Running Ruff Format:
# Format all files
uv run ruff format .
# Check if formatting is needed (doesn't modify files)
uv run ruff format --check .
# Show what would change (diff mode)
uv run ruff format --diff .
Before:
def generate_road_profile(num_points=100,x_max=80):
y=0.015 * x_norm**3 * x_max
return x,y
After:
def generate_road_profile(num_points=100, x_max=80):
y = 0.015 * x_norm**3 * x_max
return x, y
8.5 Configuring Ruff
8.5.1 Understanding pyproject.toml Sections
Remember that pyproject.toml has two main types of sections:
- Standardized sections (defined by Python PEPs):
[project]- Project metadata and dependencies[dependency-groups]- Optional dependency groups likedev
- Tool-specific sections using
[tool.*]namespace:[tool.ruff]- Ruff configuration[tool.pytest]- pytest configuration[tool.mypy]- mypy configuration
This keeps all tool configurations in one file instead of scattered across .ruff.toml, .pytest.ini, etc.
8.5.2 Why Customize Ruff?
Ruff works great with its defaults, but you’ll want to customize it because:
1. Project-Specific Needs
- Your team might use 100-character lines instead of Ruff’s default 88
- You might need specific Python version compatibility (e.g.,
py312vspy310) - Legacy code might need certain rules temporarily ignored
2. Enable More Rules
- By default, Ruff only enables basic checks (similar to Flake8)
- You can enable hundreds of additional rules:
I- Import sorting (like isort)N- Naming conventions (PEP 8)B- Bug detection (like flake8-bugbear)UP- Modern Python syntax upgrades
3. Team Consistency
- Configuration in
pyproject.tomlensures everyone uses the same rules - Avoid arguments: “It works on my machine!” → “Check the pyproject.toml!”
4. CI/CD Integration
- Same configuration runs locally and in automated pipelines
- No surprises when code passes locally but fails in CI
8.5.3 Your Ruff Configuration
Using AI to Focus on Requirements, Not Syntax
Instead of memorizing TOML syntax and Ruff configuration options, let’s use AI to translate our requirements into configuration. This is modern development: focus on what you need, let AI handle the syntax details.
Step 1: Define Your Requirements
Think about what your project needs:
- Maximum line length: 88 characters (Black/Ruff default)
- Python version: 3.12
- Additional checks beyond basics: import sorting, naming conventions, bug detection, modern syntax
- Code formatting: double quotes, Unix line endings
Step 2: Ask AI to Generate Configuration
Use a prompt like this in GitHub Copilot Chat or any AI assistant:
Create a [tool.ruff] configuration for pyproject.toml with:
- Line length: 88
- Target Python 3.12
- Exclude .git, .venv, __pycache__, build, dist
- Enable these rule groups: pycodestyle (E/W), Pyflakes (F), isort (I),
pep8-naming (N), flake8-bugbear (B), flake8-comprehensions (C4), pyupgrade (UP)
- Allow auto-fix for all rules
- Format with double quotes, Unix line endings, space indentation
Step 3: Review and Apply
The AI generates the configuration. Always review it - you’re the expert on your project requirements:
[project]
name = "road-profile-viewer"
version = "0.1.0"
description = "Interactive road profile visualization"
requires-python = ">=3.12"
[tool.ruff]
# Set maximum line length
line-length = 88
# Target Python version
target-version = "py312"
# Exclude certain directories
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
]
[tool.ruff.lint]
# Select rules to enable
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort (import sorting)
"N", # pep8-naming
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
# Ignore specific rules if needed
ignore = []
# Allow auto-fix for these rules
fixable = ["ALL"]
unfixable = []
[tool.ruff.format]
# Use double quotes (like Black)
quote-style = "double"
# Use Unix line endings
line-ending = "lf"
# Indent with spaces
indent-style = "space"
Why This Approach Works:
✅ Focus on Intent - You specify what you want, not how to write it
✅ Faster Development - No looking up exact configuration keys
✅ Learn by Reviewing - You see correct syntax and can modify it
✅ Avoid Syntax Errors - AI knows the exact TOML structure
Remember: AI is your syntax assistant, but YOU decide the requirements based on your project needs!
8.6 Ruff in VS Code - Real-Time Feedback
While command-line Ruff is powerful, the Ruff VS Code extension brings code quality directly into your editor with instant feedback as you type.
8.6.1 Installing the Ruff Extension
Method 1: VS Code Extensions Panel
- Open VS Code
- Click the Extensions icon (or press
Ctrl+Shift+X/Cmd+Shift+X) - Search for “Ruff”
- Click Install on the official extension by Astral
Method 2: Command Line
code --install-extension charliermarsh.ruff
Method 3: Quick Open
- Press
Ctrl+P/Cmd+P - Type:
ext install charliermarsh.ruff - Press Enter
8.6.2 Real-Time Linting - Instant Feedback
Once installed, Ruff automatically analyzes your code as you type, showing issues with colored underlines:
Red Squiggly Lines = Errors (must fix)
import sys,os # Red underline: E401 Multiple imports on one line
Yellow Squiggly Lines = Warnings (should fix)
def HelperFunction(val): # Yellow underline: N802 Function name should be lowercase
Hover to See Details:
- Move your mouse over any underlined code
- A popup shows:
- Rule code (e.g.,
E401) - Description of the violation
- Quick fix button if auto-fixable
- Rule code (e.g.,
Problems Panel:
- Press
Ctrl+Shift+M/Cmd+Shift+Mto open Problems panel - See all issues across all files
- Click any issue to jump to that line
- Filter by errors, warnings, or information
Benefits:
- Catch issues immediately - No need to run commands
- Learn as you code - See explanations for violations
- Context-aware - Only shows relevant issues for your file
- Team consistency - Everyone sees the same issues in their editor
8.6.3 Auto-Fix with Keyboard Shortcuts
The Ruff extension can automatically fix many issues without leaving your editor.
Quick Fix (Single Issue):
- Place cursor on underlined code
- Press
Ctrl+./Cmd+.(or click the lightbulb 💡 icon) - Select “Ruff: Fix all auto-fixable problems”
- Issue is fixed instantly!
Example:
# Before (cursor on this line)
import sys,os,json # E401 violation
# Press Ctrl+. and select fix
# After
import sys
import os
import json
Format Document (Auto-Format Everything):
- Press
Shift+Alt+F/Shift+Option+F - Or right-click → “Format Document”
- Ruff formats the entire file according to your style rules
Format Selection:
- Select specific code
- Press
Ctrl+K Ctrl+F/Cmd+K Cmd+F - Only the selected code is formatted
Fix All Auto-Fixable Issues:
- Open Command Palette:
Ctrl+Shift+P/Cmd+Shift+P - Type: “Ruff: Fix all auto-fixable problems”
- Press Enter
- All auto-fixable issues in the current file are fixed
8.6.4 Format on Save - Zero Effort
Configure VS Code to automatically format and fix issues when you save a file.
Enable Format on Save:
- Open Settings:
Ctrl+,/Cmd+, - Search for:
format on save - Check ✅ Editor: Format On Save
Configure Ruff as Default Formatter:
Add to your .vscode/settings.json (in your project):
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
}
}
What this does:
- formatOnSave: Runs
ruff formatwhen you save - fixAll.ruff: Auto-fixes all fixable issues
- organizeImports.ruff: Sorts and organizes imports
Result: Press Ctrl+S / Cmd+S → Code is instantly formatted and cleaned! 🎉
8.6.5 Practical Workflow Example
Scenario: You’re writing new code with multiple PEP 8 violations.
Without VS Code Extension:
# You write this code
def ProcessData(input,threshold=10):
result=[]
for item in input:
if item>threshold:result.append(item*2)
return result
# Later, you run command line
$ uv run ruff check main.py
# See 8 violations
# Manually fix or use --fix
# Re-run to verify
With VS Code Extension:
# As you type, you see:
def ProcessData(input,threshold=10): # Yellow underline: N802
# ^ Yellow underline: E231
result=[] # Yellow underline: E225
# ^
for item in input:
if item>threshold:result.append(item*2)
# ^E225 ^E701 compound statement
# You press Ctrl+. on function name → Quick fix "Use lowercase"
# You press Shift+Alt+F → Format entire document
# Result (instant):
def process_data(input, threshold=10):
result = []
for item in input:
if item > threshold:
result.append(item * 2)
return result
# All violations fixed in 2 seconds! 🚀
8.6.6 Configuration Tips
Workspace Settings (Project-specific)
Create .vscode/settings.json in your project:
{
"ruff.lint.args": ["--config=pyproject.toml"],
"ruff.format.args": ["--config=pyproject.toml"],
"ruff.organizeImports": true,
"ruff.fixAll": true,
"editor.rulers": [88],
"files.trimTrailingWhitespace": true
}
User Settings (All projects)
Settings → Search “ruff” → Configure:
- ✅ Ruff: Enable (default: true)
- ✅ Ruff: Organize Imports
- ✅ Ruff: Fix All
- Set Ruff: Path if using custom Ruff installation
Disable Conflicting Extensions:
If you have these installed, disable them for Python files:
- Pylint
- Flake8
- Black formatter
- autopep8
They conflict with Ruff and slow down your editor.
8.6.7 Troubleshooting
Issue: Ruff extension not working
✅ Solution:
- Check extension is installed:
Ctrl+Shift+X→ Search “Ruff” - Check extension is enabled for workspace
- Reload VS Code:
Ctrl+Shift+P→ “Reload Window” - Check Output panel: View → Output → Select “Ruff”
Issue: Format on save not working
✅ Solution:
- Verify settings.json configuration
- Check Ruff is default formatter:
Ctrl+Shift+P→ “Format Document With…” → Select “Ruff” - Save file and check Problems panel for errors
Issue: Too many warnings
✅ Solution:
- Configure
pyproject.tomlto ignore specific rules - Adjust severity: Settings → Search “ruff” → Set severity level
- Use
# noqacomments for specific lines (sparingly!)
import sys # noqa: F401 - needed for debugging
8. Part 6: Type Checking with Pyright
8.1 Why Type Checking Matters
Python is dynamically typed, which is flexible but dangerous:
def calculate_discount(price, discount_percent):
return price * (discount_percent / 100)
# This will crash at runtime!
result = calculate_discount("50", "10")
Type hints (Python 3.5+) let you add type information:
def calculate_discount(price: float, discount_percent: float) -> float:
return price * (discount_percent / 100)
# Type checker catches this BEFORE runtime
result = calculate_discount("50", "10") # Error: Expected float, got str
8.2 What is Pyright?
Pyright is a fast, feature-rich type checker for Python, developed by Microsoft.
Why Pyright over mypy?
| Feature | Pyright | mypy |
|---|---|---|
| Speed | 5-10x faster (TypeScript/Node.js) | Slower (Python-based) |
| VS Code Integration | Built into Pylance extension | Separate extension needed |
| Modern Python | Excellent 3.10+ support | Good support, slower updates |
| Type Inference | More intelligent | Good but less sophisticated |
| Active Development | Microsoft-backed, rapid updates | Slower update cycle |
For this course: We use Pyright because it matches your IDE (VS Code + Pylance), providing a consistent experience.
8.3 Installing Pyright
Just like Ruff, Pyright should be installed as a development dependency:
uv add --dev pyright
This adds Pyright to [dependency-groups.dev] in your pyproject.toml:
[dependency-groups]
dev = [
"ruff",
"pyright",
]
Sync and verify:
# Install dev dependencies
uv sync --group dev
# Test Pyright
uv run pyright --version
8.3.1 Windows Setup: Developer Mode Required
⚠️ Windows-Specific Issue:
Pyright is a TypeScript-based tool that requires Node.js. When you run uv run pyright for the first time, uv will automatically install Node.js (v25.0.0+) and create symlinks.
On Windows, you may see this error:
Error: You do not have sufficient permissions to perform this operation.
Why? Creating symlinks on Windows requires either:
- Administrator privileges, OR
- Developer Mode enabled (recommended)
Solution: Enable Windows Developer Mode
Run PowerShell as Administrator, then execute:
# Enable Developer Mode (requires admin)
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
After running this command:
- Restart your terminal
- Try
uv run pyright --versionagain - Node.js will be installed automatically and Pyright will work
Alternative (not recommended): Run VS Code as Administrator each time. Developer Mode is better because it’s a one-time setup.
8.4 Configuring Pyright - From Lenient to Strict
8.4.1 The Default Problem
By default, Pyright runs in basic mode - it’s very lenient and won’t catch many type issues:
uv run pyright
# Output: 0 errors found in 5 files
This seems good, but it’s misleading! Pyright is ignoring:
- Missing type hints
- Implicit
Anytypes - Unused variables
- Untyped function returns
Why so lenient? To work with existing Python codebases that have no type hints.
For new projects: We want stricter checking to catch issues early.
8.4.2 Using AI to Configure Strict Type Checking
Define Your Requirements:
For our course project, we want:
- Strict type checking mode
- Target Python 3.12
- Check all
.pyfiles insrc/directory - Report missing type hints
- Catch common type safety issues
Prompt for AI:
Create a [tool.pyright] configuration for pyproject.toml with:
- Type checking mode: strict
- Python version: 3.12
- Include: src directory
- Report missing type stubs: true
- Report unknown member type: warning
- Report unknown parameter type: warning
- Report unknown variable type: warning
Generated Configuration:
Add this to your pyproject.toml:
[tool.pyright]
# Strict type checking
typeCheckingMode = "strict"
# Python version
pythonVersion = "3.12"
# Include only source files
include = ["src"]
# Exclude directories
exclude = [
".venv",
"__pycache__",
"build",
"dist",
]
# Reporting options
reportMissingTypeStubs = true
reportUnknownMemberType = "warning"
reportUnknownParameterType = "warning"
reportUnknownVariableType = "warning"
reportUnknownArgumentType = "warning"
Now run Pyright again:
uv run pyright
You’ll likely see many errors now! This is good - these were hidden issues that Pyright is now catching.
Common errors you’ll see:
src/main.py:10:5 - error: Type of "x" is unknown (reportUnknownVariableType)
src/utils.py:25:15 - error: Return type is unknown (reportUnknownParameterType)
Type Checking Modes Explained:
| Mode | Strictness | Use Case |
|---|---|---|
off |
No checking | Legacy code, prototypes |
basic |
Minimal (default) | Gradual typing adoption |
standard |
Moderate | Most projects |
strict |
Maximum | New projects, high quality |
For this course: Use strict mode to learn proper type safety from the start.
8.5 Using Type Hints
Basic Types:
# Variables
name: str = "Alice"
age: int = 25
height: float = 1.75
is_student: bool = True
# Functions
def greet(name: str) -> str:
return f"Hello, {name}"
def calculate_total(items: list[float]) -> float:
return sum(items)
Optional Types (None Handling):
def find_user(user_id: int) -> User | None:
"""Returns User or None if not found."""
...
user = find_user(123)
# Pyright error: 'user' might be None
print(user.name)
# Correct way
if user is not None:
print(user.name)
Complex Types:
from typing import Callable
# Function that takes a callback
def process(callback: Callable[[int], str]) -> None:
result = callback(42)
print(result)
# Correct callback
def my_callback(x: int) -> str:
return f"Value: {x}"
process(my_callback) # OK
# Wrong callback
def wrong_callback(x: str) -> str:
return f"Value: {x}"
process(wrong_callback) # Pyright error: Signature mismatch
Applying Types to Road Profile Viewer:
import numpy as np
from numpy.typing import NDArray
def generate_road_profile(
num_points: int = 100,
x_max: float = 80
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
"""
Generate a road profile using a clothoid-like approximation.
Args:
num_points: Number of points to generate
x_max: Maximum x-coordinate value
Returns:
Tuple of (x_coordinates, y_coordinates)
"""
x = np.linspace(0, x_max, num_points)
x_norm = x / x_max
y = 0.015 * x_norm**3 * x_max + 0.3 * np.sin(2 * np.pi * x_norm)
y = y - y[0]
return x, y
def find_intersection(
x_road: NDArray[np.float64],
y_road: NDArray[np.float64],
angle_degrees: float,
camera_x: float = 0,
camera_y: float = 1.5
) -> tuple[float, float, float] | tuple[None, None, None]:
"""
Find intersection between camera ray and road profile.
Args:
x_road: X-coordinates of road profile
y_road: Y-coordinates of road profile
angle_degrees: Camera ray angle in degrees
camera_x: Camera X position
camera_y: Camera Y position
Returns:
Tuple of (x_intersect, y_intersect, distance) or (None, None, None)
"""
# Implementation...
pass
8.6 Running Pyright
After installation and configuration, check your code for type errors:
# Check all files (respects pyproject.toml configuration)
uv run pyright
# Check specific file
uv run pyright src/main.py
# Show detailed error information
uv run pyright --verbose
What to expect with strict mode:
- Initially many errors (missing type hints, unknown types)
- This is good - you’re catching issues early
- Add type hints gradually to fix errors
- Your code becomes more robust and maintainable
Example workflow:
# 1. Run Pyright
uv run pyright
# 2. Fix reported type issues in your code
# 3. Run again to verify
uv run pyright
# All clear!
# ✓ 0 errors found in 5 files
9. Summary: From Tools to Practice
9.1 Working Code ≠ Quality Code
Code that runs is just the beginning. Professional software must be:
- Readable - Easy for humans to understand
- Maintainable - Easy to modify and extend
- Consistent - Follows team and community standards
- Type-safe - Catches errors before runtime
9.2 The Tools You Need
For Quality Enforcement:
- Ruff - Fast linter and formatter (replaces Flake8, Black, isort)
- Pyright - Type checker integrated with VS Code
Run checks locally:
uv run ruff check --fix .
uv run ruff format .
uv run pyright
9.3 Follow PEP 8
Python’s style guide is not optional:
- Learn the key rules (spacing, naming, line length)
- Use tools that enforce PEP 8 automatically
- Trust the formatter to make style consistent
9.4 Type Hints Save Time
Type hints catch bugs before they reach production:
- Start with function signatures
- Use
|for union types (e.g.,str | None) - Let Pyright catch type mismatches
- Your future self will thank you
9.5 What’s Next?
You now know what to check and how to check it locally. But how do you ensure these checks run automatically on every commit, preventing bad code from reaching your repository?
In Chapter 02 (Feature Development) (Thursday, October 16th), we’ll explore:
- Continuous Integration/Continuous Deployment (CI/CD) - What it is and why it matters
- GitHub Actions - GitHub’s automation platform
- Automated Workflows - Running quality checks on every push
- Pre-commit Hooks - Local quality gates before code reaches GitHub
- Branch Protection - Enforcing quality standards before merge
We’ll transform your local quality checks into an automated pipeline that enforces quality 24/7, ensuring every commit meets your standards whether you remember to run the checks or not.
10. Understanding the Quality Landscape: Concepts and Tools
Before we dive into the practical assignment, let’s clarify the different quality concepts we’ve discussed and how they relate to each other. This landscape can be confusing because some concepts overlap, and the tools we use (Ruff and Pyright) handle multiple aspects of quality.
10.1 The Quality Concepts
10.1.1 Style Guide (PEP 8)
What it is: A document that defines coding conventions—the “grammar rules” of Python code.
What it covers:
- Code layout (indentation, line length, blank lines)
- Naming conventions (functions, classes, variables)
- Import organization
- Whitespace usage
- Comment style
Python’s realization: PEP 8 is Python’s official style guide written by Guido van Rossum and the Python community.
Analogy: Like a language style guide (APA, MLA, Chicago) for writing papers—it tells you the “correct” way to format your code.
Example rule:
“Function names should be lowercase with words separated by underscores” → calculate_total() not CalculateTotal()
10.1.2 Linting
What it is: Automated analysis of code to find errors, bugs, stylistic issues, and suspicious constructs.
What it covers:
- Style violations (spacing, naming, imports) ← overlaps with style guide
- Potential bugs (unused variables, undefined names)
- Code smells (overcomplicated code, duplicate code)
- Best practices (anti-patterns, outdated syntax)
Python’s realization with Ruff:
ruff check analyzes your code against 800+ rules including PEP 8 style rules, common bug patterns, and best practices.
Analogy: Like a grammar checker in Microsoft Word—it highlights issues and suggests fixes.
Example checks:
E501: Line too long (style issue)F401: Unused import (potential bug)B008: Do not perform function calls in argument defaults (bug-prone pattern)
Key insight: Linting is the process of checking code. PEP 8 is one of the standards being checked. Linters check for PEP 8 compliance AND much more.
10.1.3 Formatting
What it is: Automatically restructuring code to follow consistent style rules—a subset of what a linter checks.
What it covers:
- Whitespace (spaces around operators, after commas)
- Indentation (consistent tab/space usage)
- Line breaks (where to break long lines)
- Quote style (single vs. double quotes)
Python’s realization with Ruff:
ruff format automatically reformats your code to be PEP 8 compliant (Black-compatible style).
Analogy: Like “Auto-format” in Word—it fixes spacing and layout automatically.
Key distinction:
- Linter (
ruff check): Finds problems and reports them - Formatter (
ruff format): Fixes style problems automatically
Example:
# Before
x=1+2
# Linter says: "E225: Missing whitespace around operator"
# Formatter fixes it automatically:
x = 1 + 2
10.1.4 Type Hints
What it is: Optional annotations that specify what types of values variables, parameters, and return values should have.
What it covers:
- Function signatures (parameter types, return types)
- Variable annotations
- Complex types (lists, dicts, unions, optionals)
Python’s realization:
Built into Python 3.5+ using the typing module. Added to code by developers.
Syntax:
def calculate_total(prices: list[float]) -> float:
return sum(prices)
Analogy: Like declaring variable types in statically-typed languages (Java, C++), but optional in Python.
Key insight: Type hints are annotations you write. They don’t do anything by themselves—you need a type checker to validate them.
10.1.5 Type Checking
What it is: Automated analysis that verifies type hints are used consistently and correctly throughout your code.
What it covers:
- Detecting type mismatches (passing string where int expected)
- Finding missing type annotations
- Validating function call signatures
- Catching
Nonehandling errors
Python’s realization with Pyright:
pyright analyzes your type hints and infers types even where not explicitly annotated.
Analogy: Like a compiler’s type checker in Java or TypeScript—catches type errors before runtime.
Example:
def greet(name: str) -> str:
return f"Hello, {name}"
result = greet(42) # Pyright error: Argument of type 'int' cannot be assigned to parameter 'str'
Key distinction:
- Type hints: Annotations you write
- Type checking: Tool that validates those annotations
10.2 How the Concepts Overlap
Here’s where confusion often happens:
┌─────────────────────────────────────────────────────────────┐
│ PEP 8 │
│ (The Style Guide - The Rules) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Linting (ruff check) │ │
│ │ │ │
│ │ • Checks PEP 8 style rules ←───────────────────────────── Overlap!
│ │ • Checks for bugs (unused imports, undefined vars)│ │
│ │ • Checks best practices (anti-patterns) │ │
│ │ • Checks code smells │ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Formatting (ruff format) │ │ │
│ │ │ │ │ │
│ │ │ • Auto-fixes style issues ←─────────┼───────┼───── Subset!
│ │ │ • Whitespace, indentation, quotes │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Type System (Separate!) │
│ │
│ You write: Tool validates: │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ Type Hints │ ──→ │ Type Checking │ │
│ │ (Annotations) │ │ (pyright) │ │
│ └─────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key Overlaps:
- Linting includes style checking
ruff checkvalidates PEP 8 style rules- But it also checks for bugs and best practices that PEP 8 doesn’t cover
- Formatting is a subset of linting
- Both deal with code style
- Formatter auto-fixes, linter reports
- Formatter only handles layout/whitespace, linter checks logic too
- Type hints are checked by linters AND type checkers
ruff checkcan find missing type hints (if configured)pyrightvalidates that type hints are used correctly- Different tools, complementary purposes
10.3 The Python Realizations: Ruff and Pyright
10.3.1 Ruff: The Multi-Tool
Ruff combines multiple quality checks:
| Command | What it does | What concept | Example |
|---|---|---|---|
ruff check |
Linting | Style guide + bug detection | Finds E501 (line too long), F401 (unused import) |
ruff format |
Formatting | Style guide enforcement | Fixes spacing: x=1 → x = 1 |
ruff check --select I |
Import sorting | Style guide (PEP 8 imports) | Organizes imports alphabetically |
Why this is confusing: Ruff does linting AND formatting, which traditionally were separate tools (Flake8 + Black). Now it’s one tool with two modes.
Mental model:
ruff check= “Tell me what’s wrong”ruff format= “Fix the style for me”ruff check --fix= “Tell me what’s wrong AND fix what you can”
10.3.2 Pyright: The Type Police
Pyright focuses on type correctness:
| What it does | What concept | Example |
|---|---|---|
| Validates type hints | Type checking | Catches greet(42) when signature is greet(name: str) |
| Infers types | Type checking | Knows x = 5 means x: int even without annotation |
Finds None issues |
Type checking | Catches user.name when user might be None |
| Reports missing types | Code quality | Warns when functions lack type annotations |
Why use both Ruff AND Pyright?
- Ruff: Style, imports, bugs, general code quality
- Pyright: Type safety, catching type-related bugs
They complement each other—one doesn’t replace the other.
10.4 Your Quality Workflow
Step-by-step what happens:
# 1. You write code (with type hints)
def calculate_total(prices: list[float]) -> float:
x=sum(prices) # Oops, missing spaces
return x
# 2. Run the formatter
$ uv run ruff format .
→ Fixes spacing automatically: x = sum(prices)
# 3. Run the linter
$ uv run ruff check .
→ Checks PEP 8 compliance ✓
→ Checks for bugs ✓
→ Checks best practices ✓
# 4. Run the type checker
$ uv run pyright
→ Validates type hints are used correctly ✓
→ Infers types and catches mismatches ✓
Each tool has a specific job:
- Formatter (
ruff format): Makes code look consistent - Linter (
ruff check): Finds style issues + bugs + code smells - Type Checker (
pyright): Validates type safety
10.5 Common Confusion Clarified
Q: “Is linting the same as checking PEP 8?” A: No. Linting includes PEP 8 checks but also checks for bugs, best practices, and code smells. PEP 8 is just one standard that linters check against.
Q: “Do I need both ruff check and ruff format?”
A: Yes! They serve different purposes:
ruff format: Auto-fixes style (spacing, indentation)ruff check: Reports bugs, unused code, and issues that can’t be auto-fixed
Q: “Why do I need Pyright if Ruff checks code quality?” A: Ruff checks style and general bugs. Pyright checks type safety. Example:
def greet(name: str) -> str:
return f"Hello, {name}"
result = greet(42) # Ruff won't catch this, Pyright will!
Q: “Can Ruff fix everything automatically?”
A: No. ruff format fixes style. ruff check --fix fixes some issues (like unused imports). But logical bugs and naming violations need manual fixes.
Q: “Are type hints required?” A: No, Python works fine without them. But they:
- Catch bugs before runtime
- Improve IDE auto-complete
- Serve as documentation
- Make code more maintainable
Q: “If I follow PEP 8, why do I need tools?” A: Humans make mistakes and miss things. Tools:
- Never forget a rule
- Check hundreds of rules in seconds
- Are consistent across the entire team
- Free humans to focus on logic, not style
10.6 The Complete Picture
The quality stack for Python in 2025:
┌────────────────────────────────────────────┐
│ Developer writes code │
│ (with type hints) │
└─────────────────┬──────────────────────────┘
│
┌─────────┴─────────┐
│ │
▼ ▼
┌───────────────┐ ┌──────────────┐
│ Ruff │ │ Pyright │
│ │ │ │
│ • Check style │ │ • Check types│
│ • Format code │ │ • Infer types│
│ • Find bugs │ │ • Find None │
│ • Best pract. │ │ issues │
└───────────────┘ └──────────────┘
│ │
└─────────┬─────────┘
│
▼
┌─────────────────┐
│ Quality Code │
│ │
│ ✓ PEP 8 │
│ ✓ Less bugs │
│ ✓ Type-safe │
│ ✓ Consistent │
└─────────────────┘
Summary in one sentence: We use PEP 8 as our style guide, Ruff to enforce it and catch bugs, and Pyright to ensure type safety—three complementary layers of quality assurance.
11. Practical Assignment
11.1 Task: Fix the Road Profile Viewer
Let’s fix all the issues in our main.py file step by step.
11.2 Step 1: Remove Unused Imports
Before:
import sys,os # Multiple imports, both unused
After:
# Removed - not needed
11.3 Step 2: Fix Import Formatting
Before:
import numpy as np
from dash import Dash, html, dcc, Input, Output
import plotly.graph_objects as go
After:
import numpy as np
from numpy.typing import NDArray
from dash import Dash, Input, Output, dcc, html
import plotly.graph_objects as go
Notice:
- Imports grouped by category (standard lib, third-party)
- Alphabetically sorted within groups
numpy.typingadded for type hints
11.4 Step 3: Fix Function Spacing
Before:
def generate_road_profile(num_points=100,x_max=80):
y=0.015 * x_norm**3 * x_max
return x,y
After:
def generate_road_profile(num_points=100, x_max=80):
y = 0.015 * x_norm**3 * x_max
return x, y
11.5 Step 4: Add Type Hints
Before:
def generate_road_profile(num_points=100, x_max=80):
"""Generate a road profile using a clothoid-like approximation."""
# ...
return x, y
After:
def generate_road_profile(
num_points: int = 100,
x_max: float = 80
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
"""
Generate a road profile using a clothoid-like approximation.
Args:
num_points: Number of points to generate
x_max: Maximum x-coordinate value
Returns:
Tuple of (x_coordinates, y_coordinates) as NumPy arrays
"""
# ...
return x, y
11.6 Step 5: Fix Naming Convention
Before:
def HelperFunction(val):
result=val*2
return result
After:
# Removed - function was unused dead code
11.7 Step 6: Fix Variable Spacing
Before:
camera_x,camera_y = 0,2.0
marker=dict(size=12,color='red',symbol='circle')
After:
camera_x, camera_y = 0, 2.0
marker = dict(size=12, color='red', symbol='circle')
11.8 Step 7: Fix Long Lines
Before:
# Add camera ray - This is a very long comment that exceeds the recommended 79 character line length limit specified in PEP8 style guide
After:
# Add camera ray to the plot
# This shows the line from the camera in the direction of the specified angle
11.9 Running Checks After Fixes
$ uv run ruff check .
All checks passed!
$ uv run ruff format --check .
All files formatted correctly!
$ uv run pyright
0 errors, 0 warnings, 0 informations
Success! 🎉
12. Part 9: The Business Case for Code Quality
12.1 Why Companies Care About Code Quality
1. Faster Development
- Clean code is easier to modify
- Less time debugging
- Faster onboarding for new developers
2. Fewer Bugs
- Automated checks catch issues early
- Type checking prevents runtime errors
- Consistent code reduces confusion
3. Lower Maintenance Costs
- Well-structured code lasts longer
- Easier to update dependencies
- Reduced technical debt
4. Better Collaboration
- Consistent style removes friction
- Clear code reduces misunderstandings
- Automated checks eliminate style debates
5. Competitive Advantage
- Ship features faster
- Higher product quality
- More reliable software
12.2 Real-World Impact: A Case Study
Scenario: A startup with 20 developers.
Without Automated Quality Checks:
- 30 minutes per code review (mostly style issues)
- 50 pull requests per week
- 25 hours/week spent on manual review
- 5 bugs per week reach production (2 hours each to fix)
- Total cost: 35 hours/week = $175,000/year
With Automated Quality Checks:
- 10 minutes per code review (logic and design only)
- 50 pull requests per week
- 8.3 hours/week spent on human review
- 1 bug per week reaches production (2 hours to fix)
- Total cost: 10.3 hours/week = $51,500/year
Savings: $123,500 per year
Plus intangible benefits:
- Happier developers (less tedious work)
- Faster feature delivery
- Higher quality product
- Better team morale
13. Part 10: Common Pitfalls and How to Avoid Them
13.1 Pitfall 1: “I’ll Add Quality Checks Later”
The Problem: Adding quality checks to an existing codebase with thousands of violations is overwhelming.
The Solution: Start with quality checks from day one. Make the first commit be:
- Project structure
pyproject.tomlwith Ruff configuration- GitHub Actions workflow for quality checks
13.2 Pitfall 2: “My Code Works, Why Does Style Matter?”
The Problem: Focusing only on functionality, ignoring maintainability.
The Solution: Remember: code is read 10x more than written. Quality is an investment in future productivity.
13.3 Pitfall 3: “Auto-formatters Make My Code Ugly”
The Problem: Personal style preferences conflicting with automated formatters.
The Solution: Trust the formatter. Consistency across a team is more valuable than individual preference.
13.4 Pitfall 4: “Type Hints Are Too Much Work”
The Problem: Avoiding type hints because they take extra time to write.
The Solution: Type hints save far more time than they cost by catching bugs early. Start with function signatures.
13.5 Pitfall 5: “CI/CD Checks Slow Us Down”
The Problem: Seeing failed checks as blockers rather than helpers.
The Solution: Run checks locally before pushing. Use pre-commit hooks. CI becomes a safety net, not a bottleneck.
14. Part 11: Tools Comparison and Ecosystem
14.1 The Python Quality Tools Landscape
14.2 Linters: Ruff vs. Flake8 vs. Pylint
Speed
- Ruff: 10-100x faster (Rust-based)
- Flake8: Moderate speed (Python)
- Pylint: Slowest (most thorough)
- Winner: Ruff - Speed enables frequent checking
Rules
- Ruff: 800+ rules (comprehensive)
- Flake8: 200+ rules (extensible with plugins)
- Pylint: 300+ rules (opinionated)
- Winner: Ruff - Most comprehensive out of the box
Configuration
- Ruff: Simple, works with pyproject.toml
- Flake8: Uses .flake8 or setup.cfg
- Pylint: Complex .pylintrc
- Winner: Ruff - Modern configuration
Auto-fix
- Ruff: Extensive auto-fix support
- Flake8: Limited (via plugins)
- Pylint: Minimal auto-fix
- Winner: Ruff - Best auto-fix capabilities
Recommendation: Use Ruff for new projects. Consider Flake8 only if you need specific plugins not yet available in Ruff.
14.3 Formatters: Ruff Format vs. Black
Speed
- Ruff Format: 10-100x faster (Rust)
- Black: Standard speed (Python)
- Winner: Ruff Format - Significantly faster
Style
- Ruff Format: Compatible with Black
- Black: The original opinionated style
- Winner: Tie - Nearly identical output
Configuration
- Ruff Format: Part of unified Ruff config
- Black: Separate configuration
- Winner: Ruff Format - One tool, one config
Stability
- Ruff Format: Newer (2023+), rapidly stabilizing
- Black: Mature (2018+), battle-tested
- Winner: Black for ultra-conservative projects, Ruff Format for modern development
Recommendation: Use Ruff Format for unified tooling. Stick with Black only if you have specific compatibility requirements.
14.4 Type Checkers: Pyright vs. mypy
Speed
- Pyright: 5-10x faster (TypeScript/Node.js)
- mypy: Standard speed (Python)
- Winner: Pyright - Much faster on large codebases
IDE Integration
- Pyright: Built into VS Code Pylance
- mypy: Requires separate extension
- Winner: Pyright - Seamless VS Code experience
Type Inference
- Pyright: More intelligent inference
- mypy: Good inference, less sophisticated
- Winner: Pyright - Better at inferring complex types
Maturity
- Pyright: Newer (2019+), Microsoft-backed
- mypy: Older (2012+), widely adopted
- Winner: mypy for legacy projects, Pyright for new projects
Recommendation: Use Pyright if you use VS Code. Use mypy if you have existing mypy configuration or need specific plugins.
15. Summary: Key Takeaways
15.1 Working Code ≠ Quality Code
Code that runs is just the beginning. Professional software must be:
- Readable - Easy for humans to understand
- Maintainable - Easy to modify and extend
- Consistent - Follows team and community standards
- Type-safe - Catches errors before runtime
15.2 Automate Quality Checks
Don’t rely on humans to catch style issues:
- Use Ruff for linting and formatting
- Use Pyright for type checking
- Run checks in CI/CD (GitHub Actions)
- Use pre-commit hooks to catch issues early
15.3 Follow PEP 8
Python’s style guide is not optional:
- Learn the key rules (spacing, naming, line length)
- Use tools that enforce PEP 8 automatically
- Trust the formatter to make style consistent
15.4 Type Hints Are Your Friend
Type hints catch bugs before they reach production:
- Start with function signatures
- Use
|for union types (e.g.,str | None) - Let Pyright catch type mismatches
- Your future self will thank you
15.5 Build Quality In, Don’t Bolt It On
Quality is not a phase—it’s a practice:
- Configure quality tools on day one
- Run checks before committing
- Make passing checks a requirement for merging
- Continuously improve your standards
15.6 The ROI is Real
Automated quality checks save time and money:
- Faster code reviews
- Fewer bugs in production
- Easier onboarding for new developers
- Higher team productivity
16. Practical Assignment
16.1 Task: Fix the Road Profile Viewer
- Clone the repository:
git clone https://github.com/your-username/road-profile-viewer.git cd road-profile-viewer - Set up the environment:
uv sync --dev - Run quality checks:
uv run ruff check . uv run ruff format --check . uv run pyright - Fix all issues:
- Remove unused imports
- Fix spacing violations
- Correct naming conventions
- Remove dead code
- Add type hints to all functions
- Verify fixes:
uv run ruff check . # Should show 0 errors uv run ruff format --check . # Should show all files OK uv run pyright # Should show 0 errors - Commit and push:
git add . git commit -m "Fix code quality issues" git push origin main - Verify GitHub Actions: Check that all CI/CD checks pass on GitHub.
17. Additional Resources
17.1 Official Documentation
- PEP 8: python.org/dev/peps/pep-0008
- Ruff: docs.astral.sh/ruff
- Pyright: microsoft.github.io/pyright
- Python Type Hints: docs.python.org/3/library/typing.html
17.2 Learning Resources
- Real Python - Code Quality: realpython.com/python-code-quality
- Effective Python: Book by Brett Slatkin
- Clean Code in Python: Book by Mariano Anaya
17.3 Tools and Extensions
- VS Code Ruff Extension: Search “Ruff” in VS Code marketplace
- VS Code Pylance: Includes Pyright (installed by default with Python extension)
- pre-commit: pre-commit.com
19. References
[1] PEP 8 - Style Guide for Python Code. https://www.python.org/dev/peps/pep-0008/
[2] Ruff Official Documentation. https://docs.astral.sh/ruff/
[3] Astral - Modern Python Tooling. https://astral.sh/
[4] Pyright Documentation. https://microsoft.github.io/pyright/
[5] Black - The Uncompromising Code Formatter. https://black.readthedocs.io/
[6] Flake8 Documentation. https://flake8.pycqa.org/
[7] mypy - Optional Static Typing for Python. https://mypy-lang.org/
[8] Python Type Hints - Official Documentation. https://docs.python.org/3/library/typing.html
[9] Real Python - Python Code Quality Tutorial. https://realpython.com/python-code-quality/
[10] Martin, Robert C. “Clean Code: A Handbook of Agile Software Craftsmanship.” Prentice Hall, 2008.
[11] Slatkin, Brett. “Effective Python: 90 Specific Ways to Write Better Python.” Addison-Wesley, 2019.
[12] Anaya, Mariano. “Clean Code in Python: Develop Maintainable and Efficient Code.” Packt Publishing, 2021.
[13] GitHub Actions Documentation. https://docs.github.com/en/actions
[14] pre-commit Framework. https://pre-commit.com/
[15] NumPy Type Hints Documentation. https://numpy.org/doc/stable/reference/typing.html
Note: This lecture is based on Python 3.12+ and tooling available as of October 2025. Tool versions and specific features may evolve over time.