rekordbox-edit 0.4.0.dev42__tar.gz → 0.4.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.
Files changed (49) hide show
  1. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/CHANGELOG.md +3 -1
  2. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/PKG-INFO +2 -1
  3. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/pyproject.toml +2 -1
  4. rekordbox_edit-0.4.0.dev43/rekordbox_edit/args.py +120 -0
  5. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/commands/convert.py +11 -74
  6. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/commands/edit.py +15 -79
  7. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/commands/search.py +10 -39
  8. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/uv.lock +155 -1
  9. rekordbox_edit-0.4.0.dev42/rekordbox_edit/args.py +0 -125
  10. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.agent-style/RULES.md +0 -0
  11. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.agent-style/claude-code.md +0 -0
  12. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/actions/commitizen-bump/action.yml +0 -0
  13. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
  14. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/actions/install/action.yml +0 -0
  15. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/actions/lint/action.yml +0 -0
  16. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/actions/test/action.yml +0 -0
  17. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/workflows/cd.yml +0 -0
  18. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/workflows/ci.yml +0 -0
  19. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/workflows/publish.yml +0 -0
  20. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.github/workflows/release.yml +0 -0
  21. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.gitignore +0 -0
  22. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/.pre-commit-config.yaml +0 -0
  23. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/AGENTS.md +0 -0
  24. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/CLAUDE.md +0 -0
  25. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/CONTRIBUTING.md +0 -0
  26. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/LICENSE +0 -0
  27. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/Makefile +0 -0
  28. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/README.md +0 -0
  29. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/codecov.yml +0 -0
  30. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/__init__.py +0 -0
  31. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/_click.py +0 -0
  32. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/cli.py +0 -0
  33. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/commands/__init__.py +0 -0
  34. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/display.py +0 -0
  35. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/logger.py +0 -0
  36. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/query.py +0 -0
  37. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/rekordbox_edit/utils.py +0 -0
  38. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/renovate.json5 +0 -0
  39. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/ruff.toml +0 -0
  40. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/__init__.py +0 -0
  41. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/commands/__init__.py +0 -0
  42. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/commands/test_convert.py +0 -0
  43. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/commands/test_edit.py +0 -0
  44. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/commands/test_search.py +0 -0
  45. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/conftest.py +0 -0
  46. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/test_display.py +0 -0
  47. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/test_logger.py +0 -0
  48. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/test_query.py +0 -0
  49. {rekordbox_edit-0.4.0.dev42 → rekordbox_edit-0.4.0.dev43}/tests/test_utils.py +0 -0
@@ -1,6 +1,8 @@
1
- ## v0.4.0.dev42 (2026-06-05)
1
+ ## v0.4.0.dev43 (2026-06-05)
2
2
 
3
3
 
4
+ - refactor(args): compose command args via Pydantic model inheritance
5
+ - refactor(args): adopt Pydantic for component arg types
4
6
  - refactor(commands): group command-specific args into EditArgs and ConvertArgs dataclasses
5
7
  - refactor(commands): group confirmation flags into ConfirmationArgs dataclass
6
8
  - docs: update README.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rekordbox-edit
3
- Version: 0.4.0.dev42
3
+ Version: 0.4.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
@@ -13,6 +13,7 @@ Requires-Python: >=3.10
13
13
  Requires-Dist: click<=9.0.0,>=8.0.0
14
14
  Requires-Dist: ffmpeg-python>=0.2.0
15
15
  Requires-Dist: platformdirs<5.0.0,>=4.3.8
16
+ Requires-Dist: pydantic<3,>=2.0
16
17
  Requires-Dist: pyrekordbox==0.4.4
17
18
  Requires-Dist: rich<15.0.0,>=13.0.0
18
19
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rekordbox-edit"
7
- version = "0.4.0.dev42"
7
+ version = "0.4.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"
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "click>=8.0.0,<=9.0.0",
18
18
  "platformdirs>=4.3.8,<5.0.0",
19
19
  "rich>=13.0.0,<15.0.0",
20
+ "pydantic>=2.0,<3",
20
21
  ]
21
22
 
