Skip to content

Contributing to CallPyBack

Adnan Harun Doğan edited this page Jun 14, 2025 · 1 revision

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. πŸŽ‰

Clone this wiki locally