Skip to content

Commit a815305

Browse files
authored
Remove decargs and other references to Python 2 (#5859)
This PR modernizes the codebase by removing Python 2 compatibility code and simplifying several areas: - Deleted `BytesQuery` class (replaced with `PathQuery`) - Removed `decargs()` function that was a no-op in Python 3 - Simplified `print_()` function signature and implementation - Removed coding-related workarounds in various modules
2 parents e6016c1 + afe97cf commit a815305

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+126
-227
lines changed

beets/dbcore/query.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -411,39 +411,6 @@ def __init__(
411411
super().__init__(field_name, pattern_int, fast)
412412

413413

414-
class BytesQuery(FieldQuery[bytes]):
415-
"""Match a raw bytes field (i.e., a path). This is a necessary hack
416-
to work around the `sqlite3` module's desire to treat `bytes` and
417-
`unicode` equivalently in Python 2. Always use this query instead of
418-
`MatchQuery` when matching on BLOB values.
419-
"""
420-
421-
def __init__(self, field_name: str, pattern: bytes | str | memoryview):
422-
# Use a buffer/memoryview representation of the pattern for SQLite
423-
# matching. This instructs SQLite to treat the blob as binary
424-
# rather than encoded Unicode.
425-
if isinstance(pattern, (str, bytes)):
426-
if isinstance(pattern, str):
427-
bytes_pattern = pattern.encode("utf-8")
428-
else:
429-
bytes_pattern = pattern
430-
self.buf_pattern = memoryview(bytes_pattern)
431-
elif isinstance(pattern, memoryview):
432-
self.buf_pattern = pattern
433-
bytes_pattern = bytes(pattern)
434-
else:
435-
raise ValueError("pattern must be bytes, str, or memoryview")
436-
437-
super().__init__(field_name, bytes_pattern)
438-
439-
def col_clause(self) -> tuple[str, Sequence[SQLiteType]]:
440-
return self.field + " = ?", [self.buf_pattern]
441-
442-
@classmethod
443-
def value_match(cls, pattern: bytes, value: Any) -> bool:
444-
return pattern == value
445-
446-
447414
class NumericQuery(FieldQuery[str]):
448415
"""Matches numeric fields. A syntax using Ruby-style range ellipses
449416
(``..``) lets users specify one- or two-sided ranges. For example,

beets/importer/tasks.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
import mediafile
2828

29-
from beets import autotag, config, dbcore, library, plugins, util
29+
from beets import autotag, config, library, plugins, util
30+
from beets.dbcore.query import PathQuery
3031

3132
from .state import ImportState
3233

@@ -520,9 +521,7 @@ def record_replaced(self, lib: library.Library):
520521
)
521522
replaced_album_ids = set()
522523
for item in self.imported_items():
523-
dup_items = list(
524-
lib.items(query=dbcore.query.BytesQuery("path", item.path))
525-
)
524+
dup_items = list(lib.items(query=PathQuery("path", item.path)))
526525
self.replaced_items[item] = dup_items
527526
for dup_item in dup_items:
528527
if (

beets/ui/__init__.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,30 +104,15 @@ def _stream_encoding(stream, default="utf-8"):
104104
return stream.encoding or default
105105

106106

107-
def decargs(arglist):
108-
"""Given a list of command-line argument bytestrings, attempts to
109-
decode them to Unicode strings when running under Python 2.
110-
"""
111-
return arglist
112-
113-
114-
def print_(*strings, **kwargs):
107+
def print_(*strings: str, end: str = "\n") -> None:
115108
"""Like print, but rather than raising an error when a character
116109
is not in the terminal's encoding's character set, just silently
117110
replaces it.
118111
119-
The arguments must be Unicode strings: `unicode` on Python 2; `str` on
120-
Python 3.
121-
122112
The `end` keyword argument behaves similarly to the built-in `print`
123113
(it defaults to a newline).
124114
"""
125-
if not strings:
126-
strings = [""]
127-
assert isinstance(strings[0], str)
128-
129-
txt = " ".join(strings)
130-
txt += kwargs.get("end", "\n")
115+
txt = " ".join(strings or ("",)) + end
131116

132117
# Encode the string and write it to stdout.
133118
# On Python 3, sys.stdout expects text strings and uses the
@@ -1308,14 +1293,9 @@ def _set_format(
13081293
setattr(parser.values, option.dest, True)
13091294

13101295
# Use the explicitly specified format, or the string from the option.
1311-
if fmt:
1312-
value = fmt
1313-
elif value:
1314-
(value,) = decargs([value])
1315-
else:
1316-
value = ""
1317-
1296+
value = fmt or value or ""
13181297
parser.values.format = value
1298+
13191299
if target:
13201300
config[target._format_config_key].set(value)
13211301
else:

beets/ui/commands.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from beets import autotag, config, importer, library, logging, plugins, ui, util
2929
from beets.autotag import Recommendation, hooks
3030
from beets.ui import (
31-
decargs,
3231
input_,
3332
print_,
3433
print_column_layout,
@@ -1303,7 +1302,7 @@ def _get_choices(self, task):
13031302
# The import command.
13041303

13051304

1306-
def import_files(lib, paths, query):
1305+
def import_files(lib, paths: list[bytes], query):
13071306
"""Import the files in the given list of paths or matching the
13081307
query.
13091308
"""
@@ -1334,7 +1333,7 @@ def import_files(lib, paths, query):
13341333
plugins.send("import", lib=lib, paths=paths)
13351334

13361335

1337-
def import_func(lib, opts, args):
1336+
def import_func(lib, opts, args: list[str]):
13381337
config["import"].set_args(opts)
13391338

13401339
# Special case: --copy flag suppresses import_move (which would
@@ -1343,7 +1342,7 @@ def import_func(lib, opts, args):
13431342
config["import"]["move"] = False
13441343

13451344
if opts.library:
1346-
query = decargs(args)
1345+
query = args
13471346
paths = []
13481347
else:
13491348
query = None
@@ -1356,15 +1355,11 @@ def import_func(lib, opts, args):
13561355
if not paths and not paths_from_logfiles:
13571356
raise ui.UserError("no path specified")
13581357

1359-
# On Python 2, we used to get filenames as raw bytes, which is
1360-
# what we need. On Python 3, we need to undo the "helpful"
1361-
# conversion to Unicode strings to get the real bytestring
1362-
# filename.
1363-
paths = [os.fsencode(p) for p in paths]
1358+
byte_paths = [os.fsencode(p) for p in paths]
13641359
paths_from_logfiles = [os.fsencode(p) for p in paths_from_logfiles]
13651360

13661361
# Check the user-specified directories.
1367-
for path in paths:
1362+
for path in byte_paths:
13681363
if not os.path.exists(syspath(normpath(path))):
13691364
raise ui.UserError(
13701365
"no such file or directory: {}".format(
@@ -1385,14 +1380,14 @@ def import_func(lib, opts, args):
13851380
)
13861381
continue
13871382

1388-
paths.append(path)
1383+
byte_paths.append(path)
13891384

13901385
# If all paths were read from a logfile, and none of them exist, throw
13911386
# an error
13921387
if not paths:
13931388
raise ui.UserError("none of the paths are importable")
13941389

1395-
import_files(lib, paths, query)
1390+
import_files(lib, byte_paths, query)
13961391

13971392

13981393
import_cmd = ui.Subcommand(
@@ -1596,7 +1591,7 @@ def list_items(lib, query, album, fmt=""):
15961591

15971592

15981593
def list_func(lib, opts, args):
1599-
list_items(lib, decargs(args), opts.album)
1594+
list_items(lib, args, opts.album)
16001595

16011596

16021597
list_cmd = ui.Subcommand("list", help="query the library", aliases=("ls",))
@@ -1739,7 +1734,7 @@ def update_func(lib, opts, args):
17391734
return
17401735
update_items(
17411736
lib,
1742-
decargs(args),
1737+
args,
17431738
opts.album,
17441739
ui.should_move(opts.move),
17451740
opts.pretend,
@@ -1861,7 +1856,7 @@ def fmt_album(a):
18611856

18621857

18631858
def remove_func(lib, opts, args):
1864-
remove_items(lib, decargs(args), opts.album, opts.delete, opts.force)
1859+
remove_items(lib, args, opts.album, opts.delete, opts.force)
18651860

18661861

18671862
remove_cmd = ui.Subcommand(
@@ -1931,7 +1926,7 @@ def show_stats(lib, query, exact):
19311926

19321927

19331928
def stats_func(lib, opts, args):
1934-
show_stats(lib, decargs(args), opts.exact)
1929+
show_stats(lib, args, opts.exact)
19351930

19361931

19371932
stats_cmd = ui.Subcommand(
@@ -2059,7 +2054,7 @@ def modify_parse_args(args):
20592054

20602055

20612056
def modify_func(lib, opts, args):
2062-
query, mods, dels = modify_parse_args(decargs(args))
2057+
query, mods, dels = modify_parse_args(args)
20632058
if not mods and not dels:
20642059
raise ui.UserError("no modifications specified")
20652060
modify_items(
@@ -2217,7 +2212,7 @@ def move_func(lib, opts, args):
22172212
move_items(
22182213
lib,
22192214
dest,
2220-
decargs(args),
2215+
args,
22212216
opts.copy,
22222217
opts.album,
22232218
opts.pretend,
@@ -2298,7 +2293,7 @@ def write_items(lib, query, pretend, force):
22982293

22992294

23002295
def write_func(lib, opts, args):
2301-
write_items(lib, decargs(args), opts.pretend, opts.force)
2296+
write_items(lib, args, opts.pretend, opts.force)
23022297

23032298

23042299
write_cmd = ui.Subcommand("write", help="write tag information to files")

beets/util/__init__.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import tempfile
2929
import traceback
3030
from collections import Counter
31+
from collections.abc import Sequence
3132
from contextlib import suppress
3233
from enum import Enum
3334
from functools import cache
@@ -41,7 +42,6 @@
4142
AnyStr,
4243
Callable,
4344
Generic,
44-
Iterable,
4545
NamedTuple,
4646
TypeVar,
4747
Union,
@@ -53,23 +53,17 @@
5353
from beets.util import hidden
5454

5555
if TYPE_CHECKING:
56-
from collections.abc import Iterator, Sequence
56+
from collections.abc import Iterable, Iterator
5757
from logging import Logger
5858

5959
from beets.library import Item
6060

61-
if sys.version_info >= (3, 10):
62-
from typing import TypeAlias
63-
else:
64-
from typing_extensions import TypeAlias
65-
6661

6762
MAX_FILENAME_LENGTH = 200
6863
WINDOWS_MAGIC_PREFIX = "\\\\?\\"
6964
T = TypeVar("T")
70-
BytesOrStr = Union[str, bytes]
71-
PathLike = Union[BytesOrStr, Path]
72-
Replacements: TypeAlias = "Sequence[tuple[Pattern[str], str]]"
65+
PathLike = Union[str, bytes, Path]
66+
Replacements = Sequence[tuple[Pattern[str], str]]
7367

7468
# Here for now to allow for a easy replace later on
7569
# once we can move to a PathLike (mainly used in importer)
@@ -860,7 +854,9 @@ class CommandOutput(NamedTuple):
860854
stderr: bytes
861855

862856

863-
def command_output(cmd: list[BytesOrStr], shell: bool = False) -> CommandOutput:
857+
def command_output(
858+
cmd: list[str] | list[bytes], shell: bool = False
859+
) -> CommandOutput:
864860
"""Runs the command and returns its output after it has exited.
865861
866862
Returns a CommandOutput. The attributes ``stdout`` and ``stderr`` contain
@@ -878,8 +874,6 @@ def command_output(cmd: list[BytesOrStr], shell: bool = False) -> CommandOutput:
878874
This replaces `subprocess.check_output` which can have problems if lots of
879875
output is sent to stderr.
880876
"""
881-
converted_cmd = [os.fsdecode(a) for a in cmd]
882-
883877
devnull = subprocess.DEVNULL
884878

885879
proc = subprocess.Popen(
@@ -894,7 +888,7 @@ def command_output(cmd: list[BytesOrStr], shell: bool = False) -> CommandOutput:
894888
if proc.returncode:
895889
raise subprocess.CalledProcessError(
896890
returncode=proc.returncode,
897-
cmd=" ".join(converted_cmd),
891+
cmd=" ".join(map(os.fsdecode, cmd)),
898892
output=stdout + stderr,
899893
)
900894
return CommandOutput(stdout, stderr)

beets/util/artresizer.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ def version(cls) -> tuple[int, int, int]:
214214
else:
215215
return cls._version
216216

217-
convert_cmd: list[str | bytes]
218-
identify_cmd: list[str | bytes]
219-
compare_cmd: list[str | bytes]
217+
convert_cmd: list[str]
218+
identify_cmd: list[str]
219+
compare_cmd: list[str]
220220

221221
def __init__(self) -> None:
222222
"""Initialize a wrapper around ImageMagick for local image operations.
@@ -265,7 +265,7 @@ def resize(
265265
# with regards to the height.
266266
# ImageMagick already seems to default to no interlace, but we include
267267
# it here for the sake of explicitness.
268-
cmd: list[str | bytes] = self.convert_cmd + [
268+
cmd: list[str] = self.convert_cmd + [
269269
syspath(path_in, prefix=False),
270270
"-resize",
271271
f"{maxwidth}x>",
@@ -295,7 +295,7 @@ def resize(
295295
return path_out
296296

297297
def get_size(self, path_in: bytes) -> tuple[int, int] | None:
298-
cmd: list[str | bytes] = self.identify_cmd + [
298+
cmd: list[str] = self.identify_cmd + [
299299
"-format",
300300
"%w %h",
301301
syspath(path_in, prefix=False),
@@ -480,10 +480,11 @@ def can_write_metadata(self) -> bool:
480480
return True
481481

482482
def write_metadata(self, file: bytes, metadata: Mapping[str, str]) -> None:
483-
assignments = list(
484-
chain.from_iterable(("-set", k, v) for k, v in metadata.items())
483+
assignments = chain.from_iterable(
484+
("-set", k, v) for k, v in metadata.items()
485485
)
486-
command = self.convert_cmd + [file, *assignments, file]
486+
str_file = os.fsdecode(file)
487+
command = self.convert_cmd + [str_file, *assignments, str_file]
487488

488489
util.command_output(command)
489490

beetsplug/absubmit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def command(self, lib, opts, args):
137137
)
138138
else:
139139
# Get items from arguments
140-
items = lib.items(ui.decargs(args))
140+
items = lib.items(args)
141141
self.opts = opts
142142
util.par_map(self.analyze_submit, items)
143143

beetsplug/acousticbrainz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def commands(self):
116116
)
117117

118118
def func(lib, opts, args):
119-
items = lib.items(ui.decargs(args))
119+
items = lib.items(args)
120120
self._fetch_info(
121121
items,
122122
ui.should_write(),

beetsplug/badfiles.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def on_import_task_before_choice(self, task, session):
204204

205205
def command(self, lib, opts, args):
206206
# Get items from arguments
207-
items = lib.items(ui.decargs(args))
207+
items = lib.items(args)
208208
self.verbose = opts.verbose
209209

210210
def check_and_print(item):

0 commit comments

Comments
 (0)