22
23
  [dependency-groups]
@@ -0,0 +1,120 @@
1
+ """Pydantic models for CLI argument groups.
2
+
3
+ Two layers:
4
+
5
+ - **Component models** (`FilterArgs`, `ConfirmationArgs`, `EditArgs`,
6
+ `ConvertArgs`) declare cohesive subsets of inputs. Each is the typed object
7
+ consumed by a narrow internal helper — most notably `FilterArgs` →
8
+ `get_filtered_content`.
9
+ - **Command models** (`EditCommandArgs`, `ConvertCommandArgs`) compose the
10
+ components via model inheritance, producing a flat shape with every field
11
+ the corresponding command needs. The private `_edit` / `_convert` helpers
12
+ accept exactly one command model.
13
+
14
+ A caller can construct either form via kwargs or `model_validate({...})`:
15
+
16
+ _edit(EditCommandArgs(field="Title", replace_value="X", artist=["Y"]))
17
+ _edit(EditCommandArgs.model_validate(config_dict))
18
+ """
19
+
20
+ from pydantic import BaseModel, ConfigDict
21
+
22
+ from rekordbox_edit._click import PrintChoice
23
+
24
+
25
+ class FilterArgs(BaseModel):
26
+ """Filter inputs forwarded to `get_filtered_content`.
27
+
28
+ Field names mirror the Click parameter names: `track_ids` holds the
29
+ positional TRACK_IDS argument (variadic), `track_id` holds the values of
30
+ the repeated `--track-id` option.
31
+ """
32
+
33
+ model_config = ConfigDict(extra="forbid")
34
+
35
+ track_id: list[str] = []
36
+ track_ids: list[str] = []
37
+ title: list[str] = []
38
+ exact_title: list[str] = []
39
+ playlist: list[str] = []
40
+ exact_playlist: list[str] = []
41
+ artist: list[str] = []
42
+ exact_artist: list[str] = []
43
+ album: list[str] = []
44
+ exact_album: list[str] = []
45
+ path: list[str] = []
46
+ exact_path: list[str] = []
47
+ format: list[str] = []
48
+ match_all: bool = False
49
+
50
+
51
+ class ConfirmationArgs(BaseModel):
52
+ """How the user gates a side effect before it lands.
53
+
54
+ `dry_run` skips the change entirely. `yes` skips confirmation and applies
55
+ the batch. `interactive` prompts per item. With all three false, the
56
+ caller is expected to prompt once for the batch.
57
+ """
58
+
59
+ model_config = ConfigDict(extra="forbid")
60
+
61
+ dry_run: bool = False
62
+ yes: bool = False
63
+ interactive: bool = False
64
+
65
+
66
+ class EditArgs(BaseModel):
67
+ """Edit-command inputs that describe what to change.
68
+
69
+ `field` names a column from `FIELD_COLUMNS` in `commands/edit.py`.
70
+ `match_pattern` is the optional substring to find within the current value;
71
+ when omitted, the whole value is replaced. `multi` allows the edit to
72
+ apply to more than one matched track.
73
+ """
74
+
75
+ model_config = ConfigDict(extra="forbid")
76
+
77
+ field: str
78
+ replace_value: str
79
+ match_pattern: str | None = None
80
+ multi: bool = False
81
+
82
+
83
+ class ConvertArgs(BaseModel):
84
+ """Convert-command inputs that describe the output and conflict policy.
85
+
86
+ `delete` is tri-state: `None` defers to a per-format default in
87
+ `_convert`, while `True` / `False` are explicit.
88
+ """
89
+
90
+ model_config = ConfigDict(extra="forbid")
91
+
92
+ format_out: str = "aiff"
93
+ delete: bool | None = None
94
+ overwrite: bool = False
95
+
96
+
97
+ class SearchCommandArgs(FilterArgs):
98
+ """All inputs `_search` accepts. Search is a read-only command, so it
99
+ inherits only the filter group and adds `print_opt`.
100
+ """
101
+
102
+ print_opt: PrintChoice | None = None
103
+
104
+
105
+ class EditCommandArgs(FilterArgs, ConfirmationArgs, EditArgs):
106
+ """All inputs `_edit` accepts, composed from the three component groups.
107
+
108
+ Inherits via Pydantic model inheritance: every field of every parent
109
+ appears flat in this model. Liskov also holds, so an `EditCommandArgs`
110
+ can be passed anywhere a `FilterArgs`/`ConfirmationArgs`/`EditArgs` is
111
+ expected (e.g. `get_filtered_content(db, args)`).
112
+ """
113
+
114
+ print_opt: PrintChoice | None = None
115
+
116
+
117
+ class ConvertCommandArgs(FilterArgs, ConfirmationArgs, ConvertArgs):
118
+ """All inputs `_convert` accepts, composed from the three component groups."""
119
+
120
+ print_opt: PrintChoice | None = None
@@ -5,7 +5,7 @@ import os
5
5
  import signal
