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
exceptbranches: 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 withoutyearkey) - Tests for 4 previously untested modules (#219): comprehensive coverage for
year_utils.py(100%) andtrack_base.py, smoke tests forjson_utils.py(93%) andapplescript_executor.py(pure methods only)
Removed¶
- Dead code cleanup (#220):
pipeline_helpers.pymodule, unused__init__.pyre-exports (3 packages), singleton ClassVars from DependencyContainer,clear_dry_run_actions()method
Fixed¶
splitlines()bug inparse_tracks()andparse_osascript_output()— Python treats\x1e(field separator) as a line boundary, breaking single-track AppleScript responses into per-field rows; now usessplit(LINE_SEPARATOR)in ASCII mode- Replaced 4
contextlib.suppressblocks with explicittry/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 topytest.skipinformational pattern - Removed 2 empty test stubs from integration tests (#219):
test_graceful_handling_of_provider_timeoutandtest_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_albumsfixtures, placeholder test bodies - Narrowed 16 overly broad exception handlers (#214): replaced
except Exceptionwith specific types in 8 cache/persistence/pipeline locations, replacedcontextlib.suppress(Exception)with specific types in logger and encryption, added missingIndexErrorcatch 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.pyafter 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: annotatedArtistPeriodContextdicts to matchset_artist_period_contextsignature - Missing
loggerkwarg intest_custom_loggerafter rate limiter rename — test was assertinglimiter.logger is custom_loggerwithout passing the logger - Type mismatch in
test_incremental_filter.py: replaceddict[str, Any]withcreate_test_app_config()to matchIncrementalFilterService(config: AppConfig)signature
Style¶
- Removed ~44 lines of ASCII art separators (
# ===,# ---) across 9 files per CLAUDE.md formatting rules
Refactored¶
- Migrated 5
print()calls toconsole_logger.info()infull_sync.py(post-init phase where logger is available)
Changed¶
- Decompose
year_batch.pygod class (#218): extractedPrereleaseHandler(prerelease detection/handling) andTrackUpdater(track-level year updates with retry) into focused modules;YearBatchProcessorreduced from 1,021 to ~530 LOC while preserving public API; updated test docstrings to reflect new module locations - Consolidate duplicate types (#212): removed dead
ScriptTypeenum,DiscogsRelease,DiscogsSearchResultfromtrack_models.py; renamedEnhancedRateLimiter→ApiRateLimiter(API) andAppleScriptRateLimiter(Apple); standardizedget_stats()keys andwindow_size→window_secondsacross both rate limiters; fixed Pyright type errors in integration tests - Config type safety (C3): removed
_resolve_config_dict()bridge fromlogger.py; changed all 12 logger function signatures fromAppConfig | dict[str, Any]toAppConfig; replaced dict.get()lookups with typedconfig.logging.*attribute access; movedAppConfigimport toTYPE_CHECKING; addedLogLevelsConfig.normalize_log_levelvalidator for case-insensitive log level names; migrated 5 test files from dict configs tocreate_test_app_config()factory; removed lastmodel_dump()round-trip invalidate_api_auth— now acceptsApiAuthConfigdirectly - Config type safety (C2): removed
DependencyContainer.configdict property andConfig._configdict storage — all config access now goes through typedAppConfig; migratedMusicUpdaterfromdeps.configdict todeps.app_config; removed dict branches fromsearch_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()) fromConfigclass; migrated 12 test files from dict configs tocreate_test_app_config()factory - Config type safety (C1): migrated
ReleaseScorerfromdict[str, Any]to typedScoringConfigPydantic 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 fromfloattointto match config.yaml and downstream usage; removed deadScoringConfigTypedDict and_get_default_scoring_config(); updated orchestrator to passScoringConfigobject directly instead ofmodel_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,GenreManagerfromdict[str, Any]to typedAppConfig; added missingAnalyticsConfigfields; fixeddefinitive_score_threshold/diffmodel types (float→int); addedmodel_validatorto migrate legacy top-leveltest_artistsintodevelopment.test_artists; fixedmax_events=0being silently overridden (falsyor→is Nonecheck) - Config type safety (B3): migrated core year pipeline (
ExternalApiOrchestrator,YearRetriever,YearBatchProcessor,TrackUpdateExecutor,YearDetermination,TrackProcessor) fromdict[str, Any]to typedAppConfig; removed dead dict-validation methods replaced by Pydantic - Config type safety (B2): migrated
LibrarySnapshotServiceandAppleScriptClientfromdict[str, Any]to typedAppConfig; loc-based validation assertions per Sourcery - Config type safety (B1): migrated 24 services from
dict[str, Any]to typedAppConfigfor 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 annotationsto 33 source files, moved type-only imports toTYPE_CHECKINGblocks - 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¶
AlbumTypeDetectionConfigpattern fields now useNonevs[]semantics (None= defaults,[]= disabled)- Dependabot PRs failing CI due to missing env vars in
load_config()validation DiscogsClientreceived empty dict instead of typedAppConfig/YearRetrievalConfig— latent runtime crash on_get_reissue_keywords()- Test cast mismatch:
cast(Analytics, ...)→cast(AnalyticsProtocol, ...)to matchGenreManagersignature - CI failures since B1:
full_sync.pyruff format violation;test_external_api_real.pyfixtures returningdictinstead ofAppConfig - Legacy top-level
test_artistsnow emitsDeprecationWarningwhen 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
HOMEenvironment 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¶
-
Python Version: Upgrade to Python 3.13+
-
Configuration: New YAML structure required
-
Environment Variables: Now required
DISCOGS_TOKEN-
CONTACT_EMAIL -
Cache Location: Clear old cache
-
Commands: Some renamed
update→update_genres- New:
update_years,restore_release_years