pyrekordbox 0.4.2__py3-none-any.whl → 0.4.3__py3-none-any.whl

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.
pyrekordbox/config.py CHANGED
@@ -18,7 +18,7 @@ import textwrap
18
18
  import time
19
19
  import xml.etree.cElementTree as xml
20
20
  from pathlib import Path
21
- from typing import Union
21
+ from typing import Any, Dict, List, Union
22
22
 
23
23
  import blowfish
24
24
  import frida
@@ -151,7 +151,7 @@ def get_pioneer_app_dir(path: Union[str, Path] = None) -> Path: # pragma: no co
151
151
  return path
152
152
 
153
153
 
154
- def _convert_type(s):
154
+ def _convert_type(s: str) -> Union[str, int, float, List[int], List[float]]:
155
155
  # Try to parse as int, float, list of int, list of float
156
156
  types_ = int, float
157
157
  for type_ in types_:
@@ -167,7 +167,7 @@ def _convert_type(s):
167
167
  return s
168
168
 
169
169
 
170
- def read_rekordbox_settings(rekordbox_app_dir: Union[str, Path]) -> dict:
170
+ def read_rekordbox_settings(rekordbox_app_dir: Union[str, Path]) -> Dict[str, Any]:
171
171
  """Finds and parses the 'rekordbox3.settings' file in the Rekordbox 5 or 6 app-dir.
172
172
 
173
173
  The settings file usually is called 'rekordbox3.settings' and is
@@ -197,12 +197,12 @@ def read_rekordbox_settings(rekordbox_app_dir: Union[str, Path]) -> dict:
197
197
  val = _convert_type(element.attrib["val"])
198
198
  except KeyError:
199
199
  device_setup = element.find("DEVICESETUP")
200
- val = {k: _convert_type(v) for k, v in device_setup.attrib.items()}
200
+ val = {k: _convert_type(v) for k, v in device_setup.attrib.items()} # type: ignore
201
201
  settings[name] = val
202
202
  return settings
203
203
 
204
204
 
205
- def read_rekordbox6_options(pioneer_app_dir: Union[str, Path]) -> dict:
205
+ def read_rekordbox6_options(pioneer_app_dir: Union[str, Path]) -> Dict[str, Any]:
206
206
  """Finds and parses the Rekordbox 6 `options.json` file with additional settings.
207
207
 
208
208
  The options file contains additional settings used by Rekordbox 6, for example the
@@ -271,7 +271,7 @@ def read_rekordbox6_asar(rb6_install_dir: Union[str, Path]) -> str:
271
271
  return data
272
272
 
273
273
 
274
- def _extract_version(name, major_version):
274
+ def _extract_version(name: str, major_version: int) -> str:
275
275
  name = name.replace(".app", "") # Needed for MacOS
276
276
  ver_str = name.replace("rekordbox", "").strip()
277
277
  if not ver_str:
@@ -284,7 +284,7 @@ def _get_rb_config(
284
284
  pioneer_app_dir: Path,
285
285
  major_version: int,
286
286
  application_dirname: str = "",
287
- ) -> dict:
287
+ ) -> Dict[str, Any]:
288
288
  """Get the program configuration for a given Rekordbox major version.
289
289
 
290
290
  Parameters
@@ -378,7 +378,9 @@ def _get_rb_config(
378
378
  return conf
379
379
 
380
380
 
381
- def _get_rb5_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = "") -> dict:
381
+ def _get_rb5_config(
382
+ pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = ""
383
+ ) -> Dict[str, Any]:
382
384
  """Get the program configuration for Rekordbox v5.x.x."""
383
385
  major_version = 5
384
386
  conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version, dirname)
@@ -417,16 +419,16 @@ class KeyExtractor:
417
419
  """)
418
420
  # fmt: on
419
421
 
420
- def __init__(self, rekordbox_executable):
422
+ def __init__(self, rekordbox_executable: Union[str, Path]):
421
423
  self.executable = str(rekordbox_executable)
