Code Style Guide¶
This document outlines the coding standards and style guidelines for the NSGG Backend project.
Python Style Guide¶
General Guidelines¶
- Follow PEP 8 conventions
- Use 4 spaces for indentation (no tabs)
- Maximum line length: 88 characters (Black formatter default)
- 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¶
-
Group imports in the following order:
-
Use absolute imports over relative imports
- 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¶
-
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) -
Use inline comments sparingly and only for complex logic
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¶
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¶
- Code follows style guide
- Tests are included and passing
- Documentation is updated
- No unnecessary dependencies added
- Performance considerations addressed
- 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¶
- Black: Code formatting
- isort: Import sorting
- flake8: Linting
- mypy: Type checking
- pre-commit: Git hooks