Thank you for your interest in contributing to Instancepedia! This document provides guidelines and instructions for contributing.
- Code of Conduct
- Getting Started
- Development Setup
- How to Contribute
- Coding Standards
- Testing Guidelines
- Pull Request Process
- Documentation
- Project Structure
- Common Tasks
We are committed to providing a welcoming and inclusive environment for all contributors, regardless of experience level, gender, gender identity, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
- Be respectful and considerate in your communication
- Welcome newcomers and help them get started
- Focus on what is best for the project and community
- Show empathy towards other community members
- Accept constructive criticism gracefully
- Give credit where credit is due
- Harassment, discrimination, or offensive comments
- Personal attacks or trolling
- Publishing others' private information
- Other conduct which could reasonably be considered inappropriate
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, issues, and other contributions that do not align with this Code of Conduct. Violations may result in temporary or permanent bans from the project.
- Python: 3.9 or higher
- Git: For version control
- AWS Account: For testing (optional, but recommended)
- pip: Python package manager
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/instancepedia.git cd instancepedia - Set up upstream remote:
git remote add upstream https://github.com/pfrederiksen/instancepedia.git
- Create a virtual environment:
python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
- Install in development mode:
pip install -e ".[dev]" - Run tests to verify setup:
pytest
The development dependencies include testing tools and build utilities:
pip install -e ".[dev]"This installs:
pytest- Testing frameworkpytest-asyncio- Async testing supportbuild- Package buildingtwine- Package publishing
For testing with real AWS APIs:
aws configure
# Or use environment variables:
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
export AWS_DEFAULT_REGION=us-east-1Note: Most tests use mocks and don't require AWS credentials.
TUI Mode:
python -m src.main --tuiCLI Mode:
python -m src.main list --region us-east-1With Debug Mode:
python -m src.main --tui --debugBefore creating a bug report:
- Check existing issues: https://github.com/pfrederiksen/instancepedia/issues
- Try the latest version: Update to ensure the bug hasn't been fixed
- Check TROUBLESHOOTING.md: Your issue might have a known solution
When creating a bug report, include:
- Clear title: Concise description of the issue
- Environment: OS, Python version, instancepedia version
- Steps to reproduce: Exact commands/actions
- Expected behavior: What should happen
- Actual behavior: What actually happens
- Error messages: Full error text or screenshots
- Debug logs: If available (
--debugmode)
Template:
**Environment:**
- OS: macOS 13.0
- Python: 3.11.5
- Instancepedia: 0.5.0
**Steps to Reproduce:**
1. Run `instancepedia --tui --region us-east-1`
2. Press 'F' to open filters
3. ...
**Expected:** Filter modal should open
**Actual:** Application crashes with error
**Error:**[paste error here]
**Debug logs:** [if applicable]
Feature requests are welcome! To suggest a feature:
- Check existing issues: Someone might have already suggested it
- Describe the use case: Why is this feature needed?
- Propose a solution: How should it work?
- Consider alternatives: Are there other ways to achieve the goal?
Template:
**Problem:** [Describe the problem this solves]
**Proposed Solution:** [How should it work?]
**Alternatives:** [Other approaches considered]
**Use Case:** [Real-world scenario where this is needed]-
Create a feature branch:
git checkout -b feature/your-feature-name # or git checkout -b fix/your-bug-fix -
Make your changes:
- Follow coding standards (see below)
- Add tests for new functionality
- Update documentation
-
Test your changes:
pytest pytest -v # Verbose output -
Commit with clear messages:
git add . git commit -m "feat: Add instance filtering by GPU count" # or git commit -m "fix: Handle pricing timeout gracefully"
-
Push to your fork:
git push origin feature/your-feature-name
-
Create a Pull Request on GitHub
We follow PEP 8 with some project-specific conventions:
General Guidelines:
- Line length: 120 characters maximum (not strict 79)
- Indentation: 4 spaces (no tabs)
- Quotes: Use double quotes for strings by default
- Type hints: Use type hints for function signatures
- Docstrings: Use docstrings for classes and non-trivial functions
Example:
def fetch_instance_types(
region: str,
family: Optional[str] = None
) -> List[InstanceType]:
"""Fetch EC2 instance types for a region.
Args:
region: AWS region code (e.g., 'us-east-1')
family: Optional instance family filter (e.g., 't3')
Returns:
List of InstanceType objects
Raises:
AWSRegionError: If region is invalid or inaccessible
"""
# Implementation here
passNEVER use bare except: blocks:
# Bad ❌
try:
do_something()
except:
pass
# Good ✅
try:
do_something()
except SpecificException as e:
logger.debug(f"Expected error: {e}")Always explain why exceptions are expected:
try:
widget.update("status")
except Exception as e:
# Widget may not exist during screen transition
logger.debug(f"Failed to update widget: {e}")Use structured logging with appropriate levels:
import logging
logger = logging.getLogger("instancepedia")
# Debug: Verbose information
logger.debug(f"Cache hit for {instance_type}")
# Info: General operations
logger.info(f"Fetched {len(instances)} instance types")
# Warning: Unexpected but recoverable
logger.warning(f"Pricing unavailable for {instance_type}")
# Error: Errors requiring attention
logger.error(f"Failed to fetch pricing: {e}", exc_info=True)Use async/await for I/O operations:
async def fetch_pricing(instances: List[str]) -> Dict[str, float]:
"""Fetch pricing data asynchronously."""
async with AsyncAWSClient(region) as client:
pricing = await client.get_prices(instances)
return pricingUse call_later() for UI updates from workers:
def on_progress(completed: int, total: int):
def update_ui():
self.update_status_bar(f"Loading {completed}/{total}")
self.call_later(update_ui) # Thread-safe UI updateEvery feature needs tests:
def test_instance_filtering_by_family():
"""Test filtering instances by family."""
instances = [
create_instance("t3.micro"),
create_instance("t3.small"),
create_instance("m5.large"),
]
filtered = filter_by_family(instances, family="t3")
assert len(filtered) == 2
assert all(inst.instance_type.startswith("t3.") for inst in filtered)Use descriptive test names:
- Describe what is being tested
- Use
test_<feature>_<scenario>pattern - Example:
test_pricing_cache_handles_expiration
Use mocks to avoid AWS API calls:
@patch('boto3.client')
def test_fetch_instance_types(mock_boto_client):
"""Test instance type fetching without real AWS calls."""
mock_client = Mock()
mock_boto_client.return_value = mock_client
mock_client.describe_instance_types.return_value = {
'InstanceTypes': [{'InstanceType': 't3.micro'}]
}
instances = fetch_instance_types('us-east-1')
assert len(instances) == 1
assert instances[0].instance_type == 't3.micro'# Run all tests
pytest
# Run specific test file
pytest tests/test_pricing_service.py
# Run single test
pytest tests/test_pricing_service.py::TestPricingService::test_cache_hit
# Run with verbose output
pytest -v
# Run with coverage
pytest --cov=src tests/Tests are organized by component:
tests/test_cli_*.py- CLI-related teststests/test_tui_*.py- TUI-related teststests/test_*_service.py- Service layer teststests/test_cache.py- Caching tests
TUI Tests (async with Textual):
async def test_instance_list_displays():
"""Test instance list screen displays correctly."""
instances = [create_mock_instance("t3.micro")]
app = InstanceListTestApp(instances)
async with app.run_test() as pilot:
await pilot.pause() # Let UI update
# Assert on screen state
screen = app.screen
assert isinstance(screen, InstanceList)
assert len(screen.filtered_instance_types) == 1CLI Tests (with mocks):
@patch('src.services.aws_client.AWSClient')
def test_list_command(mock_aws_client):
"""Test CLI list command."""
mock_client = Mock()
mock_aws_client.return_value = mock_client
mock_client.get_instance_types.return_value = [
create_mock_instance("t3.micro")
]
# Test the command
result = cmd_list(
region="us-east-1",
output_format="json"
)
assert result is not None
assert "t3.micro" in resultUse fixtures for common test data:
@pytest.fixture
def sample_instance():
"""Create a sample instance for testing."""
return InstanceType(
instance_type="t3.micro",
vcpu_info=VCpuInfo(default_vcpus=2),
memory_info=MemoryInfo(size_in_mib=1024),
# ... other fields
)
def test_instance_display(sample_instance):
"""Test using the fixture."""
label = format_instance_label(sample_instance)
assert "t3.micro" in label-
Update from upstream:
git fetch upstream git rebase upstream/main
-
Run all tests:
pytest
-
Update documentation (if needed):
- README.md for user-facing changes
- CLAUDE.md for developer/architecture changes
- Docstrings for new functions/classes
-
Check commit messages:
- Use conventional commits format:
feat:New featuresfix:Bug fixesdocs:Documentation onlytest:Adding testsrefactor:Code refactoringperf:Performance improvements
- Use conventional commits format:
## Summary
[Brief description of changes]
## Changes
- [List of specific changes]
- [One change per line]
## Motivation
[Why is this change needed?]
## Testing
- [ ] All existing tests pass
- [ ] Added tests for new functionality
- [ ] Tested manually in TUI mode
- [ ] Tested manually in CLI mode
## Documentation
- [ ] Updated README.md (if user-facing change)
- [ ] Updated CLAUDE.md (if architectural change)
- [ ] Added/updated docstrings
## Screenshots (if applicable)
[Add screenshots for UI changes]
## Related Issues
Closes #[issue number]- Automated checks: GitHub Actions will run tests
- Code review: Maintainer will review your code
- Feedback: Address any requested changes
- Approval: Once approved, PR will be merged
- Credit: You'll be credited in the commit and release notes
Your contribution will be included in the next release! Thank you! 🎉
README.md - Update when:
- Adding new CLI commands or flags
- Adding new TUI features or keyboard shortcuts
- Changing installation process
- Adding configuration options
- Modifying user-facing behavior
CLAUDE.md - Update when:
- Adding new architectural patterns
- Creating new services or components
- Changing async/threading model
- Adding testing patterns
- Modifying TUI or CLI architecture
Example Updates:
# After adding a new feature
git add README.md CLAUDE.md
git commit -m "docs: Document new GPU filtering feature"- Be concise: Short, clear explanations
- Use examples: Show, don't just tell
- Keep updated: Update docs in the same commit as code
- Test examples: Ensure code examples actually work
instancepedia/
├── src/
│ ├── __init__.py
│ ├── main.py # Entry point
│ ├── app.py # TUI main app
│ ├── cli/ # CLI commands
│ │ ├── commands.py # Command implementations
│ │ ├── output.py # Output formatters
│ │ └── parser.py # Argument parsing
│ ├── ui/ # TUI screens
│ │ ├── instance_list.py
│ │ ├── instance_detail.py
│ │ ├── instance_comparison.py
│ │ ├── filter_modal.py
│ │ ├── region_selector.py
│ │ └── sort_options.py
│ ├── models/ # Pydantic models
│ │ ├── instance_type.py
│ │ ├── region.py
│ │ └── free_tier.py
│ ├── services/ # Business logic
│ │ ├── aws_client.py # Sync boto3
│ │ ├── async_aws_client.py # Async aioboto3
│ │ ├── instance_service.py
│ │ ├── pricing_service.py
│ │ ├── async_pricing_service.py
│ │ └── free_tier_service.py
│ ├── config/
│ │ └── settings.py
│ ├── cache.py # Pricing cache
│ ├── debug.py # Debug logging
│ ├── exceptions.py # Custom exceptions
│ └── logging_config.py # Logging setup
├── tests/ # Test suite
│ ├── test_cli_*.py
│ ├── test_tui_*.py
│ └── test_*.py
├── scripts/
│ ├── release.sh # Release automation
│ └── publish.sh # PyPI publishing
├── README.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── TROUBLESHOOTING.md
├── LICENSE
├── pyproject.toml
└── requirements.txt
Entry Point (src/main.py):
- Routes to TUI or CLI based on arguments
- Handles global options (--debug, --tui, --region)
TUI (src/app.py, src/ui/):
- Textual-based interactive interface
- Screen-based navigation
- Async pricing with workers
CLI (src/cli/):
- Headless command-line interface
- Multiple output formats (table, JSON, CSV)
- Scriptable and pipeable
Services (src/services/):
- Shared between TUI and CLI
- Handles AWS API interactions
- Implements caching and error handling
Models (src/models/):
- Pydantic models for data validation
- Type-safe data structures
- Create or modify screen in
src/ui/ - Add key binding to appropriate screen's
BINDINGS - Implement action method:
action_your_feature() - Update help text in screen's
compose()method - Add tests in
tests/test_tui_*.py - Update README.md with new keyboard shortcut
- Update CLAUDE.md if architectural change
Example:
# In src/ui/instance_list.py
BINDINGS = [
# ... existing bindings
("n", "new_feature", "New Feature"),
]
def action_new_feature(self) -> None:
"""Handle new feature action."""
# Implementation
pass- Add parser in
src/cli/parser.py - Implement command in
src/cli/commands.py - Add output formatter in
src/cli/output.py(if needed) - Add tests in
tests/test_cli_*.py - Update README.md with command documentation
Example:
# In src/cli/parser.py
subparsers.add_parser('newcommand', help='Description')
# In src/cli/commands.py
def cmd_newcommand(args):
"""Implementation of newcommand."""
# ... implementation- Add filter field to
FilterCriteriainsrc/ui/filter_modal.py - Add UI input to
FilterModal.compose() - Update
_apply_filters()to handle new filter - Update
_apply_filters()insrc/ui/instance_list.py - Add tests for filter logic
- Update README.md filter documentation
- Add method to
AWSClient(sync) insrc/services/aws_client.py - Add method to
AsyncAWSClient(async) insrc/services/async_aws_client.py - Add service method if needed
- Add error handling with custom exceptions
- Add tests with mocked boto3
- Update IAM policy in README.md if new permission needed
- GitHub Issues: https://github.com/pfrederiksen/instancepedia/issues
- Discussions: Use GitHub Discussions for questions
- Email: paul@paulfrederiksen.com
Thank you for contributing to Instancepedia! 🚀