422
424
  self.key = ""
423
425
 
424
- def on_message(self, message, data):
426
+ def on_message(self, message: Dict[str, Any], data: Any) -> None:
425
427
  payload = message["payload"]
426
428
  if payload.startswith("sqlite3_key"):
427
429
  self.key = payload.split(": ")[1]
428
430
 
429
- def run(self):
431
+ def run(self) -> str:
430
432
  pid = get_rekordbox_pid()
431
433
  if pid:
432
434
  raise RuntimeError(
@@ -489,12 +491,12 @@ def write_db6_key_cache(key: str) -> None: # pragma: no cover
489
491
  fh.write(text)
490
492
  # Set the config key to make sure the key is present after calling method
491
493
  if __config__["rekordbox6"]:
492
- __config__["rekordbox6"]["dp"] = key
494
+ __config__["rekordbox6"]["dp"] = key # type: ignore
493
495
  if __config__["rekordbox7"]:
494
- __config__["rekordbox7"]["dp"] = key
496
+ __config__["rekordbox7"]["dp"] = key # type: ignore
495
497
 
496
498
 
497
- def _update_sqlite_key(opts, conf):
499
+ def _update_sqlite_key(opts: Dict[str, Any], conf: Dict[str, Any]) -> None:
498
500
  cache_version = 0
499
501
  pw, dp = "", ""
500
502
 
@@ -537,7 +539,7 @@ def _update_sqlite_key(opts, conf):
537
539
  if not dp:
538
540
  if pw:
539
541
  cipher = blowfish.Cipher(pw.encode())
540
- dp = base64.standard_b64decode(opts["dp"])
542
+ dp = base64.standard_b64decode(opts["dp"]) # type: ignore
541
543
  dp = b"".join(cipher.decrypt_ecb(dp)).decode()
542
544
  logger.debug("Unlocked dp from pw: %s", dp)
543
545
  else:
@@ -578,7 +580,9 @@ def _update_sqlite_key(opts, conf):
578
580
  conf["dp"] = dp
579
581
 
580
582
 
581
- def _get_rb6_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = "") -> dict:
583
+ def _get_rb6_config(
584
+ pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = ""
585
+ ) -> Dict[str, Any]:
582
586
  """Get the program configuration for Rekordbox v6.x.x."""
583
587
  major_version = 6
584
588
  conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version, dirname)
@@ -596,7 +600,9 @@ def _get_rb6_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str
596
600
  return conf
597
601
 
598
602
 
599
- def _get_rb7_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = "") -> dict:
603
+ def _get_rb7_config(
604
+ pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str = ""
605
+ ) -> Dict[str, Any]:
600
606
  """Get the program configuration for Rekordbox v7.x.x."""
601
607
  major_version = 7
602
608
  conf = _get_rb_config(pioneer_prog_dir, pioneer_app_dir, major_version, dirname)
