rekordbox-edit 0.6.0.dev40__tar.gz → 0.6.0.dev44__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 (94) hide show
  1. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/workflows/cd.yml +1 -0
  2. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/CHANGELOG.md +6 -1
  3. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/PKG-INFO +1 -1
  4. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docs/api.md +2 -2
  5. rekordbox_edit-0.6.0.dev44/docs/stylesheets/extra.css +19 -0
  6. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/mkdocs.yml +9 -1
  7. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/pyproject.toml +2 -1
  8. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/api/convert.py +3 -3
  9. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/api/edit.py +3 -3
  10. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/api/search.py +2 -2
  11. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/cli/convert.py +2 -2
  12. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/cli/edit.py +3 -3
  13. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/cli/search.py +2 -2
  14. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/models.py +14 -20
  15. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/api/test_convert.py +21 -21
  16. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/api/test_edit.py +12 -12
  17. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/api/test_search.py +4 -4
  18. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/cli/test_utils.py +7 -7
  19. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/test_models.py +9 -9
  20. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/uv.lock +36 -22
  21. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.agent-style/RULES.md +0 -0
  22. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.agent-style/claude-code.md +0 -0
  23. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/build-release-notes/action.yml +0 -0
  24. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/commitizen-bump/action.yml +0 -0
  25. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
  26. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/e2e/action.yml +0 -0
  27. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/install/action.yml +0 -0
  28. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/lint/action.yml +0 -0
  29. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/actions/test/action.yml +0 -0
  30. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/workflows/ci.yml +0 -0
  31. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/workflows/publish.yml +0 -0
  32. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.github/workflows/release.yml +0 -0
  33. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.gitignore +0 -0
  34. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.pre-commit-config.yaml +0 -0
  35. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.python-version +0 -0
  36. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/.readthedocs.yaml +0 -0
  37. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/AGENTS.md +0 -0
  38. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/CLAUDE.md +0 -0
  39. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/CONTRIBUTING.md +0 -0
  40. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/LICENSE +0 -0
  41. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/Makefile +0 -0
  42. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/README.md +0 -0
  43. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/codecov.yml +0 -0
  44. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docker-compose.yml +0 -0
  45. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docs/commands/convert.md +0 -0
  46. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docs/commands/edit.md +0 -0
  47. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docs/commands/search.md +0 -0
  48. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docs/filtering.md +0 -0
  49. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/docs/index.md +0 -0
  50. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/__init__.py +0 -0
  51. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/_click.py +0 -0
  52. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/api/__init__.py +0 -0
  53. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/api/_utils.py +0 -0
  54. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/cli/__init__.py +0 -0
  55. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/cli/_utils.py +0 -0
  56. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/cli/main.py +0 -0
  57. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/display.py +0 -0
  58. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/logger.py +0 -0
  59. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/query.py +0 -0
  60. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/rekordbox_edit/utils.py +0 -0
  61. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/renovate.json5 +0 -0
  62. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/ruff.toml +0 -0
  63. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/__init__.py +0 -0
  64. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/api/__init__.py +0 -0
  65. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/api/test_utils.py +0 -0
  66. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/cli/__init__.py +0 -0
  67. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/cli/test_convert.py +0 -0
  68. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/cli/test_edit.py +0 -0
  69. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/cli/test_main.py +0 -0
  70. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/cli/test_search.py +0 -0
  71. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/conftest.py +0 -0
  72. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/Dockerfile +0 -0
  73. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/__init__.py +0 -0
  74. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/__snapshots__/test_journey/test_search_full_json_snapshot[macos].json +0 -0
  75. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/__snapshots__/test_journey/test_search_full_json_snapshot[windows].json +0 -0
  76. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/__snapshots__/test_journey.ambr +0 -0
  77. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/conftest.py +0 -0
  78. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/01-flac-44_1k-16b.flac +0 -0
  79. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/02-flac-96k-24b.flac +0 -0
  80. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/03-alac-44_1k-16b.m4a +0 -0
  81. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/04-alac-48k-24b.m4a +0 -0
  82. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/05-aiff-44_1k-16b.aiff +0 -0
  83. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/06-wav-96k-24b.wav +0 -0
  84. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/07-mp3-44_1k-320cbr.mp3 +0 -0
  85. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/08-mp3-44_1k-v0vbr.mp3 +0 -0
  86. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/09-aac-44_1k-256kbps.m4a +0 -0
  87. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/audio/10-/303/274/303/261/303/256c/303/266d/303/251-flac-44_1k-16b.flac" +0 -0
  88. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/macos/master.6.8.6.db +0 -0
  89. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/fixtures/windows/master.6.8.6.db +0 -0
  90. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/e2e/test_journey.py +0 -0
  91. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/test_display.py +0 -0
  92. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/test_logger.py +0 -0
  93. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/test_query.py +0 -0
  94. {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev44}/tests/test_utils.py +0 -0
@@ -10,6 +10,7 @@ on:
10
10
  - ".agent-style"
11
11
  - "logos/**"
12
12
  - "**/*.md"
13
+ - "mkdocs.yml"
13
14
  - ".pre-commit-config.yaml"
14
15
  - ".readthedocs.yaml"
15
16
  - "renovate.json5"
@@ -1,6 +1,11 @@
1
- ## v0.6.0.dev40 (2026-06-11)
1
+ ## v0.6.0.dev44 (2026-06-12)
2
2
 
3
3
 
