rekordbox-edit 0.4.0.dev40__tar.gz → 0.4.0.dev42__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 (48) hide show
  1. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/CHANGELOG.md +3 -1
  2. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/PKG-INFO +1 -1
  3. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/pyproject.toml +1 -1
  4. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/args.py +71 -0
  5. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/commands/convert.py +51 -18
  6. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/commands/edit.py +54 -18
  7. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/uv.lock +1 -1
  8. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.agent-style/RULES.md +0 -0
  9. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.agent-style/claude-code.md +0 -0
  10. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/actions/commitizen-bump/action.yml +0 -0
  11. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
  12. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/actions/install/action.yml +0 -0
  13. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/actions/lint/action.yml +0 -0
  14. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/actions/test/action.yml +0 -0
  15. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/workflows/cd.yml +0 -0
  16. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/workflows/ci.yml +0 -0
  17. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/workflows/publish.yml +0 -0
  18. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.github/workflows/release.yml +0 -0
  19. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.gitignore +0 -0
  20. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/.pre-commit-config.yaml +0 -0
  21. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/AGENTS.md +0 -0
  22. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/CLAUDE.md +0 -0
  23. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/CONTRIBUTING.md +0 -0
  24. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/LICENSE +0 -0
  25. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/Makefile +0 -0
  26. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/README.md +0 -0
  27. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/codecov.yml +0 -0
  28. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/__init__.py +0 -0
  29. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/_click.py +0 -0
  30. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/cli.py +0 -0
  31. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/commands/__init__.py +0 -0
  32. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/commands/search.py +0 -0
  33. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/display.py +0 -0
  34. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/logger.py +0 -0
  35. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/query.py +0 -0
  36. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/rekordbox_edit/utils.py +0 -0
  37. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/renovate.json5 +0 -0
  38. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/ruff.toml +0 -0
  39. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/__init__.py +0 -0
  40. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/commands/__init__.py +0 -0
  41. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/commands/test_convert.py +0 -0
  42. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/commands/test_edit.py +0 -0
  43. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/commands/test_search.py +0 -0
  44. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/conftest.py +0 -0
  45. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/test_display.py +0 -0
  46. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/test_logger.py +0 -0
  47. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/test_query.py +0 -0
  48. {rekordbox_edit-0.4.0.dev40 → rekordbox_edit-0.4.0.dev42}/tests/test_utils.py +0 -0
@@ -1,6 +1,8 @@
1
- ## v0.4.0.dev40 (2026-06-05)
1
+ ## v0.4.0.dev42 (2026-06-05)
2
2
 
3
3
 
4
+ - refactor(commands): group command-specific args into EditArgs and ConvertArgs dataclasses
5
+ - refactor(commands): group confirmation flags into ConfirmationArgs dataclass
4
6
  - docs: update README.md
5
7
  - refactor(query): group filter args into FilterArgs dataclass
6
8
  - refactor(cli): extract convert-specific options into convert_click_options
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rekordbox-edit
3
- Version: 0.4.0.dev40
3
+ Version: 0.4.0.dev42
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.dev40"
7
+ version = "0.4.0.dev42"
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"
@@ -52,3 +52,74 @@ def filter_args_from_kwargs(**kwargs) -> FilterArgs:
52
52
  format=list(kwargs.get("format") or []),
53
53
  match_all=bool(kwargs.get("match_all", False)),
54
54
  )
