Skip to content

Changelog

All notable changes to the Music Genre Updater.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Added

  • 4 edge-case tests for new except branches: unparseable year in re-recording check, dominant year parse failure, future-year stats with non-integer years, reissue detection with invalid release year
  • Missing-year branch coverage for _compute_future_year_stats (track without year key)
  • Tests for 4 previously untested modules (#219): comprehensive coverage for year_utils.py (100%) and track_base.py, smoke tests for json_utils.py (93%) and applescript_executor.py (pure methods only)

Removed

  • Dead code cleanup (#220): pipeline_helpers.py module, unused __init__.py re-exports (3 packages), singleton ClassVars from DependencyContainer, clear_dry_run_actions() method

Fixed

  • splitlines() bug in parse_tracks() and parse_osascript_output() — Python treats \x1e (field separator) as a line boundary, breaking single-track AppleScript responses into per-field rows; now uses split(LINE_SEPARATOR) in ASCII mode
  • Replaced 4 contextlib.suppress blocks with explicit try/except + warning logs in year processing and API modules — parse failures now leave audit trail instead of silently altering control flow
  • Added meaningful assertions to 16 assertion-free unit/regression tests (#219): test_analytics_core (report dir + GC mock), test_html_reports (default loggers file check), test_pipeline_snapshot (early-return + skip guards), test_track_processor_coverage (skip-when-no-renamer), test_request_executor (response parsing), test_year_retriever_coverage (API call + prerelease skip), test_track_sync (date_added + save_csv mocks); converted 3 regression tests to pytest.skip informational pattern
  • Removed 2 empty test stubs from integration tests (#219): test_graceful_handling_of_provider_timeout and test_graceful_handling_of_rate_limit_response (mock setup only, never invoked code under test); cleaned dead variables from 2 e2e pipeline smoke tests
  • Removed dead test infrastructure (#219): empty monitoring test dir, unused unique_artists/unique_albums fixtures, placeholder test bodies
  • Narrowed 16 overly broad exception handlers (#214): replaced except Exception with specific types in 8 cache/persistence/pipeline locations, replaced contextlib.suppress(Exception) with specific types in logger and encryption, added missing IndexError catch in MusicBrainz response parsing; kept justified safety nets in API orchestrator verification wrappers and artist context setup; added 5 error-path tests and fixed 1 vacuous test for full patch coverage
  • Flaky test_get_activity_period_classic_band — skip gracefully when MusicBrainz returns (None, None) due to rate limiting
  • Ruff format: missing blank line in track_models.py after Sourcery walrus operator refactor
  • Sourcery warnings in test_year_update.py: extracted duplicate assertion helper, removed default-value arguments, added sourcery skip for test factory import
  • Type mismatches in test_scoring_comprehensive.py: annotated ArtistPeriodContext dicts to match set_artist_period_context signature
  • Missing logger kwarg in test_custom_logger after rate limiter rename — test was asserting limiter.logger is custom_logger without passing the logger
  • Type mismatch in test_incremental_filter.py: replaced dict[str, Any] with create_test_app_config() to match IncrementalFilterService(config: AppConfig) signature

Style

  • Removed ~44 lines of ASCII art separators (# ===, # ---) across 9 files per CLAUDE.md formatting rules

Refactored

  • Migrated 5 print() calls to console_logger.info() in full_sync.py (post-init phase where logger is available)

Changed

  • Decompose year_batch.py god class (#218): extracted PrereleaseHandler (prerelease detection/handling) and TrackUpdater (track-level year updates with retry) into focused modules; YearBatchProcessor reduced from 1,021 to ~530 LOC while preserving public API; updated test docstrings to reflect new module locations
  • Consolidate duplicate types (#212): removed dead ScriptType enum, DiscogsRelease, DiscogsSearchResult from track_models.py; renamed EnhancedRateLimiterApiRateLimiter (API) and AppleScriptRateLimiter (Apple); standardized get_stats() keys and window_sizewindow_seconds across both rate limiters; fixed Pyright type errors in integration tests
  • Config type safety (C3): removed _resolve_config_dict() bridge from logger.py; changed all 12 logger function signatures from AppConfig | dict[str, Any] to AppConfig; replaced dict .get() lookups with typed config.logging.* attribute access; moved AppConfig import to TYPE_CHECKING; added LogLevelsConfig.normalize_log_level validator for case-insensitive log level names; migrated 5 test files from dict configs to create_test_app_config() factory; removed last model_dump() round-trip in validate_api_auth — now accepts ApiAuthConfig directly
  • Config type safety (C2): removed DependencyContainer.config dict property and Config._config dict storage — all config access now goes through typed AppConfig; migrated MusicUpdater from deps.config dict to deps.app_config; removed dict branches from search_strategy, album_type, html_reports; deleted 155 lines of dead accessor methods (.get(), .get_path(), .get_list(), .get_dict(), .get_bool(), .get_int(), .get_float()) from Config class; migrated 12 test files from dict configs to create_test_app_config() factory
  • Config type safety (C1): migrated ReleaseScorer from dict[str, Any] to typed ScoringConfig Pydantic model; replaced 33 .get() calls with typed attribute access; added 3 missing scoring fields (artist_substring_penalty, artist_mismatch_penalty, current_year_penalty); changed all scoring fields from float to int to match config.yaml and downstream usage; removed dead ScoringConfig TypedDict and _get_default_scoring_config(); updated orchestrator to pass ScoringConfig object directly instead of model_dump() dict

Added

  • 13 scoring branch coverage tests (TestScoringBranchCoverage): EP/single penalty, bootleg/promo status, reissue, RG first date match, artist period penalties/bonuses, year diff, future/current year, invalid RG date
  • Batch error handling tests: sequential processing, CancelledError, config validation
  • Track ID validation and bulk update mixed-results tests
  • Retry exhaustion behavior tests
  • API client boundary security tests (MusicBrainz, Discogs, iTunes)
  • Hash service adversarial input and collision resistance tests
  • CLI argument security boundary tests
  • Property-based tests for validators (Hypothesis): year validation, string sanitization
  • Property-based tests for hash service (Hypothesis): determinism, format invariants, collision resistance

Changed

  • Config type safety (B4): migrated Analytics, PendingVerificationService, DatabaseVerifier, GenreManager from dict[str, Any] to typed AppConfig; added missing AnalyticsConfig fields; fixed definitive_score_threshold/diff model types (floatint); added model_validator to migrate legacy top-level test_artists into development.test_artists; fixed max_events=0 being silently overridden (falsy oris None check)
  • Config type safety (B3): migrated core year pipeline (ExternalApiOrchestrator, YearRetriever, YearBatchProcessor, TrackUpdateExecutor, YearDetermination, TrackProcessor) from dict[str, Any] to typed AppConfig; removed dead dict-validation methods replaced by Pydantic
  • Config type safety (B2): migrated LibrarySnapshotService and AppleScriptClient from dict[str, Any] to typed AppConfig; loc-based validation assertions per Sourcery
  • Config type safety (B1): migrated 24 services from dict[str, Any] to typed AppConfig for config access (cache, tracks, API, orchestrator modules)
  • Removed dead coercion helpers (_coerce_*, _resolve_*) and unreachable try/except after AppConfig migration
  • Removed dead temp-file execution infrastructure from AppleScript executor (superseded by bulk verification)
  • Added from __future__ import annotations to 33 source files, moved type-only imports to TYPE_CHECKING blocks
  • Test fixture deduplication: shared logger fixtures in root conftest
  • Unified track factory and mock fixtures in tracks conftest
  • Migrated year_batch test files to shared fixtures (-409/+208 lines)
  • Fixed TC001 lint: moved type-only imports to TYPE_CHECKING blocks
  • Consolidated year_determinator mock into shared create_year_determinator_mock() helper
  • Pinned hypothesis==6.151.4 for reproducible test runs
  • Applied ruff format to all new test files

Fixed

  • AlbumTypeDetectionConfig pattern fields now use None vs [] semantics (None = defaults, [] = disabled)
  • Dependabot PRs failing CI due to missing env vars in load_config() validation
  • DiscogsClient received empty dict instead of typed AppConfig/YearRetrievalConfig — latent runtime crash on _get_reissue_keywords()
  • Test cast mismatch: cast(Analytics, ...)cast(AnalyticsProtocol, ...) to match GenreManager signature
  • CI failures since B1: full_sync.py ruff format violation; test_external_api_real.py fixtures returning dict instead of AppConfig
  • Legacy top-level test_artists now emits DeprecationWarning when migrated or silently ignored

[3.0.0] - 2026-01-12

Added

  • MkDocs documentation with mkdocstrings
  • Full API reference documentation
  • Architecture documentation with Mermaid diagrams

Fixed

  • Path expansion bug when HOME environment variable is not set
  • Config loader now uses Path.expanduser() for robust home directory resolution

[2.0.0] - 2025-01-09

Added

  • Year Retrieval System: Automatic album year fetching from MusicBrainz, Discogs, and iTunes
  • Scoring System: Confidence-based year selection with reissue detection
  • Library Snapshots: Fast startup with compressed track cache
  • Incremental Updates: Process only recently modified tracks
  • LaunchAgent Support: Background daemon operation
  • Pending Verification: Queue for manual verification of uncertain years
  • Restore Command: Fix albums with wrong reissue years

Changed

  • Complete async/await rewrite for better performance
  • Pydantic v2 for all data validation
  • Python 3.13 minimum requirement
  • Protocol-based dependency injection

Fixed

  • Rate limiting for all external APIs
  • Memory management for large libraries (30K+ tracks)
  • AppleScript timeout handling

[1.0.0] - 2024-06-15

Added

  • Initial release
  • Genre calculation based on artist's track library
  • AppleScript integration with Music.app
  • Basic CLI interface
  • CSV export of track data

Migration Guide

From 1.x to 2.0

  1. Python Version: Upgrade to Python 3.13+

  2. Configuration: New YAML structure required

    # Old (1.x)
    api_key: xxx
    
    # New (2.0)
    year_retrieval:
      api_auth:
        discogs_token: ${DISCOGS_TOKEN}
    

  3. Environment Variables: Now required

  4. DISCOGS_TOKEN
  5. CONTACT_EMAIL

  6. Cache Location: Clear old cache

    rm -rf cache/
    

  7. Commands: Some renamed

  8. updateupdate_genres
  9. New: update_years, restore_release_years