punimtag/docs/code-conventions.md
2025-08-15 00:57:39 -08:00

17 KiB

PunimTag Code Conventions

Overview

This document defines the coding standards and conventions for PunimTag development.

Python Conventions

Code Style

Follow PEP 8 with these specific guidelines:

# Imports
import os
import sys
from typing import List, Dict, Optional
from flask import Flask, request, jsonify

# Constants
MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif'}

# Functions
def process_image(image_path: str, max_size: int = MAX_FILE_SIZE) -> Dict[str, any]:
    """
    Process an image file and extract metadata.

    Args:
        image_path: Path to the image file
        max_size: Maximum file size in bytes

    Returns:
        Dictionary containing image metadata

    Raises:
        FileNotFoundError: If image file doesn't exist
        ValueError: If file size exceeds limit
    """
    if not os.path.exists(image_path):
        raise FileNotFoundError(f"Image file not found: {image_path}")

    file_size = os.path.getsize(image_path)
    if file_size > max_size:
        raise ValueError(f"File size {file_size} exceeds limit {max_size}")

    # Process the image
    metadata = extract_metadata(image_path)
    return metadata

# Classes
class ImageProcessor:
    """Handles image processing operations."""

    def __init__(self, config: Dict[str, any]):
        """
        Initialize the image processor.

        Args:
            config: Configuration dictionary
        """
        self.config = config
        self.supported_formats = config.get('supported_formats', ALLOWED_EXTENSIONS)

    def process_batch(self, image_paths: List[str]) -> List[Dict[str, any]]:
        """
        Process multiple images in batch.

        Args:
            image_paths: List of image file paths

        Returns:
            List of processed image metadata
        """
        results = []
        for path in image_paths:
            try:
                result = self.process_single(path)
                results.append(result)
            except Exception as e:
                logger.error(f"Failed to process {path}: {e}")
                results.append({'error': str(e), 'path': path})

        return results

Naming Conventions

Variables and Functions

# Use snake_case for variables and functions
user_name = "john_doe"
photo_count = 150
max_file_size = 10 * 1024 * 1024

def get_user_photos(user_id: int) -> List[Dict]:
    """Get photos for a specific user."""
    pass

def calculate_face_similarity(face1: List[float], face2: List[float]) -> float:
    """Calculate similarity between two face encodings."""
    pass

Classes

# Use PascalCase for classes
class PhotoManager:
    """Manages photo operations."""
    pass

class FaceRecognitionEngine:
    """Handles face recognition operations."""
    pass

Constants

# Use UPPER_CASE for constants
DATABASE_PATH = "punimtag_simple.db"
MAX_THUMBNAIL_SIZE = (200, 200)
DEFAULT_PAGE_SIZE = 20

Type Hints

from typing import List, Dict, Optional, Union, Tuple

def get_photos(
    user_id: int,
    page: int = 1,
    per_page: int = DEFAULT_PAGE_SIZE,
    filters: Optional[Dict[str, any]] = None
) -> Dict[str, Union[List[Dict], int]]:
    """
    Get photos with pagination and filtering.

    Returns:
        Dictionary with 'photos' list and 'total' count
    """
    pass

def process_face_encodings(
    encodings: List[List[float]]
) -> Tuple[List[float], float]:
    """
    Process face encodings and return average encoding and confidence.

    Returns:
        Tuple of (average_encoding, confidence_score)
    """
    pass

Error Handling

import logging
from typing import Optional

logger = logging.getLogger(__name__)

def safe_operation(func):
    """Decorator for safe operation execution."""
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logger.error(f"Error in {func.__name__}: {e}")
            return None
    return wrapper

@safe_operation
def load_image_safely(image_path: str) -> Optional[PIL.Image.Image]:
    """Load image with error handling."""
    return PIL.Image.open(image_path)

