rekordbox-edit 0.6.0.dev40__tar.gz → 0.6.0.dev43__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.
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/workflows/cd.yml +1 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/CHANGELOG.md +4 -1
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/PKG-INFO +1 -1
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docs/api.md +2 -2
- rekordbox_edit-0.6.0.dev43/docs/stylesheets/extra.css +19 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/mkdocs.yml +9 -1
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/pyproject.toml +2 -1
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/api/convert.py +3 -3
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/api/edit.py +3 -3
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/api/search.py +2 -2
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/cli/convert.py +2 -2
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/cli/edit.py +3 -3
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/cli/search.py +2 -2
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/models.py +14 -20
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/api/test_convert.py +21 -21
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/api/test_edit.py +12 -12
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/api/test_search.py +4 -4
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/cli/test_utils.py +7 -7
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/test_models.py +9 -9
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/uv.lock +15 -1
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.agent-style/RULES.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.agent-style/claude-code.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/build-release-notes/action.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/commitizen-bump/action.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/e2e/action.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/install/action.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/lint/action.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/test/action.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/workflows/ci.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/workflows/publish.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/workflows/release.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.gitignore +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.pre-commit-config.yaml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.python-version +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.readthedocs.yaml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/AGENTS.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/CLAUDE.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/CONTRIBUTING.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/LICENSE +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/Makefile +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/README.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/codecov.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docker-compose.yml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docs/commands/convert.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docs/commands/edit.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docs/commands/search.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docs/filtering.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/docs/index.md +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/_click.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/api/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/api/_utils.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/cli/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/cli/_utils.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/cli/main.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/display.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/logger.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/query.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/rekordbox_edit/utils.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/renovate.json5 +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/ruff.toml +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/api/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/api/test_utils.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/cli/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/cli/test_convert.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/cli/test_edit.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/cli/test_main.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/cli/test_search.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/conftest.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/Dockerfile +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/__init__.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/__snapshots__/test_journey/test_search_full_json_snapshot[macos].json +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/__snapshots__/test_journey/test_search_full_json_snapshot[windows].json +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/__snapshots__/test_journey.ambr +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/conftest.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/01-flac-44_1k-16b.flac +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/02-flac-96k-24b.flac +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/03-alac-44_1k-16b.m4a +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/04-alac-48k-24b.m4a +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/05-aiff-44_1k-16b.aiff +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/06-wav-96k-24b.wav +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/07-mp3-44_1k-320cbr.mp3 +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/08-mp3-44_1k-v0vbr.mp3 +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/09-aac-44_1k-256kbps.m4a +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/audio/10-/303/274/303/261/303/256c/303/266d/303/251-flac-44_1k-16b.flac" +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/macos/master.6.8.6.db +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/windows/master.6.8.6.db +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/test_journey.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/test_display.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/test_logger.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/test_query.py +0 -0
- {rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
## v0.6.0.
|
|
1
|
+
## v0.6.0.dev43 (2026-06-11)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
- refactor: rename CommandArgs types to CommandRequest
|
|
5
|
+
- docs: open external links in a new tab with an icon
|
|
6
|
+
- docs: render signatures and model fields in api reference
|
|
4
7
|
- docs: add api reference page
|
|
5
8
|
- docs: add convert command page
|
|
6
9
|
- 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.
|
|
3
|
+
Version: 0.6.0.dev43
|
|
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
|
|
8
|
+
from rekordbox_edit.models import SearchRequest
|
|
9
9
|
|
|
10
10
|
db = Rekordbox6Database()
|
|
11
|
-
response = search(db,
|
|
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.
|
|
7
|
+
version = "0.6.0.dev43"
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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** (`
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
15
|
+
ConvertRequest,
|
|
16
16
|
ConvertOp,
|
|
17
17
|
ConvertResponse,
|
|
18
18
|
ConvertResult,
|
|
19
|
-
|
|
19
|
+
EditRequest,
|
|
20
20
|
EditOp,
|
|
21
21
|
EditResponse,
|
|
22
22
|
EditResult,
|
|
23
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
7
|
+
ConvertRequest,
|
|
8
8
|
ConvertOp,
|
|
9
9
|
ConvertResponse,
|
|
10
10
|
ConvertResult,
|
|
11
|
-
|
|
11
|
+
EditRequest,
|
|
12
12
|
EditOp,
|
|
13
13
|
EditResponse,
|
|
14
14
|
EditResult,
|
|
15
15
|
FilterArgs,
|
|
16
|
-
|
|
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
|
|
77
|
+
class TestSearchRequest:
|
|
78
78
|
def test_is_filter_args(self):
|
|
79
|
-
args =
|
|
79
|
+
args = SearchRequest(artist=["X"])
|
|
80
80
|
assert isinstance(args, FilterArgs)
|
|
81
81
|
assert args.artist == ["X"]
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
class
|
|
84
|
+
class TestEditRequest:
|
|
85
85
|
def test_has_filter_and_edit_fields(self):
|
|
86
|
-
args =
|
|
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
|
|
94
|
+
class TestConvertRequest:
|
|
95
95
|
def test_has_filter_and_convert_fields(self):
|
|
96
|
-
args =
|
|
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.
|
|
1271
|
+
version = "0.6.0.dev43"
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/commitizen-bump/action.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/.github/actions/install/action.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/__snapshots__/test_journey.ambr
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/macos/master.6.8.6.db
RENAMED
|
File without changes
|
{rekordbox_edit-0.6.0.dev40 → rekordbox_edit-0.6.0.dev43}/tests/e2e/fixtures/windows/master.6.8.6.db
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|