Contributing Guidelines
Thank you for your interest in contributing to Easy PMF! This document outlines the process for contributing code, documentation, bug reports, and feature requests.
Table of Contents
- Code of Conduct
- Getting Started
- Development Setup
- Contributing Code
- Documentation
- Testing
- Code Style
- Pull Request Process
- Issue Reporting
- Release Process
Code of Conduct
This project adheres to a code of conduct that ensures a welcoming environment for all contributors. Please be respectful and constructive in all interactions.
Our Pledge
- Use welcoming and inclusive language
- Respect differing viewpoints and experiences
- Accept constructive criticism gracefully
- Focus on what is best for the community
- Show empathy towards other community members
Getting Started
Prerequisites
- Python 3.8 or higher
- Git for version control
- Familiarity with PMF (Positive Matrix Factorization) concepts
- Understanding of atmospheric chemistry or environmental data analysis (helpful but not required)
Development Environment
We use modern Python tooling for development:
- uv: For fast dependency management and virtual environments
- ruff: For code formatting and linting
- pytest: For testing
- mypy: For static type checking
- mkdocs: For documentation
- pre-commit: For automated quality checks on every commit
CI/CD Infrastructure
The project features comprehensive automated workflows:
- ✅ Continuous Integration: Automated testing across Python 3.9-3.12 on Ubuntu, macOS, and Windows
- ✅ Code Quality: Pre-commit hooks and CI checks for linting, formatting, and type validation
- ✅ Security: Automated dependency vulnerability scanning with Bandit
- ✅ Documentation: Automatic deployment to GitHub Pages on every commit
- ✅ Publishing: Automated PyPI publishing on tagged releases
- ✅ Maintenance: Weekly dependency updates and repository housekeeping
All quality checks that run in CI also run locally via pre-commit hooks, ensuring consistent code quality.
Development Setup
- Fork and Clone the Repository
# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/easy-pmf.git
cd easy-pmf
- Set Up Development Environment
# Install uv (modern Python package manager) if not already installed
# Windows: https://docs.astral.sh/uv/getting-started/installation/
# macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh
# Create development environment and install all dependencies
uv sync --all-extras
# Install pre-commit hooks for automated code quality checks
uv run pre-commit install
- Verify Installation
# Run tests to ensure everything works
uv run pytest
# Check code style and auto-fix issues
uv run ruff check --fix .
uv run ruff format .
# Verify type checking passes
uv run mypy .
# Test pre-commit hooks
uv run pre-commit run --all-files
Contributing Code
Types of Contributions
We welcome several types of contributions:
- Bug fixes: Fix issues in existing code
- New features: Add new functionality to the package
- Performance improvements: Optimize existing algorithms
- Documentation: Improve or add documentation
- Examples: Add new example analyses or datasets
- Tests: Improve test coverage
Before You Start
- Check existing issues: Look for related issues or feature requests
- Create an issue: If none exists, create one describing your proposed changes
- Discuss the approach: Get feedback before starting major work
- Check the roadmap: Ensure your contribution aligns with project goals
Development Workflow
- Create a Feature Branch
-
Make Your Changes
-
Write clean, readable code
- Follow the established code style
- Add tests for new functionality
-
Update documentation as needed
-
Test Your Changes
# Run all tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=easy_pmf
# Run code quality checks (same as pre-commit)
uv run ruff check --fix .
uv run ruff format .
# Type checking
uv run mypy .
# Test pre-commit hooks manually
uv run pre-commit run --all-files
- Commit Your Changes
git add .
git commit -m "Add feature: brief description
Longer description of what was changed and why.
Fixes #issue-number"
- Push and Create Pull Request
Then create a pull request on GitHub.
Documentation
Documentation Standards
- Use clear, concise language
- Include code examples for new features
- Add docstrings to all public functions and classes
- Update relevant guides when adding features
Documentation Types
- API Documentation: Automatically generated from docstrings
- User Guides: Step-by-step tutorials in
docs/user-guide/ - Examples: Complete analysis examples in
docs/examples/ - Contributing Docs: Development and contribution information
Writing Docstrings
Use Google-style docstrings:
def run_pmf(self, n_factors: int, n_runs: int = 20) -> PMFResult:
"""Run PMF analysis with specified parameters.
This method performs Positive Matrix Factorization on the prepared
dataset using the specified number of factors.
Args:
n_factors: Number of factors to extract (typically 3-10)
n_runs: Number of independent runs for stability (default: 20)
Returns:
PMFResult object containing factor profiles, contributions,
and model fit statistics.
Raises:
ValueError: If n_factors is less than 2 or greater than number of species
DataError: If data has not been prepared before running PMF
Example:
>>> pmf = PMF()
>>> pmf.load_data("concentrations.csv", "uncertainties.csv")
>>> pmf.prepare_data()
>>> result = pmf.run_pmf(n_factors=5, n_runs=20)
>>> print(f"Q/Qexp: {result.q_qexp:.2f}")
"""
Building Documentation
# Build documentation locally
uv run mkdocs build
# Serve documentation for development
uv run mkdocs serve
Testing
Test Philosophy
- Unit tests: Test individual functions and methods
- Integration tests: Test component interactions
- End-to-end tests: Test complete workflows
- Example tests: Ensure examples in documentation work
Writing Tests
- Test File Organization
tests/
├── unit/ # Unit tests for individual components
├── integration/ # Integration tests
├── examples/ # Tests for example code
└── data/ # Test datasets
- Test Naming Convention
def test_function_name_expected_behavior():
"""Test that function_name does expected_behavior when given specific input."""
- Test Structure
def test_pmf_run_with_valid_data():
"""Test that PMF runs successfully with valid input data."""
# Arrange
pmf = PMF()
pmf.load_test_data()
pmf.prepare_data()
# Act
result = pmf.run_pmf(n_factors=3)
# Assert
assert result.q_qexp > 0
assert result.factor_profiles.shape == (pmf.n_species, 3)
assert result.factor_contributions.shape == (pmf.n_samples, 3)
Running Tests
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/unit/test_core.py
# Run with coverage
uv run pytest --cov=easy_pmf --cov-report=html
# Run tests and open coverage report
uv run pytest --cov=easy_pmf --cov-report=html
# Then open htmlcov/index.html
Code Style
Python Style Guide
We follow PEP 8 with some modifications:
- Line length: 88 characters (Black default)
- Import sorting: isort compatible
- Docstrings: Google style
- Type hints: Required for all public functions
Automated Formatting
# Format code automatically
uv run ruff format .
# Check for style issues
uv run ruff check .
# Fix auto-fixable issues
uv run ruff check --fix .
Code Quality Standards
- Type Hints
from typing import Optional, List, Tuple
import pandas as pd
import numpy as np
def process_data(
data: pd.DataFrame,
species_list: Optional[List[str]] = None
) -> Tuple[pd.DataFrame, np.ndarray]:
"""Process input data and return processed results."""
- Error Handling
def load_data(self, filepath: str) -> None:
"""Load data from file with proper error handling."""
try:
data = pd.read_csv(filepath)
except FileNotFoundError:
raise FileNotFoundError(f"Data file not found: {filepath}")
except pd.errors.EmptyDataError:
raise ValueError(f"Data file is empty: {filepath}")
if data.empty:
raise ValueError("Loaded data is empty")
- Logging
import logging
logger = logging.getLogger(__name__)
def run_analysis(self):
"""Run analysis with appropriate logging."""
logger.info("Starting PMF analysis")
try:
result = self._perform_calculation()
logger.info(f"Analysis completed successfully: Q/Qexp = {result.q_qexp:.3f}")
return result
except Exception as e:
logger.error(f"Analysis failed: {str(e)}")
raise
Pull Request Process
Before Submitting
- Ensure all tests pass
- Update documentation if needed
- Add tests for new functionality
- Check code style compliance
- Update CHANGELOG.md if applicable
Pull Request Template
When creating a pull request, include:
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring
## Testing
- [ ] All existing tests pass
- [ ] New tests added for new functionality
- [ ] Manual testing completed
## Documentation
- [ ] Documentation updated
- [ ] Docstrings added/updated
- [ ] Examples updated if needed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Breaking changes documented
Review Process
- Automated checks: All CI checks must pass
- Code review: At least one maintainer review required
- Testing: Verify tests cover new functionality
- Documentation: Ensure documentation is complete
Merge Requirements
- All CI checks passing
- At least one approving review from maintainer
- No requested changes outstanding
- Branch up to date with main
Issue Reporting
Bug Reports
When reporting bugs, include:
- Clear title describing the issue
- Steps to reproduce the problem
- Expected vs actual behavior
- Environment information (Python version, OS, package version)
- Minimal example that demonstrates the issue
- Error messages and stack traces
Feature Requests
When requesting features, include:
- Clear description of the proposed feature
- Use case explaining why it's needed
- Proposed implementation if you have ideas
- Examples of how it would be used
Issue Labels
We use labels to categorize issues:
bug: Something isn't workingenhancement: New feature or requestdocumentation: Improvements or additions to documentationgood first issue: Good for newcomershelp wanted: Extra attention is neededquestion: Further information is requested
Release Process
Version Numbering
We follow Semantic Versioning:
- MAJOR: Incompatible API changes
- MINOR: New functionality in backward-compatible manner
- PATCH: Backward-compatible bug fixes
Release Checklist
- Update version in
pyproject.toml - Update
CHANGELOG.md - Create release notes
- Tag the release
- Build and publish to PyPI
- Update documentation
Getting Help
Communication Channels
- GitHub Issues: Bug reports and feature requests
- GitHub Discussions: General questions and community discussion
- Documentation: Comprehensive guides and API reference
Mentorship
New contributors are welcome! If you're new to open source:
- Look for issues labeled
good first issue - Ask questions in GitHub Discussions
- Start with documentation improvements
- Reach out to maintainers for guidance
Recognition
Contributors are recognized in:
- CHANGELOG.md: Major contributions noted in release notes
- README.md: Contributors section
- GitHub: Contributor graphs and statistics
Thank you for contributing to Easy PMF! Your contributions help make atmospheric data analysis more accessible to researchers worldwide.