# 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: ```python # 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 ```python # 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 ```python # Use PascalCase for classes class PhotoManager: """Manages photo operations.""" pass class FaceRecognitionEngine: """Handles face recognition operations.""" pass ``` #### Constants ```python # Use UPPER_CASE for constants DATABASE_PATH = "punimtag_simple.db" MAX_THUMBNAIL_SIZE = (200, 200) DEFAULT_PAGE_SIZE = 20 ``` ### Type Hints ```python 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 ```python 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: ```javascript // 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} 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} 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 ```javascript // 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 ```javascript // Use PascalCase for classes class PhotoManager { // Manages photo operations } class FaceRecognitionEngine { // Handles face recognition operations } ``` #### Constants ```javascript // 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 ```javascript // 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 ```sql -- 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 ```sql -- 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 ```sql -- 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 ```python # 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 ```javascript // 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} Processing result */ async processPhoto(photoPath) { // Implementation } } // Export for module systems if (typeof module !== "undefined" && module.exports) { module.exports = PhotoManager; } ``` ## Documentation Standards ### Function Documentation ```python 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 ```python 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 ```python # 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 ```python # 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 ```javascript // 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 ```python 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 ```python # 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() ```