def process_user_request(user_data: Dict) -> Dict[str, any]:
    """Process user request with comprehensive error handling."""
    try:
        # Validate input
        if not user_data.get('user_id'):
            return {'success': False, 'error': 'Missing user_id'}

        # Process request
        result = perform_operation(user_data)
        return {'success': True, 'data': result}

    except ValueError as e:
        logger.warning(f"Validation error: {e}")
        return {'success': False, 'error': str(e)}
    except FileNotFoundError as e:
        logger.error(f"File not found: {e}")
        return {'success': False, 'error': 'File not found'}
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        return {'success': False, 'error': 'Internal server error'}

JavaScript Conventions

Code Style

Follow ESLint with these specific guidelines:

// Constants
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif"];

// Functions
function processImage(imagePath, maxSize = MAX_FILE_SIZE) {
  /**
   * Process an image file and extract metadata.
   * @param {string} imagePath - Path to the image file
   * @param {number} maxSize - Maximum file size in bytes
   * @returns {Promise<Object>} Image metadata
   */
  return new Promise((resolve, reject) => {
    if (!imagePath) {
      reject(new Error("Image path is required"));
      return;
    }

    // Process the image
    resolve(extractMetadata(imagePath));
  });
}

// Classes
class ImageProcessor {
  /**
   * Handles image processing operations.
   * @param {Object} config - Configuration object
   */
  constructor(config) {
    this.config = config;
    this.supportedFormats = config.supportedFormats || ALLOWED_EXTENSIONS;
  }

  /**
   * Process multiple images in batch.
   * @param {string[]} imagePaths - Array of image file paths
   * @returns {Promise<Object[]>} Array of processed image metadata
   */
  async processBatch(imagePaths) {
    const results = [];

    for (const path of imagePaths) {
      try {
        const result = await this.processSingle(path);
        results.push(result);
      } catch (error) {
        console.error(`Failed to process ${path}:`, error);
        results.push({ error: error.message, path });
      }
    }

    return results;
  }
}

Naming Conventions

Variables and Functions

// Use camelCase for variables and functions
const userName = "johnDoe";
const photoCount = 150;
const maxFileSize = 10 * 1024 * 1024;

function getUserPhotos(userId) {
  // Get photos for a specific user
}

function calculateFaceSimilarity(face1, face2) {
  // Calculate similarity between two face encodings
}

Classes

// Use PascalCase for classes
class PhotoManager {
  // Manages photo operations
}

class FaceRecognitionEngine {
  // Handles face recognition operations
}

Constants

// Use UPPER_SNAKE_CASE for constants
const DATABASE_PATH = "punimtag_simple.db";
const MAX_THUMBNAIL_SIZE = { width: 200, height: 200 };
const DEFAULT_PAGE_SIZE = 20;

Error Handling

// Async/await with try-catch
async function processUserRequest(userData) {
  try {
    // Validate input
    if (!userData.userId) {
      return { success: false, error: "Missing userId" };
    }

    // Process request
    const result = await performOperation(userData);
    return { success: true, data: result };
  } catch (error) {
    console.error("Error processing request:", error);
    return { success: false, error: "Internal server error" };
  }
}

// Promise-based error handling
function loadImageSafely(imagePath) {
  return new Promise((resolve, reject) => {
    if (!imagePath) {
      reject(new Error("Image path is required"));
      return;
    }

    // Load image logic
    resolve(imageData);
  }).catch((error) => {
    console.error("Error loading image:", error);
    return null;
  });
}

Database Conventions

Table Naming

