Contributing to CallPyBack - adnanhd/observer-pattern GitHub Wiki

Contributing to CallPyBack

Thank you for your interest in contributing to CallPyBack! This guide will help you get started with contributing to our advanced callback decorator framework.

๐ŸŽฏ Ways to Contribute

We welcome all types of contributions:

  • ๐Ÿ› Bug Reports - Help us identify and fix issues
  • ๐Ÿ’ก Feature Requests - Suggest new functionality
  • ๐Ÿ“ Documentation - Improve guides, examples, and API docs
  • ๐Ÿงช Testing - Add test cases and improve coverage
  • ๐Ÿ”ง Code - Fix bugs, implement features, optimize performance
  • ๐ŸŽจ Examples - Create real-world usage examples
  • ๐Ÿ“Š Performance - Benchmark and optimize the framework

๐Ÿš€ Getting Started

1. Fork and Clone

# Fork the repository on GitHub, then clone your fork
git clone https://github.com/yourusername/callpyback.git
cd callpyback

# Add the original repository as upstream
git remote add upstream https://github.com/adnanharundogan/callpyback.git

2. Set Up Development Environment

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"

# Install pre-commit hooks
pre-commit install

3. Verify Setup

# Run tests to ensure everything works
pytest tests/ -v

# Run linting
flake8 callpyback/
black --check callpyback/
mypy callpyback/

# Run all checks
make test-all

๐Ÿ“‹ Development Workflow

Branch Strategy

We use a feature branch workflow:

# Create feature branch from main
git checkout main
git pull upstream main
git checkout -b feature/your-feature-name

# Make your changes
# ... code, test, document ...

# Push your branch
git push origin feature/your-feature-name

# Create Pull Request on GitHub

Branch Naming Convention

  • feature/description - New features
  • bugfix/description - Bug fixes
  • docs/description - Documentation updates
  • perf/description - Performance improvements
  • refactor/description - Code refactoring
  • test/description - Test improvements

Commit Message Format

We follow conventional commits:

type(scope): description

[optional body]

[optional footer]

Types:

  • feat - New features
  • fix - Bug fixes
  • docs - Documentation
  • test - Tests
  • refactor - Code refactoring
  • perf - Performance improvements
  • chore - Maintenance tasks

Examples:

feat(observers): add database audit observer
fix(state-machine): handle edge case in transition validation  
docs(wiki): update custom observer examples
test(integration): add concurrent execution tests

๐Ÿงช Testing Guidelines

Test Structure

tests/
โ”œโ”€โ”€ unit/                 # Unit tests for individual components
โ”‚   โ”œโ”€โ”€ test_core/
โ”‚   โ”œโ”€โ”€ test_observers/
โ”‚   โ””โ”€โ”€ test_management/
โ”œโ”€โ”€ integration/          # Integration tests
โ”œโ”€โ”€ performance/          # Performance benchmarks
โ””โ”€โ”€ examples/            # Example usage tests

Writing Tests

Unit Tests

import pytest
from unittest.mock import Mock, patch
from callpyback import CallPyBack
from callpyback.observers.base import BaseObserver

class TestMyFeature:
    def test_basic_functionality(self):
        """Test basic functionality with clear assertions."""
        # Arrange
        observer = Mock()
        decorator = CallPyBack(observers=[observer])
        
        # Act
        @decorator
        def test_function():
            return "result"
        
        result = test_function()
        
        # Assert
        assert result == "result"
        observer.update.assert_called()
    
    def test_error_conditions(self):
        """Test error conditions and edge cases."""
        with pytest.raises(ConfigurationError):
            CallPyBack(exception_classes="invalid")
    
    @patch('callpyback.core.time_sources.time.time')
    def test_with_mocks(self, mock_time):
        """Test with external dependencies mocked."""
        mock_time.return_value = 1000.0
        # Test implementation

Integration Tests

class TestObserverIntegration:
    def test_multiple_observers_cooperation(self):
        """Test that multiple observers work together correctly."""
        metrics = MetricsObserver()
        logger = LoggingObserver()
        
        @CallPyBack(observers=[metrics, logger])
        def integrated_function():
            return "success"
        
        integrated_function()
        
        # Verify both observers were notified
        assert metrics.get_metrics()["total_executions"] == 1
        # Check logging output if needed

