Contributing¶
Guidelines for contributing to Music Genre Updater.
Development Setup¶
Prerequisites¶
- Python 3.13+
- uv package manager
- macOS with Music.app
Installation¶
```bash test="skip"
Clone the repository¶
git clone https://github.com/barad1tos/GenreUpdater.git cd GenreUpdater
Install dependencies¶
uv sync
Set up prek hooks (pre-commit alternative)¶
prek install
### Environment Variables
Create a `.env` file:
```bash test="skip"
DISCOGS_TOKEN=your_discogs_token
CONTACT_EMAIL=your@email.com
Code Quality¶
Linting¶
# Run ruff linter
uv run ruff check src/
# Auto-fix issues
uv run ruff check --fix src/
# Format code
uv run ruff format src/
Type Checking¶
This project uses ty (not mypy):
All Checks¶
Testing¶
Run Tests¶
# All tests
uv run pytest
# Specific file
uv run pytest tests/unit/core/test_genre_manager.py
# With coverage
uv run pytest --cov=src --cov-report=html
Test Categories¶
| Marker | Description |
|---|---|
unit |
Fast, isolated tests |
integration |
Tests with real cache |
e2e |
Requires Music.app |
slow |
Long-running tests |
Run specific categories:
Git Workflow¶
Branch Strategy¶
main— Protected stable branchdev— Primary development branchfeature/*— Feature branches
Commit Messages¶
Use Conventional Commits:
feat(domain): add artist name normalization
fix(cache): prevent duplicate cache writes
refactor(api): extract scoring logic
docs: update architecture documentation
Pull Request Process¶
- Create feature branch from
dev - Make changes
- Run all checks:
prek run --all-files - Push and create PR to
dev - After review, squash merge to
dev - Periodically,
devis merged tomainvia PR
Code Style¶
Docstrings¶
Use Google style:
```python test="skip" def fetch_year(self, artist: str, album: str) -> tuple[int | None, int]: """Fetch album release year from external API.
Args:
artist: Artist name
album: Album name
Returns:
Tuple of (year, confidence_score). Year is None if not found.
Raises:
RateLimitError: If API rate limit exceeded
"""
```
Type Hints¶
All functions must have type hints:
python test="skip"
async def process_tracks(
self,
tracks: list[Track],
*,
force: bool = False,
) -> ProcessingResult:
...
Error Handling¶
Follow the project's error design principles:
```python test="skip"
✅ Good - includes context¶
except OSError as e: logger.warning( "Error reading %s for artist %s: %s", file_path, artist_name, e, )
❌ Bad - no context¶
except OSError as e: logger.warning("Error: %s", e)
## Architecture Guidelines
### Adding New Features
1. Check if infrastructure already exists (see CLAUDE.md)
2. Follow the layer separation:
- `app/` — Presentation
- `core/` — Business logic
- `services/` — External integrations
3. Use dependency injection via `DependencyContainer`
4. Define interfaces with `Protocol`
### Adding API Clients
1. Inherit from `BaseApiClient`
2. Implement `ExternalApiServiceProtocol`
3. Add rate limiting
4. Register in `ExternalApiOrchestrator`
## Documentation
### Building Docs
```bash
# Serve locally
uv run mkdocs serve
# Build static site
uv run mkdocs build
Adding Pages¶
- Create
.mdfile indocs/ - Add to
navsection inmkdocs.yml - Use mkdocstrings for API docs:
Questions?¶
- Open an issue on GitHub
- Check existing documentation
- Review the codebase's CLAUDE.md for detailed guidelines