6
6
  import sys
7
7
  from pathlib import Path
8
- from typing import List, Tuple
8
+ from typing import Tuple
9
9
 
10
10
  import click
11
11
  import ffmpeg
@@ -22,14 +22,7 @@ from rekordbox_edit._click import (
22
22
  print_option,
23
23
  track_ids_argument,
24
24
  )
25
- from rekordbox_edit.args import (
26
- ConfirmationArgs,
27
- ConvertArgs,
28
- FilterArgs,
29
- confirmation_args_from_kwargs,
30
- convert_args_from_kwargs,
31
- filter_args_from_kwargs,
32
- )
25
+ from rekordbox_edit.args import ConvertCommandArgs
33
26
  from rekordbox_edit.logger import get_debug_file_path, set_level
34
27
  from rekordbox_edit.query import get_filtered_content
35
28
  from rekordbox_edit.display import PrintableField, print_track_info
@@ -263,29 +256,7 @@ def get_output_path(content, output_format) -> Tuple[str, str, str]:
263
256
  track_ids_argument,
264
257
  ]
265
258
  )
266
- def convert_command(
267
- dry_run,
268
- yes,
269
- delete,
270
- overwrite,
271
- interactive,
272
- format_out,
273
- track_id: List[str] | None,
274
- track_ids: List[str] | None,
275
- title: List[str] | None,
276
- exact_title: List[str] | None,
277
- album: List[str] | None,
278
- exact_album: List[str] | None,
279
- artist: List[str] | None,
280
- exact_artist: List[str] | None,
281
- playlist: List[str] | None,
282
- exact_playlist: List[str] | None,
283
- path: List[str] | None,
284
- exact_path: List[str] | None,
285
- format: List[str] | None,
286
- match_all: bool,
287
- print_opt: PrintChoice | None,
288
- ):
259
+ def convert_command(**kwargs):
289
260
  """Convert lossless audio files between formats and update RekordBox database.
290
261
 
291
262
  Supports conversion from any lossless format (FLAC, AIFF, WAV) to:
@@ -293,57 +264,23 @@ def convert_command(
293
264
 
294
265
  Skips lossy formats and files already in the target format.
295
266
  """
296
- filters = filter_args_from_kwargs(
297
- track_id=track_id,
298
- track_ids=track_ids,
299
- playlist=playlist,
300
- exact_playlist=exact_playlist,
301
- album=album,
302
- exact_album=exact_album,
303
- artist=artist,
304
- exact_artist=exact_artist,
305
- title=title,
306
- exact_title=exact_title,
307
- path=path,
308
- exact_path=exact_path,
309
- format=format,
310
- match_all=match_all,
311
- )
312
- confirmation = confirmation_args_from_kwargs(
313
- dry_run=dry_run, yes=yes, interactive=interactive
314
- )
315
- convert_args = convert_args_from_kwargs(
316
- format_out=format_out, delete=delete, overwrite=overwrite
317
- )
318
- _convert(filters, confirmation, convert_args, print_opt)
267
+ _convert(ConvertCommandArgs(**kwargs))
319
268
 
320
269
 