Test Requirements

  • Coverage: Maintain > 90% test coverage
  • Performance: Tests should run in < 30 seconds total
  • Isolation: Tests should not depend on external services
  • Deterministic: Tests should pass consistently
  • Clear: Test names should describe what they test

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=callpyback --cov-report=html

# Run specific test file
pytest tests/unit/test_core/test_decorator.py

# Run tests matching pattern
pytest -k "test_observer"

# Run tests with verbose output
pytest -v

# Run performance tests
pytest tests/performance/ --benchmark-only

๐Ÿ“ Documentation Standards

Code Documentation

Docstrings

def complex_function(param1: str, param2: int = 10) -> Dict[str, Any]:
    """
    Brief description of what the function does.
    
    Longer description if needed, explaining the purpose,
    behavior, and any important details.
    
    Args:
        param1: Description of param1
        param2: Description of param2 with default value
        
    Returns:
        Dictionary containing result data with keys:
        - 'status': Operation status
        - 'data': Processed data
        
    Raises:
        ValueError: When param1 is empty
        ConnectionError: When external service is unavailable
        
    Example:
        >>> result = complex_function("test", 20)
        >>> print(result['status'])
        'success'
    """

Type Hints

from typing import List, Dict, Optional, Union, Callable
from typing_extensions import TypedDict

class ConfigDict(TypedDict):
    priority: int
    name: str
    enabled: bool

def typed_function(
    observers: List[Observer],
    config: Optional[ConfigDict] = None
) -> Callable[[Callable], Callable]:
    """Function with comprehensive type hints."""

Wiki Documentation

When updating wiki pages:

  1. Follow the style guide - Use consistent formatting and structure
  2. Include examples - Every concept should have working code examples
  3. Link related pages - Create cross-references to related documentation
  4. Test examples - Ensure all code examples actually work
  5. Update table of contents - Keep navigation current

Example Documentation

# examples/new_feature_example.py
"""
New Feature Example
Demonstrates the usage of new feature with real-world scenarios.
"""

from callpyback import CallPyBack

# Clear, commented example
@CallPyBack(
    observers=[...],  # Explain what observers do
    exception_classes=(ValueError,),  # Explain why these exceptions
)
def example_function(param):
    """Example function showing new feature."""
    # Implementation with comments
    return result

if __name__ == "__main__":
    # Runnable example
    result = example_function("test")
    print(f"Result: {result}")

๐ŸŽจ Code Style Guidelines

Python Style

We follow PEP 8 with some modifications:

# Line length: 88 characters (Black default)
# Use Black for formatting
# Use flake8 for linting
# Use mypy for type checking

Naming Conventions

# Classes: PascalCase
class ObserverManager:
    pass

# Functions/methods: snake_case
def get_execution_metrics():
    pass

# Constants: UPPER_SNAKE_CASE
MAX_EXECUTION_TIME = 300

# Private methods: _leading_underscore
def _internal_method():
    pass

# Protected attributes: _leading_underscore
self._protected_attribute = value

Import Organization

# Standard library imports
import os
import time
from typing import Dict, List

# Third-party imports
import pytest

# Local imports
from callpyback.core.context import ExecutionContext
from callpyback.observers.base import BaseObserver

Code Quality Tools

Pre-commit Configuration

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

  - repo: https://github.com/pycqa/flake8
    rev: 4.0.1
    hooks:
      - id: flake8
        additional_dependencies: [flake8-docstrings]

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v0.942
    hooks:
      - id: mypy
        additional_dependencies: [types-all]

Quality Checks

# Format code
black callpyback/ tests/

# Check linting
flake8 callpyback/ tests/

# Type checking
mypy callpyback/

# Security scanning
bandit -r callpyback/

# Import sorting
isort callpyback/ tests/

๐Ÿ—๏ธ Architecture Guidelines

Design Principles

  1. SOLID Principles

    • Single Responsibility: Each class has one reason to change
    • Open/Closed: Open for extension, closed for modification
    • Liskov Substitution: Subtypes must be substitutable for base types
    • Interface Segregation: Many specific interfaces > one general interface
    • Dependency Inversion: Depend on abstractions, not concretions
  2. Observer Pattern Integrity

    • Observers must be loosely coupled
    • Subject shouldn't know observer details
    • Observer failures shouldn't affect subject or other observers
  3. Thread Safety

    • All public APIs must be thread-safe
    • Use appropriate locking mechanisms
    • Avoid deadlocks and race conditions
  4. Performance

    • Minimize observer overhead
    • Use efficient data structures
    • Lazy evaluation where possible