@@ -615,7 +621,7 @@ def _get_rb7_config(pioneer_prog_dir: Path, pioneer_app_dir: Path, dirname: str
615
621
 
616
622
 
617
623
  # noinspection PyPackageRequirements,PyUnresolvedReferences
618
- def _read_config_file(path: str) -> dict:
624
+ def _read_config_file(path: Union[str, Path]) -> Dict[str, Any]:
619
625
  path = Path(path)
620
626
  if not path.exists():
621
627
  raise FileNotFoundError(f"No such file or directory: '{path}'")
@@ -626,20 +632,20 @@ def _read_config_file(path: str) -> dict:
626
632
 
627
633
  parser = ConfigParser()
628
634
  parser.read(path)
629
- return parser
635
+ return {k: v for k, v in parser.items() if v is not None}
630
636
  elif ext == ".toml":
631
637
  import toml
632
638
 
633
- return toml.load(path)
639
+ return dict(toml.load(path))
634
640
  elif ext in (".yaml", ".yml"):
635
641
  import yaml
636
642
 
637
643
  with open(path, "r") as stream:
638
- return yaml.safe_load(stream)
644
+ return dict(yaml.safe_load(stream))
639
645
  return dict()
640
646
 
641
647
 
642
- def read_pyrekordbox_configuration():
648
+ def read_pyrekordbox_configuration() -> Dict[str, Any]:
643
649
  """Reads the pyrekordbox configuration.
644
650
 
645
651
  So far only the `pioneer-install-dir` and `pioneer-app-dir` fileds in the
@@ -677,7 +683,7 @@ def update_config(
677
683
  rb5_install_dirname: str = "",
678
684
  rb6_install_dirname: str = "",
679
685
  rb7_install_dirname: str = "",
680
- ):
686
+ ) -> None:
681
687
  """Update the pyrekordbox configuration.
682
688
 
683
689
  This method scans the system for the Rekordbox installation and application data
@@ -758,7 +764,7 @@ def update_config(
758
764
  logger.info(e)
759
765
 
760
766
 
761
- def get_config(section: str, key: str = None):
767
+ def get_config(section: str, key: str = None) -> Any:
762
768
  """Gets a section or value of the pyrekordbox configuration.
763
769
 
764
770
  Parameters
@@ -5,10 +5,20 @@
5
5
  import xml.etree.cElementTree as xml
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
+ from typing import Any, Dict, List, Optional, Union
8
9
 
9
10
  from ..config import get_config
10
11
  from ..utils import pretty_xml
11
12
 
13
+ Attribs = Dict[str, Any]
14
+
15
+
16
+ class XmlElementNotInitializedError(Exception):
17
+ """Raised when an XML element is not initialized."""
18
+
19
+ def __init__(self, name: str) -> None:
20
+ super().__init__(f"XML element {name} is not initialized!")
21
+
12
22
 
13
23
  class MasterPlaylistXml:
14
24
  """Rekordbox v6 masterPlaylists6.xml file handler.
@@ -26,7 +36,7 @@ class MasterPlaylistXml:
26
36
 
27
37
  KEYS = ["Id", "ParentId", "Attributes", "Timestamp", "Lib_Type", "CheckType"]
28
38
 
29
- def __init__(self, path=None, db_dir=None):
39
+ def __init__(self, path: Union[str, Path] = None, db_dir: Union[str, Path] = None):
30
40
  if path is None:
31
41
  if db_dir is None:
32
42
  db_dir = get_config("rekordbox6", "db_dir")
@@ -40,29 +50,33 @@ class MasterPlaylistXml:
40
50
  self._changed = False
41
51
 
42
52
  @property
43
- def version(self):
53
+ def version(self) -> str:
44
54
  return self.root.attrib["Version"]
45
55
 
46
56
  @property
47
- def automatic_sync(self):
57
+ def automatic_sync(self) -> str:
48
58
  return self.root.attrib["AutomaticSync"]
49
59
 
50
60
  @property
51
- def rekordbox_version(self):
61
+ def rekordbox_version(self) -> str:
62
+ if self.product is None:
63
+ raise XmlElementNotInitializedError("product")
52
64
  return self.product.attrib["Version"]
53
65
 
54
66
  @property
55
- def modified(self):
67
+ def modified(self) -> bool:
56
68
  return self._changed
57
69
 
58
- def get_playlists(self):
70
+ def get_playlists(self) -> List[Dict[str, Any]]:
59
71
  """Returns a list of the attributes of all playlist elements."""
72
+ if self.playlists is None:
73
+ raise XmlElementNotInitializedError("playlists")
60
74
  items = list()
61
75
  for playlist in self.playlists:
62
76
  items.append(playlist.attrib)
63
77
  return items
64
78
 
65
- def get(self, playlist_id):
79
+ def get(self, playlist_id: Union[str, int]) -> Optional[Attribs]:
66
80
  """Returns element attribs with the PlaylistID used in the `master.db` database.
67
81
 
68
82
  Parameters
@@ -75,11 +89,13 @@ class MasterPlaylistXml:
75
89
  -------
76
90
  playlist : dict
77
91
  """
92
+ if self.playlists is None:
93
+ raise XmlElementNotInitializedError("playlists")
78
94
  hex_id = f"{int(playlist_id):X}"
79
95
  element = self.playlists.find(f'.//NODE[@Id="{hex_id}"]')
80
96
  if element is None:
81
97
  return None
82
- attribs = dict(element.attrib)
98
+ attribs: Attribs = dict(element.attrib)
83
99
  attribs["Attribute"] = int(attribs["Attribute"])
84
100
  attribs["Timestamp"] = datetime.fromtimestamp(int(attribs["Timestamp"]) / 1000)
85
101
  attribs["Lib_Type"] = int(attribs["Lib_Type"])
@@ -94,7 +110,7 @@ class MasterPlaylistXml:
94
110
  updated_at: datetime,
95
111
  lib_type: int = 0,
96
112
  check_type: int = 0,
97
- ):
113
+ ) -> xml.Element:
98
114
  """Adds a new element with the PlaylistID used in the `master.db` database.
99
115
 
100
116
  Parameters
@@ -119,6 +135,9 @@ class MasterPlaylistXml:
119
135
  element : xml.Element
120
136
  The newly created element.
121
137
  """