55
+
56
+
57
+ @dataclass
58
+ class ConfirmationArgs:
59
+ """How the user gates a side effect before it lands.
60
+
61
+ `dry_run` skips the change entirely. `yes` skips confirmation and applies
62
+ the batch. `interactive` prompts per item. With all three false, the
63
+ caller is expected to prompt once for the batch.
64
+ """
65
+
66
+ dry_run: bool = False
67
+ yes: bool = False
68
+ interactive: bool = False
69
+
70
+
71
+ def confirmation_args_from_kwargs(**kwargs) -> ConfirmationArgs:
72
+ """Pack the flat Click kwargs for the `global_click_confirmations` group into a ConfirmationArgs."""
73
+ return ConfirmationArgs(
74
+ dry_run=bool(kwargs.get("dry_run", False)),
75
+ yes=bool(kwargs.get("yes", False)),
76
+ interactive=bool(kwargs.get("interactive", False)),
77
+ )
78
+
79
+
80
+ @dataclass
81
+ class EditArgs:
82
+ """Edit-command inputs that describe what to change.
83
+
84
+ `field` names a column from `FIELD_COLUMNS` in `commands/edit.py`.
85
+ `match_pattern` is the optional substring to find within the current value;
86
+ when omitted, the whole value is replaced. `multi` allows the edit to
87
+ apply to more than one matched track.
88
+ """
89
+
90
+ field: str
91
+ replace_value: str
92
+ match_pattern: str | None = None
93
+ multi: bool = False
94
+
95
+
96
+ def edit_args_from_kwargs(**kwargs) -> EditArgs:
97
+ """Pack the flat Click kwargs for the `edit_click_options` group plus `field` into an EditArgs."""
98
+ return EditArgs(
99
+ field=kwargs["field"],
100
+ replace_value=kwargs["replace_value"],
101
+ match_pattern=kwargs.get("match_pattern"),
102
+ multi=bool(kwargs.get("multi", False)),
103
+ )
104
+
105
+
106
+ @dataclass
107
+ class ConvertArgs:
108
+ """Convert-command inputs that describe the output and conflict policy.
109
+
110
+ `delete` is tri-state: `None` defers to a per-format default in
111
+ `_convert`, while `True` / `False` are explicit.
112
+ """
113
+
114
+ format_out: str
115
+ delete: bool | None = None
116
+ overwrite: bool = False
117
+
118
+
119
+ def convert_args_from_kwargs(**kwargs) -> ConvertArgs:
120
+ """Pack the flat Click kwargs for the `convert_click_options` group into a ConvertArgs."""
121
+ return ConvertArgs(
122
+ format_out=kwargs["format_out"],
123
+ delete=kwargs.get("delete"),
124
+ overwrite=bool(kwargs.get("overwrite", False)),
125
+ )
@@ -22,7 +22,14 @@ from rekordbox_edit._click import (
22
22
  print_option,
23
23
  track_ids_argument,
24
24
  )
25
- from rekordbox_edit.args import filter_args_from_kwargs
25
+ from rekordbox_edit.args import (
26
+ ConfirmationArgs,
27
+ ConvertArgs,
28
+ FilterArgs,
29
+ confirmation_args_from_kwargs,
30
+ convert_args_from_kwargs,
31
+ filter_args_from_kwargs,
32
+ )
26
33
  from rekordbox_edit.logger import get_debug_file_path, set_level
27
34
  from rekordbox_edit.query import get_filtered_content
28
35
  from rekordbox_edit.display import PrintableField, print_track_info
