Python & Django Best Practices

Learn industry best practices including PEP 8, type hints, virtual environments, testing strategies, logging, and production-ready code standards.

๐Ÿ“ PEP 8 & Code Style

  • What is PEP 8?
    PEP 8 is Python's official style guide for writing readable and consistent code.
    Key Guidelines:
    • Use 4 spaces for indentation (not tabs)
    • Line length: 79 characters max
    • Use snake_case for functions and variables
    • Use PascalCase for class names
    • Use UPPERCASE for constants
    • Two blank lines between top-level functions/classes
    • One blank line between methods in a class
    • Imports at the top, grouped: standard โ†’ third-party โ†’ local
    # Good
    def calculate_total_price(items):
        total = 0
        for item in items:
            total += item.price
        return total
    
    class ShoppingCart:
        MAX_ITEMS = 100
        
        def __init__(self):
            self.items = []
        
        def add_item(self, item):
            self.items.append(item)
  • Tools for Code Quality
    flake8 - Lintingblack - Formattingpylint - Analysismypy - Type checkingisort - Import sorting
    # Install tools
    pip install flake8 black pylint mypy isort
    
    # Run checks
    flake8 .
    black --check .
    pylint myproject/
    mypy myproject/
    isort --check-only .

๐Ÿ”ค Type Hints

  • Using Type Hints in Python
    Type hints improve code readability, enable static type checking, and provide better IDE support.
    from typing import List, Dict, Optional, Union, Tuple
    
    # Basic types
    def greet(name: str) -> str:
        return f"Hello, {name}"
    
    # Collections
    def process_items(items: List[int]) -> Dict[str, int]:
        return {"total": sum(items), "count": len(items)}
    
    # Optional (can be None)
    def find_user(user_id: int) -> Optional[User]:
        try:
            return User.objects.get(id=user_id)
        except User.DoesNotExist:
            return None
    
    # Union (multiple types)
    def parse_value(value: Union[str, int, float]) -> float:
        return float(value)
    
    # Tuple
    def get_coordinates() -> Tuple[float, float]:
        return (40.7128, -74.0060)
    
    # Class attributes
    class User:
        name: str
        age: int
        email: Optional[str] = None
        
        def __init__(self, name: str, age: int):
            self.name = name
            self.age = age

๐ŸŒ Virtual Environments

  • Managing Virtual Environments
    Virtual environments isolate project dependencies, preventing conflicts between projects.
    # Create virtual environment
    python3 -m venv venv
    
    # Activate
    # Linux/Mac
    source venv/bin/activate
    # Windows
    venv\Scripts\activate
    
    # Install dependencies
    pip install -r requirements.txt
    
    # Save dependencies
    pip freeze > requirements.txt
    
    # Deactivate
    deactivate
  • Using Poetry (Modern Alternative)
    # Install Poetry
    curl -sSL https://install.python-poetry.org | python3 -
    
    # Initialize project
    poetry init
    
    # Add dependencies
    poetry add django djangorestframework
    
    # Add dev dependencies
    poetry add --dev pytest black flake8
    
    # Install all dependencies
    poetry install
    
    # Activate shell
    poetry shell

๐Ÿงช Testing

  • Testing Best Practices
    # test_models.py
    from django.test import TestCase
    from .models import Product
    
    class ProductTestCase(TestCase):
        def setUp(self):
            self.product = Product.objects.create(
                name="Test Product",
                price=99.99
            )
        
        def test_product_creation(self):
            self.assertEqual(self.product.name, "Test Product")
            self.assertEqual(self.product.price, 99.99)
        
        def test_product_str(self):
            self.assertEqual(str(self.product), "Test Product")
    
    # test_views.py
    from rest_framework.test import APITestCase
    from rest_framework import status
    
    class ProductAPITestCase(APITestCase):
        def test_list_products(self):
            response = self.client.get('/api/products/')
            self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        def test_create_product(self):
            data = {'name': 'New Product', 'price': 149.99}
            response = self.client.post('/api/products/', data)
            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  • Using Pytest
    # Install
    pip install pytest pytest-django
    
    # pytest.ini
    [pytest]
    DJANGO_SETTINGS_MODULE = myproject.settings
    python_files = tests.py test_*.py *_tests.py
    
    # test_example.py
    import pytest
    from .models import Product
    
    @pytest.mark.django_db
    def test_product_creation():
        product = Product.objects.create(name="Test", price=99.99)
        assert product.name == "Test"
        assert product.price == 99.99
    
    # Run tests
    pytest
    pytest -v  # Verbose
    pytest --cov  # With coverage