138
+ if self.playlists is None:
139
+ raise XmlElementNotInitializedError("playlists")
140
+
122
141
  hex_id = f"{int(playlist_id):X}"
123
142
  parent_id = f"{int(parent_id):X}" if parent_id != "root" else "0"
124
143
  timestamp = int(updated_at.timestamp() * 1000)
@@ -136,7 +155,7 @@ class MasterPlaylistXml:
136
155
  self._changed = True
137
156
  return element
138
157
 
139
- def remove(self, playlist_id):
158
+ def remove(self, playlist_id: Union[str, int]) -> None:
140
159
  """Removes the element with the PlaylistID used in the `master.db` database.
141
160
 
142
161
  Parameters
@@ -145,6 +164,9 @@ class MasterPlaylistXml:
145
164
  The playlist ID used in the main `master.db` database. This id is converted
146
165
  to hexadecimal format before searching.
147
166
  """
167
+ if self.playlists is None:
168
+ raise XmlElementNotInitializedError("playlists")
169
+
148
170
  hex_id = f"{int(playlist_id):X}"
149
171
  element = self.playlists.find(f'.//NODE[@Id="{hex_id}"]')
150
172
  if element is None:
@@ -160,7 +182,7 @@ class MasterPlaylistXml:
160
182
  updated_at: datetime = None,
161
183
  lib_type: int = None,
162
184
  check_type: int = None,
163
- ):
185
+ ) -> None:
164
186
  """Updates the element with the PlaylistID used in the `master.db` database.
165
187
 
166
188
  Parameters
@@ -180,6 +202,9 @@ class MasterPlaylistXml:
180
202
  check_type : int, optional
181
203
  The check type. It seems to be always 0.
182
204
  """
205
+ if self.playlists is None:
206
+ raise XmlElementNotInitializedError("playlists")
207
+
183
208
  hex_id = f"{int(playlist_id):X}"
184
209
  element = self.playlists.find(f'.//NODE[@Id="{hex_id}"]')
185
210
  if element is None:
@@ -200,10 +225,11 @@ class MasterPlaylistXml:
200
225
  element.attrib.update(attribs)
201
226
  self._changed = True
202
227
 
203
- def to_string(self, indent=None):
204
- return pretty_xml(self.root, indent, encoding="utf-8")
228
+ def to_string(self, indent: str = None) -> str:
229
+ text: str = pretty_xml(self.root, indent, encoding="utf-8")
230
+ return text
205
231
 
206
- def save(self, path=None, indent=None):
232
+ def save(self, path: Union[str, Path] = None, indent: str = None) -> None:
207
233
  if path is None:
208
234
  path = self.path
209
235
  path = str(path)