Skip to content

Support += and -= for multi-value fields in modify#6648

Open
Victor0700 wants to merge 2 commits into
beetbox:masterfrom
Victor0700:modify-multivalue-operators
Open

Support += and -= for multi-value fields in modify#6648
Victor0700 wants to merge 2 commits into
beetbox:masterfrom
Victor0700:modify-multivalue-operators

Conversation

@Victor0700
Copy link
Copy Markdown

Description

Fixes #6587.

This PR adds += and -= support to beet modify for multi-value fields.

For example:

beet modify genres+=Funk
beet modify genres-=Blues

With this change, genres+=Funk appends Funk to the existing genres values if it is not already present, while genres-=Blues removes only the exact value Blues and preserves other values such as Blues Rock.

The existing field=value replacement behavior is preserved.

Main behavior changes:

  • genres=Jazz; Blues still replaces the full field value as before.
  • genres+=Funk appends Funk to the existing multi-value field.
  • genres+=Funk does not create duplicate values.
  • genres-=Blues removes only the exact value Blues.
  • Removing Blues does not remove partial matches like Blues Rock.
  • Existing value order is preserved.
  • += and -= are rejected for scalar fields with a clear user-facing error.

To Do

  • Documentation. I have not added documentation yet. I can add this to the modify command documentation if maintainers prefer.
  • Changelog. I have not added a changelog entry yet and can add one after review if this change is accepted.
  • Tests. Added tests for multi-value assignment, append, duplicate prevention, exact removal, partial-match preservation, ordering, and scalar-field operator errors.

Verification

Ran:

poetry run pytest test/ui/commands/test_modify.py -q

@Victor0700 Victor0700 requested a review from a team as a code owner May 17, 2026 10:24
@github-actions
Copy link
Copy Markdown

Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.48%. Comparing base (aa33b1c) to head (910fa5f).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6648      +/-   ##
==========================================
+ Coverage   72.44%   72.48%   +0.04%     
==========================================
  Files         160      160              
  Lines       20690    20722      +32     
  Branches     3272     3281       +9     
==========================================
+ Hits        14989    15021      +32     
  Misses       4976     4976              
  Partials      725      725              
Files with missing lines Coverage Δ
beets/ui/commands/modify.py 95.95% <100.00%> (+1.92%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JOJ0
Copy link
Copy Markdown
Member

JOJ0 commented May 19, 2026

I really love the feature idea and the straightforward syntax suggestion but don't really have time to look into it these days....

If I'm not mistaken this feature was requested already, maybe even multiple times. Do you mind looking up those issues or did you already and couldn't find them?

Copy link
Copy Markdown
Member

@snejus snejus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done. I think we'd like ModifyOperation class to be a bit smarter and own all relevant methods, see my comments



@dataclass(frozen=True)
class ModifyOperation:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified to a NamedTuple.

)


def _apply_modify_operation(obj, field, mod, value):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want this to be a method on ModifyOperation, e.g. def apply.

key, operator = key[:-1], key[-1]
key = maybe_replace_legacy_field(key, is_album, modify=True)
mods[key] = val
mods[key] = ModifyOperation(operator, val) if operator else val
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's only have ModifyOperations here! Simply set operator=None when we have no operator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Addition and subtraction operators for multi-value fields

3 participants