Code Style Guide

This document outlines the coding standards and style guidelines for the NSGG Backend project.

Python Style Guide

General Guidelines

  1. Follow PEP 8 conventions
  2. Use 4 spaces for indentation (no tabs)
  3. Maximum line length: 88 characters (Black formatter default)
  4. Use meaningful variable and function names

Naming Conventions

# Classes: CamelCase
class ProductCategory:
    pass

# Functions and variables: snake_case
def calculate_total_price():
    item_count = 0

# Constants: UPPERCASE with underscores
MAX_ITEMS_PER_PAGE = 100
DEFAULT_CURRENCY = 'USD'

# Protected members: single leading underscore
class User:
    def __init__(self):
        self._password_hash = None

# Private members: double leading underscore
class Order:
    def __init__(self):
        self.__transaction_id = None

Imports

  1. Group imports in the following order:

    # Standard library imports
    import os
    import json
    from datetime import datetime
    
    # Third-party imports
    import django
    from rest_framework import viewsets
    
    # Local application imports
    from .models import Product
    from .serializers import ProductSerializer
    

  2. Use absolute imports over relative imports

  3. Avoid using from module import *

String Formatting

# Prefer f-strings for string formatting
name = "John"
age = 30
message = f"User {name} is {age} years old"

# Use .format() for complex formatting
"{:,.2f}".format(1234.5678)  # Returns: "1,234.57"

# Use % formatting only when necessary (e.g., logging)
logging.info("%s attempted to %s", user, action)

Comments and Documentation

  1. Docstrings for all public modules, functions, classes, and methods

    def calculate_discount(price: float, percentage: float) -> float:
        """Calculate the discount amount for a given price.
    
        Args:
            price: Original price
            percentage: Discount percentage (0-100)
    
        Returns:
            float: Discounted amount
    
        Raises:
            ValueError: If percentage is not between 0 and 100
        """
        if not 0 <= percentage <= 100:
            raise ValueError("Percentage must be between 0 and 100")
        return price * (percentage / 100)
    

  2. Use inline comments sparingly and only for complex logic

    # Bad - obvious comment
    x += 1  # Increment x
    
    # Good - explains complex logic
    adjusted_weight = base_weight * coefficient  # Apply regional weight adjustment
    

Type Hints

Use type hints for function arguments and return values:

from typing import List, Optional, Dict

def get_active_products(
    category_id: int,
    limit: Optional[int] = None
) -> List[Dict[str, any]]:
    """Retrieve active products for a category."""
    products = Product.objects.filter(
        category_id=category_id,
        is_active=True
    )
    if limit:
        products = products[:limit]
    return list(products.values())

Django-Specific Guidelines

Models

from django.db import models
from django.utils.translation import gettext_lazy as _

class Product(models.Model):
    """
    Product model representing items in the store.
    """
    name = models.CharField(
        _("product name"),
        max_length=255,
        help_text=_("Name of the product")
    )
    price = models.DecimalField(
        _("price"),
        max_digits=10,
        decimal_places=2
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']
        verbose_name = _("product")
        verbose_name_plural = _("products")

    def __str__(self):
        return self.name

Views

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action

class ProductViewSet(viewsets.ModelViewSet):
    """
    ViewSet for managing product operations.
    """
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        """
        Customize queryset based on request parameters.
        """
        queryset = super().get_queryset()
        category = self.request.query_params.get('category')
        if category:
            queryset = queryset.filter(category_id=category)
        return queryset

Serializers

from rest_framework import serializers

class ProductSerializer(serializers.ModelSerializer):
    """
    Serializer for Product model.
    """
    category_name = serializers.CharField(
        source='category.name',
        read_only=True
    )

    class Meta:
        model = Product
        fields = [
            'id',
            'name',
            'price',
            'category',
            'category_name',
            'created_at'
        ]
        read_only_fields = ['created_at']

Testing Guidelines

Test Structure

from django.test import TestCase
from .models import Product

class ProductTests(TestCase):
    """Tests for Product model."""

    def setUp(self):
        """Set up test data."""
        self.product = Product.objects.create(
            name="Test Product",
            price=99.99
        )

    def test_product_creation(self):
        """Test product can be created."""
        self.assertEqual(self.product.name, "Test Product")
        self.assertEqual(float(self.product.price), 99.99)

Test Naming

def test_should_create_product_when_valid_data():
    pass

def test_should_raise_error_when_price_negative():
    pass

def test_should_return_404_when_product_not_found():
    pass

Git Commit Guidelines

Commit Message Format

<type>(<scope>): <subject>

<body>

<footer>

Types: - feat: New feature - fix: Bug fix - docs: Documentation changes - style: Code style changes (formatting, etc) - refactor: Code refactoring - test: Adding or modifying tests - chore: Maintenance tasks

Example:

feat(products): add pagination to product list API

- Add PageNumberPagination to ProductViewSet
- Set default page size to 20 items
- Add max page size limit of 100

Closes #123

Code Review Guidelines

Pull Request Checklist

  1. Code follows style guide
  2. Tests are included and passing
  3. Documentation is updated
  4. No unnecessary dependencies added
  5. Performance considerations addressed
  6. Security implications considered

Review Comments

  • Be specific and constructive
  • Explain why changes are needed
  • Provide examples when possible
  • Use a friendly and professional tone

IDE Configuration

VSCode Settings

{
    "python.linting.enabled": true,
    "python.linting.pylintEnabled": true,
    "python.formatting.provider": "black",
    "editor.formatOnSave": true,
    "editor.rulers": [88],
    "files.trimTrailingWhitespace": true
}

PyCharm Settings

  • Enable PEP 8 checks
  • Set line length to 88
  • Enable "Optimize imports on the fly"
  • Configure Black as external tool

Tools and Automation

Required Tools

  1. Black: Code formatting
  2. isort: Import sorting
  3. flake8: Linting
  4. mypy: Type checking
  5. pre-commit: Git hooks

pre-commit Configuration

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 22.3.0
    hooks:
      - id: black
        language_version: python3

  - repo: https://github.com/pycqa/isort
    rev: 5.10.1
    hooks:
      - id: isort

  - repo: https://github.com/pycqa/flake8
    rev: 4.0.1
    hooks:
      - id: flake8