@@ -286,15 +293,57 @@ def convert_command(
286
293
 
287
294
  Skips lossy formats and files already in the target format.
288
295
  """
296
+ filters = filter_args_from_kwargs(
297
+ track_id=track_id,
298
+ track_ids=track_ids,
299
+ playlist=playlist,
300
+ exact_playlist=exact_playlist,
301
+ album=album,
302
+ exact_album=exact_album,
303
+ artist=artist,
304
+ exact_artist=exact_artist,
305
+ title=title,
306
+ exact_title=exact_title,
307
+ path=path,
308
+ exact_path=exact_path,
309
+ format=format,
310
+ match_all=match_all,
311
+ )
312
+ confirmation = confirmation_args_from_kwargs(
313
+ dry_run=dry_run, yes=yes, interactive=interactive
314
+ )
315
+ convert_args = convert_args_from_kwargs(
316
+ format_out=format_out, delete=delete, overwrite=overwrite
317
+ )
318
+ _convert(filters, confirmation, convert_args, print_opt)
319
+
320
+
321
+ def _convert(
322
+ filters: FilterArgs,
323
+ confirmation: ConfirmationArgs,
324
+ convert_args: ConvertArgs,
325
+ print_opt: PrintChoice | None,
326
+ ) -> None:
327
+ """Convert audio files for tracks matching `filters`."""
289
328
  from rekordbox_edit.utils import ffmpeg_in_path, get_ffmpeg_directions
290
329
 
330
+ dry_run, yes, interactive = (
331
+ confirmation.dry_run,
332
+ confirmation.yes,
333
+ confirmation.interactive,
334
+ )
335
+ format_out, delete, overwrite = (
336
+ convert_args.format_out,
337
+ convert_args.delete,
338
+ convert_args.overwrite,
339
+ )
291
340
  set_level(print_opt)
292
341
 
293
342
  piped_stdin = not sys.stdin.isatty()
294
343
  if piped_stdin:
295
344
  stdin_data = sys.stdin.read().strip()
296
345
  if stdin_data:
297
- track_ids = list(track_ids or []) + stdin_data.split()
346
+ filters.track_ids = list(filters.track_ids) + stdin_data.split()
298
347
 
299
348
  # Validate --print option requirements
300
349
  scripting_mode = print_opt in (PrintChoice.IDS, PrintChoice.SILENT)
@@ -362,22 +411,6 @@ def convert_command(
362
411
  logger.debug("Database connection established")
363
412
 
364
413
  # === QUERY & FILTER ===
365
- filters = filter_args_from_kwargs(
366
- track_id=track_id,
367
- track_ids=track_ids,
368
- playlist=playlist,
369
- exact_playlist=exact_playlist,
370
- album=album,
371
- exact_album=exact_album,
372
- artist=artist,
373
- exact_artist=exact_artist,
374
- title=title,
375
- exact_title=exact_title,
376
- path=path,
377
- exact_path=exact_path,
378
- format=format,
379
- match_all=match_all,
380
- )
381
414
  result = get_filtered_content(db, filters)
382
415
  filtered_content = result.scalars().all()
383
416
  logger.debug(f"Query returned {len(filtered_content)} tracks")
@@ -16,7 +16,14 @@ from rekordbox_edit._click import (
16
16
  print_option,
17
17
  track_ids_argument,
18
18
  )
19
- from rekordbox_edit.args import filter_args_from_kwargs
19
+ from rekordbox_edit.args import (
20
+ ConfirmationArgs,
21
+ EditArgs,
22
+ FilterArgs,
23
+ confirmation_args_from_kwargs,
24
+ edit_args_from_kwargs,
25
+ filter_args_from_kwargs,
26
+ )
20
27
  from rekordbox_edit.logger import get_debug_file_path, set_level
21
28
  from rekordbox_edit.query import get_filtered_content
22
29
  from rekordbox_edit.display import PrintableField, print_track_info
@@ -84,7 +91,52 @@ def edit_command(
84
91
  print_opt: PrintChoice | None,
85
92
  ):
86
93
  """Edit a metadata field on tracks in the RekordBox database."""
94
+ filters = filter_args_from_kwargs(
95
+ track_id=track_id,
96
+ track_ids=track_ids,
97
+ playlist=playlist,
98
+ exact_playlist=exact_playlist,
99
+ album=album,
100
+ exact_album=exact_album,
101
+ artist=artist,
102
+ exact_artist=exact_artist,
103
+ title=title,
104
+ exact_title=exact_title,
105
+ path=path,
106
+ exact_path=exact_path,
107
+ format=format,
108
+ match_all=match_all,
109
+ )
110
+ confirmation = confirmation_args_from_kwargs(
111
+ dry_run=dry_run, yes=yes, interactive=interactive
112
+ )
113
+ edit_args = edit_args_from_kwargs(
114
+ field=field,
115
+ replace_value=replace_value,
116
+ match_pattern=match_pattern,
117
+ multi=multi,
118
+ )
119
+ _edit(filters, confirmation, edit_args, print_opt)
120
+
87
121
 
122
+ def _edit(
123
+ filters: FilterArgs,
124
+ confirmation: ConfirmationArgs,
125
+ edit_args: EditArgs,
126
+ print_opt: PrintChoice | None,
127
+ ) -> None:
128
+ """Apply a field edit to tracks matching `filters`."""
129
+ dry_run, yes, interactive = (
130
+ confirmation.dry_run,
131
+ confirmation.yes,
132
+ confirmation.interactive,
133
+ )
134
+ field, replace_value, match_pattern, multi = (
135
+ edit_args.field,
136
+ edit_args.replace_value,
137
+ edit_args.match_pattern,
138
+ edit_args.multi,
139
+ )
88
140
  set_level(print_opt)
89
141
 
90
142
  piped_stdin = False
@@ -92,7 +144,7 @@ def edit_command(
92
144
  stdin_data = sys.stdin.read().strip()
93
145
  if stdin_data:
94
146
  piped_stdin = True
95
- track_ids = list(track_ids or []) + stdin_data.split()
147
+ filters.track_ids = list(filters.track_ids) + stdin_data.split()
96
148
 
97
149
  scripting_mode = print_opt in (PrintChoice.IDS, PrintChoice.SILENT)
98
150
  if scripting_mode and not (dry_run or yes):
@@ -107,22 +159,6 @@ def edit_command(
107
159
  if not db.session:
108
160
  raise RuntimeError("Failed to connect to Rekordbox Database: No Session.")
109
161
 
110
- filters = filter_args_from_kwargs(
111
- track_id=track_id,
112
- track_ids=track_ids,
113
- playlist=playlist,
114
- exact_playlist=exact_playlist,
115
- album=album,
116
- exact_album=exact_album,
117
- artist=artist,
118
- exact_artist=exact_artist,
119
- title=title,
120
- exact_title=exact_title,
121
- path=path,
122
- exact_path=exact_path,
123
- format=format,
124
- match_all=match_all,
125
- )
126
162
  result = get_filtered_content(db, filters)
127
163
  tracks = result.scalars().all()
128
164
 
@@ -1010,7 +1010,7 @@ wheels = [
1010
1010
 
1011
1011
  [[package]]
1012
1012
  name = "rekordbox-edit"
1013
- version = "0.4.0.dev40"
1013
+ version = "0.4.0.dev42"
1014
1014
  source = { editable = "." }
1015
1015
  dependencies = [
1016
1016
  { name = "click" },