4
+ - chore(deps): update linters to v0.0.45 (#90)
5
+ - Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
6
+ - refactor: rename CommandArgs types to CommandRequest
7
+ - docs: open external links in a new tab with an icon
8
+ - docs: render signatures and model fields in api reference
4
9
  - docs: add api reference page
5
10
  - docs: add convert command page
6
11
  - docs: add edit command page
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rekordbox-edit
3
- Version: 0.6.0.dev40
3
+ Version: 0.6.0.dev44
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
@@ -5,10 +5,10 @@ Everything the CLI does is available from Python. The public surface is the thre
5
5
  ```python
6
6
  from pyrekordbox import Rekordbox6Database
7
7
  from rekordbox_edit.api import search
8
- from rekordbox_edit.models import SearchArgs
8
+ from rekordbox_edit.models import SearchRequest
9
9
 
10
10
  db = Rekordbox6Database()
11
- response = search(db, SearchArgs(artist=["Daft Punk"], format=["flac"], match_all=True))
11
+ response = search(db, SearchRequest(artist=["Daft Punk"], format=["flac"], match_all=True))
12
12
  for track in response.tracks:
13
13
  print(track.ID, track.Title)
14
14
  ```
@@ -0,0 +1,19 @@
1
+ /* External links (opened in a new tab by the open-in-new-tab plugin) get an
2
+ "open in new" icon. Scoped to content so theme chrome is unaffected. The
3
+ mask + currentColor keeps the icon in sync with the surrounding text color
4
+ across light and dark palettes. */
5
+ .md-typeset a[target="_blank"]::after {
6
+ content: "";
7
+ display: inline-block;
8
+ width: 0.7em;
9
+ height: 0.7em;
10
+ margin-left: 0.2em;
11
+ vertical-align: text-top;
12
+ background-color: currentColor;
13
+ mask-repeat: no-repeat;
14
+ mask-size: contain;
15
+ -webkit-mask-repeat: no-repeat;
16
+ -webkit-mask-size: contain;
17
+ mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"/></svg>');
18
+ -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"/></svg>');
19
+ }
@@ -25,14 +25,19 @@ theme:
25
25
 
26
26
  plugins:
27
27
  - search
28
+ - open-in-new-tab
28
29
  - mkdocstrings:
29
30
  handlers:
30
31
  python:
31
32
  options:
32
- filters: ["!^_"]
33
+ filters: ["!^_", "!^model_config$"]
33
34
  show_root_heading: true
34
35
  show_source: false
35
36
  members_order: source
37
+ show_if_no_docstring: true
38
+ show_signature_annotations: true
39
+ separate_signature: true
40
+ docstring_section_style: table
36
41
 
37
42
  markdown_extensions:
38
43
  - admonition
@@ -44,6 +49,9 @@ markdown_extensions:
44
49
  check_paths: true
45
50
  - pymdownx.superfences
46
51
 
52
+ extra_css:
53
+ - stylesheets/extra.css
54
+
47
55
  nav:
48
56
  - Getting Started: index.md
49
57
  - Filtering: filtering.md
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rekordbox-edit"
7
- version = "0.6.0.dev40"
7
+ version = "0.6.0.dev44"
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"
@@ -38,6 +38,7 @@ docs = [
38
38
  "mkdocstrings[python]>=0.27,<1",
39
39
  "mkdocs-click>=0.8,<1",
40
40
  "markdown-callouts>=0.4,<1",
41
+ "mkdocs-open-in-new-tab>=1.0,<2",
41
42
  ]
42
43
 
43
44
  [tool.pytest.ini_options]
@@ -14,7 +14,7 @@ from sqlalchemy import select
14
14
 
15
15
  from rekordbox_edit.api._utils import _order_tracks_by_op
16
16
  from rekordbox_edit.models import (
17
- ConvertArgs,
17
+ ConvertRequest,
18
18
  ConvertOp,
19
19
  ConvertResponse,
20
20
  ConvertResult,
@@ -209,7 +209,7 @@ def _get_output_path(content, output_format) -> Tuple[str, str, str]:
209
209
  # ── Classifier ────────────────────────────────────────────────────────────
210
210
 
211
211
 
212
- def _classify_convert(content, args: ConvertArgs) -> ConvertOp | SkippedTrack:
212
+ def _classify_convert(content, args: ConvertRequest) -> ConvertOp | SkippedTrack:
213
213
  """Return ConvertOp if this track should be converted, or SkippedTrack with
214
214
  reason if not."""
215
215
  target = get_file_type_for_format(args.format_out)
@@ -239,7 +239,7 @@ def _classify_convert(content, args: ConvertArgs) -> ConvertOp | SkippedTrack:
239
239
 
240
240
  def convert(
241
241
  db: Rekordbox6Database,
242
- args: ConvertArgs,
242
+ args: ConvertRequest,
243
243
  *,
244
244
  dry_run: bool = False,
245
245
  ) -> ConvertResponse:
@@ -6,7 +6,7 @@ from pyrekordbox import Rekordbox6Database
6
6
 
7
7
  from rekordbox_edit.api._utils import _order_tracks_by_op
8
8
  from rekordbox_edit.models import (
9
- EditArgs,
9
+ EditRequest,
10
10
  EditOp,
11
11
  EditResponse,
12
12
  EditResult,
@@ -33,7 +33,7 @@ def _compute_new_value(
33
33
  return replace_value
34
34
 
35
35
 
36
- def _classify_edit(content, args: EditArgs) -> EditOp | SkippedTrack:
36
+ def _classify_edit(content, args: EditRequest) -> EditOp | SkippedTrack:
37
37
  """Return EditOp if this track should be edited, or SkippedTrack with
38
38
  reason if not."""
39
39
  col_name = FIELD_COLUMNS[args.field]
@@ -50,7 +50,7 @@ def _classify_edit(content, args: EditArgs) -> EditOp | SkippedTrack:
50
50
 
51
51
  def edit(
52
52
  db: Rekordbox6Database,
53
- args: EditArgs,
53
+ args: EditRequest,
54
54
  *,
55
55
  dry_run: bool = False,
56
56
  ) -> EditResponse:
@@ -5,13 +5,13 @@ import logging
5
5
  from pyrekordbox import Rekordbox6Database
6
6
 
7
7
  from rekordbox_edit.api._utils import _track_from_content
8
- from rekordbox_edit.models import SearchArgs, SearchResponse
8
+ from rekordbox_edit.models import SearchRequest, SearchResponse
9
9
  from rekordbox_edit.query import get_filtered_content
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
13
 
14
- def search(db: Rekordbox6Database, args: SearchArgs) -> SearchResponse:
14
+ def search(db: Rekordbox6Database, args: SearchRequest) -> SearchResponse:
15
15
  logger.debug("search start")
16
16
  result = get_filtered_content(db, args)
17
17
  tracks = [_track_from_content(c) for c in result.scalars().all()]
@@ -26,7 +26,7 @@ from rekordbox_edit.cli._utils import (
26
26
  )
27
27
  from rekordbox_edit.display import PrintableField, print_track_info
28
28
  from rekordbox_edit.logger import get_debug_file_path, set_level
29
- from rekordbox_edit.models import ConvertArgs
29
+ from rekordbox_edit.models import ConvertRequest
30
30
  from rekordbox_edit.utils import UserQuit, confirm
31
31
 
32
32
  logger = logging.getLogger(__name__)
@@ -56,7 +56,7 @@ def convert_command(db, **kwargs):
56
56
  dry_run = kwargs.pop("dry_run", False)
57
57
  yes = kwargs.pop("yes", False)
58
58
  interactive = kwargs.pop("interactive", False)
59
- args = _build_args(ConvertArgs, kwargs)
59
+ args = _build_args(ConvertRequest, kwargs)
60
60
  set_level(print_opt)
61
61
  piped_stdin = _handle_stdin(args)
62
62
  _validate_scripting_preconditions(
@@ -26,7 +26,7 @@ from rekordbox_edit.cli._utils import (
26
26
  )
27
27
  from rekordbox_edit.display import PrintableField, print_track_info
28
28
  from rekordbox_edit.logger import get_debug_file_path, set_level
29
- from rekordbox_edit.models import EditArgs
29
+ from rekordbox_edit.models import EditRequest
30
30
  from rekordbox_edit.utils import UserQuit, confirm
31
31
 
32
32
  logger = logging.getLogger(__name__)
@@ -52,11 +52,11 @@ logger = logging.getLogger(__name__)
52
52
  def edit_command(db, **kwargs):
53
53
  """Edit a metadata field on tracks in the RekordBox database."""
54
54
  print_opt = kwargs.pop("print_opt", None)
55
- # CLI-only flags pulled out of kwargs before constructing EditArgs.
55
+ # CLI-only flags pulled out of kwargs before constructing EditRequest.
56
56
  dry_run = kwargs.pop("dry_run", False)
57
57
  yes = kwargs.pop("yes", False)
58
58
  interactive = kwargs.pop("interactive", False)
59
- args = _build_args(EditArgs, kwargs)
59
+ args = _build_args(EditRequest, kwargs)
60
60
  set_level(print_opt)
61
61
  piped_stdin = _handle_stdin(args)
62
62
 
@@ -21,7 +21,7 @@ from rekordbox_edit.cli._utils import (
21
21
  )
22
22
  from rekordbox_edit.display import print_track_info
23
23
  from rekordbox_edit.logger import get_debug_file_path, set_level
24
- from rekordbox_edit.models import SearchArgs
24
+ from rekordbox_edit.models import SearchRequest
25
25
 
26
26
  logger = logging.getLogger(__name__)
27
27
 
@@ -34,7 +34,7 @@ logger = logging.getLogger(__name__)
34
34
  def search_command(db, **kwargs):
35
35
  """Search the RekordBox database."""
36
36
  print_opt = kwargs.pop("print_opt", None)
37
- args = _build_args(SearchArgs, kwargs)
37
+ args = _build_args(SearchRequest, kwargs)
38
38
  set_level(print_opt)
39
39
  _handle_stdin(args)
40
40
 
@@ -4,7 +4,7 @@ Three layers:
4
4
 
5
5
  - **Filter base** (`FilterArgs`) declares track-selection criteria shared by all
6
6
  commands.
7
- - **Command args** (`SearchArgs`, `EditArgs`, `ConvertArgs`) extend `FilterArgs`
7
+ - **Command args** (`SearchRequest`, `EditRequest`, `ConvertRequest`) extend `FilterArgs`
8
8
  with command-specific fields.
9
9
  - **Domain types** (`Track`, `EditOp`, `ConvertOp`, `SkippedTrack`) and
10
10
  **response envelopes** (`SearchResponse`, `EditResponse`, `ConvertResponse`)
@@ -25,7 +25,6 @@ from typing import Literal, TypeAlias
25
25
 
26
26
  from pydantic import BaseModel, ConfigDict, Field, model_validator
27
27
 
28
-
29
28
  # ── Filter base ───────────────────────────────────────────────────────────
30
29
 
31
30
 
@@ -61,17 +60,12 @@ class FilterArgs(BaseModel):
61
60
  # ── Command args ──────────────────────────────────────────────────────────
62
61
 
63
62
 
64
- class SearchArgs(FilterArgs):
65
- """Inputs for search().
66
-
67
- Currently equivalent to FilterArgs. Kept as a distinct type so the public
68
- signature reads `search(db, SearchArgs)` and so search-specific fields
69
- can be added later without changing the call surface.
70
- """
63
+ class SearchRequest(FilterArgs):
64
+ """Inputs for search(): the shared track filters, with no search-specific fields."""
71
65
 
72
66
 
73
- class EditArgs(FilterArgs):
74
- """Inputs for edit()."""
67
+ class EditRequest(FilterArgs):
68
+ """Inputs for edit(): the shared track filters plus the field to change and its new value."""
75
69
 
76
70
  field: str
77
71
  replace_value: str
@@ -79,15 +73,14 @@ class EditArgs(FilterArgs):
79
73
  multi: bool = False
80
74
 
81
75
 
82
- class ConvertArgs(FilterArgs):
83
- """Inputs for convert().
84
-
85
- `delete` is tri-state: None defers to a per-format default in convert()
86
- (True for lossless, False for MP3).
87
- """
76
+ class ConvertRequest(FilterArgs):
77
+ """Inputs for convert(): the shared track filters plus output format and original-file handling."""
88
78
 
89
79
  format_out: str = "aiff"
90
80
  delete: bool | None = None
81
+ """Whether to delete the original files after conversion: True deletes them,
82
+ False keeps them, and None applies the per-format default (delete for
83
+ lossless output, keep for MP3)."""
91
84
  overwrite: bool = False
92
85
 
93
86
 
@@ -95,9 +88,10 @@ class ConvertArgs(FilterArgs):
95
88
 
96
89
 
97
90
  class Track(BaseModel):
98
- """A Rekordbox track. Field names mirror DjmdContent column names.
99
-
100
- `extra="allow"` lets undeclared columns ride along as untyped attributes.
91
+ """A Rekordbox track.
92
+ Field names mirror the [DjmdContent](https://pyrekordbox.readthedocs.io/en/latest/formats/db6.html#djmdcontent) table's column names,
93
+ except where additional derived fields have been added for convenience. All database columns are silently included in each object
94
+ for convenience, but what's defined here is just what RBE cares about.
101
95
  """
102
96
 
103
97
  model_config = ConfigDict(extra="allow")
@@ -15,7 +15,7 @@ from rekordbox_edit.api.convert import (
15
15
  convert,
16
16
  )
17
17
  from rekordbox_edit.models import (
18
- ConvertArgs,
18
+ ConvertRequest,
19
19
  ConvertOp,
20
20
  ConvertResponse,
21
21
  SkippedTrack,
@@ -31,7 +31,7 @@ class TestClassifyConvert:
31
31
  )
32
32
  content = make_djmd_content_item(ID="1", FileType=1) # already AIFF
33
33
 
34
- result = _classify_convert(content, ConvertArgs(format_out="aiff"))
34
+ result = _classify_convert(content, ConvertRequest(format_out="aiff"))
35
35
 
36
36
  assert isinstance(result, SkippedTrack)
37
37
  assert result.reason == "already_target_format"
@@ -43,7 +43,7 @@ class TestClassifyConvert:
43
43
  )
44
44
  content = make_djmd_content_item(ID="2", FileType=5) # MP3
45
45
 
46
- result = _classify_convert(content, ConvertArgs(format_out="aiff"))
46
+ result = _classify_convert(content, ConvertRequest(format_out="aiff"))
47
47
 
48
48
  assert isinstance(result, SkippedTrack)
49
49
  assert result.reason == "already_target_format"
@@ -61,7 +61,7 @@ class TestClassifyConvert:
61
61
  content = make_djmd_content_item(ID="3", FileType=11) # WAV
62
62
 
63
63
  result = _classify_convert(
64
- content, ConvertArgs(format_out="aiff", overwrite=False)
64
+ content, ConvertRequest(format_out="aiff", overwrite=False)
65
65
  )
66
66
 
67
67
  assert isinstance(result, SkippedTrack)
@@ -80,7 +80,7 @@ class TestClassifyConvert:
80
80
  content = make_djmd_content_item(ID="3", FileType=11, FolderPath="/in.wav")
81
81
 
82
82
  result = _classify_convert(
83
- content, ConvertArgs(format_out="aiff", overwrite=True)
83
+ content, ConvertRequest(format_out="aiff", overwrite=True)
84
84
  )
85
85
 
86
86
  assert isinstance(result, ConvertOp)
@@ -101,7 +101,7 @@ class TestClassifyConvert:
101
101
  ID="4", FileType=11, FolderPath="/music/song.wav"
102
102
  )
103
103
 
104
- result = _classify_convert(content, ConvertArgs(format_out="aiff"))
104
+ result = _classify_convert(content, ConvertRequest(format_out="aiff"))
105
105
 
106
106
  assert isinstance(result, ConvertOp)
107
107
  assert result.id == "4"
@@ -143,7 +143,7 @@ class TestConvertDryRun:
143
143
  content = make_djmd_content_item(ID="1", FileType=11, FolderPath="/in.wav")
144
144
  _seed_filter(mock_gfc, content)
145
145
 
146
- response = convert(mock_db, ConvertArgs(format_out="aiff"), dry_run=True)
146
+ response = convert(mock_db, ConvertRequest(format_out="aiff"), dry_run=True)
147
147
 
148
148
  assert isinstance(response, ConvertResponse)
149
149
  assert response.result.format_out == "aiff"
@@ -169,7 +169,7 @@ class TestConvertDryRun:
169
169
  content = make_djmd_content_item(ID="1", FileType=1) # already AIFF
170
170
  _seed_filter(mock_gfc, content)
171
171
 
172
- response = convert(mock_db, ConvertArgs(format_out="aiff"), dry_run=True)
172
+ response = convert(mock_db, ConvertRequest(format_out="aiff"), dry_run=True)
173
173
 
174
174
  assert response.result.converted == []
175
175
  assert response.tracks == []
@@ -181,7 +181,7 @@ class TestConvertRealRun:
181
181
  @patch("rekordbox_edit.utils.ffmpeg_in_path", return_value=False)
182
182
  def test_no_ffmpeg_raises_immediately(self, _, mock_db):
183
183
  with pytest.raises(RuntimeError, match="FFmpeg"):
184
- convert(mock_db, ConvertArgs(format_out="aiff"))
184
+ convert(mock_db, ConvertRequest(format_out="aiff"))
185
185
 
186
186
  @patch("rekordbox_edit.utils.ffmpeg_in_path", return_value=True)
187
187
  @patch("rekordbox_edit.api.convert.get_filtered_content")
@@ -190,7 +190,7 @@ class TestConvertRealRun:
190
190
  ):
191
191
  _seed_filter(mock_gfc)
192
192
 
193
- response = convert(mock_db, ConvertArgs(format_out="aiff"))
193
+ response = convert(mock_db, ConvertRequest(format_out="aiff"))
194
194
 
195
195
  assert response.result.converted == []
196
196
  assert response.tracks == []
@@ -224,7 +224,7 @@ class TestConvertRealRun:
224
224
  _seed_db(mock_db, content) # post-commit re-query returns the same row
225
225
 
226
226
  response = convert(
227
- mock_db, ConvertArgs(format_out="aiff", delete=False, overwrite=True)
227
+ mock_db, ConvertRequest(format_out="aiff", delete=False, overwrite=True)
228
228
  )
229
229
 
230
230
  mock_lossless.assert_called_once_with("/in.wav", "/out.aif", OutputFormats.AIFF)
@@ -264,7 +264,7 @@ class TestConvertRealRun:
264
264
  _seed_db(mock_db, content)
265
265
 
266
266
  response = convert(
267
- mock_db, ConvertArgs(format_out="aiff", delete=True, overwrite=True)
267
+ mock_db, ConvertRequest(format_out="aiff", delete=True, overwrite=True)
268
268
  )
269
269
 
270
270
  mock_remove.assert_called_once_with("/in.wav")
@@ -297,7 +297,7 @@ class TestConvertRealRun:
297
297
  _seed_filter(mock_gfc, content)
298
298
 
299
299
  with pytest.raises(RuntimeError, match="Conversion failed"):
300
- convert(mock_db, ConvertArgs(format_out="aiff", overwrite=True))
300
+ convert(mock_db, ConvertRequest(format_out="aiff", overwrite=True))
301
301
 
302
302
  mock_rollback.assert_called_once()
303
303
 
@@ -336,7 +336,7 @@ class TestConvertRealRun:
336
336
 
337
337
  with pytest.raises(KeyboardInterrupt):
338
338
  convert(
339
- mock_db, ConvertArgs(format_out="aiff", delete=True, overwrite=True)
339
+ mock_db, ConvertRequest(format_out="aiff", delete=True, overwrite=True)
340
340
  )
341
341
 
342
342
  mock_db.session.commit.assert_called_once()
@@ -380,7 +380,7 @@ class TestConvertRealRun:
380
380
  _seed_db(mock_db, contents[2], contents[0], contents[1])
381
381
 
382
382
  response = convert(
383
- mock_db, ConvertArgs(format_out="aiff", delete=False, overwrite=True)
383
+ mock_db, ConvertRequest(format_out="aiff", delete=False, overwrite=True)
384
384
  )
385
385
 
386
386
  assert [op.id for op in response.result.converted] == ["A", "B", "C"]
@@ -415,7 +415,7 @@ class TestConvertRealRun:
415
415
  mock_db.session.execute.side_effect = RuntimeError("post-commit query failed")
416
416
 
417
417
  response = convert(
418
- mock_db, ConvertArgs(format_out="aiff", delete=False, overwrite=True)
418
+ mock_db, ConvertRequest(format_out="aiff", delete=False, overwrite=True)
419
419
  )
420
420
 
421
421
  # Commit happened
@@ -450,7 +450,7 @@ class TestConvertRealRun:
450
450
  _seed_filter(mock_gfc, content)
451
451
 
452
452
  # Default overwrite=False with output path existing -> classifier should skip.
453
- response = convert(mock_db, ConvertArgs(format_out="aiff"))
453
+ response = convert(mock_db, ConvertRequest(format_out="aiff"))
454
454
 
455
455
  assert response.result.converted == []
456
456
  assert len(response.result.skipped) == 1
@@ -482,7 +482,7 @@ class TestConvertRealRun:
482
482
  _seed_filter(mock_gfc, content)
483
483
 
484
484
  with pytest.raises(RuntimeError, match="Source not found"):
485
- convert(mock_db, ConvertArgs(format_out="aiff", overwrite=True))
485
+ convert(mock_db, ConvertRequest(format_out="aiff", overwrite=True))
486
486
  mock_rollback.assert_called_once()
487
487
 
488
488
  @patch("rekordbox_edit.api.convert._rollback_and_cleanup")
@@ -517,7 +517,7 @@ class TestConvertRealRun:
517
517
  _seed_filter(mock_gfc, content)
518
518
 
519
519
  with pytest.raises(RuntimeError, match="DB error"):
520
- convert(mock_db, ConvertArgs(format_out="aiff", overwrite=True))
520
+ convert(mock_db, ConvertRequest(format_out="aiff", overwrite=True))
521
521
  mock_rollback.assert_called_once()
522
522
 
523
523
  @patch("rekordbox_edit.api.convert._update_database_record")
@@ -547,7 +547,7 @@ class TestConvertRealRun:
547
547
  _seed_filter(mock_gfc, content)
548
548
  _seed_db(mock_db, content)
549
549
 
550
- convert(mock_db, ConvertArgs(format_out="mp3", overwrite=True))
550
+ convert(mock_db, ConvertRequest(format_out="mp3", overwrite=True))
551
551
 
552
552
  mock_mp3.assert_called_once_with("/in.wav", "/out.mp3")
553
553
 
@@ -581,7 +581,7 @@ class TestConvertRealRun:
581
581
  _seed_db(mock_db, content)
582
582
 
583
583
  response = convert(
584
- mock_db, ConvertArgs(format_out="aiff", delete=False, overwrite=True)
584
+ mock_db, ConvertRequest(format_out="aiff", delete=False, overwrite=True)
585
585
  )
586
586
 
587
587
  mock_remove.assert_not_called()
@@ -3,7 +3,7 @@ from unittest.mock import patch
3
3
 
4
4
  from rekordbox_edit.api.edit import _classify_edit, edit
5
5
  from rekordbox_edit.models import (
6
- EditArgs,
6
+ EditRequest,
7
7
  EditOp,
8
8
  EditResponse,
9
9
  SkippedTrack,
@@ -13,7 +13,7 @@ from rekordbox_edit.models import (
13
13
  class TestClassifyEdit:
14
14
  def test_returns_edit_op_when_value_would_change(self, make_djmd_content_item):
15
15
  content = make_djmd_content_item(ID="1", Title="Old")
16
- args = EditArgs(field="Title", replace_value="New")
16
+ args = EditRequest(field="Title", replace_value="New")
17
17
 
18
18
  result = _classify_edit(content, args)
19
19
 
@@ -23,7 +23,7 @@ class TestClassifyEdit:
23
23
 
24
24
  def test_returns_skipped_when_value_equals_current(self, make_djmd_content_item):
25
25
  content = make_djmd_content_item(ID="1", Title="Same")
26
- args = EditArgs(field="Title", replace_value="Same")
26
+ args = EditRequest(field="Title", replace_value="Same")
27
27
 
28
28
  result = _classify_edit(content, args)
29
29
 
@@ -33,7 +33,7 @@ class TestClassifyEdit:
33
33
 
34
34
  def test_returns_skipped_when_current_is_none(self, make_djmd_content_item):
35
35
  content = make_djmd_content_item(ID="1", Title=None)
36
- args = EditArgs(field="Title", replace_value="New")
36
+ args = EditRequest(field="Title", replace_value="New")
37
37
 
38
38
  result = _classify_edit(content, args)
39
39
 
@@ -42,7 +42,7 @@ class TestClassifyEdit:
42
42
 
43
43
  def test_match_pattern_applied(self, make_djmd_content_item):
44
44
  content = make_djmd_content_item(ID="1", Title="Hello World")
45
- args = EditArgs(field="Title", replace_value="Earth", match_pattern="World")
45
+ args = EditRequest(field="Title", replace_value="Earth", match_pattern="World")
46
46
 
47
47
  result = _classify_edit(content, args)
48
48
 
@@ -59,7 +59,7 @@ class TestEditDryRun:
59
59
  mock_gfc.return_value.scalars.return_value.all.return_value = [content]
60
60
 
61
61
  response = edit(
62
- mock_db, EditArgs(field="Title", replace_value="New"), dry_run=True
62
+ mock_db, EditRequest(field="Title", replace_value="New"), dry_run=True
63
63
  )
64
64
 
65
65
  assert isinstance(response, EditResponse)
@@ -74,7 +74,7 @@ class TestEditDryRun:
74
74
  mock_gfc.return_value.scalars.return_value.all.return_value = [content]
75
75
 
76
76
  response = edit(
77
- mock_db, EditArgs(field="Title", replace_value="Same"), dry_run=True
77
+ mock_db, EditRequest(field="Title", replace_value="Same"), dry_run=True
78
78
  )
79
79
 
80
80
  assert response.result.edits == []
@@ -97,7 +97,7 @@ class TestEditDryRun:
97
97
  with pytest.raises(ValueError, match="multi"):
98
98
  edit(
99
99
  mock_db,
100
- EditArgs(field="Title", replace_value="New", multi=False),
100
+ EditRequest(field="Title", replace_value="New", multi=False),
101
101
  dry_run=True,
102
102
  )
103
103
 
@@ -110,7 +110,7 @@ class TestEditRealRun:
110
110
  content = make_djmd_content_item(ID="1", Title="Old")
111
111
  mock_gfc.return_value.scalars.return_value.all.return_value = [content]
112
112
 
113
- response = edit(mock_db, EditArgs(field="Title", replace_value="New"))
113
+ response = edit(mock_db, EditRequest(field="Title", replace_value="New"))
114
114
 
115
115
  assert content.Title == "New"
116
116
  mock_db.session.commit.assert_called_once()
@@ -123,7 +123,7 @@ class TestEditRealRun:
123
123
  ):
124
124
  mock_gfc.return_value.scalars.return_value.all.return_value = []
125
125
 
126
- response = edit(mock_db, EditArgs(field="Title", replace_value="New"))
126
+ response = edit(mock_db, EditRequest(field="Title", replace_value="New"))
127
127
 
128
128
  assert response.result.edits == []
129
129
  assert response.tracks == []
@@ -140,7 +140,7 @@ class TestEditRealRun:
140
140
  mock_gfc.return_value.scalars.return_value.all.return_value = contents
141
141
 
142
142
  response = edit(
143
- mock_db, EditArgs(field="Title", replace_value="New", multi=True)
143
+ mock_db, EditRequest(field="Title", replace_value="New", multi=True)
144
144
  )
145
145
 
146
146
  assert len(response.result.edits) == 2
@@ -158,7 +158,7 @@ class TestEditRealRun:
158
158
  ]
159
159
  mock_gfc.return_value.scalars.return_value.all.return_value = contents
160
160
 
161
- response = edit(mock_db, EditArgs(field="Title", replace_value="x", multi=True))
161
+ response = edit(mock_db, EditRequest(field="Title", replace_value="x", multi=True))
162
162
 
163
163
  # The order of result.edits follows the classifier (i.e. the order
164
164
  # of contents). tracks aligns to edits.
@@ -1,7 +1,7 @@
1
1
  from unittest.mock import Mock, patch
2
2
 
3
3
  from rekordbox_edit.api.search import search
4
- from rekordbox_edit.models import SearchArgs, SearchResponse, Track
4
+ from rekordbox_edit.models import SearchRequest, SearchResponse, Track
5
5
 
6
6
 
7
7
  class TestSearch:
@@ -12,7 +12,7 @@ class TestSearch:
12
12
  mock_result.scalars.return_value.all.return_value = [content]
13
13
  mock_gfc.return_value = mock_result
14
14
 
15
- response = search(mock_db, SearchArgs())
15
+ response = search(mock_db, SearchRequest())
16
16
 
17
17
  assert isinstance(response, SearchResponse)
18
18
  assert len(response.tracks) == 1
@@ -25,7 +25,7 @@ class TestSearch:
25
25
  mock_result.scalars.return_value.all.return_value = []
26
26
  mock_gfc.return_value = mock_result
27
27
 
28
- response = search(mock_db, SearchArgs())
28
+ response = search(mock_db, SearchRequest())
29
29
 
30
30
  assert response.tracks == []
31
31
 
@@ -35,7 +35,7 @@ class TestSearch:
35
35
  mock_result.scalars.return_value.all.return_value = []
36
36
  mock_gfc.return_value = mock_result
37
37
 
38
- args = SearchArgs(artist=["Daft Punk"], match_all=True)
38
+ args = SearchRequest(artist=["Daft Punk"], match_all=True)
39
39
  search(mock_db, args)
40
40
 
41
41
  mock_gfc.assert_called_once_with(mock_db, args)
@@ -12,15 +12,15 @@ from rekordbox_edit.cli._utils import (
12
12
  _validate_scripting_preconditions,
13
13
  )
14
14
  from rekordbox_edit.models import (
15
- ConvertArgs,
15
+ ConvertRequest,
16
16
  ConvertOp,
17
17
  ConvertResponse,
18
18
  ConvertResult,
19
- EditArgs,
19
+ EditRequest,
20
20
  EditOp,
21
21
  EditResponse,
22
22
  EditResult,
23
- SearchArgs,
23
+ SearchRequest,
24
24
  SearchResponse,
25
25
  Track,
26
26
  )
@@ -88,7 +88,7 @@ class TestValidateScriptingPreconditions:
88
88
 
89
89
  class TestNarrowToTrackIds:
90
90
  def test_clears_other_filter_criteria(self):
91
- args = ConvertArgs(
91
+ args = ConvertRequest(
92
92
  artist=["X"],
93
93
  format=["flac"],
94
94
  format_out="aiff",
@@ -107,7 +107,7 @@ class TestNarrowToTrackIds:
107
107
  assert narrowed.delete is True
108
108
 
109
109
  def test_works_for_edit_args(self):
110
- args = EditArgs(
110
+ args = EditRequest(
111
111
  artist=["X"],
112
112
  field="Title",
113
113
  replace_value="N",
@@ -126,7 +126,7 @@ class TestNarrowToTrackIds:
126
126
  assert narrowed.multi is True
127
127
 
128
128
  def test_clears_first(self):
129
- args = SearchArgs(first=2)
129
+ args = SearchRequest(first=2)
130
130
 
131
131
  narrowed = _narrow_to_track_ids(args, ["a", "b", "c"])
132
132
 
@@ -134,7 +134,7 @@ class TestNarrowToTrackIds:
134
134
  assert narrowed.track_ids == ["a", "b", "c"]
135
135
 
136
136
  def test_clears_last(self):
137
- args = SearchArgs(last=2)
137
+ args = SearchRequest(last=2)
138
138
 
139
139
  narrowed = _narrow_to_track_ids(args, ["a"])
140
140
 
@@ -4,16 +4,16 @@ import pytest
4
4
  from pydantic import ValidationError
5
5
 
6
6
  from rekordbox_edit.models import (
7
- ConvertArgs,
7
+ ConvertRequest,
8
8
  ConvertOp,
9
9
  ConvertResponse,
10
10
  ConvertResult,
11
- EditArgs,
11
+ EditRequest,
12
12
  EditOp,
13
13
  EditResponse,
14
14
  EditResult,
15
15
  FilterArgs,
16
- SearchArgs,
16
+ SearchRequest,
17
17
  SearchResponse,
18
18
  SkippedTrack,
19
19
  Track,
@@ -74,16 +74,16 @@ class TestFilterArgs:
74
74
  FilterArgs(first=2, last=3)
75
75
 
76
76
 
77
- class TestSearchArgs:
77
+ class TestSearchRequest:
78
78
  def test_is_filter_args(self):
79
- args = SearchArgs(artist=["X"])
79
+ args = SearchRequest(artist=["X"])
80
80
  assert isinstance(args, FilterArgs)
81
81
  assert args.artist == ["X"]
82
82
 
83
83
 
84
- class TestEditArgs:
84
+ class TestEditRequest:
85
85
  def test_has_filter_and_edit_fields(self):
86
- args = EditArgs(field="Title", replace_value="New", artist=["X"])
86
+ args = EditRequest(field="Title", replace_value="New", artist=["X"])
87
87
  assert isinstance(args, FilterArgs)
88
88
  assert args.field == "Title"
89
89
  assert args.replace_value == "New"
@@ -91,9 +91,9 @@ class TestEditArgs:
91
91
  assert args.multi is False
92
92
 
93
93
 
94
- class TestConvertArgs:
94
+ class TestConvertRequest:
95
95
  def test_has_filter_and_convert_fields(self):
96
- args = ConvertArgs(format_out="mp3", overwrite=True, artist=["X"])
96
+ args = ConvertRequest(format_out="mp3", overwrite=True, artist=["X"])
97
97
  assert isinstance(args, FilterArgs)
98
98
  assert args.format_out == "mp3"
99
99
  assert args.overwrite is True
@@ -713,6 +713,18 @@ wheels = [
713
713
  { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
714
714
  ]
715
715
 
716
+ [[package]]
717
+ name = "mkdocs-open-in-new-tab"
718
+ version = "1.0.8"
719
+ source = { registry = "https://pypi.org/simple" }
720
+ dependencies = [
721
+ { name = "mkdocs" },
722
+ ]
723
+ sdist = { url = "https://files.pythonhosted.org/packages/0a/0e/f72a506a21bdb27b807124e00c688226848a388d1fd3980b80ae3cc27203/mkdocs_open_in_new_tab-1.0.8.tar.gz", hash = "sha256:3e0dad08cc9938b0b13097be8e0aa435919de1eeb2d1a648e66b5dee8d57e048", size = 5791, upload-time = "2024-11-18T13:15:13.977Z" }
724
+ wheels = [
725
+ { url = "https://files.pythonhosted.org/packages/21/94/44f3c868495481c868d08eea065c82803f1affd8553d3383b782f497613c/mkdocs_open_in_new_tab-1.0.8-py3-none-any.whl", hash = "sha256:051d767a4467b12d89827e1fea0ec660b05b027c726317fe4fceee5456e36ad2", size = 7717, upload-time = "2024-11-18T13:15:12.286Z" },
726
+ ]
727
+
716
728
  [[package]]
717
729
  name = "mkdocstrings"
718
730
  version = "0.30.1"
@@ -1256,7 +1268,7 @@ wheels = [
1256
1268
 
1257
1269
  [[package]]
1258
1270
  name = "rekordbox-edit"
1259
- version = "0.6.0.dev40"
1271
+ version = "0.6.0.dev44"
1260
1272
  source = { editable = "." }
1261
1273
  dependencies = [
1262
1274
  { name = "click" },
@@ -1284,6 +1296,7 @@ docs = [
1284
1296
  { name = "markdown-callouts" },
1285
1297
  { name = "mkdocs-click" },
1286
1298
  { name = "mkdocs-material" },
1299
+ { name = "mkdocs-open-in-new-tab" },
1287
1300
  { name = "mkdocstrings", extra = ["python"] },
1288
1301
  ]
1289
1302
 
@@ -1314,6 +1327,7 @@ docs = [
1314
1327
  { name = "markdown-callouts", specifier = ">=0.4,<1" },
1315
1328
  { name = "mkdocs-click", specifier = ">=0.8,<1" },
1316
1329
  { name = "mkdocs-material", specifier = ">=9.5,<10" },
1330
+ { name = "mkdocs-open-in-new-tab", specifier = ">=1.0,<2" },
1317
1331
  { name = "mkdocstrings", extras = ["python"], specifier = ">=0.27,<1" },
1318
1332
  ]
1319
1333
 
@@ -1606,27 +1620,27 @@ wheels = [
1606
1620
 
1607
1621
  [[package]]
1608
1622
  name = "ty"
1609
- version = "0.0.44"
1610
- source = { registry = "https://pypi.org/simple" }
1611
- sdist = { url = "https://files.pythonhosted.org/packages/13/f4/fbb120226e4f239652525a664bad976a23fea58c646d1323f2296fee8a61/ty-0.0.44.tar.gz", hash = "sha256:5886229830ab77022842a1c55d2ef57405621a91fc465969fa6d538661898173", size = 5803665, upload-time = "2026-06-05T03:33:48.612Z" }
1612
- wheels = [
1613
- { url = "https://files.pythonhosted.org/packages/e8/c6/b5b8c4762efb4d85401652658786506867553ecfc2beac3bcf361a15937f/ty-0.0.44-py3-none-linux_armv6l.whl", hash = "sha256:272d31e7ad49b1dc5e8465a9fe700354e14c755b40d9c75f08f031d786903df3", size = 11607267, upload-time = "2026-06-05T03:33:27.154Z" },
1614
- { url = "https://files.pythonhosted.org/packages/1c/5c/f4b405570737f44ab0fc4214117fe43353f8f0825a1823d9e99e9c8e57be/ty-0.0.44-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b92c4ddd7a3daf2049715edec9dc70cf6fd31a5a318ee647258f90dd75495eed", size = 11382826, upload-time = "2026-06-05T03:33:54.374Z" },
1615
- { url = "https://files.pythonhosted.org/packages/9d/aa/fb9835aa492b148d7754cb4c3db07f31a7e2e09f0d8e0e8e297f01125dd2/ty-0.0.44-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4d42cfd84a690f6654b2a4f0515027c21b692cf2512d32e6433f754893a95609", size = 10809741, upload-time = "2026-06-05T03:33:33.22Z" },
1616
- { url = "https://files.pythonhosted.org/packages/47/f5/0b20ba6b66837a5a37bab7f74ac0732c66e766b5f0b2d55b30816b15f348/ty-0.0.44-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc47ae87e4cb7db2a9166bb23b78a905c3626e523296ec5bccf36b5e89bda6b", size = 11318153, upload-time = "2026-06-05T03:34:09.403Z" },
1617
- { url = "https://files.pythonhosted.org/packages/ca/bb/b82ea730774a4f950f06d355fbc120d51eac7da23b57fc79ef6ff7c79cbb/ty-0.0.44-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46d867e80f16f421ac72c9a85240dbf050d62d9b3fbd10a8b5b082fb21679e0b", size = 11403108, upload-time = "2026-06-05T03:33:57.745Z" },
1618
- { url = "https://files.pythonhosted.org/packages/8b/41/e2c83856165291049c702eda4e2ef3d3ebd875e8a0a77b8cc4ef3156aa1c/ty-0.0.44-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:411f5de0f96a4e4e5cccc3e0d55954c41f6a99ee6ca1fe5a7226cbc68406e053", size = 11944815, upload-time = "2026-06-05T03:34:15.793Z" },
1619
- { url = "https://files.pythonhosted.org/packages/66/95/1fa6a101eb9d5bec042b87e5ca9c8fc349b75961beca6306f95af5cd5539/ty-0.0.44-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b15f01ecb4e2b46c05a1769293f9d32c3d4a1e4e7dfccf37c604d705dc3e3f4", size = 12476121, upload-time = "2026-06-05T03:33:51.529Z" },
1620
- { url = "https://files.pythonhosted.org/packages/72/6a/da4b45b1229d39207c6140681c2aaf4f5691bcb1dc830b84450ca25c8f57/ty-0.0.44-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edd32b7467af509c99c0244c2226a4e4c03400699003ec33373282ab931654d9", size = 12091340, upload-time = "2026-06-05T03:33:36.289Z" },
1621
- { url = "https://files.pythonhosted.org/packages/16/c7/e1c9260ea5188195962ff1214ace418b5d69187e8fa7c0a1ec4994b8071b/ty-0.0.44-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:503a585f4007387c3afc58bae23a7ca1b9f236cbdb1a881dc36110655ceb1937", size = 11986201, upload-time = "2026-06-05T03:34:00.624Z" },
1622
- { url = "https://files.pythonhosted.org/packages/92/f9/312bb112da9b1a7da295bb0426be85e72ad48da4e4266c36d77256b4058d/ty-0.0.44-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d28bcfa83243d77c2316944e8cf197f73597bf17d1ddc047d0b10a762531252", size = 12168475, upload-time = "2026-06-05T03:33:30.386Z" },
1623
- { url = "https://files.pythonhosted.org/packages/02/de/64978d603f6c3e5dd7cb97eca2214567d8ad0c85fa4a7435b7852ae4b779/ty-0.0.44-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:56fd2dd0192def189715b25f5338f6222fb827884dc34111e50aa1c4e061cee5", size = 11292937, upload-time = "2026-06-05T03:34:06.448Z" },
1624
- { url = "https://files.pythonhosted.org/packages/64/63/a625d8a3c71dcaa01988d330f849c465fe72ead4b0bbab44fe4bd6e672b5/ty-0.0.44-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7f8d990489032de1984e73c159f3e760d754cf83a602b874827d943821f63595", size = 11421560, upload-time = "2026-06-05T03:33:23.995Z" },
1625
- { url = "https://files.pythonhosted.org/packages/99/96/61aeba0e629b0c91bd316ff94d00e38817ec493ae4316f39508988daa287/ty-0.0.44-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f61ffe72996a755432922fe90b28db593f572eb5cbf48e3ef4e67b282533d1b0", size = 11580282, upload-time = "2026-06-05T03:34:03.308Z" },
1626
- { url = "https://files.pythonhosted.org/packages/fa/f7/256e1538ce21cab67b381201444c42454de69d310059c4929d92a0ee9c48/ty-0.0.44-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2b237a143bac4f30cec9257d45f01e72da97030a80a09a2b69cfef065f09c37f", size = 12085723, upload-time = "2026-06-05T03:33:45.953Z" },
1627
- { url = "https://files.pythonhosted.org/packages/d3/76/ec3957c10872643a98db7a7895101ad89c5b7cba4bc6c4aebbbfc91756cc/ty-0.0.44-py3-none-win32.whl", hash = "sha256:6a24586c65419223ac5bab4822d49ab493a5d19ea2a897514284c232b9d6166a", size = 10892978, upload-time = "2026-06-05T03:34:12.603Z" },
1628
- { url = "https://files.pythonhosted.org/packages/a5/7d/ba24050432196e7d7f03945e5c379951593c48e04e5c5d5275cfc4624791/ty-0.0.44-py3-none-win_amd64.whl", hash = "sha256:8cccb27e348c89a9733fbad1b2efadfbad79b107c7e52adb52dfd8a70156a38d", size = 11987058, upload-time = "2026-06-05T03:33:42.692Z" },
1629
- { url = "https://files.pythonhosted.org/packages/71/34/16ec3f1fec75292d9c56a8b5fef037ceaba85a5c30562206c1a245a00a67/ty-0.0.44-py3-none-win_arm64.whl", hash = "sha256:58049504e7a12bf1957f24a5384a332c94d5590127083a80db5e5a1bed34190b", size = 11329961, upload-time = "2026-06-05T03:33:39.427Z" },
1623
+ version = "0.0.45"
1624
+ source = { registry = "https://pypi.org/simple" }
1625
+ sdist = { url = "https://files.pythonhosted.org/packages/35/7e/f568c8731ee814c1b529e0b66c5f24f03bc8ee6b5c47ed35746a1aec1d6d/ty-0.0.45.tar.gz", hash = "sha256:ce66b6774052ab5eb5a00b75e5ee2ec0b8f854a3c4731367362de90ba6f6d0ff", size = 5830486, upload-time = "2026-06-08T21:33:59.23Z" }
1626
+ wheels = [
1627
+ { url = "https://files.pythonhosted.org/packages/b4/5b/127c6792771e588f84797f7bc368ffe0be34b44a247575d18b9e7da957ab/ty-0.0.45-py3-none-linux_armv6l.whl", hash = "sha256:2854ed2cbb931add10a8672abfffec762ab57d2bcb44f17e11a3e1667030b949", size = 11730185, upload-time = "2026-06-08T21:33:38.794Z" },
1628
+ { url = "https://files.pythonhosted.org/packages/12/46/fe640a2c576f18d86b3143192b3878d32408eb9c7fb2e6d230e2d19d8dd6/ty-0.0.45-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:704d59f0f6949d89d170ecdd46a1910c59442987bfffbe7addb56b7d42cccb83", size = 11493760, upload-time = "2026-06-08T21:34:05.747Z" },
1629
+ { url = "https://files.pythonhosted.org/packages/bb/76/106f367cc282ffa55ca5ae547f7086e21b7a50ff1c56a39084126eed4971/ty-0.0.45-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8d6041cfb8fc52204c343f4cf18596b1481c0fd26e2539eb49485fafbda9d5db", size = 10897600, upload-time = "2026-06-08T21:33:36.299Z" },
1630
+ { url = "https://files.pythonhosted.org/packages/9f/72/3e63ecdef835eeabdab2a0cc60208633a4cdc15f9ff17616817452d26342/ty-0.0.45-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6637c03861d6e32f08039bcc9bbe33515ec935f477a96a34659cdfb0b346e5e4", size = 11402259, upload-time = "2026-06-08T21:33:43.637Z" },
1631
+ { url = "https://files.pythonhosted.org/packages/c0/ad/df9bb88a32ae23e13c4d2e50f19e8e3fefb3e38d39a89aff297dd27a8a88/ty-0.0.45-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c6b1ac9bd0d6d3205108bbedba18532561019ca0967b39a24bc0efe863a8ca8", size = 11521721, upload-time = "2026-06-08T21:33:41.132Z" },
1632
+ { url = "https://files.pythonhosted.org/packages/93/fd/047f10297791c7ed11f86c15d6755b62fe3d89814627993cc7934ea48044/ty-0.0.45-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6ab32fbf622a1c46c79c1f5ba52d8b3c7a215b6f81b9485b7db1e6444e0db24", size = 12004144, upload-time = "2026-06-08T21:33:45.868Z" },
1633
+ { url = "https://files.pythonhosted.org/packages/5a/17/dc661e2be1233eeff1b441d10cca43914cf25c3e53cd1dcdf129526741a2/ty-0.0.45-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e28edfd83b591b8c92030f192019c988c49cfdf20e5cc81a24f7185e61bea39f", size = 12578455, upload-time = "2026-06-08T21:34:10.349Z" },
1634
+ { url = "https://files.pythonhosted.org/packages/63/ac/e21ac8049db03412c5857974bbc2d2019d6ee99aee4a848adff8cfa7e881/ty-0.0.45-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3cc8b7f5d333787e250367e28793a9a7e784afe4d305eec3bfea85768336b93c", size = 12283890, upload-time = "2026-06-08T21:33:54.788Z" },
1635
+ { url = "https://files.pythonhosted.org/packages/1d/93/bc1b20b9fb89fc32b8e92693a56eaf4e623859c3c1bc5d6cd7557dcc0383/ty-0.0.45-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a5d57c8f034cebe723cb3ee1aa90877c7b3997326cbc120fe6ff499de3b9542", size = 12084586, upload-time = "2026-06-08T21:33:48.023Z" },
1636
+ { url = "https://files.pythonhosted.org/packages/44/29/01977ff3a3526ba431efd22ab082a4542d4209515864847271a031462c6b/ty-0.0.45-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7adec00717a9518b6f64fe8ccbed5a40eb1d9f0a4350945bb079de95492cd710", size = 12293282, upload-time = "2026-06-08T21:34:01.308Z" },
1637
+ { url = "https://files.pythonhosted.org/packages/9e/99/056f47fa368babe80353892f29b2a37f633483c75a78d1ee00678581201f/ty-0.0.45-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b5e72751d95daf6c37e78d122a174f6b7b692a1664445baba44abdf42f1236ff", size = 11384252, upload-time = "2026-06-08T21:33:50.203Z" },
1638
+ { url = "https://files.pythonhosted.org/packages/66/21/f4ebe9fb51065e6748389473451778de35416968f5d8d290d98eb92f8858/ty-0.0.45-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2060d7074b5a7431ce583aa3d312d17b5f1b3f77fc5b2e1c54434b7eca1c087a", size = 11534716, upload-time = "2026-06-08T21:33:52.543Z" },
1639
+ { url = "https://files.pythonhosted.org/packages/9e/b7/b0a34c499133312864f2b15c8866ba1d16040bd832b2c87ef3a27d5cff62/ty-0.0.45-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7468761236a65b7376937a042696c07ee10955804bd70e696f859bda772fd490", size = 11643991, upload-time = "2026-06-08T21:34:07.936Z" },
1640
+ { url = "https://files.pythonhosted.org/packages/dc/a8/ed22c06349cc8700162b5ad76a87c47cf49985bcfaaf6ecc51f2dc8ca8ab/ty-0.0.45-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7cd3e7c8dfa7aefff6d4ff13096d1e694802df25694bca264e0023ab5c1d6077", size = 12185308, upload-time = "2026-06-08T21:34:12.58Z" },
1641
+ { url = "https://files.pythonhosted.org/packages/ea/01/d2ccb192590036f4e1f59184986b192fee89995a105cb5143ffd42855876/ty-0.0.45-py3-none-win32.whl", hash = "sha256:34937651a1fb7200334b7111a2781efedc38526add60690ba942cf1edc33fd02", size = 10972453, upload-time = "2026-06-08T21:34:03.33Z" },
1642
+ { url = "https://files.pythonhosted.org/packages/3f/ac/701e6042105dc597957a0f2efbc9e01ff3b4d4b7873ab69b05e94f6f87d0/ty-0.0.45-py3-none-win_amd64.whl", hash = "sha256:dfb45582d3198cc083d5ed617a27cd290f3d6a6fd8d8a198d3db8f7a4b94ca24", size = 12097658, upload-time = "2026-06-08T21:33:57.032Z" },
1643
+ { url = "https://files.pythonhosted.org/packages/57/35/1c30d2991fddf7f11aac626f55b84f7a158eb6f707d3757f4ffa52535af6/ty-0.0.45-py3-none-win_arm64.whl", hash = "sha256:15130a29cb33b284e2b6ef00afd76d5d61344937c7c885317243c6d256b8f8ab", size = 11434717, upload-time = "2026-06-08T21:34:14.791Z" },
1630
1644
  ]
1631
1645
 
1632
1646
  [[package]]