๐Ÿ“Š Logging

  • Proper Logging Configuration
    # settings.py
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'verbose': {
                'format': '{levelname} {asctime} {module} {message}',
                'style': '{',
            },
        },
        'handlers': {
            'file': {
                'level': 'INFO',
                'class': 'logging.FileHandler',
                'filename': 'app.log',
                'formatter': 'verbose',
            },
            'console': {
                'level': 'DEBUG',
                'class': 'logging.StreamHandler',
                'formatter': 'verbose',
            },
        },
        'root': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
        },
        'loggers': {
            'django': {
                'handlers': ['console', 'file'],
                'level': 'INFO',
                'propagate': False,
            },
        },
    }
    # Using logger
    import logging
    
    logger = logging.getLogger(__name__)
    
    def process_order(order_id):
        logger.info(f"Processing order {order_id}")
        try:
            # Process order
            logger.debug(f"Order {order_id} details fetched")
        except Exception as e:
            logger.error(f"Error processing order {order_id}: {e}")
            raise

๐Ÿ“ฆ Package Management

  • Requirements Management
    # requirements.txt - Production
    Django==4.2.0
    djangorestframework==3.14.0
    psycopg2-binary==2.9.6
    gunicorn==20.1.0
    redis==4.5.5
    
    # requirements-dev.txt - Development
    -r requirements.txt
    pytest==7.3.1
    pytest-django==4.5.2
    black==23.3.0
    flake8==6.0.0
    mypy==1.3.0
    
    # Install
    pip install -r requirements.txt
    pip install -r requirements-dev.txt

๐Ÿ”’ Security Best Practices

  • Security Checklist
    • โœ… Never commit secrets (use .env files)
    • โœ… Use environment variables for sensitive data
    • โœ… Set DEBUG=False in production
    • โœ… Use HTTPS only (SECURE_SSL_REDIRECT=True)
    • โœ… Set secure cookie flags
    • โœ… Implement CSRF protection
    • โœ… Validate all user inputs
    • โœ… Use parameterized queries (ORM does this)
    • โœ… Keep dependencies updated
    • โœ… Use strong password hashing (Django default is good)
    • โœ… Implement rate limiting
    • โœ… Set proper CORS policies
    # .env file
    SECRET_KEY=your-secret-key-here
    DEBUG=False
    DATABASE_URL=postgresql://user:pass@localhost/dbname
    ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
    
    # settings.py
    import environ
    
    env = environ.Env()
    environ.Env.read_env()
    
    SECRET_KEY = env('SECRET_KEY')
    DEBUG = env.bool('DEBUG', default=False)
    ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
    
    # Security settings
    SECURE_SSL_REDIRECT = True
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_BROWSER_XSS_FILTER = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    X_FRAME_OPTIONS = 'DENY'

๐Ÿš€ Performance Tips

  • Optimization Strategies
    • Use database indexes on frequently queried fields
    • Use select_related() and prefetch_related()
    • Implement caching (Redis/Memcached)
    • Use database connection pooling
    • Optimize queries (avoid N+1 problems)
    • Use pagination for large datasets
    • Compress static files
    • Use CDN for static files
    • Enable query optimization in Django Debug Toolbar
    • Use async views for I/O-bound operations
    • Profile code with cProfile or Django Silk

๐Ÿ“ Documentation

  • Code Documentation Best Practices
    def calculate_discount(price: float, discount_percent: float) -> float:
        """
        Calculate the discounted price.
        
        Args:
            price (float): Original price of the item
            discount_percent (float): Discount percentage (0-100)
        
        Returns:
            float: Final price after discount
        
        Raises:
            ValueError: If discount_percent is not between 0 and 100
        
        Example:
            >>> calculate_discount(100, 20)
            80.0
        """
        if not 0 <= discount_percent <= 100:
            raise ValueError("Discount must be between 0 and 100")
        
        return price * (1 - discount_percent / 100)