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 featuresbugfix/description
- Bug fixesdocs/description
- Documentation updatesperf/description
- Performance improvementsrefactor/description
- Code refactoringtest/description
- Test improvements
Commit Message Format
We follow conventional commits:
type(scope): description
[optional body]
[optional footer]
Types:
feat
- New featuresfix
- Bug fixesdocs
- Documentationtest
- Testsrefactor
- Code refactoringperf
- Performance improvementschore
- 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:
- Follow the style guide - Use consistent formatting and structure
- Include examples - Every concept should have working code examples
- Link related pages - Create cross-references to related documentation
- Test examples - Ensure all code examples actually work
- 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
-
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
-
Observer Pattern Integrity
- Observers must be loosely coupled
- Subject shouldn't know observer details
- Observer failures shouldn't affect subject or other observers
-
Thread Safety
- All public APIs must be thread-safe
- Use appropriate locking mechanisms
- Avoid deadlocks and race conditions
-
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:
- Baseline measurement - Measure current performance
- Implement changes - Make your improvements
- Measure again - Verify improvement
- 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
-
Input Validation
- Validate all user inputs
- Sanitize data before logging
- Prevent injection attacks
-
Data Protection
- Mask sensitive data in logs
- Secure transmission of observer data
- Respect data privacy regulations
-
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:
- Check documentation - Wiki and README first
- Search issues - Your question might be answered already
- Ask in discussions - Community can help
- Contact maintainers - For complex questions
๐ฏ First-Time Contributors
Good First Issues
Look for issues labeled:
good first issue
- Beginner-friendly issuesdocumentation
- Documentation improvementshelp wanted
- Community help neededbug
- 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
- Fork and branch - Create feature branch from main
- Make changes - Implement your feature/fix
- Add tests - Ensure good test coverage
- Update docs - Document new features
- Run checks - Ensure all quality checks pass
- 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
- Automated checks - CI/CD pipeline runs
- Maintainer review - Code and design review
- Community feedback - Open for community input
- Iterations - Address review feedback
- Approval - Maintainer approval
- Merge - Squash and merge to main
๐ Advanced Contributions
Performance Optimizations
When contributing performance improvements:
- Profile first - Identify actual bottlenecks
- Benchmark - Measure before and after
- Document - Explain the optimization
- Test thoroughly - Ensure correctness maintained
New Observer Types
For complex observer contributions:
- Design document - Outline the observer's purpose
- Interface design - Define clear APIs
- Implementation - Follow framework patterns
- Comprehensive testing - Unit and integration tests
- Documentation - Usage examples and guides
Framework Extensions
For major framework extensions:
- RFC process - Propose changes for discussion
- Prototype - Build proof of concept
- Community feedback - Get input early
- Iterative development - Incremental implementation
- Migration guide - Help users adopt changes
Thank you for contributing to CallPyBack! Your contributions help make function observability better for everyone. ๐