rekordbox-edit 0.4.0.dev22__tar.gz → 0.4.0.dev23__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/CHANGELOG.md +2 -1
  2. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/PKG-INFO +1 -1
  3. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/pyproject.toml +1 -1
  4. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/commands/edit.py +8 -2
  5. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/commands/test_edit.py +101 -0
  6. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/uv.lock +1 -1
  7. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.agent-style/RULES.md +0 -0
  8. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.agent-style/claude-code.md +0 -0
  9. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/actions/commitizen-bump/action.yml +0 -0
  10. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
  11. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/actions/install/action.yml +0 -0
  12. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/actions/lint/action.yml +0 -0
  13. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/actions/test/action.yml +0 -0
  14. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/workflows/cd.yml +0 -0
  15. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/workflows/ci.yml +0 -0
  16. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/workflows/publish.yml +0 -0
  17. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.github/workflows/release.yml +0 -0
  18. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.gitignore +0 -0
  19. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/.pre-commit-config.yaml +0 -0
  20. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/AGENTS.md +0 -0
  21. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/CLAUDE.md +0 -0
  22. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/CONTRIBUTING.md +0 -0
  23. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/LICENSE +0 -0
  24. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/Makefile +0 -0
  25. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/README.md +0 -0
  26. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/codecov.yml +0 -0
  27. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/__init__.py +0 -0
  28. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/_click.py +0 -0
  29. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/cli.py +0 -0
  30. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/commands/__init__.py +0 -0
  31. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/commands/convert.py +0 -0
  32. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/commands/search.py +0 -0
  33. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/logger.py +0 -0
  34. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/query.py +0 -0
  35. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/rekordbox_edit/utils.py +0 -0
  36. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/renovate.json5 +0 -0
  37. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/ruff.toml +0 -0
  38. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/__init__.py +0 -0
  39. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/commands/__init__.py +0 -0
  40. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/commands/test_convert.py +0 -0
  41. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/commands/test_search.py +0 -0
  42. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/conftest.py +0 -0
  43. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/test_logger.py +0 -0
  44. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/test_query.py +0 -0
  45. {rekordbox_edit-0.4.0.dev22 → rekordbox_edit-0.4.0.dev23}/tests/test_utils.py +0 -0
@@ -1,6 +1,7 @@
1
- ## v0.4.0.dev22 (2026-05-17)
1
+ ## v0.4.0.dev23 (2026-05-17)
2
2
 
3
3
 
4
+ - feat(edit): add --multi to allow batch edits past single-track guard
4
5
  - feat(edit): add --match for literal find/replace within field value
5
6
  - Introduces a _compute_new_value helper and --match option so that
6
7
  rbe edit can replace a substring of the current field value instead of
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rekordbox-edit
3
- Version: 0.4.0.dev22
3
+ Version: 0.4.0.dev23
4
4
  Summary: Tools for managing and modifying a RekordBox library en-masse
5
5
  Project-URL: Homepage, https://github.com/jviall/rekordbox-edit
6
6
  Project-URL: Repository, https://github.com/jviall/rekordbox-edit
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rekordbox-edit"
7
- version = "0.4.0.dev22"
7
+ version = "0.4.0.dev23"
8
8
  description = "Tools for managing and modifying a RekordBox library en-masse"
9
9
  authors = [{ name = "James Viall", email= "jamesviall@pm.me"}]
10
10
  license = "MIT"
@@ -73,6 +73,11 @@ def _compute_new_value(
73
73
  metavar="PATTERN",
74
74
  help="Find this literal string within the field value and replace only that portion",
75
75
  )
76
+ @click.option(
77
+ "--multi",
78
+ is_flag=True,
79
+ help="Allow editing more than one track (required when filters match multiple tracks)",
80
+ )
76
81
  @track_ids_argument
