Read sort case sensitivity in FieldSort

Move sort case-sensitivity config lookup into `FieldSort` as a cached
class property instead of threading a `case_insensitive` flag through
query parsing helpers.

This centralizes sort behavior at the sort implementation boundary and
simplifies parse/orchestration call paths while preserving configurable
case-sensitive ordering.
This commit is contained in:
Šarūnas Nejus
2026-05-19 19:58:16 +01:00
parent 8e62d66474
commit f6621ffa1a
4 changed files with 34 additions and 22 deletions

View File

@@ -171,15 +171,11 @@ def query_from_strings(
return query_cls(subqueries)
def construct_sort_part(
model_cls: type[LibModel], part: str, case_insensitive: bool = True
) -> sort.Sort:
def construct_sort_part(model_cls: type[LibModel], part: str) -> sort.FieldSort:
"""Create a `Sort` from a single string criterion.
`model_cls` is the `Model` being queried. `part` is a single string
ending in ``+`` or ``-`` indicating the sort. `case_insensitive`
indicates whether or not the sort should be performed in a case
sensitive manner.
ending in ``+`` or ``-`` indicating the sort.
"""
assert part, "part must be a field name and + or -"
field = part[:-1]
@@ -197,27 +193,25 @@ def construct_sort_part(
# Flexible or computed.
sort_cls = sort.SlowFieldSort
return sort_cls(field, is_ascending, case_insensitive)
return sort_cls(field, is_ascending)
def sort_from_strings(
model_cls: type[LibModel],
sort_parts: Sequence[str],
case_insensitive: bool = True,
model_cls: type[LibModel], sort_parts: Sequence[str]
) -> sort.Sort:
"""Create a `Sort` from a list of sort criteria (strings)."""
if not sort_parts:
return sort.NullSort()
if len(sort_parts) == 1:
return construct_sort_part(model_cls, sort_parts[0], case_insensitive)
return construct_sort_part(model_cls, sort_parts[0])
s = sort.MultipleSort()
for part in sort_parts:
s.add_sort(construct_sort_part(model_cls, part, case_insensitive))
s.add_sort(construct_sort_part(model_cls, part))
return s
def parse_sorted_query(
model_cls: type[LibModel], parts: list[str], case_insensitive: bool = True
model_cls: type[LibModel], parts: list[str]
) -> tuple[query.Query, sort.Sort]:
"""Given a list of strings, create the `Query` and `Sort` that they
represent.
@@ -251,5 +245,5 @@ def parse_sorted_query(
# Avoid needlessly wrapping single statements in an OR
q = query.OrQuery(query_parts) if len(query_parts) > 1 else query_parts[0]
s = sort_from_strings(model_cls, sort_parts, case_insensitive)
s = sort_from_strings(model_cls, sort_parts)
return q, s

View File

@@ -4,6 +4,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any
import beets
from beets.util import cached_classproperty
if TYPE_CHECKING:
from beets.dbcore.db import AnyModel, Model
@@ -103,12 +106,13 @@ class FieldSort(Sort):
any kind).
"""
def __init__(
self, field: str, ascending: bool = True, case_insensitive: bool = True
):
def __init__(self, field: str, ascending: bool = True):
self.field = field
self.ascending = ascending
self.case_insensitive = case_insensitive
@cached_classproperty
def case_insensitive(cls) -> bool:
return beets.config["sort_case_insensitive"].get(bool)
def sort(self, objs: list[AnyModel]) -> list[AnyModel]:
# TODO: Support flexible attributes with different types (e.g. a mix

View File

@@ -2,7 +2,6 @@ from __future__ import annotations
import shlex
import beets
from beets import dbcore, logging
log = logging.getLogger("beets")
@@ -25,9 +24,7 @@ def parse_query_parts(parts, model_cls):
for s in parts
]
case_insensitive = beets.config["sort_case_insensitive"].get(bool)
query, sort = dbcore.parse_sorted_query(model_cls, parts, case_insensitive)
query, sort = dbcore.parse_sorted_query(model_cls, parts)
log.debug("Parsed query: {!r}", query)
log.debug("Parsed sort: {!r}", sort)
return query, sort

View File

@@ -160,6 +160,23 @@ class TestCaseSensitivity:
helper.add_item(artist="artist", flex1="flex1", track=10)
helper.add_item(artist="Artist", flex1="Flex1", track=2)
@pytest.fixture
def config(self, monkeypatch, config):
"""Monkeypatch the config to clear the cached sort settings.
This is needed because ``FieldSort`` is accessed multiple times during
the test.
"""
def _set_config(_, key, value):
"""Invalidate cached sort settings before updating the config."""
if key == "sort_case_insensitive":
util.cached_classproperty.cache.clear()
config.set({key: value})
monkeypatch.setattr("confuse.core.ConfigView.__setitem__", _set_config)
return config
@pytest.mark.parametrize(
"getter,query,attr,expected_insensitive,expected_sensitive",
[