From PEP 8
Overview¶
This guide helps Python developers transition from PEP 8 (the standard Python style guide) to the DevOps Engineering Style Guide. Our guide builds on PEP 8's foundation while adding modern DevOps-oriented enhancements for infrastructure automation, type safety, and security.
What This Guide Covers¶
- Compatibility assessment: What stays the same vs. what changes
- Enhanced requirements beyond PEP 8
- Tool configuration updates
- Step-by-step migration checklist
- Common migration pitfalls and solutions
Who Should Use This Guide¶
- Teams currently following PEP 8 who want enhanced DevOps standards
- Projects transitioning to infrastructure-as-code automation
- Python developers adding type hints and security best practices
- Organizations standardizing on modern Python tooling (Black, mypy)
Quick Compatibility Summary¶
graph LR
PEP8[PEP 8 Standards] --> Same[100% Compatible<br/>Base Conventions]
PEP8 --> Enhanced[Enhanced Requirements<br/>Type Hints, Docs, Security]
PEP8 --> Relaxed[Relaxed Constraints<br/>Line Length 88 chars]
Same --> Migration[Migration Path]
Enhanced --> Migration
Relaxed --> Migration
Migration --> DukesGuide[DevOps Engineering<br/>Style Guide]
style Same fill:#e8f5e9
style Enhanced fill:#fff3e0
style Relaxed fill:#e3f2fd
style DukesGuide fill:#f3e5f5
What Stays the Same¶
DevOps Engineering Style Guide maintains full compatibility with PEP 8 core conventions. If your code follows PEP 8, these aspects require no changes:
Naming Conventions ✅¶
| Element | Convention | PEP 8 | Our Guide |
|---|---|---|---|
| Variables | snake_case |
✅ | ✅ Same |
| Functions | snake_case |
✅ | ✅ Same |
| Constants | UPPER_SNAKE_CASE |
✅ | ✅ Same |
| Classes | PascalCase |
✅ | ✅ Same |
| Modules | snake_case |
✅ | ✅ Same |
| Private | _leading_underscore |
✅ | ✅ Same |
Code Structure ✅¶
- Indentation: 4 spaces (never tabs)
- Blank lines: 2 between top-level definitions, 1 between methods
- Import order: Standard library → third-party → local
- Whitespace: Consistent spacing around operators
- Comments:
#for inline,"""for docstrings
Formatting ✅¶
- String quotes (prefer double, use single to avoid escapes)
- Parentheses for line continuation
- Trailing commas in multi-line structures
- No trailing whitespace
Example - No Changes Needed:
## This PEP 8 code is already compliant
import os
import sys
import requests
from myapp.utils import helper
MAX_RETRIES = 3
class UserService:
"""Service for user management."""
def get_user(self, user_id: int):
"""Retrieve user by ID."""
return database.query(user_id)
What Changes: Enhancements Beyond PEP 8¶
1. Line Length: 79 → 88 Characters¶
PEP 8: Maximum 79 characters Our Guide: Maximum 88 characters (Black default)
Why: Modern displays support wider lines, and Black's 88-character limit reduces unnecessary line breaks while maintaining readability.
Migration:
## PEP 8 (79 chars) - line breaks needed
user_data = database.query(User).filter(
User.is_active == True
).all()
## Our Guide (88 chars) - more natural flow
user_data = database.query(User).filter(User.is_active == True).all()
Action Required:
- Update editor rulers to 88 characters
- Configure Black formatter with default settings
- Run
black .to automatically reformat
2. Type Hints: Optional → Required¶
PEP 8: Type hints are optional (PEP 484 compliance) Our Guide: Type hints required for all function signatures
Why: Type hints enable static analysis, improve IDE support, prevent runtime errors, and serve as inline documentation for DevOps automation code.
Migration:
## PEP 8 - acceptable without type hints
def get_user(user_id):
"""Retrieve user by ID."""
return database.query(User).filter(User.id == user_id).first()
## Our Guide - type hints required
from typing import Optional
def get_user(user_id: int) -> Optional[User]:
"""Retrieve user by ID."""
return database.query(User).filter(User.id == user_id).first()
Action Required:
- Add type hints to all function signatures
- Import typing module:
from typing import List, Dict, Optional, Union - Run
mypyto validate type correctness - Update docstrings to remove redundant type info (now in hints)
3. Documentation: Basic → Enhanced Metadata¶
PEP 8: Docstrings required for public modules, classes, functions Our Guide: Enhanced module docstrings with structured metadata
Why: Structured metadata enables automated documentation generation, dependency tracking, and AI-assisted code understanding.
Migration:
## PEP 8 - basic module docstring
"""User authentication module."""
import jwt
from fastapi import HTTPException
## Our Guide - enhanced metadata
"""
@module user_authentication
@description Handles user authentication, session management, and JWT token generation
@dependencies fastapi, pyjwt, passlib, python-dotenv
@version 1.2.0
@author Tyler Dukes
@last_updated 2025-12-07
@status stable
@security_classification internal
@python_version >= 3.9
"""
import jwt
from fastapi import HTTPException
Action Required:
- Add metadata tags to module docstrings
- Document dependencies explicitly
- Add version and status information
- Specify minimum Python version requirements
4. Docstrings: PEP 257 → Google/NumPy Style with Examples¶
PEP 8/257: Basic docstring format Our Guide: Structured docstrings with Args, Returns, Raises, Examples
Why: Consistent structured docstrings improve API documentation, enable automated doc generation, and provide usage examples.
Migration:
## PEP 8/257 - basic docstring
def authenticate_user(username, password):
"""Authenticate user and return user object or None."""
pass
## Our Guide - structured with examples
def authenticate_user(username: str, password: str) -> Optional[User]:
"""
Authenticate user credentials and return user object if valid.
Args:
username: User's username or email address
password: Plain text password to verify
Returns:
User object if authentication succeeds, None otherwise
Raises:
DatabaseError: If database connection fails
ValidationError: If username format is invalid
Example:
>>> user = authenticate_user("john@example.com", "secret123")
>>> if user:
... print(f"Welcome {user.name}")
"""
pass
Action Required:
- Restructure docstrings with Args/Returns/Raises sections
- Add usage examples to complex functions
- Document all exceptions that may be raised
- Use consistent formatting (Google or NumPy style)
5. Testing: Recommended → Required Coverage¶
PEP 8: No specific testing requirements Our Guide: 80%+ unit test coverage required, structured test naming
Why: High test coverage ensures reliability for infrastructure automation where failures have production impact.
Migration:
## No PEP 8 equivalent - testing not mandated
## Our Guide - required test structure
import pytest
from app.services.user_service import UserService
def test_should_return_user_when_valid_id_provided():
"""Test get_user_by_id returns user for valid ID."""
# Arrange
user_id = 123
# Act
user = get_user_by_id(user_id)
# Assert
assert user.id == user_id
assert user is not None
def test_should_raise_error_when_user_not_found():
"""Test get_user_by_id raises NotFoundError for invalid ID."""
with pytest.raises(NotFoundError):
get_user_by_id(999999)
Action Required:
- Create
tests/directory mirroringsrc/structure - Write tests for all business logic (target 80%+ coverage)
- Use naming convention:
test_should_<behavior>_when_<condition> - Set up pytest with coverage reporting
- Add pytest configuration to
pyproject.toml
6. Security: Implicit → Explicit Best Practices¶
PEP 8: No security-specific requirements Our Guide: Mandatory security practices for DevOps code
Why: Infrastructure automation code handles sensitive data, credentials, and production systems. Security must be explicit.
Migration - Input Validation:
## PEP 8 - basic validation
def get_user_by_email(email):
query = f"SELECT * FROM users WHERE email = '{email}'"
return db.execute(query)
## Our Guide - security-first with validation
from pydantic import BaseModel, EmailStr, validator
class UserQuery(BaseModel):
"""Validated user query request."""
email: EmailStr
def get_user_by_email(email: str) -> Optional[User]:
"""Get user by email using parameterized query."""
# Input validation with Pydantic
user_query = UserQuery(email=email)
# Parameterized query prevents SQL injection
query = text("SELECT * FROM users WHERE email = :email")
result = db.execute(query, {"email": user_query.email})
return result.first()
Migration - Secret Management:
## PEP 8 - no specific requirements
DATABASE_URL = "postgresql://user:pass@localhost/db"
API_KEY = "sk_live_abc123"
## Our Guide - environment-based secrets
import os
from functools import lru_cache
@lru_cache()
def get_settings():
"""Get application settings from environment."""
return {
"database_url": os.getenv("DATABASE_URL"),
"api_key": os.getenv("API_KEY"),
"secret_key": os.getenv("SECRET_KEY")
}
Action Required:
- Add input validation with Pydantic or custom validators
- Replace string concatenation with parameterized queries
- Move all secrets to environment variables
- Add
.envto.gitignore - Use validation libraries (Pydantic, Cerberus)
- Run security scanners (Bandit, Safety)
7. Error Handling: General → Specific Exceptions¶
PEP 8: No specific error handling patterns Our Guide: Fail-fast with specific custom exceptions
Migration:
## PEP 8 - generic exception handling
def fetch_data(url):
try:
response = requests.get(url)
return response.json()
except Exception as e:
print(f"Error: {e}")
return None
## Our Guide - specific exceptions and proper cleanup
class APIError(Exception):
"""Base exception for API-related errors."""
pass
class APITimeoutError(APIError):
"""Raised when API request times out."""
pass
def fetch_data(url: str) -> Dict:
"""Fetch data from remote API with retry logic."""
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
except requests.Timeout:
logger.error(f"Timeout fetching data from {url}")
raise APITimeoutError(f"Request to {url} timed out")
except requests.HTTPError as e:
logger.error(f"HTTP error {e.response.status_code}: {url}")
raise APIError(f"Failed to fetch data: {e}")
except ValueError:
logger.error(f"Invalid JSON response from {url}")
raise DataFormatError("Response is not valid JSON")
Action Required:
- Create custom exception hierarchies for your domain
- Replace generic
except Exceptionwith specific exceptions - Add proper logging before raising exceptions
- Use context managers for resource cleanup
- Never silently catch exceptions (
except: pass)
Tool Configuration Changes¶
Black Formatter (New Requirement)¶
Black is required for consistent code formatting. PEP 8 doesn't mandate a specific formatter.
Installation:
pip install black
Configuration (pyproject.toml):
[tool.black]
line-length = 88
target-version = ['py39', 'py310', 'py311']
include = '\.pyi?$'
Usage:
## Format entire project
black .
## Check without modifying
black --check .
## Format specific file
black src/mymodule.py
mypy Type Checker (New Requirement)¶
Static type checking is required. Configure mypy for your project.
Installation:
pip install mypy
Configuration (mypy.ini or pyproject.toml):
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_any_unimported = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
check_untyped_defs = true
strict_equality = true
Usage:
## Type check entire project
mypy src/
## Type check specific module
mypy src/mymodule.py
isort Import Organizer (Enhanced)¶
Configure isort to work with Black.
Configuration (pyproject.toml):
[tool.isort]
profile = "black"
line_length = 88
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
pytest with Coverage (New Requirement)¶
Installation:
pip install pytest pytest-cov pytest-mock
Configuration (pyproject.toml):
[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q --strict-markers --cov=src --cov-report=term-missing --cov-fail-under=80"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
Usage:
## Run tests with coverage
pytest --cov=src tests/
## Generate HTML coverage report
pytest --cov=src --cov-report=html tests/
Pre-commit Hooks (New Requirement)¶
Automate code quality checks before commits.
Installation:
pip install pre-commit
Configuration (.pre-commit-config.yaml):
repos:
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
args: ["--max-line-length=88", "--extend-ignore=E203"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: [types-requests]
- repo: https://github.com/PyCQA/bandit
rev: 1.7.9
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
Setup:
## Install hooks
pre-commit install
## Run manually on all files
pre-commit run --all-files
Migration Checklist¶
Use this checklist to systematically migrate your Python project from PEP 8 to the DevOps Engineering Style Guide.
Phase 1: Setup and Configuration¶
- [ ] Install required tools
pip install black isort mypy pytest pytest-cov pre-commit bandit safety
- [ ] Create
pyproject.tomlconfiguration - Add Black configuration (88 char line length)
- Add isort configuration (Black-compatible)
- Add mypy strict configuration
-
Add pytest configuration (80% coverage minimum)
-
[ ] Create
.pre-commit-config.yaml - Add Black hook
- Add isort hook
- Add Flake8 hook (with 88 char limit)
- Add mypy hook
- Add Bandit security scanner
-
Run
pre-commit install -
[ ] Update
.gitignore
# Python
__pycache__/
*.py[cod]
*$py.class
.mypy_cache/
.pytest_cache/
htmlcov/
.coverage
# Environment
.env
.env.local
venv/
Phase 2: Code Formatting¶
- [ ] Run Black on entire codebase
black .
- Review changes (line length adjustments)
-
Commit formatted code
-
[ ] Run isort on imports
isort .
- Verify import grouping
-
Commit organized imports
-
[ ] Fix Flake8 issues
flake8 . --max-line-length=88 --extend-ignore=E203
- Address remaining style violations
- Commit fixes
Phase 3: Add Type Hints¶
- [ ] Add type hints to function signatures
- Start with public APIs and exported functions
- Use
from typing import List, Dict, Optional, Union, Tuple -
Work module by module
-
[ ] Run mypy incrementally
mypy src/module_name.py
- Fix type errors as you add hints
-
Use
# type: ignoresparingly for complex cases -
[ ] Add return type annotations
- Ensure all functions have
-> ReturnType - Use
-> Nonefor functions without return -
Use
Optional[Type]for nullable returns -
[ ] Validate with mypy
mypy src/
- Achieve zero mypy errors
- Commit type-hinted code
Phase 4: Enhance Documentation¶
- [ ] Update module docstrings with metadata
- Add
@module,@description,@dependenciestags - Add
@version,@author,@statustags -
Document security classification if applicable
-
[ ] Restructure function docstrings
- Convert to Google/NumPy style with sections
- Add
Args:,Returns:,Raises:sections - Add usage examples for complex functions
-
Remove redundant type info (now in type hints)
-
[ ] Document exceptions
- List all exceptions in
Raises:section - Create custom exception classes
- Add docstrings to exception classes
Phase 5: Add Tests¶
- [ ] Create test directory structure
mkdir -p tests/
# Mirror src/ structure in tests/
- [ ] Write unit tests
- Aim for 80%+ coverage
- Use naming:
test_should_<behavior>_when_<condition> -
Follow Arrange-Act-Assert pattern
-
[ ] Configure pytest
- Add pytest configuration to
pyproject.toml - Set up coverage reporting
-
Configure test discovery patterns
-
[ ] Run tests and verify coverage
pytest --cov=src --cov-report=term-missing tests/
- Achieve 80%+ coverage target
- Add tests for uncovered code
Phase 6: Security Enhancements¶
- [ ] Move secrets to environment variables
- Create
.env.exampletemplate - Update code to use
os.getenv() - Add
.envto.gitignore -
Document required environment variables
-
[ ] Add input validation
- Use Pydantic models for request validation
- Add custom validators for business rules
-
Validate all external inputs
-
[ ] Replace SQL string concatenation
- Use parameterized queries
- Use ORM query builders (SQLAlchemy)
-
Never use f-strings for SQL
-
[ ] Run security scanners
bandit -r src/
safety check
- Fix identified vulnerabilities
- Document any accepted risks
Phase 7: CI/CD Integration¶
- [ ] Create GitHub Actions workflow (if using GitHub)
# .github/workflows/python-ci.yml
name: Python CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: black --check .
- run: isort --check .
- run: flake8 .
- run: mypy src/
- run: pytest --cov=src tests/
- [ ] Add CI/CD badges to README
- Test status badge
- Coverage badge
-
Code quality badge
-
[ ] Configure branch protection
- Require CI checks to pass
- Require code review
- Enforce pre-commit hooks
Phase 8: Documentation and Training¶
- [ ] Update README with tool requirements
- List Black, mypy, pytest as requirements
- Add setup instructions
-
Document development workflow
-
[ ] Create CONTRIBUTING guide
- Reference DevOps Engineering Style Guide
- Explain pre-commit hook usage
-
Provide testing guidelines
-
[ ] Team training
- Share this migration guide with team
- Conduct style guide review session
- Set up IDE/editor configurations
Common Migration Pitfalls¶
1. Type Hint Complexity Overload¶
Problem: Trying to add perfect type hints to complex legacy code all at once.
Solution: Migrate incrementally, module by module. Use # type: ignore temporarily for complex cases, then refactor.
## During migration - acceptable temporarily
result = complex_function() # type: ignore
## Target state after refactoring
result: Dict[str, List[User]] = complex_function()
2. Over-Reliance on Any Type¶
Problem: Using typing.Any to satisfy mypy without actual type safety.
Solution: Use specific types or Union types. Reserve Any for truly dynamic cases.
## Avoid - defeats purpose of type hints
def process(data: Any) -> Any:
pass
## Better - specific types
def process(data: Union[str, int, List[str]]) -> Dict[str, int]:
pass
3. Docstring Duplication¶
Problem: Repeating type information in both type hints and docstrings.
Solution: Remove type information from docstrings when type hints are present.
## Redundant - types in both places
def get_user(user_id: int) -> Optional[User]:
"""
Get user by ID.
Args:
user_id (int): The user ID
Returns:
Optional[User]: User object or None
"""
pass
## Better - types in hints, descriptions in docstrings
def get_user(user_id: int) -> Optional[User]:
"""
Retrieve user from database by ID.
Args:
user_id: Unique identifier for the user
Returns:
User object if found, None otherwise
"""
pass
4. Test Naming Confusion¶
Problem: Inconsistent test naming makes test intent unclear.
Solution: Follow the pattern test_should_<behavior>_when_<condition> consistently.
## Inconsistent
def test_user_creation():
pass
def test_invalid_email():
pass
## Consistent and clear
def test_should_create_user_when_valid_data_provided():
pass
def test_should_raise_error_when_invalid_email_provided():
pass
5. Ignoring Pre-commit Hook Failures¶
Problem: Committing code that fails pre-commit checks using --no-verify.
Solution: Fix the issues, don't bypass the checks. Pre-commit hooks catch real problems.
## Wrong - bypassing checks
git commit --no-verify -m "quick fix"
## Right - fix issues first
black .
isort .
mypy src/
pytest
git commit -m "feat: add user validation"
Gradual Adoption Strategy¶
If immediate full migration is not feasible, adopt incrementally:
Week 1-2: Tooling Setup¶
- Install Black, mypy, pytest, pre-commit
- Configure
pyproject.toml - Run Black to reformat entire codebase
- Set up CI/CD with formatting checks
Week 3-4: Type Hints for New Code¶
- Require type hints for all new functions
- Add type hints to recently modified modules
- Start mypy checking on new modules only
Week 5-8: Documentation Enhancement¶
- Add structured metadata to module docstrings
- Improve docstrings for public APIs
- Add usage examples to key functions
Week 9-12: Testing and Coverage¶
- Add pytest configuration
- Write tests for new features (require 80% coverage)
- Incrementally add tests to existing critical modules
Month 4+: Complete Migration¶
- Systematically add type hints to remaining modules
- Achieve 80%+ test coverage across codebase
- Enable strict mypy checking project-wide
- Full security audit with Bandit/Safety
Success Metrics¶
Track these metrics to measure migration progress:
- Type Hint Coverage: % of functions with complete type hints (Target: 100%)
- mypy Pass Rate: % of modules passing strict mypy checks (Target: 100%)
- Test Coverage: % of code covered by tests (Target: 80%+)
- Security Scan: Bandit/Safety issues count (Target: 0 high/critical)
- Pre-commit Pass: % of commits passing all hooks (Target: 100%)
- Docstring Completeness: % of functions with structured docstrings (Target: 100%)
Support and Resources¶
Documentation References¶
- Python Style Guide - Full DevOps Engineering Python standards
- Testing Strategies - pytest patterns and best practices
- Security Scanning Guide - Bandit, Safety integration
- GitHub Actions Guide - Python CI/CD workflows
- IDE Integration Guide - VS Code, PyCharm setup
Tool Documentation¶
- Black - Code formatter
- mypy - Static type checker
- pytest - Testing framework
- Bandit - Security linter
Community Support¶
- GitHub Issues: Report migration challenges
- Team Discussions: Share migration experiences
- Office Hours: Schedule style guide review sessions
Conclusion¶
Migrating from PEP 8 to the DevOps Engineering Style Guide enhances your Python codebase with:
✅ Type Safety - Catch errors before runtime with mypy ✅ Consistency - Automated formatting with Black ✅ Quality - 80%+ test coverage requirement ✅ Security - Built-in security best practices ✅ Documentation - Structured, AI-parseable metadata ✅ Automation - Pre-commit hooks and CI/CD integration
The migration builds on PEP 8's solid foundation while adding modern DevOps-oriented practices essential for infrastructure automation and production reliability.
Questions or need help? Open an issue or consult the Getting Started Guide.