Developer Guide
This guide is for developers who want to contribute to PhyloForester or extend its functionality.
Architecture Overview
PhyloForester follows a layered architecture:
┌─────────────────────────────┐
│ PhyloForester.py │ Main Application
│ (Qt Main Window) │
└──────────────┬──────────────┘
│
┌──────────────┴──────────────┐
│ PfDialog.py │ Dialog Layer
│ (UI Components) │
└──────────────┬──────────────┘
│
┌──────────────┴──────────────┐
│ PfModel.py │ Data Model Layer
│ (Peewee ORM) │
└──────────────┬──────────────┘
│
┌──────────────┴──────────────┐
│ PfUtils.py │ Utility Layer
│ (Helpers, Parsers) │
└─────────────────────────────┘
Core Components
PhyloForester.py (Main Application)
PhyloForesterMainWindow: Main window classTree view management
Signal/slot connections
Menu and toolbar setup
PfModel.py (Database Models)
PfProject: Project modelPfDatamatrix: Datamatrix modelPfAnalysis: Analysis modelPfTree: Tree modelPfPackage: External software metadata
PfDialog.py (Dialogs)
ProjectDialog: Project propertiesDatamatrixDialog: Datamatrix editorAnalysisDialog: Analysis configurationAnalysisViewer: Results viewerTreeViewer: Tree visualization
PfUtils.py (Utilities)
PhyloDatafile: Data import/exportPhyloTreefile: Tree parsingFile format parsers (Nexus, Phylip, TNT)
Fitch algorithm for ancestral reconstruction
Setting Up Development Environment
Prerequisites
Python 3.9+
Git
Qt5 development libraries (for PyQt5)
Clone Repository
git clone https://github.com/jikhanjung/PhyloForester.git
cd PhyloForester
Create Virtual Environment
python -m venv venv
# Windows
venv\\Scripts\\activate
# macOS/Linux
source venv/bin/activate
Install Dependencies
# Runtime dependencies
pip install -r requirements.txt
# Development dependencies
pip install -r requirements-ci.txt
Run from Source
python PhyloForester.py
Code Structure
Data Storage
Runtime state stored in self.data_storage dictionary:
data_storage = {
'project': {
<project_id>: {
'object': <PfProject instance>,
'widget': <QWidget>,
'tree_item': <QStandardItem>,
'datamatrix': {} # nested datamatrices
}
},
'datamatrix': {
<datamatrix_id>: {
'object': <PfDatamatrix instance>,
'widget': <QWidget>,
'tree_item': <QStandardItem>,
'analysis': {} # nested analyses
}
},
'analysis': {
<analysis_id>: {
'object': <PfAnalysis instance>,
'widget': <QWidget>,
'tree_item': <QStandardItem>
}
}
}
This prevents redundant database queries and maintains UI consistency.
Database Schema
class PfProject(BaseModel):
name = CharField()
description = TextField(null=True)
class PfDatamatrix(BaseModel):
project = ForeignKeyField(PfProject, backref='datamatrices')
name = CharField()
datamatrix_json = TextField() # JSON serialized matrix
taxa_list_json = TextField() # JSON serialized taxa names
character_list_json = TextField() # JSON serialized characters
class PfAnalysis(BaseModel):
datamatrix = ForeignKeyField(PfDatamatrix, backref='analyses')
name = CharField()
analysis_type = CharField() # 'Parsimony', 'ML', 'Bayesian'
status = CharField() # 'READY', 'RUNNING', 'COMPLETED', etc.
parameters_json = TextField()
class PfTree(BaseModel):
analysis = ForeignKeyField(PfAnalysis, backref='trees')
tree_newick = TextField()
tree_options_json = TextField()
Running Tests
PhyloForester uses pytest for testing.
Run All Tests
pytest tests/ -v
Run Specific Test File
pytest tests/test_utils.py -v
Run with Coverage
pytest tests/ --cov=. --cov-report=html
View coverage report: htmlcov/index.html
Test Categories
Tests are marked by category:
@pytest.mark.unit: Unit tests@pytest.mark.model: Database/model tests@pytest.mark.dialog: UI/dialog tests
Run specific category:
pytest -m unit
Code Quality
Linting
PhyloForester uses Ruff for linting:
ruff check .
Auto-fix issues:
ruff check . --fix
Code Style
Follow PEP 8 guidelines
Use meaningful variable names
Add docstrings to public functions/classes
Keep functions focused and short
Type Hints
Use type hints where practical:
def parse_nexus_file(filepath: str) -> Dict[str, Any]:
"""Parse a Nexus format file.
Args:
filepath: Path to Nexus file
Returns:
Dictionary with parsed data
"""
pass
Contributing
Workflow
Fork the repository
Create a feature branch:
git checkout -b feature/my-featureMake changes and commit:
git commit -m "Add my feature"Push to branch:
git push origin feature/my-featureCreate Pull Request on GitHub
Commit Messages
Follow conventional commits:
feat:: New featurefix:: Bug fixdocs:: Documentation changesrefactor:: Code refactoringtest:: Test additions/changeschore:: Maintenance tasks
Example:
feat: Add support for FASTA format import
- Implement FASTA parser in PfUtils
- Add FASTA to import dialog options
- Add tests for FASTA parsing
Pull Request Guidelines
Describe what the PR does
Reference any related issues
Include tests for new features
Update documentation if needed
Ensure CI tests pass
Building Documentation
Install Sphinx:
pip install -r docs/requirements.txt
Build HTML docs:
cd docs
sphinx-build -b html . _build/html
View: docs/_build/html/index.html
Auto-rebuild on changes:
sphinx-autobuild docs docs/_build/html
Building Executables
PhyloForester uses PyInstaller for building standalone executables.
Build Script
Use the build.py script:
python build.py
This automatically:
Detects your platform
Reads version from
version.pyBundles all dependencies
Creates executable in
dist/
Manual PyInstaller
Or use PyInstaller directly:
pyinstaller PhyloForester.spec
Platform-Specific Notes
Windows:
Creates
.exefileOptional Inno Setup installer creation
Requires Visual C++ Redistributable on target systems
macOS:
Creates
.appbundleMay need code signing for distribution
Use
create-dmgfor DMG images
Linux:
Creates standalone executable
May need to ship Qt5 libraries
Consider AppImage for distribution
Version Management
PhyloForester uses semantic versioning (semver).
Update Version
Use the manage_version.py script:
# Increment patch (0.1.0 -> 0.1.1)
python manage_version.py patch
# Increment minor (0.1.0 -> 0.2.0)
python manage_version.py minor
# Increment major (0.1.0 -> 1.0.0)
python manage_version.py major
# Start pre-release (0.1.0 -> 0.2.0-alpha.1)
python manage_version.py preminor
The script:
Updates
version.pyUpdates
CHANGELOG.mdCreates git commit and tag
Prompts for confirmation
Release Process
Update version:
python manage_version.py minorUpdate
CHANGELOG.mdwith release notesCommit changes
Push to GitHub:
git push origin mainCreate and push tag:
git push origin v0.2.0GitHub Actions automatically builds and releases
CI/CD Pipeline
PhyloForester uses GitHub Actions for CI/CD.
Workflows
test.yml - Automated Testing
Runs on push/PR
Tests Python 3.9, 3.10, 3.11
Measures code coverage
Runs linter
build.yml - Build Artifacts
Runs on push to main
Builds Windows/macOS/Linux
Uploads build artifacts
Uses
build.pyscript
release.yml - Automated Release
Triggers on git tag (v*.*.*)
Runs tests first
Builds all platforms
Creates GitHub release
Uploads installers/packages
Manual Release
Use GitHub Actions UI:
Go to Actions → Manual Release
Click “Run workflow”
Enter version number
Choose pre-release/draft options
Click “Run workflow”
Adding Features
Adding a New Analysis Type
Update PfModel.py:
Add new analysis type constant
Update AnalysisDialog:
Add configuration UI for new type
Implement runner:
Add execution logic in
startAnalysis()Add parser:
Parse output files in
PfUtils.pyUpdate tests:
Add tests for new analysis type
Adding a New Import Format
Update PfUtils.py:
class PhyloDatafile: def load_myformat(self, filepath): """Load MyFormat data file.""" # Parse file # Return taxa_list, character_list, datamatrix
Update import dialog:
Add format to file filter
Add tests:
Create test file in
data/Add test intests/test_utils.py
Extending the UI
Custom widgets should:
Inherit from appropriate Qt widget
Use signal/slot for communication
Be added to
PfDialog.pyor inline inPhyloForester.py
Example:
class CustomTableView(QTableView):
cellChanged = pyqtSignal(int, int, str)
def __init__(self, parent=None):
super().__init__(parent)
# Custom initialization
def custom_method(self):
# Custom functionality
self.cellChanged.emit(row, col, value)
Debugging
Logging
PhyloForester uses Python’s logging module:
import logging
logger = logging.getLogger(__name__)
logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message")
Logs are written to PaleoBytes/PhyloForester/Logs/
PyQt Debugging
Enable Qt warnings:
export QT_DEBUG_PLUGINS=1
python PhyloForester.py
Database Inspection
Use SQLite browser:
sqlite3 ~/PaleoBytes/PhyloForester/PhyloForester.db
Or use DB Browser for SQLite (GUI)
Resources
PyQt5 Documentation: https://www.riverbankcomputing.com/static/Docs/PyQt5/
Peewee ORM: http://docs.peewee-orm.com/
Pytest: https://docs.pytest.org/
Sphinx: https://www.sphinx-doc.org/
Contact
Next Steps
See User Guide for user-facing features
See Troubleshooting for common issues
See Changelog for version history