Auto-Generated Class Diagrams¶
These diagrams are automatically generated from the source code using pyreverse.
Auto-Updated
These diagrams update automatically when the documentation is built. They reflect the current state of the codebase.
Core Track Processing¶
classDiagram
class ArtistRenamer {
console_logger : Logger
error_logger : Logger
has_mapping : bool
rename_tracks(tracks: list[TrackDict]) list[TrackDict]
}
class BaseProcessor {
analytics : AnalyticsProtocol
config : AppConfig
console_logger : Logger
dry_run : bool
error_logger : Logger
get_dry_run_actions() list[dict[str, Any]]
}
class BatchTrackFetcher {
analytics : NoneType
ap_client : AppleScriptClientProtocol
cache_service : CacheServiceProtocol
config : AppConfig
console_logger : Logger
dry_run : bool
error_logger : Logger
fetch_all_tracks(batch_size: int) list[TrackDict]
}
class GenreManager {
track_processor
deduplicate_tracks_by_id(tracks: list[TrackDict]) list[TrackDict]
filter_tracks_for_incremental_update(tracks: list[TrackDict], last_run_time: datetime | None) list[TrackDict]
get_dry_run_actions() list[dict[str, Any]]
is_missing_or_unknown_genre(track: TrackDict) bool
parse_date_added(track: TrackDict) datetime | None
process_batch_results(batch_results: list[Any], updated_tracks: list[TrackDict], change_logs: list[ChangeLogEntry]) None
test_filter_tracks_for_update(artist_tracks: list[TrackDict], last_run: datetime | None, force_flag: bool, dominant_genre: str | None) list[TrackDict]
test_gather_with_error_handling(tasks: list[Any], operation_name: str) list[Any]
test_process_artist_genres(artist_name: str, all_artist_tracks: list[TrackDict], force_update: bool, applescript_semaphore: asyncio.Semaphore, tracks_to_update: list[TrackDict] | None) tuple[list[TrackDict], list[ChangeLogEntry]]
test_process_single_artist_wrapper(artist_name: str, artist_tracks: list[TrackDict], last_run: datetime | None, force: bool, artist_semaphore: asyncio.Semaphore, applescript_semaphore: asyncio.Semaphore | None) tuple[list[TrackDict], list[ChangeLogEntry]]
test_select_tracks_to_update_for_artist(artist_tracks: list[TrackDict], last_run: datetime | None, force_flag: bool, dominant_genre: str | None) list[TrackDict]
test_update_track_genre(track: TrackDict, new_genre: str, force_update: bool) tuple[TrackDict | None, ChangeLogEntry | None]
update_genres_by_artist_async(tracks: list[TrackDict], last_run_time: datetime | None, force: bool, fresh: bool) tuple[list[TrackDict], list[ChangeLogEntry]]
}
class IncrementalFilterService {
filter_tracks_for_incremental_update(tracks: list[TrackDict], last_run_time: datetime | None) list[TrackDict]
get_dry_run_actions() list[dict[str, Any]]
}
class PrereleaseHandler {
config
console_logger
pending_verification
prerelease_recheck_days
handle_prerelease_tracks(artist: str, album: str, album_tracks: list[TrackDict]) tuple[bool, list[TrackDict]]
}
class TrackCacheManager {
cache_service : CacheServiceProtocol
console_logger : Logger
snapshot_service : LibrarySnapshotServiceProtocol | None
can_use_snapshot() bool
get_cached_tracks(cache_key: str) Sequence[TrackDict] | None
load_snapshot() list[TrackDict] | None
merge_tracks(existing: list[TrackDict], updates: list[TrackDict]) list[TrackDict]
update_snapshot(tracks: list[TrackDict], processed_track_ids: Sequence[str] | None) None
}
class TrackDelta {
new_ids : list[str]
removed_ids : list[str]
updated_ids : list[str]
has_removals() bool
has_updates() bool
is_empty() bool
}
class TrackProcessor {
analytics
ap_client : AppleScriptClientProtocol
artist_renamer : ArtistRenamer | None
batch_fetcher
cache_manager
cache_service : CacheServiceProtocol
config
console_logger
dry_run : bool
dry_run_mode : str
dry_run_test_artists : set[str]
error_logger
security_validator : SecurityValidator
snapshot_service : NoneType
update_executor
fetch_tracks_async(artist: str | None, force_refresh: bool, dry_run_test_tracks: list[TrackDict] | None, ignore_test_filter: bool) list[TrackDict]
fetch_tracks_by_ids(track_ids: list[str]) list[TrackDict]
fetch_tracks_in_batches(batch_size: int) list[TrackDict]
get_dry_run_actions() list[dict[str, Any]]
set_artist_renamer(renamer: ArtistRenamer) None
set_dry_run_context(mode: str, test_artists: set[str]) None
update_artist_async(track: TrackDict, new_artist_name: str) bool
update_track_async(track_id: str, new_track_name: str | None, new_album_name: str | None, new_genre: str | None, new_year: str | None, track_status: str | None, original_artist: str | None, original_album: str | None, original_track: str | None) bool
}
class TrackUpdateExecutor {
analytics : AnalyticsProtocol
ap_client : AppleScriptClientProtocol
cache_service : CacheServiceProtocol
config : AppConfig
console_logger : Logger
dry_run : bool
error_logger : Logger
security_validator : SecurityValidator
get_dry_run_actions() list[dict[str, Any]]
set_dry_run(dry_run: bool) None
update_artist_async(track: TrackDict, new_artist_name: str) bool
update_track_async(track_id: str, new_track_name: str | None, new_album_name: str | None, new_genre: str | None, new_year: str | None, track_status: str | None, original_artist: str | None, original_album: str | None, original_track: str | None) bool
}
class TrackUpdater {
config
console_logger
error_logger
retry_handler
track_processor
record_successful_updates(tracks: list[TrackDict], year: str, artist: str, album: str, updated_tracks: list[TrackDict], changes_log: list[ChangeLogEntry]) None
update_album_tracks_bulk_async(tracks: list[TrackDict], year: str, artist: str, album: str) tuple[int, int]
update_tracks_for_album(artist: str, album: str, album_tracks: list[TrackDict], year: str, updated_tracks: list[TrackDict], changes_log: list[ChangeLogEntry]) None
}
class YearBatchProcessor {
analytics
config
console_logger
dry_run : bool
error_logger
year_determinator
get_dry_run_actions() list[dict[str, Any]]
group_tracks_by_album(tracks: list[TrackDict]) dict[tuple[str, str], list[TrackDict]]
process_albums_in_batches(grouped_albums: dict[tuple[str, str], list[TrackDict]], updated_tracks: list[TrackDict], changes_log: list[ChangeLogEntry], force: bool) None
update_album_tracks_bulk_async(tracks: list[TrackDict], year: str, artist: str, album: str) tuple[int, int]
}
class YearConsistencyChecker {
console_logger
dominance_min_share : float
parity_threshold : int
suspicion_threshold_years : int
top_years_count : int
get_consensus_release_year(tracks: list[TrackDict]) str | None
get_dominant_year(tracks: list[TrackDict]) str | None
get_earliest_track_added_year(tracks: list[TrackDict]) int | None
get_most_common_year(tracks: list[TrackDict]) str | None
}
class YearDeterminator {
cache_service
config
consistency_checker
console_logger
error_logger
external_api
fallback_handler
future_year_threshold
pending_verification
prerelease_recheck_days
skip_prerelease
check_prerelease_status(artist: str, album: str, album_tracks: list[TrackDict]) bool
check_suspicious_album(artist: str, album: str, album_tracks: list[TrackDict]) bool
determine_album_year(artist: str, album: str, album_tracks: list[TrackDict], force: bool) str | None
extract_future_years(album_tracks: list[TrackDict]) list[int]
extract_release_years(album_tracks: list[TrackDict]) list[str]
handle_future_years(artist: str, album: str, album_tracks: list[TrackDict], future_years: list[int]) bool
should_skip_album(album_tracks: list[TrackDict], artist: str, album: str, force: bool) tuple[bool, str]
}
class YearFallbackHandler {
absurd_year_threshold
api_orchestrator : NoneType
console_logger
fallback_enabled
min_confidence_for_new_year : int
pending_verification
trust_api_score_threshold : int
year_difference_threshold
apply_year_fallback(proposed_year: str, album_tracks: list[TrackDict], is_definitive: bool, confidence_score: int, artist: str, album: str, year_scores: dict[str, int] | None, release_year: str | None) str | None
get_existing_year_from_tracks(tracks: list[TrackDict]) str | None
is_year_change_dramatic(existing: str, proposed: str) bool
}
class YearRetriever {
DEFAULT_ABSURD_YEAR_THRESHOLD : int
DEFAULT_FALLBACK_ENABLED : bool
DEFAULT_SUSPICION_THRESHOLD_YEARS : int
DEFAULT_YEAR_DIFFERENCE_THRESHOLD : int
DOMINANCE_MIN_SHARE : float
MAX_RETRY_DELAY_SECONDS : float
MIN_VALID_YEAR : int
PARITY_THRESHOLD : int
SUSPICIOUS_ALBUM_MIN_LEN : int
SUSPICIOUS_MANY_YEARS : int
TOP_YEARS_COUNT : int
absurd_year_threshold
analytics : AnalyticsProtocol
cache_service : CacheServiceProtocol
config : AppConfig
console_logger : Logger
dry_run : bool
error_logger : Logger
external_api : ExternalApiServiceProtocol
fallback_enabled
min_confidence_for_new_year : int
normalize_collaboration_artist : staticmethod
pending_verification : PendingVerificationServiceProtocol
suspicion_threshold_years
track_processor
year_consistency_checker
year_difference_threshold
year_fallback_handler
get_album_years_with_logs(tracks: list[TrackDict], force: bool) tuple[list[TrackDict], list[ChangeLogEntry]]
get_dry_run_actions() list[dict[str, Any]]
get_last_updated_tracks() list[TrackDict]
process_album_years(tracks: list[TrackDict], force: bool, fresh: bool) bool
set_last_updated_tracks(tracks: list[TrackDict]) None
update_album_tracks_bulk_async(tracks: list[TrackDict], year: str, artist: str, album: str) tuple[int, int]
update_years_from_discogs(tracks: list[TrackDict], force: bool) tuple[list[TrackDict], list[ChangeLogEntry]]
}
GenreManager --|> BaseProcessor
IncrementalFilterService --|> BaseProcessor
BatchTrackFetcher --* TrackProcessor : batch_fetcher
TrackCacheManager --* TrackProcessor : cache_manager
TrackUpdateExecutor --* TrackProcessor : update_executor
YearConsistencyChecker --* YearRetriever : year_consistency_checker
YearFallbackHandler --* YearRetriever : year_fallback_handler
ArtistRenamer --o TrackProcessor : artist_renamer
TrackProcessor --o GenreManager : track_processor
TrackProcessor --o YearRetriever : track_processor
Data Models¶
classDiagram
class AlbumCacheEntry {
album : str
artist : str
confidence : int
timestamp : float
year : str
}
class AlbumType {
name
}
class AlbumTypeDetectionConfig {
compilation_patterns : list[str] | None
reissue_patterns : list[str] | None
soundtrack_patterns : list[str] | None
special_patterns : list[str] | None
various_artists_names : list[str] | None
}
class AlbumTypeInfo {
album_type
detected_pattern : str | None
strategy
}
class AlbumTypePatterns {
compilation : frozenset[str]
reissue : frozenset[str]
special : frozenset[str]
from_config(config: AppConfig) AlbumTypePatterns
from_defaults() AlbumTypePatterns
}
class AnalyticsConfig {
compact_time : bool
duration_thresholds
enable_gc_collect : bool
enabled : bool
max_events : Optional[int]
time_format : str
}
class AnalyticsProtocol {
batch_mode(message: str, console: Console | None) AbstractAsyncContextManager[Status]
execute_async_wrapped_call(func: Callable[..., Any], event_type: str) Any
execute_sync_wrapped_call(func: Callable[..., Any], event_type: str) Any
}
class ApiAuthConfig {
contact_email : str
discogs_token : str
musicbrainz_app_name : str
}
class AppConfig {
album_type_detection : Optional[AlbumTypeDetectionConfig]
album_years_cache_file : str
analytics
api_cache_file : str
apple_script_concurrency : Optional[int]
apple_script_rate_limit : Optional[AppleScriptRateLimitConfig]
apple_scripts_dir : str
applescript_retry : Optional[AppleScriptRetryConfig]
applescript_timeout_seconds : Optional[int]
applescript_timeouts : Optional[AppleScriptTimeoutsConfig]
artist_renamer : Optional[ArtistRenamerConfig]
batch_processing : Optional[BatchProcessingConfig]
cache_ttl_seconds : Optional[int]
caching : Optional[CachingConfig]
cleaning
database_verification
development
dry_run : bool
exceptions
experimental : Optional[ExperimentalConfig]
genre_update
incremental_interval_minutes : Optional[int]
logging
logs_base_dir : str
max_generic_entries : Optional[int]
max_retries : Optional[int]
music_library_path : str
pending_verification : Optional[PendingVerificationConfig]
python_settings
reporting : Optional[ReportingConfig]
retry_delay_seconds : Optional[float]
test_artists : Optional[list[str]]
year_retrieval
migrate_legacy_test_artists() AppConfig
}
class AppleScriptClientProtocol {
apple_scripts_dir : str | None
fetch_all_track_ids(timeout: float | None) list[str]
fetch_tracks_by_ids(track_ids: list[str], batch_size: int, timeout: float | None) list[dict[str, str]]
initialize() None
run_script(script_name: str, arguments: list[str] | None, timeout: float | None, context_artist: str | None, context_album: str | None, context_track: str | None, label: str | None) str | None
}
class AppleScriptFieldIndex {
name
}
class AppleScriptRateLimitConfig {
enabled : bool
requests_per_window : Optional[int]
window_size_seconds : Optional[float]
}
class AppleScriptRetryConfig {
base_delay_seconds : Optional[float]
jitter_range : Optional[float]
max_delay_seconds : Optional[float]
max_retries : Optional[int]
operation_timeout_seconds : Optional[float]
}
class AppleScriptTimeoutsConfig {
batch_update : Optional[int]
default : Optional[int]
full_library_fetch : Optional[int]
ids_batch_fetch : Optional[int]
single_artist_fetch : Optional[int]
}
class ArtistRenamerConfig {
config_path : str
}
class BatchProcessingConfig {
batch_size : Optional[int]
ids_batch_size : Optional[int]
}
class CacheServiceProtocol {
clear() None
generate_album_key(artist: str, album: str) str
get_album_year_entry_from_cache(artist: str, album: str) AlbumCacheEntry | None
get_album_year_from_cache(artist: str, album: str) str | None
get_async(key_data: Literal['ALL'], compute_func: None) list[TrackDict]
get_cached_api_result(artist: str, album: str, source: str) CachedApiResult | None
get_last_run_timestamp() datetime
initialize() None
invalidate(key_data: CacheableKey) None
invalidate_album_cache(artist: str, album: str) None
invalidate_all_albums() None
invalidate_for_track(track: TrackDict) None
load_cache() None
save_cache() None
set(key_data: CacheableKey, value: CacheableValue, ttl: int | None) None
set_async(key_data: CacheableKey, value: CacheableValue, ttl: int | None) None
set_cached_api_result(artist: str, album: str, source: str, year: str | None) None
store_album_year_in_cache(artist: str, album: str, year: str, confidence: int) None
sync_cache() None
}
class CachedApiResult {
album : str
api_response : dict[str, Any] | None
artist : str
metadata : Optional[dict[str, Any]]
source : str
timestamp : float
ttl : int | None
year : str | None
model_dump() dict[str, Any]
}
class CachingConfig {
album_cache_sync_interval : Optional[int]
api_result_cache_path : str
cleanup_error_retry_delay : Optional[int]
cleanup_interval_seconds : Optional[int]
default_ttl_seconds : Optional[int]
library_snapshot : Optional[LibrarySnapshotConfig]
negative_result_ttl : Optional[float]
}
class ChangeDisplayMode {
name
}
class ChangeLogEntry {
album_name : str
artist : str
change_type : str
new_album_name : str | None
new_genre : str | None
new_track_name : str | None
old_album_name : str | None
old_genre : str | None
old_track_name : str | None
timestamp : str
track_id : str
track_name : str
year_before_mgu : str | None
year_set_by_mgu : str | None
}
class CleaningConfig {
album_suffixes_to_remove : list[str]
remaster_keywords : list[str]
}
class CodeAction {
code : str
}
class CodeActionExtended {
args : Optional[list[str]]
code : str
type : Literal['code']
}
class DatabaseVerificationConfig {
auto_verify_days : Optional[int]
batch_size : Optional[int]
}
class DevelopmentConfig {
debug_mode : bool
test_artists : list[str]
parse_test_artists(v: str | list[str] | tuple[str, ...]) list[str]
}
class DiscogsArtist {
id : int
name : str
resource_url : str | None
}
class DurationThresholds {
long_max : Optional[float]
medium_max : Optional[float]
short_max : Optional[float]
}
class ExceptionsConfig {
track_cleaning : list[TrackCleaningException]
}
class ExperimentalConfig {
batch_updates_enabled : bool
max_batch_size : Optional[int]
}
class ExternalApiServiceProtocol {
close() None
get_album_year(artist: str, album: str, current_library_year: str | None, earliest_track_added_year: int | None) tuple[str | None, bool, int, dict[str, int]]
get_artist_activity_period(artist_norm: str) tuple[int | None, int | None]
get_artist_start_year(artist_norm: str) int | None
get_year_from_discogs(artist: str, album: str) str | None
initialize(force: bool) None
}
class FallbackConfig {
enabled : bool
trust_api_score_threshold : Optional[float]
year_difference_threshold : Optional[int]
}
class GenreUpdateConfig {
batch_size : Optional[int]
concurrent_limit : Optional[int]
}
class LibraryCacheMetadata {
last_force_scan_time : str | None
last_full_scan : datetime
library_mtime : datetime
snapshot_hash : str
track_count : int
version : str
from_dict(data: dict[str, Any]) LibraryCacheMetadata
to_dict() dict[str, Any]
}
class LibraryDeltaCache {
field_hashes : dict[str, str]
last_run : datetime
processed_track_ids : set[str]
tracked_since : datetime | None
add_processed_ids(track_ids: Iterable[str]) None
from_dict(data: dict[str, Any]) LibraryDeltaCache
should_reset() bool
to_dict() dict[str, Any]
}
class LibrarySnapshotConfig {
cache_file : str
compress : bool
compress_level : Optional[int]
delta_enabled : bool
enabled : bool
max_age_hours : Optional[int]
}
class LibrarySnapshotServiceProtocol {
get_library_mtime() datetime
get_snapshot_metadata() LibraryCacheMetadata | None
is_delta_enabled() bool
is_enabled() bool
is_snapshot_valid() bool
load_delta() LibraryDeltaCache | None
load_snapshot() list[TrackDict] | None
save_delta(delta: LibraryDeltaCache) None
save_snapshot(tracks: Sequence[TrackDict]) str
update_snapshot_metadata(metadata: LibraryCacheMetadata) None
}
class LogLevel {
name
}
class LogLevelsConfig {
analytics_file
console
main_file
normalize_log_level(value: str) str
}
class LoggingConfig {
analytics_log_file : str
changes_report_file : str
csv_output_file : str
dry_run_report_file : str
last_db_verify_log : str
last_incremental_run_file : str
levels
main_log_file : str
max_runs : Optional[int]
pending_verification_file : str
}
class LogicConfig {
absurd_year_threshold : Optional[int]
definitive_score_diff : Optional[int]
definitive_score_threshold : Optional[int]
major_market_codes : list[str]
min_confidence_for_new_year : Optional[float]
min_valid_year : Optional[int]
preferred_countries : list[str]
suspicion_threshold_years : Optional[int]
}
class MBArtist {
disambiguation : str | None
id : str
name : str
sort_name : str | None
type : str | None
}
class MBArtistCredit {
artist
joinphrase : str | None
name : str | None
}
class MusicBrainzArtist {
disambiguation : str | None
id : str
name : str
sort_name : str | None
}
class MusicBrainzRelease {
artist_credit : Optional[list[MBArtistCredit]]
country : str | None
date : str | None
id : str
release_group : MusicBrainzReleaseGroup | None
status : str | None
title : str
}
class MusicBrainzReleaseGroup {
first_release_date : str | None
id : str
primary_type : str | None
secondary_types : Optional[list[str]]
title : str
}
class MusicBrainzSearchResult {
count : int
offset : int
releases : Optional[list[MusicBrainzRelease]]
}
class PendingAlbumEntry {
album : str
artist : str
attempt_count : int
metadata : str
reason
timestamp : datetime
}
class PendingVerificationConfig {
auto_verify_days : Optional[int]
}
class PendingVerificationServiceProtocol {
generate_problematic_albums_report(min_attempts: int) int
get_all_pending_albums() list[PendingAlbumEntry]
get_attempt_count(artist: str, album: str) int
get_entry(artist: str, album: str) PendingAlbumEntry | None
initialize() None
is_verification_needed(artist: str, album: str) bool
mark_for_verification(artist: str, album: str, reason: str, metadata: dict[str, Any] | None, recheck_days: int | None) None
remove_from_pending(artist: str, album: str) None
should_auto_verify() bool
update_verification_timestamp() None
}
class PreferredApi {
name
}
class ProcessingConfig {
adaptive_delay : bool
batch_size : Optional[int]
cache_ttl_days : Optional[int]
delay_between_batches : Optional[float]
future_year_threshold : Optional[int]
pending_verification_interval_days : Optional[int]
prerelease_handling : str
prerelease_recheck_days : Optional[int]
skip_prerelease : bool
}
class PythonSettings {
prevent_bytecode : bool
}
class RateLimiterProtocol {
acquire() float
get_stats() dict[str, Any]
release() None
}
class RateLimitsConfig {
concurrent_api_calls : Optional[int]
discogs_requests_per_minute : Optional[int]
musicbrainz_requests_per_second : Optional[float]
}
class ReissueDetectionConfig {
reissue_keywords : list[str]
}
class ReportingConfig {
change_display_mode
min_attempts_for_report : Optional[float]
problematic_albums_path : str
}
class RevertTarget {
album : str | None
track_id : str | None
track_name : str
year_before_mgu : str
}
class ScoringConfig {
album_exact_match_bonus : int
album_substring_penalty : Optional[int]
album_unrelated_penalty : Optional[int]
album_variation_bonus : int
artist_cross_script_penalty : Optional[int]
artist_exact_match_bonus : int
artist_mismatch_penalty : Optional[int]
artist_substring_penalty : Optional[int]
base_score : int
country_artist_match_bonus : int
country_major_market_bonus : int
current_year_penalty : int
future_year_penalty : Optional[int]
mb_release_group_match_bonus : int
perfect_match_bonus : int
reissue_penalty : Optional[int]
soundtrack_compensation_bonus : int
source_discogs_bonus : int
source_itunes_bonus : int
source_mb_bonus : int
status_bootleg_penalty : Optional[int]
status_official_bonus : int
status_promo_penalty : Optional[int]
type_album_bonus : int
type_compilation_live_penalty : Optional[int]
type_ep_single_penalty : Optional[int]
year_after_end_penalty : Optional[int]
year_before_start_penalty : Optional[int]
year_diff_max_penalty : Optional[int]
year_diff_penalty_scale : Optional[int]
year_near_start_bonus : int
}
class ScriptAction {
args : list[Any] | None
script : str
}
class ScriptActionExtended {
args : Optional[list[str]]
content : str
script_path : str
type : Literal['script']
}
class ScriptApiPriority {
fallback : Optional[list[str]]
primary : list[str]
}
class ScriptType {
name
}
class SearchStrategy {
name
}
class SearchStrategyInfo {
detected_pattern : str | None
modified_album : str | None
modified_artist : str | None
strategy
}
class SecurityValidationError {
dangerous_pattern : str | None
field : str | None
}
class SecurityValidator {
logger
sanitize_string(value: str, field_name: str | None) str
validate_api_input(data: dict[str, Any], max_depth: int, _current_depth: int) dict[str, Any]
validate_file_path(file_path: str, allowed_base_paths: list[str] | None) str
validate_track_data(track_data: dict[str, Any]) dict[str, Any]
}
class SupportsDictConversion {
model_dump() dict[str, Any]
}
class TrackCleaningException {
album : str
artist : str
}
class TrackDict {
album : str
album_artist : str | None
artist : str
date_added : str | None
genre : str | None
id : str
last_modified : str | None
model_config : ConfigDict
name : str
original_album : str | None
original_artist : str | None
original_pos : int | None
release_year : str | None
track_status : str | None
year : str | None
year_before_mgu : str | None
year_set_by_mgu : str | None
copy() TrackDict
get(key: str, default: TrackFieldValue) TrackFieldValue
}
class TrackProcessorProtocol {
update_artist_async(track: TrackDict, new_artist_name: str) bool
}
class VerificationReason {
name
from_string(value: str) VerificationReason
}
class YearHandlingStrategy {
name
}
class YearRetrievalConfig {
api_auth
enabled : bool
fallback : Optional[FallbackConfig]
logic
preferred_api
processing
rate_limits
reissue_detection
scoring
script_api_priorities : Optional[dict[str, ScriptApiPriority]]
}
class YearRevertProcessorProtocol {
fetch_tracks_async(artist: str | None, force_refresh: bool, dry_run_test_tracks: list[TrackDict] | None, ignore_test_filter: bool) list[TrackDict]
update_track_async(track_id: str, new_track_name: str | None, new_album_name: str | None, new_genre: str | None, new_year: str | None, track_status: str | None, original_artist: str | None, original_album: str | None, original_track: str | None) bool
}
AlbumTypeInfo --> AlbumType : album_type
AlbumTypeInfo --> YearHandlingStrategy : strategy
PendingAlbumEntry --> VerificationReason : reason
SearchStrategyInfo --> SearchStrategy : strategy
AppConfig --> AnalyticsConfig : analytics
YearRetrievalConfig --> ApiAuthConfig : api_auth
ReportingConfig --> ChangeDisplayMode : change_display_mode
AppConfig --> CleaningConfig : cleaning
AppConfig --> DatabaseVerificationConfig : database_verification
AppConfig --> DevelopmentConfig : development
AnalyticsConfig --> DurationThresholds : duration_thresholds
AppConfig --> ExceptionsConfig : exceptions
AppConfig --> GenreUpdateConfig : genre_update
LogLevelsConfig --> LogLevel : console
LogLevelsConfig --> LogLevel : main_file
LogLevelsConfig --> LogLevel : analytics_file
LoggingConfig --> LogLevelsConfig : levels
AppConfig --> LoggingConfig : logging
YearRetrievalConfig --> LogicConfig : logic
MBArtistCredit --> MBArtist : artist
YearRetrievalConfig --> PreferredApi : preferred_api
YearRetrievalConfig --> ProcessingConfig : processing
AppConfig --> PythonSettings : python_settings
YearRetrievalConfig --> RateLimitsConfig : rate_limits
YearRetrievalConfig --> ReissueDetectionConfig : reissue_detection
YearRetrievalConfig --> ScoringConfig : scoring
AppConfig --> YearRetrievalConfig : year_retrieval
API Clients¶
classDiagram
class Alias {
locale : str | None
name : str | None
primary : bool | None
sort_name : str | None
type : str | None
}
class ApiRateLimiter {
call_times : list[float]
lock : Lock
requests_per_window : int
total_requests : int
total_wait_time : float
window_seconds : float
acquire() float
get_stats() dict[str, Any]
release()* None
}
class ApiRequestExecutor {
api_call_durations : dict[str, list[float]]
cache_service
cache_ttl_days
console_logger
default_max_retries
default_retry_delay
discogs_token
error_logger
rate_limiters
request_counts : dict[str, int]
session : NoneType, aiohttp.ClientSession | None
user_agent
execute_request(api_name: str, url: str, params: dict[str, str] | None, headers_override: dict[str, str] | None, max_retries: int | None, base_delay: float | None, timeout_override: float | None) dict[str, Any] | None
set_session(session: aiohttp.ClientSession | None) None
}
class AppleMusicClient {
base_url : str
console_logger : Logger
country_code : str
entity : str
error_logger : Logger
limit
make_api_request_func : Callable[..., Coroutine[Any, Any, dict[str, Any] | None]]
score_release_func : Callable[..., float]
get_artist_start_year(artist_norm: str) int | None
get_scored_releases(artist_norm: str, album_norm: str) list[ScoredRelease]
}
class Area {
name : str | None
sort_name : str | None
type : str | None
}
class ArtistCredit {
artist : MBArtist
joinphrase : str | None
name : str | None
}
class ArtistPeriodContext {
end_year : int | None
start_year : int | None
}
class BaseApiClient {
compilation_pattern
console_logger : Logger
error_logger : Logger
}
class CoverArtArchive {
artwork : bool
back : bool
count : int
front : bool
}
class DiscogsClient {
analytics : Analytics
cache_service
cache_ttl_days : int
config
scoring_config
token : str
get_scored_releases(artist_norm: str, album_norm: str, artist_region: str | None) list[ScoredRelease]
get_year_from_discogs(artist: str, album: str) str | None
}
class DiscogsFormat {
descriptions : list[str]
name : str
qty : str | None
text : str | None
}
class DiscogsMasterRelease {
data_quality : str
genres : list[str]
id : int
main_release : int
main_release_url : str
resource_url : str
styles : list[str]
title : str
uri : str
year : int | None
}
class DiscogsRelease {
country : str | None
cover_image : str | None
formats : list[DiscogsFormat]
genre : list[str]
id : int | str
label : list[str]
master_id : int | None
master_url : str | None
released : str | None
resource_url : str
style : list[str]
thumb : str | None
title : str
type : str
uri : str
year : int | str | None
}
class DiscogsSearchResponse {
pagination : dict[str, Any]
results : list[DiscogsRelease]
}
class ExternalApiOrchestrator {
analytics : Analytics
api_call_durations : dict
applemusic_client
artist_period_context : ArtistPeriodContext | None
cache_service : CacheOrchestrator
cache_ttl_days
config : AppConfig
console_logger : Logger
contact_email : NoneType, str
current_year
default_api_max_retries
default_api_retry_delay
definitive_score_diff
definitive_score_threshold
discogs_client
discogs_token
error_logger : Logger
future_year_threshold
major_market_codes
min_valid_year
musicbrainz_app_name
musicbrainz_client
pending_verification_service : PendingVerificationService
preferred_api : str
prerelease_recheck_days
rate_limiters : dict
rate_limits_config
release_scorer
remaster_keywords : list[str]
request_counts : dict
request_executor
scoring_config
secure_config : NoneType, SecureConfig | None
session : NoneType, aiohttp.ClientSession | None
skip_prerelease
user_agent
year_score_resolver
year_search_coordinator
close() None
get_album_year(artist: str, album: str, current_library_year: str | None, earliest_track_added_year: int | None) tuple[str | None, bool, int, dict[str, int]]
get_artist_activity_period(artist_norm: str) tuple[int | None, int | None]
get_artist_start_year(artist_norm: str) int | None
get_year_from_discogs(artist: str, album: str) str | None
initialize(force: bool) None
}
class Label {
disambiguation : str | None
id : str
label_code : str | None
name : str
}
class LabelInfo {
catalog_number : str | None
label : Label | None
}
class LifeSpan {
begin : str | None
end : str | None
ended : bool | None
}
class Medium {
disc_count : int | None
format : str | None
position : int | None
title : str | None
track_count : int | None
tracks : list[Track] | None
}
class MusicBrainzClient {
analytics : Analytics
get_artist_activity_period(artist_norm: str) tuple[str | None, str | None]
get_artist_info(artist_norm: str, include_aliases: bool) dict[str, Any] | None
get_artist_region(artist_norm: str) str | None
get_canonical_artist_name(artist_norm: str) str | None
get_scored_releases(artist_norm: str, album_norm: str, artist_region: str | None) list[ScoredRelease]
}
class MusicBrainzReleasesResponse {
release_count : int
release_offset : int
releases : list[Release]
}
class Recording {
artist_credit : list[ArtistCredit] | None
disambiguation : str | None
id : str
length : int | None
title : str
}
class Release {
artist_credit : list[ArtistCredit] | None
barcode : str | None
country : str | None
cover_art_archive : CoverArtArchive | None
date : str | None
disambiguation : str | None
id : str
label_info : list[LabelInfo] | None
media : list[Medium] | None
packaging : str | None
release_events : list[ReleaseEvent] | None
release_group : dict[str, Any] | None
status : str | None
status_id : str | None
text_representation : TextRepresentation | None
title : str
year : int | None
}
class ReleaseEvent {
area : Area | None
date : str | None
}
class ReleaseGroup {
artist_credit : list[ArtistCredit] | None
disambiguation : str | None
first_release_date : str | None
id : str
primary_type : str | None
primary_type_id : str | None
releases : list[Release] | None
secondary_type_ids : list[str] | None
secondary_types : list[str] | None
title : str
}
class ReleaseScorer {
YEAR_LENGTH : int
artist_period_context : ArtistPeriodContext | None, NoneType
console_logger
current_year
definitive_score_threshold : int
major_market_codes : list
min_valid_year : int
remaster_keywords : list
scoring_config : ScoringConfig
clear_artist_period_context() None
score_original_release(release: dict[str, Any], artist_norm: str, album_norm: str) int
set_artist_period_context(context: ArtistPeriodContext | None) None
}
class ScoredRelease {
album_type : str | None
artist : str | None
barcode : str | None
catalog_number : str | None
country : str | None
disambiguation : str | None
format : str | None
is_reissue : NotRequired[bool]
label : str | None
score : float
source : str
status : str | None
title : str
year : str | None
}
class TextRepresentation {
language : str | None
script : str | None
}
class Track {
id : str
length : int | None
number : str | None
position : int | None
recording : Recording | None
title : str
}
class YearScoreResolver {
console_logger
current_year
definitive_score_diff
definitive_score_threshold
min_valid_year
remaster_keywords : list
aggregate_year_scores(all_releases: list[ScoredRelease]) defaultdict[str, list[int]]
select_best_year(year_scores: defaultdict[str, list[int]], all_releases: list[ScoredRelease] | None, existing_year: str | None) tuple[str, bool, int]
}
class YearSearchCoordinator {
applemusic_client
config
console_logger
discogs_client
error_logger
musicbrainz_client
preferred_api
release_scorer
fetch_all_api_results(artist_norm: str, album_norm: str, artist_region: str | None, log_artist: str, log_album: str) list[ScoredRelease]
}
class _RegionAwareApi {
get_scored_releases(artist_norm: str, album_norm: str, artist_region: str | None) list[ScoredRelease]
}
class _SimpleApi {
get_scored_releases(artist_norm: str, album_norm: str) list[ScoredRelease]
}
DiscogsClient --|> BaseApiClient
MusicBrainzClient --|> BaseApiClient
AppleMusicClient --* ExternalApiOrchestrator : applemusic_client
DiscogsClient --* ExternalApiOrchestrator : discogs_client
MusicBrainzClient --* ExternalApiOrchestrator : musicbrainz_client
ApiRequestExecutor --* ExternalApiOrchestrator : request_executor
YearScoreResolver --* ExternalApiOrchestrator : year_score_resolver
ArtistPeriodContext --* ExternalApiOrchestrator : artist_period_context
ReleaseScorer --* ExternalApiOrchestrator : release_scorer
YearSearchCoordinator --* ExternalApiOrchestrator : year_search_coordinator
Cache Services¶
classDiagram
class AlbumCacheService {
album_years_cache : dict[str, AlbumCacheEntry]
album_years_cache_file : Path
cache_config
config : AppConfig
logger
policy
get_album_year(artist: str, album: str) str | None
get_album_year_entry(artist: str, album: str) AlbumCacheEntry | None
get_stats() dict[str, Any]
initialize() None
invalidate_album(artist: str, album: str) None
invalidate_all() None
save_to_disk() None
store_album_year(artist: str, album: str, year: str, confidence: int) None
}
class ApiCacheService {
api_cache : dict[str, CachedApiResult]
api_cache_file : Path
cache_config
config : AppConfig
event_manager
logger
cleanup_expired() int
emit_track_modified(track_id: str, old_artist: str, old_album: str) None
emit_track_removed(track_id: str, artist: str, album: str) None
get_cached_result(artist: str, album: str, source: str) CachedApiResult | None
get_stats() dict[str, Any]
initialize() None
invalidate_all() None
invalidate_for_album(artist: str, album: str) None
save_to_disk() None
set_cached_result(artist: str, album: str, source: str, success: bool, data: dict[str, Any] | None, metadata: dict[str, Any] | None) None
shutdown() None
}
class CacheContentType {
name
}
class CacheEvent {
event_type
fingerprint : str | None
metadata : dict[str, Any] | None
track_id : str | None
}
class CacheEventType {
name
}
class CacheOrchestrator {
album_service
album_years_cache : dict[str, tuple[str, str, str]]
api_cache : dict[str, Any]
api_service
cache : dict[str, Any]
config : AppConfig
config_manager : NoneType
generic_service
logger
clear() None
generate_album_key(artist: str, album: str) str
get(key_data: CacheableKey) CacheableValue | None
get_album_year(artist: str, album: str) str | None
get_album_year_entry_from_cache(artist: str, album: str) AlbumCacheEntry | None
get_album_year_from_cache(artist: str, album: str) str | None
get_async(key_data: CacheableKey, compute_func: Callable[[], asyncio.Future[CacheableValue]] | None) list[TrackDict] | CacheableValue
get_cached_api_result(artist: str, album: str, source: str) CachedApiResult | None
get_last_run_timestamp() datetime
initialize() None
invalidate(key_data: CacheableKey) None
invalidate_album_cache(artist: str, album: str) None
invalidate_all() None
invalidate_all_albums() None
invalidate_for_track(track: TrackDict) None
load_cache()* None
save_all_to_disk() None
save_cache() None
set(key_data: CacheableKey, value: CacheableValue, ttl: int | None) None
set_async(key_data: CacheableKey, value: CacheableValue, ttl: int | None) None
set_cached_api_result(artist: str, album: str, source: str, year: str | None) None
shutdown() None
store_album_year(artist: str, album: str, year: str, confidence: int) None
store_album_year_in_cache(artist: str, album: str, year: str, confidence: int) None
sync_cache() None
}
class CachePolicy {
cleanup_threshold : float
content_type
description : str
invalidation_strategy
max_size_mb : int | None
ttl_seconds : int
}
class EventDrivenCacheManager {
config
logger : NoneType, RootLogger
emit_event(event: CacheEvent) None
register_event_handler(event_type: CacheEventType, handler: Callable[[CacheEvent], None]) None
should_invalidate_for_event(content_type: CacheContentType, event: CacheEvent) bool
}
class FingerprintGenerationError {
track_data : dict[str, Any] | None
}
class FingerprintGenerator {
ENCODING : str
HASH_ALGORITHM : str
OPTIONAL_PROPERTIES : ClassVar[dict[str, Any]]
REQUIRED_PROPERTIES : ClassVar[set[str]]
logger
generate_track_fingerprint(track_data: dict[str, Any]) str
validate_fingerprint(fingerprint: Any) bool
}
class GenericCacheService {
cache : OrderedDict[str, tuple[CacheableValue, float]]
cache_config
cache_file : Path
config : AppConfig
default_ttl
logger
max_size : int
cleanup_expired() int
enforce_size_limits() int
get(key_data: CacheableKey) CacheableValue | None
get_stats() dict[str, Any]
initialize() None
invalidate(key_data: CacheableKey) bool
invalidate_all() None
save_to_disk() None
set(key_data: CacheableKey, value: CacheableValue, ttl: int | None) None
stop_cleanup_task() None
}
class InvalidationStrategy {
name
}
class LibrarySnapshotService {
compress
compress_level
config : AppConfig
delta_enabled
enabled
logger
max_age : timedelta
clear_snapshot() bool
compute_smart_delta(applescript_client: AppleScriptClientProtocol, force: bool) TrackDelta | None
compute_snapshot_hash(payload: Sequence[dict[str, Any]]) str
get_library_mtime() datetime
get_snapshot_metadata() LibraryCacheMetadata | None
initialize() None
is_delta_enabled() bool
is_enabled() bool
is_snapshot_valid() bool
load_delta() LibraryDeltaCache | None
load_snapshot() list[TrackDict] | None
save_delta(delta: LibraryDeltaCache) None
save_snapshot(tracks: Sequence[TrackDict]) str
should_force_scan(force_flag: bool) tuple[bool, str]
update_snapshot_metadata(metadata: LibraryCacheMetadata) None
}
class SmartCacheConfig {
DAY : int
DEFAULT_NEGATIVE_RESULT_TTL : int
HOUR : int
INFINITE_TTL : int
MINUTE : int
MONTH : int
WEEK : int
logger : NoneType, RootLogger
get_policy(content_type: CacheContentType) CachePolicy
get_ttl(content_type: CacheContentType) int
is_persistent_cache(content_type: CacheContentType) bool
should_use_event_invalidation(content_type: CacheContentType) bool
should_use_fingerprint_validation(content_type: CacheContentType) bool
}
class UnifiedHashService {
ALGORITHM : str
hash_album_key(artist: str, album: str) str
hash_api_key(artist: str, album: str, source: str) str
hash_generic_key(data: Any) str
hash_pending_key(track_id: str) str
}
CachePolicy --> CacheContentType : content_type
CacheEvent --> CacheEventType : event_type
CachePolicy --> InvalidationStrategy : invalidation_strategy
AlbumCacheService --* CacheOrchestrator : album_service
ApiCacheService --* CacheOrchestrator : api_service
EventDrivenCacheManager --* ApiCacheService : event_manager
SmartCacheConfig --* AlbumCacheService : cache_config
SmartCacheConfig --* ApiCacheService : cache_config
SmartCacheConfig --* GenericCacheService : cache_config
GenericCacheService --* CacheOrchestrator : generic_service
SmartCacheConfig --o EventDrivenCacheManager : config
Apple Music Integration¶
classDiagram
class AppleScriptClient {
analytics : Analytics
apple_scripts_dir
config : AppConfig
console_logger : NoneType, RootLogger
error_logger : NoneType, RootLogger
executor
file_validator
rate_limiter : AppleScriptRateLimiter | None
sanitizer
semaphore : asyncio.Semaphore | None
fetch_all_track_ids(timeout: float | None) list[str]
fetch_tracks_by_ids(track_ids: list[str], batch_size: int, timeout: float | None) list[dict[str, str]]
initialize() None
run_script(script_name: str, arguments: list[str] | None, timeout: float | None, context_artist: str | None, context_album: str | None, context_track: str | None, label: str | None) str | None
}
class AppleScriptExecutionError {
label : str
}
class AppleScriptExecutor {
apple_scripts_directory : str | None
console_logger : Logger
error_logger : Logger
rate_limiter : AppleScriptRateLimiter | None
retry_handler : DatabaseRetryHandler | None
semaphore : asyncio.Semaphore | None
cleanup_process(proc: asyncio.subprocess.Process, label: str) None
handle_subprocess_execution(cmd: list[str], label: str, timeout_seconds: float) str | None
log_script_success(label: str, script_result: str, elapsed: float) None
run_osascript(cmd: list[str], label: str, timeout_seconds: float) str | None
update_rate_limiter(rate_limiter: AppleScriptRateLimiter) None
update_semaphore(semaphore: asyncio.Semaphore) None
}
class AppleScriptFileValidator {
apple_scripts_directory : str | None
console_logger : Logger
error_logger : Logger
validate_script_file_access(script_path: str) bool
validate_script_path(script_path: str) bool
}
class AppleScriptRateLimiter {
logger
max_concurrent : int
request_timestamps : deque[float]
requests_per_window : int
semaphore : asyncio.Semaphore | None
total_requests : int
total_wait_time : float
window_seconds : float
acquire() float
get_stats() RateLimiterStats
initialize() None
release() None
}
class AppleScriptSanitizationError {
dangerous_pattern : str | None
}
class AppleScriptSanitizer {
logger
create_safe_command(script_code: str, arguments: list[str] | None) list[str]
sanitize_string(value: Any) str
validate_script_code(script_code: str | None, allow_music_app: bool) None
}
class RateLimiterStats {
avg_wait_time : float
current_calls_in_window : int
requests_per_window : int
total_requests : int
total_wait_time : float
window_seconds : float
}
AppleScriptExecutor --* AppleScriptClient : executor
AppleScriptFileValidator --* AppleScriptClient : file_validator
AppleScriptRateLimiter --* AppleScriptClient : rate_limiter
AppleScriptSanitizer --* AppleScriptClient : sanitizer
AppleScriptRateLimiter --o AppleScriptExecutor : rate_limiter
Application Layer¶
classDiagram
class BatchProcessor {
console_logger : Logger
error_logger : Logger
music_updater
print_summary(results: dict[str, list[str]], total: int) None
process_artists(artists: list[str], operation: str, force: bool) dict[str, list[str]]
process_from_file(file_path: str, operation: str, force: bool) dict[str, list[str]]
}
class CLI {
parser : ArgumentParser
parse_args(args: list[str] | None) argparse.Namespace
print_help() None
}
class Config {
config_path : str | None
resolved_path : str
load() AppConfig
}
class CryptographyError {
details : dict
message : str
}
class CryptographyManager {
key_file_path : Path
logger : Logger
decrypt_token(encrypted_token: str, key: str | None, passphrase: str | None) str
encrypt_token(token: str, key: str | None, passphrase: str | None) str
get_secure_config_status() dict[str, Any]
is_token_encrypted(token: str) bool
rotate_key(new_passphrase: str | None, backup_old_key: bool) None
}
class DatabaseVerifier {
analytics
ap_client : AppleScriptClientProtocol
config
console_logger : Logger
db_verify_logger : Logger
dry_run : bool
error_logger : Logger
can_run_incremental(force_run: bool) bool
get_dry_run_actions() list[DryRunAction]
should_auto_verify() bool
update_last_incremental_run() None
verify_and_clean_track_database(force: bool, apply_test_filter: bool) int
}
class DecryptionError {
}
class DryRunAction {
action : str
count : int
track_ids : list[str]
}
class EncryptionError {
}
class GenreUpdateService {
get_tracks_for_genre_update(artist: str | None) list[TrackDict] | None
run_update_genres(artist: str | None, force: bool) None
set_test_artists(test_artists: set[str] | None) None
}
class InvalidKeyError {
}
class InvalidTokenError {
}
class KeyGenerationError {
}
class MusicUpdater {
analytics
app_config
artist_renamer : ArtistRenamer
cleaning_service
console_logger
database_verifier
deps : DependencyContainer
dry_run_mode : str
dry_run_test_artists : set[str]
error_logger
genre_manager : GenreManager
genre_service
incremental_filter : IncrementalFilterService
snapshot_manager
track_processor : TrackProcessor
year_retriever : YearRetriever
year_service
run_clean_artist(artist: str) None
run_main_pipeline(force: bool, fresh: bool) None
run_restore_release_years(artist: str | None, album: str | None, threshold: int) None
run_revert_years(artist: str, album: str | None, backup_csv: str | None) None
run_update_genres(artist: str | None, force: bool) None
run_update_years(artist: str | None, force: bool, fresh: bool) None
run_verify_database(force: bool) None
run_verify_pending(_force: bool) None
set_dry_run_context(mode: str, test_artists: set[str]) None
}
class Orchestrator {
COMMANDS_BYPASSING_MUSIC_CHECK : ClassVar[set[str]]
config
console_logger
deps : DependencyContainer
error_logger
music_updater
run_command(args: argparse.Namespace) None
}
class PipelineSnapshotManager {
clear() None
get_snapshot() list[TrackDict] | None
merge_smart_delta(snapshot_tracks: list[TrackDict], delta: TrackDelta) list[TrackDict] | None
persist_to_disk() bool
reset() None
set_snapshot(tracks: list[TrackDict]) None
update_tracks(updated_tracks: Iterable[TrackDict]) None
}
class TrackCleaningService {
clean_all_metadata_with_logs(tracks: list[TrackDict]) list[ChangeLogEntry]
extract_and_clean_metadata(track: TrackDict) tuple[TrackFieldValue, str, TrackFieldValue, TrackFieldValue, str, str]
process_all_tracks(tracks: list[TrackDict], artist: str) tuple[list[TrackDict], list[ChangeLogEntry]]
process_single_track(track: TrackDict, artist_override: str | None) tuple[TrackDict | None, ChangeLogEntry | None]
}
class YearUpdateService {
get_tracks_for_year_update(artist: str | None) list[TrackDict] | None
run_restore_release_years(artist: str | None, album: str | None, threshold: int) None
run_revert_years(artist: str, album: str | None, backup_csv: str | None) None
run_update_years(artist: str | None, force: bool, fresh: bool) None
set_test_artists(test_artists: set[str] | None) None
update_all_years_with_logs(tracks: list[TrackDict], force: bool, fresh: bool) list[ChangeLogEntry]
}
DecryptionError --|> CryptographyError
EncryptionError --|> CryptographyError
InvalidKeyError --|> CryptographyError
InvalidTokenError --|> CryptographyError
KeyGenerationError --|> CryptographyError
DatabaseVerifier --* MusicUpdater : database_verifier
GenreUpdateService --* MusicUpdater : genre_service
MusicUpdater --* Orchestrator : music_updater
PipelineSnapshotManager --* MusicUpdater : snapshot_manager
TrackCleaningService --* MusicUpdater : cleaning_service
YearUpdateService --* MusicUpdater : year_service
MusicUpdater --o BatchProcessor : music_updater
Metrics & Analytics¶
classDiagram
class Analytics {
GC_COLLECTION_THRESHOLD : int
analytics_logger
call_counts : dict[str, int]
compact_time
config : AppConfig
console_logger
decorator_overhead : dict[str, float]
enable_gc_collect
enabled
error_logger
events : list[dict[str, Any]]
instance_id : int
long_max
max_events
medium_max
short_max
success_counts : dict[str, int]
time_format
batch_mode(message: str, console: Console | None) AsyncIterator[Status]
clear_old_events(days: int) int
execute_async_wrapped_call(func: Callable[..., Any], event_type: str) Any
execute_sync_wrapped_call(func: Callable[..., Any], event_type: str) Any
execute_wrapped_call(func: Callable[..., Any], event_type: str, is_async: bool) Any
generate_reports(force_mode: bool) None
get_stats(function_filter: str | list[str] | None) dict[str, Any]
log_summary() None
merge_with(other: Analytics) None
track(event_type: str) Callable[[Callable[..., Any]], Callable[..., Any]]
}
class CallInfo {
event_type : str
func_name : str
success : bool
}
class ChangeType {
GENRE : str
NAME : str
OTHER : str
YEAR : str
}
class Format {
ARROW : str
COL_WIDTH_10 : int
COL_WIDTH_30 : int
COL_WIDTH_38 : int
COL_WIDTH_40 : int
HEADER_ITEM_NAME : str
HEADER_ITEM_TYPE : str
HEADER_OLD_NEW : str
ITEM_TYPE_ALBUM : str
ITEM_TYPE_OTHER : str
ITEM_TYPE_TRACK : str
SEPARATOR_100 : int
SEPARATOR_80 : int
TRUNCATE_SUFFIX : str
}
class Key {
ALBUM : str
ARTIST : str
CHANGE_TYPE : str
NEW_ALBUM_NAME : str
NEW_GENRE : str
NEW_TRACK_NAME : str
OLD_ALBUM_NAME : str
OLD_GENRE : str
OLD_TRACK_NAME : str
TIMESTAMP : str
TRACK_NAME : str
YEAR_BEFORE_MGU : str
YEAR_SET_BY_MGU : str
}
class LoggerContainer {
analytics : Logger
console : Logger
error : Logger
}
class Misc {
CHANGES_REPORT_TYPE : str
DURATION_FIELD : str
EMOJI_CHANGE : str
EMOJI_REPORT : str
UNKNOWN : str
UNKNOWN_ALBUM : str
UNKNOWN_ARTIST : str
UNKNOWN_TRACK : str
}
class ParsedTrackFields {
date_added : str
last_modified : str
track_status : str
year : str
}
class TimingInfo {
duration : float
end : float
overhead : float
start : float
}