321
- def _convert(
322
- filters: FilterArgs,
323
- confirmation: ConfirmationArgs,
324
- convert_args: ConvertArgs,
325
- print_opt: PrintChoice | None,
326
- ) -> None:
327
- """Convert audio files for tracks matching `filters`."""
270
+ def _convert(args: ConvertCommandArgs) -> None:
271
+ """Convert audio files for tracks matching `args`."""
328
272
  from rekordbox_edit.utils import ffmpeg_in_path, get_ffmpeg_directions
329
273
 
330
- dry_run, yes, interactive = (
331
- confirmation.dry_run,
332
- confirmation.yes,
333
- confirmation.interactive,
334
- )
335
- format_out, delete, overwrite = (
336
- convert_args.format_out,
337
- convert_args.delete,
338
- convert_args.overwrite,
339
- )
274
+ dry_run, yes, interactive = args.dry_run, args.yes, args.interactive
275
+ format_out, delete, overwrite = args.format_out, args.delete, args.overwrite
276
+ print_opt = args.print_opt
340
277
  set_level(print_opt)
341
278
 
342
279
  piped_stdin = not sys.stdin.isatty()
343
280
  if piped_stdin:
344
281
  stdin_data = sys.stdin.read().strip()
345
282
  if stdin_data:
346
- filters.track_ids = list(filters.track_ids) + stdin_data.split()
283
+ args.track_ids = list(args.track_ids) + stdin_data.split()
347
284
 
348
285
  # Validate --print option requirements
349
286
  scripting_mode = print_opt in (PrintChoice.IDS, PrintChoice.SILENT)