77
82
  @click.argument(
78
83
  "field",
@@ -82,6 +87,7 @@ def edit_command(
82
87
  field: str,
83
88
  replace_value: str,
84
89
  match_pattern: str | None,
90
+ multi: bool,
85
91
  dry_run: bool,
86
92
  yes: bool,
87
93
  interactive: bool,
@@ -157,10 +163,10 @@ def edit_command(
157
163
  logger.info("No changes to make.")
158
164
  return
159
165
 
160
- if len(edits) > 1:
166
+ if len(edits) > 1 and not multi:
161
167
  raise click.UsageError(
162
168
  f"Found {len(edits)} tracks that would be edited. "
163
- "Refine your filters, or use --dry-run to inspect."
169
+ "Refine your filters, use --dry-run to inspect, or pass --multi to edit all."
164
170
  )
165
171
 
166
172
  print_track_info([t for t, _ in edits])
@@ -427,3 +427,104 @@ class TestEditCommandUnicode:
427
427
 
428
428
  assert result.exit_code == 0
429
429
  mock_db.session.commit.assert_not_called()
430
+
431
+
432
+ class TestEditCommandPhase4:
433
+ """Phase 4: --multi flag to allow batch edits past the single-track guard."""
434
+
435
+ @patch("rekordbox_edit.commands.edit.confirm")
436
+ @patch("rekordbox_edit.commands.edit.get_filtered_content")
437
+ @patch("rekordbox_edit.commands.edit.Rekordbox6Database")
438
+ def test_multi_allows_editing_multiple_tracks(
439
+ self, mock_db_class, mock_gfc, mock_confirm, make_djmd_content_item
440
+ ):
441
+ """--multi bypasses the single-track guard and edits all matched tracks."""
442
+ tracks = [
443
+ make_djmd_content_item(Title="Track A (feat. X)"),
444
+ make_djmd_content_item(Title="Track B (feat. Y)"),
445
+ ]
446
+ mock_db, mock_result = _make_db_and_result(tracks)
447
+ mock_db_class.return_value = mock_db
448
+ mock_gfc.return_value = mock_result
449
+ mock_confirm.return_value = True
450
+
451
+ from click.testing import CliRunner
452
+ result = CliRunner().invoke(
453
+ edit_command,
454
+ ["Title", "--match", "feat.", "--replace", "ft.", "--multi"],
455
+ )
456
+
457
+ assert result.exit_code == 0
458
+ assert tracks[0].Title == "Track A (ft. X)"
459
+ assert tracks[1].Title == "Track B (ft. Y)"
460
+ mock_db.session.commit.assert_called_once()
461
+
462
+ @patch("rekordbox_edit.commands.edit.get_filtered_content")
463
+ @patch("rekordbox_edit.commands.edit.Rekordbox6Database")
464
+ def test_without_multi_still_aborts_on_multiple(
465
+ self, mock_db_class, mock_gfc, make_djmd_content_item
466
+ ):
467
+ """Without --multi, the single-track guard still aborts on multiple matches."""
468
+ tracks = [
469
+ make_djmd_content_item(Title="Track A"),
470
+ make_djmd_content_item(Title="Track B"),
471
+ ]
472
+ mock_db, mock_result = _make_db_and_result(tracks)
473
+ mock_db_class.return_value = mock_db
474
+ mock_gfc.return_value = mock_result
475
+
476
+ from click.testing import CliRunner
477
+ result = CliRunner().invoke(
478
+ edit_command, ["Title", "--replace", "New", "--yes"]
479
+ )
480
+
481
+ assert result.exit_code != 0
482
+ mock_db.session.commit.assert_not_called()
483
+
484
+ @patch("rekordbox_edit.commands.edit.confirm")
485
+ @patch("rekordbox_edit.commands.edit.get_filtered_content")
486
+ @patch("rekordbox_edit.commands.edit.Rekordbox6Database")
487
+ def test_multi_with_yes_skips_confirm(
488
+ self, mock_db_class, mock_gfc, mock_confirm, make_djmd_content_item
489
+ ):
490
+ """--multi --yes edits multiple tracks without prompting."""
491
+ tracks = [
492
+ make_djmd_content_item(Title="Track A"),
493
+ make_djmd_content_item(Title="Track B"),
494
+ ]
495
+ mock_db, mock_result = _make_db_and_result(tracks)
496
+ mock_db_class.return_value = mock_db
497
+ mock_gfc.return_value = mock_result
498
+
499
+ from click.testing import CliRunner
500
+ result = CliRunner().invoke(
501
+ edit_command,
502
+ ["Title", "--replace", "New", "--multi", "--yes"],
503
+ )
504
+
505
+ assert result.exit_code == 0
506
+ mock_confirm.assert_not_called()
507
+ mock_db.session.commit.assert_called_once()
508
+
509
+ @patch("rekordbox_edit.commands.edit.confirm")
510
+ @patch("rekordbox_edit.commands.edit.get_filtered_content")
511
+ @patch("rekordbox_edit.commands.edit.Rekordbox6Database")
512
+ def test_multi_single_track_still_works(
513
+ self, mock_db_class, mock_gfc, mock_confirm, make_djmd_content_item
514
+ ):
515
+ """--multi with only one matching track works fine (guard is not inverted)."""
516
+ track = make_djmd_content_item(Title="Only Track")
517
+ mock_db, mock_result = _make_db_and_result([track])
518
+ mock_db_class.return_value = mock_db
519
+ mock_gfc.return_value = mock_result
520
+ mock_confirm.return_value = True
521
+
522
+ from click.testing import CliRunner
523
+ result = CliRunner().invoke(
524
+ edit_command,
525
+ ["Title", "--replace", "New Title", "--multi"],
526
+ )
527
+
528
+ assert result.exit_code == 0
529
+ assert track.Title == "New Title"
530
+ mock_db.session.commit.assert_called_once()
@@ -985,7 +985,7 @@ wheels = [
985
985
 
986
986
  [[package]]
987
987
  name = "rekordbox-edit"
988
- version = "0.4.0.dev22"
988
+ version = "0.4.0.dev23"
989
989
  source = { editable = "." }
990
990
  dependencies = [
991
991
  { name = "click" },