From 6c52252672862abb7bc80b141560aecc858da388 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Wed, 7 Jan 2026 15:57:48 +0100 Subject: [PATCH] Readded licence. Removed last legacy occurrences of `artist` and replaced them with `field`. Removed unnecessary default parameters where applicable. --- beetsplug/random.py | 37 +++++++++++++++++++++++++------------ test/plugins/test_random.py | 14 +++++++++----- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/beetsplug/random.py b/beetsplug/random.py index ba687f477..9714c8e53 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -1,17 +1,31 @@ -"""Get a random song or album from the library.""" +# This file is part of beets. +# Copyright 2016, Philippe Mongeau. +# Copyright 2025, Sebastian Mohr. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. from __future__ import annotations import random from itertools import groupby, islice from operator import attrgetter -from typing import TYPE_CHECKING, Any, Iterable +from typing import TYPE_CHECKING, Any from beets.plugins import BeetsPlugin from beets.ui import Subcommand, print_ if TYPE_CHECKING: import optparse + from collections.abc import Iterable from beets.library import LibModel, Library @@ -24,10 +38,10 @@ def random_func(lib: Library, opts: optparse.Values, args: list[str]): # Print a random subset. for obj in random_objs( objs=objs, + equal_chance_field=opts.field, number=opts.number, time_minutes=opts.time, equal_chance=opts.equal_chance, - equal_chance_field=opts.field, ): print_(format(obj)) @@ -45,7 +59,7 @@ random_cmd.parser.add_option( "-e", "--equal-chance", action="store_true", - help="each artist has the same chance", + help="each field has the same chance", ) random_cmd.parser.add_option( "-t", @@ -55,10 +69,10 @@ random_cmd.parser.add_option( help="total length in minutes of objects to choose", ) random_cmd.parser.add_option( - "-f", "--field", action="store", type="string", + default="albumartist", help="field to use for equal chance sampling (default: albumartist)", ) random_cmd.parser.add_all_common_options() @@ -74,13 +88,13 @@ NOT_FOUND_SENTINEL = object() def _equal_chance_permutation( - objs: Iterable[LibModel], field: str = "albumartist" + objs: Iterable[LibModel], field: str ) -> Iterable[LibModel]: """Generate (lazily) a permutation of the objects where every group with equal values for `field` have an equal chance of appearing in any given position. """ - # Group the objects by artist so we can sample from them. + # Group the objects by field so we can sample from them. key = attrgetter(field) def get_attr(obj: LibModel) -> Any: @@ -126,10 +140,10 @@ def _take_time( def random_objs( objs: Iterable[LibModel], + equal_chance_field: str, number: int = 1, time_minutes: float | None = None, equal_chance: bool = False, - equal_chance_field: str = "albumartist", ) -> Iterable[LibModel]: """Get a random subset of items, optionally constrained by time or count. @@ -138,15 +152,14 @@ def random_objs( - number: The number of objects to select. - time_minutes: If specified, the total length of selected objects should not exceed this many minutes. - - equal_chance: If True, each artist has the same chance of being + - equal_chance: If True, each field has the same chance of being selected, regardless of how many tracks they have. - random_gen: An optional random generator to use for shuffling. """ # Permute the objects either in a straightforward way or an - # artist-balanced way. - perm: Iterable[LibModel] + # field-balanced way. if equal_chance: - perm = _equal_chance_permutation(objs, field=equal_chance_field) + perm = _equal_chance_permutation(objs, equal_chance_field) else: perm = list(objs) random.shuffle(perm) diff --git a/test/plugins/test_random.py b/test/plugins/test_random.py index e061473d0..975bb8ffa 100644 --- a/test/plugins/test_random.py +++ b/test/plugins/test_random.py @@ -140,13 +140,15 @@ class TestRandomObjs: def test_random_selection_by_count(self): """Test selecting a specific number of items.""" - selected = list(random_objs(self.items, number=2)) + selected = list(random_objs(self.items, "artist", number=2)) assert len(selected) == 2 assert all(item in self.items for item in selected) def test_random_selection_by_time(self): """Test selecting items constrained by total time (minutes).""" - selected = list(random_objs(self.items, time_minutes=6)) # 6 minutes + selected = list( + random_objs(self.items, "artist", time_minutes=6) + ) # 6 minutes total_time = ( sum(item.length for item in selected) / 60 ) # Convert to minutes @@ -160,7 +162,9 @@ class TestRandomObjs: helper.create_item(artist=self.artist1, length=180) ) - selected = list(random_objs(self.items, number=10, equal_chance=True)) + selected = list( + random_objs(self.items, "artist", number=10, equal_chance=True) + ) artist_counts = {} for item in selected: artist_counts[item.artist] = artist_counts.get(item.artist, 0) + 1 @@ -170,11 +174,11 @@ class TestRandomObjs: def test_empty_input_list(self): """Test behavior with an empty input list.""" - selected = list(random_objs([], number=1)) + selected = list(random_objs([], "artist", number=1)) assert len(selected) == 0 def test_no_constraints_returns_all(self): """Test that no constraints return all items in random order.""" - selected = list(random_objs(self.items, 3)) + selected = list(random_objs(self.items, "artist", number=3)) assert len(selected) == len(self.items) assert set(selected) == set(self.items)