Python Development Environment Best Practices¶
Objective: Master senior-level Python development environment setup for production systems. When you need to manage Python versions, dependencies, and tooling efficiently, when you want to ensure reproducible development environments, when you need enterprise-grade development workflowsโthese best practices become your weapon of choice.
Core Principles¶
- Version Management: Use modern Python version managers
- Dependency Management: Lock dependencies for reproducibility
- Tooling Integration: Integrate essential development tools
- Environment Isolation: Isolate project environments
- Automation: Automate common development tasks
Python Version Management¶
Modern Python Version Managers¶
# python/01-version-management.py
"""
Modern Python version management with pyenv and uv
"""
import subprocess
import sys
from pathlib import Path
from typing import List, Optional
class PythonVersionManager:
"""Manage Python versions and environments"""
def __init__(self, version_manager: str = "pyenv"):
self.version_manager = version_manager
self.available_versions = self._get_available_versions()
def _get_available_versions(self) -> List[str]:
"""Get available Python versions"""
try:
if self.version_manager == "pyenv":
result = subprocess.run(
["pyenv", "install", "--list"],
capture_output=True,
text=True,
check=True
)
versions = [line.strip() for line in result.stdout.split('\n')
if line.strip() and not line.startswith('Available')]
return [v for v in versions if v.startswith(('3.8', '3.9', '3.10', '3.11', '3.12'))]
else:
return []
except subprocess.CalledProcessError:
return []
def install_python_version(self, version: str) -> bool:
"""Install a specific Python version"""
try:
if self.version_manager == "pyenv":
subprocess.run(
["pyenv", "install", version],
check=True
)
return True
return False
except subprocess.CalledProcessError:
return False
def set_local_version(self, version: str, project_path: Path) -> bool:
"""Set local Python version for a project"""
try:
if self.version_manager == "pyenv":
subprocess.run(
["pyenv", "local", version],
cwd=project_path,
check=True
)
return True
return False
except subprocess.CalledProcessError:
return False
def get_current_version(self) -> Optional[str]:
"""Get current Python version"""
try:
result = subprocess.run(
["python", "--version"],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return None
# Usage example
def setup_python_environment(project_path: Path, python_version: str = "3.11"):
"""Setup Python environment for a project"""
manager = PythonVersionManager()
# Install Python version if not available
if python_version not in manager.available_versions:
print(f"Installing Python {python_version}...")
manager.install_python_version(python_version)
# Set local version
manager.set_local_version(python_version, project_path)
print(f"Python environment setup complete: {manager.get_current_version()}")
UV Package Manager¶
# python/02-uv-package-manager.py
"""
Modern Python package management with uv
"""
import subprocess
import json
from pathlib import Path
from typing import Dict, List, Optional
class UVPackageManager:
"""Manage Python packages with uv"""
def __init__(self, project_path: Path):
self.project_path = project_path
self.pyproject_toml = project_path / "pyproject.toml"
self.uv_lock = project_path / "uv.lock"
def init_project(self, name: str, version: str = "0.1.0") -> bool:
"""Initialize a new Python project"""
try:
subprocess.run(
["uv", "init", name, "--python", "3.11"],
cwd=self.project_path,
check=True
)
return True
except subprocess.CalledProcessError:
return False
def add_dependency(self, package: str, version: Optional[str] = None) -> bool:
"""Add a dependency to the project"""
try:
cmd = ["uv", "add", package]
if version:
cmd.append(f"=={version}")
subprocess.run(cmd, cwd=self.project_path, check=True)
return True
except subprocess.CalledProcessError:
return False
def add_dev_dependency(self, package: str, version: Optional[str] = None) -> bool:
"""Add a development dependency"""
try:
cmd = ["uv", "add", "--dev", package]
if version:
cmd.append(f"=={version}")
subprocess.run(cmd, cwd=self.project_path, check=True)
return True
except subprocess.CalledProcessError:
return False
def install_dependencies(self) -> bool:
"""Install project dependencies"""
try:
subprocess.run(["uv", "sync"], cwd=self.project_path, check=True)
return True
except subprocess.CalledProcessError:
return False
def run_command(self, command: str) -> bool:
"""Run a command in the project environment"""
try:
subprocess.run(["uv", "run", command], cwd=self.project_path, check=True)
return True
except subprocess.CalledProcessError:
return False
def get_dependencies(self) -> Dict[str, str]:
"""Get project dependencies"""
if not self.pyproject_toml.exists():
return {}
try:
with open(self.pyproject_toml, 'r') as f:
content = f.read()
# Parse TOML content (simplified)
dependencies = {}
in_deps = False
for line in content.split('\n'):
if line.strip() == '[dependencies]':
in_deps = True
elif line.startswith('[') and line.strip() != '[dependencies]':
in_deps = False
elif in_deps and '=' in line:
key, value = line.split('=', 1)
dependencies[key.strip()] = value.strip().strip('"')
return dependencies
except Exception:
return {}
# Usage example
def setup_project_with_uv(project_path: Path, project_name: str):
"""Setup a new project with uv"""
uv_manager = UVPackageManager(project_path)
# Initialize project
uv_manager.init_project(project_name)
# Add core dependencies
uv_manager.add_dependency("fastapi")
uv_manager.add_dependency("uvicorn")
uv_manager.add_dependency("pydantic")
# Add development dependencies
uv_manager.add_dev_dependency("pytest")
uv_manager.add_dev_dependency("black")
uv_manager.add_dev_dependency("ruff")
uv_manager.add_dev_dependency("mypy")
# Install dependencies
uv_manager.install_dependencies()
print("Project setup complete with uv")
Project Structure¶
Modern Python Project Layout¶
# python/03-project-structure.py
"""
Modern Python project structure and organization
"""
from pathlib import Path
from typing import Dict, List
import json
class PythonProjectStructure:
"""Manage Python project structure"""
def __init__(self, project_path: Path):
self.project_path = project_path
self.src_path = project_path / "src"
self.tests_path = project_path / "tests"
self.docs_path = project_path / "docs"
def create_project_structure(self, project_name: str) -> bool:
"""Create standard Python project structure"""
try:
# Create directories
directories = [
self.src_path / project_name,
self.tests_path,
self.docs_path,
self.project_path / "scripts",
self.project_path / "data",
self.project_path / "notebooks"
]
for directory in directories:
directory.mkdir(parents=True, exist_ok=True)
# Create __init__.py files
(self.src_path / project_name / "__init__.py").touch()
(self.tests_path / "__init__.py").touch()
return True
except Exception:
return False
def create_pyproject_toml(self, project_name: str, version: str = "0.1.0") -> bool:
"""Create pyproject.toml configuration"""
try:
pyproject_content = f'''[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "{project_name}"
version = "{version}"
description = "A Python project"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.100.0",
"uvicorn[standard]>=0.23.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
"mypy>=1.0.0",
"pre-commit>=3.0.0",
]
[project.scripts]
{project_name} = "{project_name}.cli:main"
[tool.black]
line-length = 88
target-version = ['py311']
[tool.ruff]
line-length = 88
target-version = "py311"
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
'''
with open(self.project_path / "pyproject.toml", 'w') as f:
f.write(pyproject_content)
return True
except Exception:
return False
def create_gitignore(self) -> bool:
"""Create .gitignore file"""
try:
gitignore_content = '''# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Project specific
data/
*.log
*.sqlite
*.db
'''
with open(self.project_path / ".gitignore", 'w') as f:
f.write(gitignore_content)
return True
except Exception:
return False
# Usage example
def setup_complete_project(project_path: Path, project_name: str):
"""Setup a complete Python project"""
structure = PythonProjectStructure(project_path)
# Create project structure
structure.create_project_structure(project_name)
# Create configuration files
structure.create_pyproject_toml(project_name)
structure.create_gitignore()
print(f"Project {project_name} structure created successfully")
Development Tools¶
Essential Development Tools¶
# python/04-development-tools.py
"""
Essential Python development tools and configuration
"""
import subprocess
from pathlib import Path
from typing import Dict, List
class DevelopmentTools:
"""Manage development tools and configuration"""
def __init__(self, project_path: Path):
self.project_path = project_path
def setup_pre_commit(self) -> bool:
"""Setup pre-commit hooks"""
try:
pre_commit_config = '''repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.0
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
hooks:
- id: mypy
additional_dependencies: [types-all]
'''
with open(self.project_path / ".pre-commit-config.yaml", 'w') as f:
f.write(pre_commit_config)
# Install pre-commit
subprocess.run(["pre-commit", "install"], cwd=self.project_path, check=True)
return True
except subprocess.CalledProcessError:
return False
def setup_vscode_settings(self) -> bool:
"""Setup VS Code settings"""
try:
vscode_dir = self.project_path / ".vscode"
vscode_dir.mkdir(exist_ok=True)
settings = {
"python.defaultInterpreterPath": "./.venv/bin/python",
"python.linting.enabled": True,
"python.linting.pylintEnabled": False,
"python.linting.flake8Enabled": False,
"python.linting.mypyEnabled": True,
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length", "88"],
"editor.formatOnSave": True,
"editor.codeActionsOnSave": {
"source.organizeImports": True
}
}
with open(vscode_dir / "settings.json", 'w') as f:
json.dump(settings, f, indent=2)
return True
except Exception:
return False
def setup_makefile(self) -> bool:
"""Create Makefile for common tasks"""
try:
makefile_content = '''.PHONY: help install test lint format clean
help: ## Show this help message
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\\n", $$1, $$2}' $(MAKEFILE_LIST)
install: ## Install dependencies
uv sync
test: ## Run tests
uv run pytest
test-cov: ## Run tests with coverage
uv run pytest --cov=src --cov-report=html --cov-report=term
lint: ## Run linting
uv run ruff check .
uv run mypy src/
format: ## Format code
uv run black src/ tests/
uv run ruff check --fix .
clean: ## Clean up
find . -type f -name "*.pyc" -delete
find . -type d -name "__pycache__" -delete
rm -rf .pytest_cache/
rm -rf htmlcov/
rm -rf .coverage
'''
with open(self.project_path / "Makefile", 'w') as f:
f.write(makefile_content)
return True
except Exception:
return False
# Usage example
def setup_development_environment(project_path: Path):
"""Setup complete development environment"""
tools = DevelopmentTools(project_path)
# Setup tools
tools.setup_pre_commit()
tools.setup_vscode_settings()
tools.setup_makefile()
print("Development environment setup complete")
Environment Management¶
Environment Configuration¶
# python/05-environment-management.py
"""
Environment management and configuration
"""
import os
from pathlib import Path
from typing import Dict, Optional
import json
class EnvironmentManager:
"""Manage development environments"""
def __init__(self, project_path: Path):
self.project_path = project_path
self.env_file = project_path / ".env"
self.env_example = project_path / ".env.example"
def create_env_example(self, variables: Dict[str, str]) -> bool:
"""Create .env.example file"""
try:
with open(self.env_example, 'w') as f:
for key, description in variables.items():
f.write(f"# {description}\n")
f.write(f"{key}=\n\n")
return True
except Exception:
return False
def load_env_variables(self) -> Dict[str, str]:
"""Load environment variables from .env file"""
env_vars = {}
if self.env_file.exists():
with open(self.env_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
env_vars[key.strip()] = value.strip()
return env_vars
def setup_environment_config(self) -> bool:
"""Setup environment configuration"""
try:
# Create environment variables template
env_variables = {
"DEBUG": "Enable debug mode",
"SECRET_KEY": "Application secret key",
"DATABASE_URL": "Database connection string",
"REDIS_URL": "Redis connection string",
"API_KEY": "External API key",
"LOG_LEVEL": "Logging level (DEBUG, INFO, WARNING, ERROR)"
}
self.create_env_example(env_variables)
# Create environment-specific configurations
environments = ["development", "testing", "staging", "production"]
for env in environments:
config = {
"environment": env,
"debug": env == "development",
"log_level": "DEBUG" if env == "development" else "INFO"
}
config_file = self.project_path / f"config_{env}.json"
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
return True
except Exception:
return False
# Usage example
def setup_environment_management(project_path: Path):
"""Setup environment management"""
env_manager = EnvironmentManager(project_path)
env_manager.setup_environment_config()
print("Environment management setup complete")
TL;DR Runbook¶
Quick Start¶
# 1. Setup Python version management
from pathlib import Path
project_path = Path("my-project")
project_path.mkdir(exist_ok=True)
# 2. Setup project with uv
from python.dev_environment import setup_project_with_uv
setup_project_with_uv(project_path, "my-project")
# 3. Setup complete project structure
from python.project_structure import setup_complete_project
setup_complete_project(project_path, "my-project")
# 4. Setup development tools
from python.development_tools import setup_development_environment
setup_development_environment(project_path)
# 5. Setup environment management
from python.environment_management import setup_environment_management
setup_environment_management(project_path)
Essential Patterns¶
# Complete Python development environment setup
def create_python_development_environment(project_path: Path, project_name: str):
"""Create complete Python development environment"""
# Setup Python version management
manager = PythonVersionManager()
manager.install_python_version("3.11")
manager.set_local_version("3.11", project_path)
# Setup project with uv
uv_manager = UVPackageManager(project_path)
uv_manager.init_project(project_name)
# Add essential dependencies
uv_manager.add_dependency("fastapi")
uv_manager.add_dependency("uvicorn")
uv_manager.add_dev_dependency("pytest")
uv_manager.add_dev_dependency("black")
uv_manager.add_dev_dependency("ruff")
# Create project structure
structure = PythonProjectStructure(project_path)
structure.create_project_structure(project_name)
structure.create_pyproject_toml(project_name)
structure.create_gitignore()
# Setup development tools
tools = DevelopmentTools(project_path)
tools.setup_pre_commit()
tools.setup_vscode_settings()
tools.setup_makefile()
# Setup environment management
env_manager = EnvironmentManager(project_path)
env_manager.setup_environment_config()
print(f"Python development environment for {project_name} setup complete!")
This guide provides the complete machinery for setting up Python development environments. Each pattern includes implementation examples, configuration strategies, and real-world usage patterns for enterprise Python development.