Code Organization

callpyback/
โ”œโ”€โ”€ core/              # Core framework components
โ”‚   โ”œโ”€โ”€ context.py     # Execution context and results
โ”‚   โ”œโ”€โ”€ decorator.py   # Main CallPyBack decorator
โ”‚   โ”œโ”€โ”€ state_machine.py
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ observers/         # Observer implementations
โ”‚   โ”œโ”€โ”€ base.py       # Base observer classes
โ”‚   โ”œโ”€โ”€ builtin.py    # Built-in observers
โ”‚   โ”œโ”€โ”€ callback.py   # Callback observer wrapper
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ management/        # Observer and error management
โ”œโ”€โ”€ protocols.py       # Type protocols and interfaces
โ”œโ”€โ”€ errors.py         # Exception classes
โ””โ”€โ”€ factories.py      # Factory functions

Adding New Features

1. Core Components

# For core framework changes
class NewComponent:
    """
    New component following framework patterns.
    
    Must be:
    - Thread-safe
    - Well-documented
    - Thoroughly tested
    - Follow dependency injection patterns
    """
    
    def __init__(self, dependencies: Dependencies):
        # Inject dependencies for testability
        pass

2. Observers

# For new observer types
class NewObserver(BaseObserver):
    """
    New observer type with specific purpose.
    
    Should:
    - Inherit from BaseObserver
    - Handle errors gracefully
    - Be memory efficient
    - Provide clear configuration options
    """
    
    def update(self, context: ExecutionContext) -> None:
        # Implementation
        pass

3. Integration Points

# For framework integrations
def integrate_with_framework(framework_config):
    """
    Integration function for external framework.
    
    Should:
    - Be optional (not break if framework not installed)
    - Provide clear error messages
    - Follow framework conventions
    - Include comprehensive examples
    """

๐Ÿ”ง Performance Guidelines

Benchmarking

When making performance-related changes:

  1. Baseline measurement - Measure current performance
  2. Implement changes - Make your improvements
  3. Measure again - Verify improvement
  4. Document impact - Record performance changes
# Example benchmark
import time
import statistics

def benchmark_observer_overhead():
    """Benchmark observer notification overhead."""
    # Setup
    observer = TestObserver()
    decorator = CallPyBack(observers=[observer])
    
    @decorator
    def test_function():
        return "result"
    
    # Baseline (no observers)
    baseline_times = []
    for _ in range(1000):
        start = time.time()
        test_function()
        baseline_times.append(time.time() - start)
    
    # With observers
    observer_times = []
    for _ in range(1000):
        start = time.time()
        test_function()
        observer_times.append(time.time() - start)
    
    # Analysis
    baseline_avg = statistics.mean(baseline_times)
    observer_avg = statistics.mean(observer_times)
    overhead = observer_avg - baseline_avg
    
    print(f"Baseline: {baseline_avg*1000:.3f}ms")
    print(f"With observers: {observer_avg*1000:.3f}ms")
    print(f"Overhead: {overhead*1000:.3f}ms")

Performance Requirements

  • Observer overhead: < 1ms per observer
  • Memory usage: Minimal allocation in hot paths
  • Scalability: Linear performance with observer count
  • Thread safety: No performance degradation under contention

๐Ÿšจ Security Guidelines

Security Considerations

  1. Input Validation

    • Validate all user inputs
    • Sanitize data before logging
    • Prevent injection attacks
  2. Data Protection

    • Mask sensitive data in logs
    • Secure transmission of observer data
    • Respect data privacy regulations
  3. Access Control

    • Implement observer-level permissions where needed
    • Audit security-relevant operations
    • Follow principle of least privilege