@@ -411,7 +348,7 @@ def _convert(
411
348
  logger.debug("Database connection established")
412
349
 
413
350
  # === QUERY & FILTER ===
414
- result = get_filtered_content(db, filters)
351
+ result = get_filtered_content(db, args)
415
352
  filtered_content = result.scalars().all()
416
353
  logger.debug(f"Query returned {len(filtered_content)} tracks")
417
354
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  import sys
5
- from typing import List
6
5
 
7
6
  import click
8
7
  from pyrekordbox import Rekordbox6Database
@@ -16,14 +15,7 @@ from rekordbox_edit._click import (
16
15
  print_option,
17
16
  track_ids_argument,
18
17
  )
19
- from rekordbox_edit.args import (
20
- ConfirmationArgs,
21
- EditArgs,
22
- FilterArgs,
23
- confirmation_args_from_kwargs,
24
- edit_args_from_kwargs,
25
- filter_args_from_kwargs,
26
- )
18
+ from rekordbox_edit.args import EditCommandArgs
27
19
  from rekordbox_edit.logger import get_debug_file_path, set_level
28
20
  from rekordbox_edit.query import get_filtered_content
29
21
  from rekordbox_edit.display import PrintableField, print_track_info
@@ -66,77 +58,21 @@ def _compute_new_value(
66
58
  "field",
67
59
  type=click.Choice(list(FIELD_COLUMNS.keys()), case_sensitive=False),
68
60
  )
69
- def edit_command(
70
- field: str,
71
- replace_value: str,
72
- match_pattern: str | None,
73
- multi: bool,
74
- dry_run: bool,
75
- yes: bool,
76
- interactive: bool,
77
- track_ids: List[str] | None,
78
- track_id: List[str] | None,
79
- playlist: List[str] | None,
80
- exact_playlist: List[str] | None,
81
- album: List[str] | None,
82
- exact_album: List[str] | None,
83
- artist: List[str] | None,
84
- exact_artist: List[str] | None,
85
- title: List[str] | None,
86
- exact_title: List[str] | None,
87
- path: List[str] | None,
88
- exact_path: List[str] | None,
89
- format: List[str] | None,
90
- match_all: bool,
91
- print_opt: PrintChoice | None,
92
- ):
61
+ def edit_command(**kwargs):
93
62
  """Edit a metadata field on tracks in the RekordBox database."""
94
- filters = filter_args_from_kwargs(
95
- track_id=track_id,
96
- track_ids=track_ids,
97
- playlist=playlist,
98
- exact_playlist=exact_playlist,
99
- album=album,
100
- exact_album=exact_album,
101
- artist=artist,
102
- exact_artist=exact_artist,
103
- title=title,
104
- exact_title=exact_title,
105
- path=path,
106
- exact_path=exact_path,
107
- format=format,
108
- match_all=match_all,
109
- )
110
- confirmation = confirmation_args_from_kwargs(
111
- dry_run=dry_run, yes=yes, interactive=interactive
112
- )
113
- edit_args = edit_args_from_kwargs(
114
- field=field,
115
- replace_value=replace_value,
116
- match_pattern=match_pattern,
117
- multi=multi,
118
- )
119
- _edit(filters, confirmation, edit_args, print_opt)
120
-
121
-
122
- def _edit(
123
- filters: FilterArgs,
124
- confirmation: ConfirmationArgs,
125
- edit_args: EditArgs,
126
- print_opt: PrintChoice | None,
127
- ) -> None:
128
- """Apply a field edit to tracks matching `filters`."""
129
- dry_run, yes, interactive = (
130
- confirmation.dry_run,
131
- confirmation.yes,
132
- confirmation.interactive,
133
- )
63
+ _edit(EditCommandArgs(**kwargs))
64
+
65
+
66
+ def _edit(args: EditCommandArgs) -> None:
67
+ """Apply a field edit to tracks matching `args`."""
68
+ dry_run, yes, interactive = args.dry_run, args.yes, args.interactive
134
69
  field, replace_value, match_pattern, multi = (
135
- edit_args.field,
136
- edit_args.replace_value,
137
- edit_args.match_pattern,
138
- edit_args.multi,
70
+ args.field,
71
+ args.replace_value,
72
+ args.match_pattern,
73
+ args.multi,
139
74
  )
75
+ print_opt = args.print_opt
140
76
  set_level(print_opt)
141
77
 
142
78
  piped_stdin = False
@@ -144,7 +80,7 @@ def _edit(
144
80
  stdin_data = sys.stdin.read().strip()
145
81
  if stdin_data:
146
82
  piped_stdin = True
147
- filters.track_ids = list(filters.track_ids) + stdin_data.split()
83
+ args.track_ids = list(args.track_ids) + stdin_data.split()
148
84
 
149
85
  scripting_mode = print_opt in (PrintChoice.IDS, PrintChoice.SILENT)
150
86
  if scripting_mode and not (dry_run or yes):
@@ -159,7 +95,7 @@ def _edit(
159
95
  if not db.session:
160
96
  raise RuntimeError("Failed to connect to Rekordbox Database: No Session.")
161
97
 
162
- result = get_filtered_content(db, filters)
98
+ result = get_filtered_content(db, args)
163
99
  tracks = result.scalars().all()
164
100
 
165
101
  col_name = FIELD_COLUMNS[field]
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  import sys
5
- from typing import List
6
5
 
7
6
  import click
8
7
  from pyrekordbox import Rekordbox6Database
@@ -14,7 +13,7 @@ from rekordbox_edit._click import (
14
13
  print_option,
15
14
  track_ids_argument,
16
15
  )
17
- from rekordbox_edit.args import filter_args_from_kwargs
16
+ from rekordbox_edit.args import SearchCommandArgs
18
17
  from rekordbox_edit.logger import get_debug_file_path, set_level
19
18
  from rekordbox_edit.query import get_filtered_content
20
19
  from rekordbox_edit.display import print_track_info
@@ -26,57 +25,29 @@ logger = logging.getLogger(__name__)
26
25
  epilog=f"Debug logs for each run can be found at:\n{get_debug_file_path().parent}"
27
26
  )
28
27
  @add_click_options([*global_click_filters, print_option, track_ids_argument])
29
- def search_command(
30
- track_id: List[str] | None,
31
- track_ids: List[str] | None,
32
- playlist: List[str] | None,
33
- exact_playlist: List[str] | None,
34
- album: List[str] | None,
35
- exact_album: List[str] | None,
36
- artist: List[str] | None,
37
- exact_artist: List[str] | None,
38
- title: List[str] | None,
39
- exact_title: List[str] | None,
40
- path: List[str] | None,
41
- exact_path: List[str] | None,
42
- format: List[str] | None,
43
- match_all: bool,
44
- print_opt: PrintChoice | None,
45
- ):
28
+ def search_command(**kwargs):
46
29
  """Search the RekordBox database."""
30
+ _search(SearchCommandArgs(**kwargs))
47
31
 
32
+
33
+ def _search(args: SearchCommandArgs) -> None:
34
+ """Run a read-only query matching `args` and emit results per `print_opt`."""
35
+ print_opt = args.print_opt
48
36
  set_level(print_opt)
49
37
 
50
38
  if not sys.stdin.isatty():
51
39
  stdin_data = sys.stdin.read().strip()
52
40
  if stdin_data:
53
- track_ids = list(track_ids or []) + stdin_data.split()
54
-
55
- filters = filter_args_from_kwargs(
56
- track_id=track_id,
57
- track_ids=track_ids,
58
- playlist=playlist,
59
- exact_playlist=exact_playlist,
60
- album=album,
61
- exact_album=exact_album,
62
- artist=artist,
63
- exact_artist=exact_artist,
64
- title=title,
65
- exact_title=exact_title,
66
- path=path,
67
- exact_path=exact_path,
68
- format=format,
69
- match_all=match_all,
70
- )
41
+ args.track_ids = list(args.track_ids) + stdin_data.split()
71
42
 
72
- logger.debug(f"Search filters: {filters}")
43
+ logger.debug(f"Search filters: {args}")
73
44
  logger.debug("Connecting to RekordBox database...")
74
45
 
75
46
  db = Rekordbox6Database()
76
47
  if not db.session:
77
48
  raise RuntimeError("Failed to connect to Rekordbox Database: No Session.")
78
49
 
79
- filtered_result = get_filtered_content(db, filters)
50
+ filtered_result = get_filtered_content(db, args)
80
51
 
81
52
  if print_opt is PrintChoice.SILENT:
82
53
  pass
@@ -10,6 +10,15 @@ resolution-markers = [
10
10
  exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
11
11
  exclude-newer-span = "P3D"
12
12
 
13
+ [[package]]
14
+ name = "annotated-types"
15
+ version = "0.7.0"
16
+ source = { registry = "https://pypi.org/simple" }
17
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
18
+ wheels = [
19
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
20
+ ]
21
+
13
22
  [[package]]
14
23
  name = "argcomplete"
15
24
  version = "3.6.3"
@@ -821,6 +830,137 @@ wheels = [
821
830
  { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
822
831
  ]
823
832
 
833
+ [[package]]
834
+ name = "pydantic"
835
+ version = "2.13.4"
836
+ source = { registry = "https://pypi.org/simple" }
837
+ dependencies = [
838
+ { name = "annotated-types" },
839
+ { name = "pydantic-core" },
840
+ { name = "typing-extensions" },
841
+ { name = "typing-inspection" },
842
+ ]
843
+ sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
844
+ wheels = [
845
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
846
+ ]
847
+
848
+ [[package]]
849
+ name = "pydantic-core"
850
+ version = "2.46.4"
851
+ source = { registry = "https://pypi.org/simple" }
852
+ dependencies = [
853
+ { name = "typing-extensions" },
854
+ ]
855
+ sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
856
+ wheels = [
857
+ { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" },
858
+ { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" },
859
+ { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" },
860
+ { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" },
861
+ { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" },
862
+ { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" },
863
+ { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" },
864
+ { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" },
865
+ { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" },
866
+ { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" },
867
+ { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" },
868
+ { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" },
869
+ { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" },
870
+ { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" },
871
+ { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
872
+ { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
873
+ { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
874
+ { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
875
+ { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
876
+ { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
877
+ { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
878
+ { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
879
+ { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
880
+ { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
881
+ { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
882
+ { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
883
+ { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
884
+ { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
885
+ { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
886
+ { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
887
+ { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
888
+ { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
889
+ { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
890
+ { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
891
+ { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
892
+ { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
893
+ { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
894
+ { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
895
+ { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
896
+ { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
897
+ { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
898
+ { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
899
+ { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
900
+ { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
901
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
902
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
903
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
904
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
905
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
906
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
907
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
908
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
909
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
910
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
911
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
912
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
913
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
914
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
915
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
916
+ { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
917
+ { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
918
+ { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
919
+ { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
920
+ { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
921
+ { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
922
+ { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
923
+ { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
924
+ { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
925
+ { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
926
+ { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
927
+ { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
928
+ { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
929
+ { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
930
+ { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
931
+ { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
932
+ { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
933
+ { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
934
+ { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
935
+ { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
936
+ { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
937
+ { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
938
+ { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
939
+ { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
940
+ { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
941
+ { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
942
+ { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
943
+ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
944
+ { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
945
+ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
946
+ { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
947
+ { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
948
+ { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
949
+ { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
950
+ { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
951
+ { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
952
+ { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
953
+ { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
954
+ { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
955
+ { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
956
+ { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
957
+ { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
958
+ { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
959
+ { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
960
+ { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
961
+ { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
962
+ ]
963
+
824
964
  [[package]]
825
965
  name = "pygments"
826
966
  version = "2.20.0"
@@ -1010,12 +1150,13 @@ wheels = [
1010
1150
 
1011
1151
  [[package]]
1012
1152
  name = "rekordbox-edit"
1013
- version = "0.4.0.dev42"
1153
+ version = "0.4.0.dev43"
1014
1154
  source = { editable = "." }
1015
1155
  dependencies = [
1016
1156
  { name = "click" },
1017
1157
  { name = "ffmpeg-python" },
1018
1158
  { name = "platformdirs" },
1159
+ { name = "pydantic" },
1019
1160
  { name = "pyrekordbox" },
1020
1161
  { name = "rich" },
1021
1162
  ]
@@ -1038,6 +1179,7 @@ requires-dist = [
1038
1179
  { name = "click", specifier = ">=8.0.0,<=9.0.0" },
1039
1180
  { name = "ffmpeg-python", specifier = ">=0.2.0" },
1040
1181
  { name = "platformdirs", specifier = ">=4.3.8,<5.0.0" },
1182
+ { name = "pydantic", specifier = ">=2.0,<3" },
1041
1183
  { name = "pyrekordbox", specifier = "==0.4.4" },
1042
1184
  { name = "rich", specifier = ">=13.0.0,<15.0.0" },
1043
1185
  ]
@@ -1372,6 +1514,18 @@ wheels = [
1372
1514
  { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
1373
1515
  ]
1374
1516
 
1517
+ [[package]]
1518
+ name = "typing-inspection"
1519
+ version = "0.4.2"
1520
+ source = { registry = "https://pypi.org/simple" }
1521
+ dependencies = [
1522
+ { name = "typing-extensions" },
1523
+ ]
1524
+ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
1525
+ wheels = [
1526
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
1527
+ ]
1528
+
1375
1529
  [[package]]
1376
1530
  name = "virtualenv"
1377
1531
  version = "21.2.0"
@@ -1,125 +0,0 @@
1
- """Dataclass containers for CLI argument groups.
2
-
3
- These types are the public API of the functional layer below the CLI: callers
4
- of `get_filtered_content` and the private command helpers receive them in lieu
5
- of long flat parameter lists. Each `*_from_kwargs` factory packs the matching
6
- Click parameters into its dataclass.
7
- """
8
-
9
- from dataclasses import dataclass, field
10
-
11
-
12
- @dataclass
13
- class FilterArgs:
14
- """Filter inputs forwarded to `get_filtered_content`.
15
-
16
- Field names mirror the Click parameter names: `track_ids` holds the
17
- positional TRACK_IDS argument (variadic), `track_id` holds the values of
18
- the repeated `--track-id` option.
19
- """
20
-
21
- track_id: list[str] = field(default_factory=list)
22
- track_ids: list[str] = field(default_factory=list)
23
- title: list[str] = field(default_factory=list)
24
- exact_title: list[str] = field(default_factory=list)
25
- playlist: list[str] = field(default_factory=list)
26
- exact_playlist: list[str] = field(default_factory=list)
27
- artist: list[str] = field(default_factory=list)
28
- exact_artist: list[str] = field(default_factory=list)
29
- album: list[str] = field(default_factory=list)
30
- exact_album: list[str] = field(default_factory=list)
31
- path: list[str] = field(default_factory=list)
32
- exact_path: list[str] = field(default_factory=list)
33
- format: list[str] = field(default_factory=list)
34
- match_all: bool = False
35
-
36
-
37
- def filter_args_from_kwargs(**kwargs) -> FilterArgs:
38
- """Pack the flat Click kwargs for the `global_click_filters` group into a FilterArgs."""
39
- return FilterArgs(
40
- track_id=list(kwargs.get("track_id") or []),
41
- track_ids=list(kwargs.get("track_ids") or []),
42
- title=list(kwargs.get("title") or []),
43
- exact_title=list(kwargs.get("exact_title") or []),
44
- playlist=list(kwargs.get("playlist") or []),
45
- exact_playlist=list(kwargs.get("exact_playlist") or []),
46
- artist=list(kwargs.get("artist") or []),
47
- exact_artist=list(kwargs.get("exact_artist") or []),
48
- album=list(kwargs.get("album") or []),
49
- exact_album=list(kwargs.get("exact_album") or []),
50
- path=list(kwargs.get("path") or []),
51
- exact_path=list(kwargs.get("exact_path") or []),
52
- format=list(kwargs.get("format") or []),
53
- match_all=bool(kwargs.get("match_all", False)),
54
- )
55
-
56
-
57
- @dataclass
58
- class ConfirmationArgs:
59
- """How the user gates a side effect before it lands.
60
-
61
- `dry_run` skips the change entirely. `yes` skips confirmation and applies
62
- the batch. `interactive` prompts per item. With all three false, the
63
- caller is expected to prompt once for the batch.
64
- """
65
-
66
- dry_run: bool = False
67
- yes: bool = False
68
- interactive: bool = False
69
-
70
-
71
- def confirmation_args_from_kwargs(**kwargs) -> ConfirmationArgs:
72
- """Pack the flat Click kwargs for the `global_click_confirmations` group into a ConfirmationArgs."""
73
- return ConfirmationArgs(
74
- dry_run=bool(kwargs.get("dry_run", False)),
75
- yes=bool(kwargs.get("yes", False)),
76
- interactive=bool(kwargs.get("interactive", False)),
77
- )
78
-
79
-
80
- @dataclass
81
- class EditArgs:
82
- """Edit-command inputs that describe what to change.
83
-
84
- `field` names a column from `FIELD_COLUMNS` in `commands/edit.py`.
85
- `match_pattern` is the optional substring to find within the current value;
86
- when omitted, the whole value is replaced. `multi` allows the edit to
87
- apply to more than one matched track.
88
- """
89
-
90
- field: str
91
- replace_value: str
92
- match_pattern: str | None = None
93
- multi: bool = False
94
-
95
-
96
- def edit_args_from_kwargs(**kwargs) -> EditArgs:
97
- """Pack the flat Click kwargs for the `edit_click_options` group plus `field` into an EditArgs."""
98
- return EditArgs(
99
- field=kwargs["field"],
100
- replace_value=kwargs["replace_value"],
101
- match_pattern=kwargs.get("match_pattern"),
102
- multi=bool(kwargs.get("multi", False)),
103
- )
104
-
105
-
106
- @dataclass
107
- class ConvertArgs:
108
- """Convert-command inputs that describe the output and conflict policy.
109
-
110
- `delete` is tri-state: `None` defers to a per-format default in
111
- `_convert`, while `True` / `False` are explicit.
112
- """
113
-
114
- format_out: str
115
- delete: bool | None = None
116
- overwrite: bool = False
117
-
118
-
119
- def convert_args_from_kwargs(**kwargs) -> ConvertArgs:
120
- """Pack the flat Click kwargs for the `convert_click_options` group into a ConvertArgs."""
121
- return ConvertArgs(
122
- format_out=kwargs["format_out"],
123
- delete=kwargs.get("delete"),
124
- overwrite=bool(kwargs.get("overwrite", False)),
125
- )