-- Use snake_case for table names
CREATE TABLE user_profiles (
    id INTEGER PRIMARY KEY,
    user_name TEXT NOT NULL,
    email_address TEXT UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE photo_metadata (
    id INTEGER PRIMARY KEY,
    image_id INTEGER REFERENCES images(id),
    exif_data TEXT,
    gps_coordinates TEXT,
    processing_status TEXT DEFAULT 'pending'
);

Column Naming

-- Use snake_case for column names
CREATE TABLE images (
    id INTEGER PRIMARY KEY,
    file_name TEXT NOT NULL,
    file_path TEXT NOT NULL,
    file_size INTEGER,
    date_taken TIMESTAMP,
    upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_processed BOOLEAN DEFAULT FALSE
);

Index Naming

-- Use descriptive names for indexes
CREATE INDEX idx_images_date_taken ON images(date_taken);
CREATE INDEX idx_faces_person_id ON faces(person_id);
CREATE INDEX idx_photos_user_id_date ON photos(user_id, date_taken);

File Organization

Python Files

# File: src/backend/photo_manager.py
"""
Photo management module.

This module handles all photo-related operations including
upload, processing, and metadata extraction.
"""

import os
import logging
from typing import List, Dict, Optional
from PIL import Image

# Constants
MAX_FILE_SIZE = 10 * 1024 * 1024
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif'}

# Logging
logger = logging.getLogger(__name__)

class PhotoManager:
    """Manages photo operations."""

    def __init__(self, config: Dict[str, any]):
        self.config = config
        self.storage_path = config.get('storage_path', './photos')

    def process_photo(self, photo_path: str) -> Dict[str, any]:
        """Process a single photo."""
        # Implementation
        pass

# Main execution (if applicable)
if __name__ == "__main__":
    # Test or standalone execution
    pass

JavaScript Files

// File: src/frontend/photoManager.js
/**
 * Photo management module.
 *
 * This module handles all photo-related operations including
 * upload, processing, and metadata extraction.
 */

// Constants
const MAX_FILE_SIZE = 10 * 1024 * 1024;
const ALLOWED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif"];

// Logging
const logger = {
  info: (msg) => console.log(`[INFO] ${msg}`),
  error: (msg) => console.error(`[ERROR] ${msg}`),
  warn: (msg) => console.warn(`[WARN] ${msg}`),
};

class PhotoManager {
  /**
   * Manages photo operations.
   * @param {Object} config - Configuration object
   */
  constructor(config) {
    this.config = config;
    this.storagePath = config.storagePath || "./photos";
  }

  /**
   * Process a single photo.
   * @param {string} photoPath - Path to the photo
   * @returns {Promise<Object>} Processing result
   */
  async processPhoto(photoPath) {
    // Implementation
  }
}

// Export for module systems
if (typeof module !== "undefined" && module.exports) {
  module.exports = PhotoManager;
}

Documentation Standards

Function Documentation

def extract_face_features(image_path: str, face_coordinates: Tuple[int, int, int, int]) -> List[float]:
    """
    Extract face features from an image region.

    This function takes an image and face coordinates, then extracts
    128-dimensional feature vectors using dlib's face recognition model.

    Args:
        image_path: Path to the source image file
        face_coordinates: Tuple of (left, top, right, bottom) coordinates

    Returns:
        List of 128 float values representing face features

    Raises:
        FileNotFoundError: If image file doesn't exist
        ValueError: If face coordinates are invalid
        RuntimeError: If face recognition model fails

    Example:
        >>> coords = (100, 100, 200, 200)
        >>> features = extract_face_features("photo.jpg", coords)
        >>> len(features)
        128
    """
    pass

Class Documentation

class FaceRecognitionEngine:
    """
    Engine for face recognition operations.

    This class provides methods for detecting faces in images,
    extracting face features, and comparing face similarities.

    Attributes:
        model_path (str): Path to the face recognition model
        confidence_threshold (float): Minimum confidence for face detection
        max_faces (int): Maximum number of faces to detect per image

    Example:
        >>> engine = FaceRecognitionEngine()
        >>> faces = engine.detect_faces("group_photo.jpg")
        >>> print(f"Found {len(faces)} faces")
    """

    def __init__(self, model_path: str = None, confidence_threshold: float = 0.6):
        """
        Initialize the face recognition engine.

        Args:
            model_path: Path to the face recognition model file
            confidence_threshold: Minimum confidence for face detection
        """
        pass

Testing Conventions

Test File Structure

# File: tests/unit/test_photo_manager.py
"""
Unit tests for PhotoManager class.
"""

import pytest
from unittest.mock import Mock, patch
from src.backend.photo_manager import PhotoManager

class TestPhotoManager:
    """Test cases for PhotoManager class."""

    @pytest.fixture
    def photo_manager(self):
        """Create a PhotoManager instance for testing."""
        config = {'storage_path': '/test/path'}
        return PhotoManager(config)

    def test_process_photo_with_valid_file(self, photo_manager):
        """Test processing a valid photo file."""
        # Test implementation
        pass

    def test_process_photo_with_invalid_file(self, photo_manager):
        """Test processing an invalid photo file."""
        # Test implementation
        pass

Git Conventions

Commit Messages

feat: add face recognition feature
fix: resolve duplicate photo detection issue
docs: update API documentation
test: add unit tests for photo processing
refactor: improve error handling in face detection
style: format code according to PEP 8
perf: optimize thumbnail generation
chore: update dependencies

Branch Naming

feature/face-recognition
bugfix/duplicate-detection
hotfix/security-vulnerability
docs/api-documentation
test/photo-processing
refactor/error-handling

Performance Guidelines

Python Performance

# Use list comprehensions instead of loops when appropriate
# Good
squares = [x**2 for x in range(1000)]

# Avoid
squares = []
for x in range(1000):
    squares.append(x**2)

# Use generators for large datasets
def process_large_dataset(file_path):
    """Process large dataset using generator."""
    with open(file_path, 'r') as file:
        for line in file:
            yield process_line(line)

# Use appropriate data structures
from collections import defaultdict, Counter

# Use defaultdict for counting
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1

# Use Counter for frequency analysis
word_freq = Counter(words)

JavaScript Performance

// Use appropriate array methods
// Good
const squares = Array.from({ length: 1000 }, (_, i) => i ** 2);

// Avoid
const squares = [];
for (let i = 0; i < 1000; i++) {
  squares.push(i ** 2);
}

// Use async/await for I/O operations
async function processImages(imagePaths) {
  const results = await Promise.all(
    imagePaths.map((path) => processImage(path))
  );
  return results;
}

// Use appropriate data structures
const wordCount = new Map();
words.forEach((word) => {
  wordCount.set(word, (wordCount.get(word) || 0) + 1);
});

Security Guidelines

Input Validation

import re
from pathlib import Path

def validate_filename(filename: str) -> bool:
    """Validate filename for security."""
    # Check for dangerous characters
    dangerous_chars = r'[<>:"/\\|?*]'
    if re.search(dangerous_chars, filename):
        return False

    # Check for path traversal
    if '..' in filename or filename.startswith('/'):
        return False

    # Check length
    if len(filename) > 255:
        return False

    return True

def sanitize_user_input(user_input: str) -> str:
    """Sanitize user input to prevent injection attacks."""
    # Remove HTML tags
    import html
    sanitized = html.escape(user_input)

    # Remove SQL injection patterns
    sql_patterns = [';', '--', '/*', '*/', 'union', 'select', 'drop']
    for pattern in sql_patterns:
        sanitized = sanitized.replace(pattern.lower(), '')

    return sanitized

Database Security

# Always use parameterized queries
def get_user_photos(user_id: int):
    """Get photos for a user using parameterized query."""
    cursor.execute(
        'SELECT * FROM photos WHERE user_id = ?',
        (user_id,)
    )
    return cursor.fetchall()

# Never use string formatting for SQL
# BAD - vulnerable to SQL injection
def bad_get_user_photos(user_id: int):
    cursor.execute(f'SELECT * FROM photos WHERE user_id = {user_id}')
    return cursor.fetchall()