# Example: Secure observer implementation
class SecureObserver(BaseObserver):
    def __init__(self, allowed_functions=None):
        super().__init__(priority=50, name="Secure")
        self.allowed_functions = allowed_functions or set()
    
    def update(self, context):
        # Check permissions
        if self.allowed_functions and context.function_signature.name not in self.allowed_functions:
            return  # Skip unauthorized functions
        
        # Sanitize sensitive data
        safe_args = self._sanitize_arguments(context.arguments)
        
        # Proceed with secure processing
        self._process_securely(safe_args, context)
    
    def _sanitize_arguments(self, arguments):
        """Remove or mask sensitive data."""
        sensitive_keys = {'password', 'token', 'secret', 'key'}
        sanitized = {}
        
        for key, value in arguments.items():
            if any(sensitive in key.lower() for sensitive in sensitive_keys):
                sanitized[key] = "***MASKED***"
            else:
                sanitized[key] = value
        
        return sanitized

๐Ÿ“Š Release Process

Version Numbering

We follow semantic versioning (SemVer):

  • MAJOR (X.0.0): Breaking changes
  • MINOR (0.X.0): New features, backward compatible
  • PATCH (0.0.X): Bug fixes, backward compatible

Release Checklist

Pre-release

  • All tests passing
  • Documentation updated
  • CHANGELOG.md updated
  • Version bumped in __init__.py
  • Performance benchmarks run
  • Security review completed

Release

  • Create release branch
  • Final testing on release branch
  • Tag release in git
  • Build and upload to PyPI
  • Update GitHub release notes
  • Announce release

Post-release

  • Monitor for issues
  • Update documentation site
  • Plan next release cycle

๐Ÿค Community Guidelines

Code of Conduct

We follow the Contributor Covenant Code of Conduct:

  • Be respectful - Treat everyone with respect and kindness
  • Be inclusive - Welcome contributors from all backgrounds
  • Be collaborative - Work together constructively
  • Be professional - Keep discussions focused and productive

Communication Channels

  • GitHub Issues - Bug reports and feature requests
  • GitHub Discussions - General questions and community discussion
  • Pull Requests - Code review and collaboration
  • Email - Direct contact for sensitive issues

Getting Help

If you need help with contributions:

  1. Check documentation - Wiki and README first
  2. Search issues - Your question might be answered already
  3. Ask in discussions - Community can help
  4. Contact maintainers - For complex questions

๐ŸŽฏ First-Time Contributors

Good First Issues

Look for issues labeled:

  • good first issue - Beginner-friendly issues
  • documentation - Documentation improvements
  • help wanted - Community help needed
  • bug - Bug fixes (often straightforward)

Mentorship

We provide mentorship for new contributors:

  • Code review feedback
  • Architecture guidance
  • Testing assistance
  • Documentation support

๐Ÿ“‹ Pull Request Process

Before Submitting

  1. Fork and branch - Create feature branch from main
  2. Make changes - Implement your feature/fix
  3. Add tests - Ensure good test coverage
  4. Update docs - Document new features
  5. Run checks - Ensure all quality checks pass
  6. Commit properly - Follow commit message format

PR Template

## Description
Brief description of changes

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Refactoring

## Testing
- [ ] All tests pass
- [ ] New tests added
- [ ] Manual testing completed

## Documentation
- [ ] Documentation updated
- [ ] Examples added/updated
- [ ] Wiki updated (if needed)

## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Comments added to complex code
- [ ] No breaking changes (or documented)

Review Process

  1. Automated checks - CI/CD pipeline runs
  2. Maintainer review - Code and design review
  3. Community feedback - Open for community input
  4. Iterations - Address review feedback
  5. Approval - Maintainer approval
  6. Merge - Squash and merge to main

๐Ÿš€ Advanced Contributions

Performance Optimizations

When contributing performance improvements:

  1. Profile first - Identify actual bottlenecks
  2. Benchmark - Measure before and after
  3. Document - Explain the optimization
  4. Test thoroughly - Ensure correctness maintained

New Observer Types

For complex observer contributions:

  1. Design document - Outline the observer's purpose
  2. Interface design - Define clear APIs
  3. Implementation - Follow framework patterns
  4. Comprehensive testing - Unit and integration tests
  5. Documentation - Usage examples and guides

Framework Extensions

For major framework extensions:

  1. RFC process - Propose changes for discussion
  2. Prototype - Build proof of concept
  3. Community feedback - Get input early
  4. Iterative development - Incremental implementation
  5. Migration guide - Help users adopt changes

Thank you for contributing to CallPyBack! Your contributions help make function observability better for everyone. ๐ŸŽ‰