Coverage for src/nendo/schema/plugin.py: 83%

283 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-23 10:03 +0100

1# -*- encoding: utf-8 -*- 

2"""Plugin classes of Nendo Core.""" 

3from __future__ import annotations 

4 

5import functools 

6import os 

7from abc import abstractmethod 

8from typing import ( 

9 TYPE_CHECKING, 

10 Any, 

11 Callable, 

12 Dict, 

13 Iterator, 

14 List, 

15 Optional, 

16 Tuple, 

17 Union, 

18) 

19 

20from pydantic import ConfigDict, DirectoryPath, FilePath 

21 

22from nendo.schema.core import ( 

23 NendoBlob, 

24 NendoCollection, 

25 NendoCollectionSlim, 

26 NendoPlugin, 

27 NendoPluginData, 

28 NendoStorage, 

29 NendoTrack, 

30) 

31from nendo.schema.exception import NendoError, NendoPluginRuntimeError 

32from nendo.utils import ensure_uuid 

33 

34if TYPE_CHECKING: 

35 import uuid 

36 

37 import numpy as np 

38 

39 

40class NendoAnalysisPlugin(NendoPlugin): 

41 """Basic class for nendo analysis plugins. 

42 

43 Analysis plugins are plugins that analyze a track or a collection 

44 and add metadata and other properties to the track or collection. 

45 Decorate your methods with `@NendoAnalysisPlugin.plugin_data` to add the 

46 return values of your methods as plugin data to the track or collection. 

47 

48 Decorate your methods with `@NendoAnalysisPlugin.run_track` to run your method 

49 on a track and use `@NendoAnalysisPlugin.run_collection` to run your method on 

50 a collection. 

51 

52 Examples: 

53 ```python 

54 from nendo import Nendo, NendoConfig 

55 

56 class MyPlugin(NendoAnalysisPlugin): 

57 ... 

58 

59 @NendoAnalysisPlugin.plugin_data 

60 def my_plugin_data_function_one(self, track): 

61 # do something analysis on the track 

62 return {"key": "value"} 

63 

64 @NendoAnalysisPlugin.plugin_data 

65 def my_plugin_data_function_two(self, track): 

66 # do some more analysis on the track 

67 return {"key": "value"} 

68 

69 @NendoAnalysisPlugin.run_track 

70 def my_run_track_function(self, track): 

71 my_plugin_data_function_one(track) 

72 my_plugin_data_function_two(track) 

73 ``` 

74 """ 

75 

76 # decorators 

77 # ---------- 

78 @staticmethod 

79 def plugin_data( 

80 func: Callable[[NendoPlugin, NendoTrack], Dict[str, Any]], 

81 ) -> Callable[[NendoPlugin, NendoTrack], Dict[str, Any]]: 

82 """Decorator to enrich a NendoTrack with data from a plugin. 

83 

84 Args: 

85 func: Callable[[NendoPlugin, NendoTrack], Dict[str, Any]]: The function to register. 

86 

87 Returns: 

88 Callable[[NendoPlugin, NendoTrack], Dict[str, Any]]: The wrapped function. 

89 """ 

90 

91 def wrapper(self, track: NendoTrack): 

92 try: 

93 f_result = func(self, track) 

94 except NendoError as e: 

95 raise NendoPluginRuntimeError( 

96 f"Error running plugin function: {e}", 

97 ) from None 

98 for k, v in f_result.items(): 

99 track.add_plugin_data( 

100 plugin_name=self.plugin_name, 

101 plugin_version=self.plugin_version, 

102 key=str(k), 

103 value=v, 

104 # replace=False, 

105 ) 

106 return f_result 

107 

108 return wrapper 

109 

110 # ---------- 

111 

112 @property 

113 def plugin_type(self) -> str: 

114 """Return type of plugin.""" 

115 return "AnalysisPlugin" 

116 

117 @staticmethod 

118 def run_collection( 

119 func: Callable[[NendoPlugin, NendoCollection, Any], None], 

120 ) -> Callable[[NendoPlugin, Any], NendoCollection]: 

121 """Decorator to register a function as a collection running function for a `NendoAnalysisPlugin`. 

122 

123 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

124 

125 Args: 

126 func: Callable[[NendoPlugin, NendoCollection, Any], None]: The function to register. 

127 

128 Returns: 

129 Callable[[NendoPlugin, Any], NendoCollection]: The wrapped function. 

130 """ 

131 

132 @functools.wraps(func) 

133 def wrapper(self, **kwargs: Any) -> NendoCollection: 

134 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

135 **kwargs, 

136 ) 

137 if isinstance(track_or_collection, NendoCollection): 

138 func(self, track_or_collection, **kwargs) 

139 return self.nendo_instance.library.get_collection( 

140 track_or_collection.id, 

141 ) 

142 

143 tmp_collection = self.nendo_instance.library.add_collection( 

144 name="tmp", 

145 track_ids=[track_or_collection.id], 

146 collection_type="temp", 

147 ) 

148 func(self, tmp_collection, **kwargs) 

149 return self.nendo_instance.library.get_track(track_or_collection.id) 

150 

151 return wrapper 

152 

153 @staticmethod 

154 def run_track( 

155 func: Callable[[NendoPlugin, NendoTrack, Any], None], 

156 ) -> Callable[[NendoPlugin, Any], Union[NendoTrack, NendoCollection]]: 

157 """Decorator to register a function as a track running function for a `NendoAnalysisPlugin`. 

158 

159 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

160 

161 Args: 

162 func: Callable[[NendoPlugin, NendoTrack, Any], None]: The function to register. 

163 

164 Returns: 

165 Callable[[NendoPlugin, Any], NendoCollection]: The wrapped function. 

166 """ 

167 

168 @functools.wraps(func) 

169 def wrapper(self, **kwargs: Any) -> Union[NendoTrack, NendoCollection]: 

170 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

171 **kwargs, 

172 ) 

173 if isinstance(track_or_collection, NendoTrack): 

174 func(self, track_or_collection, **kwargs) 

175 return self.nendo_instance.library.get_track(track_or_collection.id) 

176 [func(self, track, **kwargs) for track in track_or_collection.tracks()] 

177 return self.nendo_instance.library.get_collection(track_or_collection.id) 

178 

179 return wrapper 

180 

181 

182class NendoGeneratePlugin(NendoPlugin): 

183 """Basic class for nendo generate plugins. 

184 

185 Generate plugins are plugins that generate new tracks or collections, either from scratch or 

186 based on existing tracks or collections. 

187 Decorate your methods with `@NendoGeneratePlugin.run_track` to run your method 

188 on a track, use `@NendoGeneratePlugin.run_collection` to run your method on 

189 a collection and use `@NendoGeneratePlugin.run_signal` to run your method on 

190 a signal. 

191 

192 Examples: 

193 ```python 

194 from nendo import Nendo, NendoConfig 

195 

196 class MyPlugin(NendoGeneratePlugin): 

197 ... 

198 

199 @NendoAnalysisPlugin.run_track 

200 def my_generate_track_function(self, track, arg_one="foo"): 

201 # generate some new audio 

202 

203 # add audio to the nendo library 

204 return self.nendo_instance.library.add_related_track_from_signal( 

205 signal, 

206 sr, 

207 related_track_id=track.id, 

208 ) 

209 ``` 

210 """ 

211 

212 @property 

213 def plugin_type(self) -> str: 

214 """Return type of plugin.""" 

215 return "GeneratePlugin" 

216 

217 @staticmethod 

218 def run_collection( 

219 func: Callable[[NendoPlugin, Optional[NendoCollection], Any], NendoCollection], 

220 ) -> Callable[[NendoPlugin, Any], NendoCollection]: 

221 """Decorator to register a function as a collection running function for a `NendoGeneratePlugin`. 

222 

223 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

224 

225 Args: 

226 func: Callable[[NendoPlugin, NendoCollection, Any], NendoCollection]: The function to register. 

227 

228 Returns: 

229 Callable[[NendoPlugin, Any], NendoCollection]: The wrapped function. 

230 """ 

231 

232 @functools.wraps(func) 

233 def wrapper(self, **kwargs: Any) -> NendoCollection: 

234 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

235 **kwargs, 

236 ) 

237 if track_or_collection is None: 

238 return func(self, **kwargs) 

239 

240 if isinstance(track_or_collection, NendoCollection): 

241 return func(self, track_or_collection, **kwargs) 

242 

243 tmp_collection = self.nendo_instance.library.add_collection( 

244 name="tmp", 

245 track_ids=[track_or_collection.id], 

246 collection_type="temp", 

247 ) 

248 return func(self, tmp_collection, **kwargs) 

249 

250 return wrapper 

251 

252 @staticmethod 

253 def run_signal( 

254 func: Callable[ 

255 [NendoPlugin, Optional[np.ndarray], Optional[int], Any], 

256 Tuple[np.ndarray, int], 

257 ], 

258 ) -> Callable[[NendoPlugin, Any], Union[NendoTrack, NendoCollection]]: 

259 """Decorator to register a function as a signal running function for a `NendoGeneratePlugin`. 

260 

261 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

262 

263 Args: 

264 func: Callable[[NendoPlugin, np.ndarray, int, Any], Tuple[np.ndarray, int]]: The function to register. 

265 

266 Returns: 

267 Callable[[NendoPlugin, Any], NendoCollection]: The wrapped function. 

268 """ 

269 

270 @functools.wraps(func) 

271 def wrapper(self, **kwargs: Any) -> Union[NendoTrack, NendoCollection]: 

272 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

273 **kwargs, 

274 ) 

275 if track_or_collection is None: 

276 signal, sr = func(self, **kwargs) 

277 return self.nendo_instance.library.add_track_from_signal( 

278 signal, 

279 sr, 

280 ) 

281 if isinstance(track_or_collection, NendoTrack): 

282 signal, sr = track_or_collection.signal, track_or_collection.sr 

283 new_signal, new_sr = func(self, signal, sr, **kwargs) 

284 return self.nendo_instance.library.add_related_track_from_signal( 

285 new_signal, 

286 sr, 

287 related_track_id=track_or_collection.id, 

288 ) 

289 processed_tracks = [] 

290 for track in track_or_collection.tracks(): 

291 new_signal, new_sr = func( 

292 self, 

293 track.signal, 

294 track.sr, 

295 **kwargs, 

296 ) 

297 processed_tracks.append( 

298 self.nendo_instance.library.add_related_track_from_signal( 

299 new_signal, 

300 track.sr, 

301 related_track_id=track.id, 

302 ), 

303 ) 

304 return self.nendo_instance.library.add_collection( 

305 name="tmp", 

306 track_ids=[track.id for track in processed_tracks], 

307 collection_type="temp", 

308 ) 

309 

310 return wrapper 

311 

312 @staticmethod 

313 def run_track( 

314 func: Callable[ 

315 [NendoPlugin, Optional[NendoTrack], Any], 

316 Union[NendoTrack, List[NendoTrack]], 

317 ], 

318 ) -> Callable[[NendoPlugin, Any], Union[NendoTrack, NendoCollection]]: 

319 """Decorator to register a function as a track running function for a `NendoGeneratePlugin`. 

320 

321 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

322 

323 Args: 

324 func: Callable[[NendoPlugin, NendoTrack, Any], Union[NendoTrack, List[NendoTrack]]]: The function to register. 

325 

326 Returns: 

327 Callable[[NendoPlugin, Any], NendoCollection]: The wrapped function. 

328 """ 

329 

330 @functools.wraps(func) 

331 def wrapper(self, **kwargs: Any) -> Union[NendoTrack, NendoCollection]: 

332 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

333 **kwargs, 

334 ) 

335 processed_tracks = [] 

336 if track_or_collection is None: 

337 track = func(self, **kwargs) 

338 

339 # may be multiple tracks as a result 

340 if not isinstance(track, list): 

341 return track 

342 processed_tracks.extend(track) 

343 elif isinstance(track_or_collection, NendoTrack): 

344 track = func(self, track_or_collection, **kwargs) 

345 

346 # may be multiple tracks as a result 

347 if not isinstance(track, list): 

348 return track 

349 processed_tracks.extend(track) 

350 else: 

351 for track in track_or_collection.tracks(): 

352 processed_track = func(self, track, **kwargs) 

353 

354 # may be multiple tracks as a result 

355 if isinstance(processed_track, list): 

356 processed_tracks.extend(processed_track) 

357 else: 

358 processed_tracks.append(processed_track) 

359 

360 return self.nendo_instance.library.add_collection( 

361 name="tmp", 

362 track_ids=[track.id for track in processed_tracks], 

363 collection_type="temp", 

364 ) 

365 

366 return wrapper 

367 

368 

369class NendoEffectPlugin(NendoPlugin): 

370 """Basic class for nendo effects plugins. 

371 

372 Effects plugins are plugins that add effects to tracks or collections. 

373 Decorate your methods with `@NendoGeneratePlugin.run_track` to run your method 

374 on a track, use `@NendoGeneratePlugin.run_collection` to run your method on 

375 a collection and use `@NendoGeneratePlugin.run_signal` to run your method on 

376 a signal. 

377 

378 Examples: 

379 ```python 

380 from nendo import Nendo, NendoConfig 

381 

382 class MyPlugin(NendoGeneratePlugin): 

383 ... 

384 

385 @NendoAnalysisPlugin.run_signal 

386 def my_effect_function(self, signal, sr, arg_one="foo"): 

387 # add some effect to the signal 

388 new_signal = apply_effect(signal, sr, arg_one) 

389 

390 return new_signal, sr 

391 ``` 

392 """ 

393 

394 @property 

395 def plugin_type(self) -> str: 

396 """Return type of plugin.""" 

397 return "EffectPlugin" 

398 

399 @staticmethod 

400 def run_collection( 

401 func: Callable[[NendoPlugin, NendoCollection, Any], NendoCollection], 

402 ) -> Callable[[NendoPlugin, Any], NendoCollection]: 

403 """Decorator to register a function as a collection running function for a `NendoEffectPlugin`. 

404 

405 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

406 

407 Args: 

408 func: Callable[[NendoPlugin, NendoCollection, Any], NendoCollection]: The function to register. 

409 

410 Returns: 

411 Callable[[NendoPlugin, Any], NendoCollection]: The wrapped function. 

412 """ 

413 

414 @functools.wraps(func) 

415 def wrapper(self, **kwargs: Any) -> NendoCollection: 

416 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

417 **kwargs, 

418 ) 

419 if isinstance(track_or_collection, NendoCollection): 

420 return func(self, track_or_collection, **kwargs) 

421 

422 tmp_collection = self.nendo_instance.library.add_collection( 

423 name="tmp", 

424 track_ids=[track_or_collection.id], 

425 collection_type="temp", 

426 ) 

427 return func(self, tmp_collection, **kwargs) 

428 

429 return wrapper 

430 

431 @staticmethod 

432 def run_signal( 

433 func: Callable[[NendoPlugin, np.ndarray, int, Any], Tuple[np.ndarray, int]], 

434 ) -> Callable[[NendoPlugin, Any], Union[NendoTrack, NendoCollection]]: 

435 """Decorator to register a function as a signal running function for a `NendoEffectPlugin`. 

436 

437 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

438 

439 Args: 

440 func: Callable[[NendoPlugin, np.ndarray, int, Any], Tuple[np.ndarray, int]]: The function to register. 

441 

442 Returns: 

443 Callable[[NendoPlugin, Any], NendoTrack]: The wrapped function. 

444 """ 

445 

446 @functools.wraps(func) 

447 def wrapper(self, **kwargs: Any) -> Union[NendoTrack, NendoCollection]: 

448 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

449 **kwargs, 

450 ) 

451 processed_tracks = [] 

452 if isinstance(track_or_collection, NendoTrack): 

453 signal, sr = track_or_collection.signal, track_or_collection.sr 

454 new_signal, new_sr = func(self, signal, sr, **kwargs) 

455 

456 # TODO update track instead of adding a new one to the library 

457 return self.nendo_instance.library.add_related_track_from_signal( 

458 new_signal, 

459 sr, 

460 related_track_id=track_or_collection.id, 

461 ) 

462 

463 for track in track_or_collection.tracks(): 

464 new_signal, new_sr = func( 

465 self, 

466 track.signal, 

467 track.sr, 

468 **kwargs, 

469 ) 

470 

471 # TODO update track instead of adding a new one to the library 

472 processed_tracks.append( 

473 self.nendo_instance.library.add_related_track_from_signal( 

474 new_signal, 

475 track.sr, 

476 related_track_id=track.id, 

477 ), 

478 ) 

479 

480 return self.nendo_instance.library.add_collection( 

481 name="tmp", 

482 track_ids=[track.id for track in processed_tracks], 

483 collection_type="temp", 

484 ) 

485 

486 return wrapper 

487 

488 @staticmethod 

489 def run_track( 

490 func: Callable[ 

491 [NendoPlugin, NendoTrack, Any], 

492 Union[NendoTrack, List[NendoTrack]], 

493 ], 

494 ) -> Callable[[NendoPlugin, Any], NendoTrack]: 

495 """Decorator to register a function as a track running function for a `NendoEffectPlugin`. 

496 

497 This decorator wraps the function and allows a plugin user to call the plugin with either a collection or a track. 

498 

499 Args: 

500 func: Callable[[NendoPlugin, NendoTrack, Any], Union[NendoTrack, List[NendoTrack]]]: The function to register. 

501 

502 Returns: 

503 Callable[[NendoPlugin, Any], NendoTrack]: The wrapped function. 

504 """ 

505 

506 @functools.wraps(func) 

507 def wrapper(self, **kwargs: Any) -> Union[NendoTrack, NendoCollection]: 

508 track_or_collection, kwargs = self._get_track_or_collection_from_args( 

509 **kwargs, 

510 ) 

511 if isinstance(track_or_collection, NendoTrack): 

512 return func(self, track_or_collection, **kwargs) 

513 

514 processed_tracks = [ 

515 func(self, track, **kwargs) for track in track_or_collection.tracks() 

516 ] 

517 return self.nendo_instance.library.add_collection( 

518 name="tmp", 

519 track_ids=[track.id for track in processed_tracks], 

520 collection_type="temp", 

521 ) 

522 

523 return wrapper 

524 

525 

526class NendoLibraryPlugin(NendoPlugin): 

527 """Basic class for nendo library plugins.""" 

528 

529 model_config = ConfigDict( 

530 arbitrary_types_allowed=True, 

531 ) 

532 

533 storage_driver: Optional[NendoStorage] = None 

534 

535 # ========================== 

536 # 

537 # TRACK MANAGEMENT FUNCTIONS 

538 # 

539 # ========================== 

540 

541 @abstractmethod 

542 def add_track( 

543 self, 

544 file_path: Union[FilePath, str], 

545 track_type: str = "track", 

546 copy_to_library: Optional[bool] = None, 

547 skip_duplicate: Optional[bool] = None, 

548 user_id: Optional[uuid.UUID] = None, 

549 meta: Optional[dict] = None, 

550 ) -> NendoTrack: 

551 """Add the track given by path to the library. 

552 

553 Args: 

554 file_path (Union[FilePath, str]): Path to the file to add as track. 

555 track_type (str): Type of the track. Defaults to "track". 

556 copy_to_library (bool, optional): Flag that specifies whether 

557 the file should be copied into the library directory. 

558 Defaults to None. 

559 skip_duplicate (bool, optional): Flag that specifies whether a 

560 file should be added that already exists in the library, based on its 

561 file checksum. Defaults to None. 

562 user_id (UUID, optional): ID of user adding the track. 

563 meta (dict, optional): Metadata to attach to the track upon adding. 

564 

565 Returns: 

566 NendoTrack: The track that was added to the library. 

567 """ 

568 raise NotImplementedError 

569 

570 @abstractmethod 

571 def add_track_from_signal( 

572 self, 

573 signal: np.ndarray, 

574 sr: int, 

575 track_type: str = "track", 

576 user_id: Optional[uuid.UUID] = None, 

577 meta: Optional[Dict[str, Any]] = None, 

578 ) -> NendoTrack: 

579 """Add a track to the library that is described by the given signal. 

580 

581 Args: 

582 signal (np.ndarray): The numpy array containing the audio signal. 

583 sr (int): Sample rate 

584 track_type (str): Track type. Defaults to "track". 

585 user_id (UUID, optional): The ID of the user adding the track. 

586 meta (Dict[str, Any], optional): Track metadata. Defaults to {}. 

587 

588 Returns: 

589 schema.NendoTrack: The added NendoTrack 

590 """ 

591 raise NotImplementedError 

592 

593 @abstractmethod 

594 def add_related_track( 

595 self, 

596 file_path: Union[FilePath, str], 

597 related_track_id: Union[str, uuid.UUID], 

598 track_type: str = "str", 

599 user_id: Optional[Union[str, uuid.UUID]] = None, 

600 track_meta: Optional[Dict[str, Any]] = None, 

601 relationship_type: str = "relationship", 

602 meta: Optional[Dict[str, Any]] = None, 

603 ) -> NendoTrack: 

604 """Add track that is related to another `NendoTrack`. 

605 

606 Add the track found in the given path to the library and create a relationship 

607 in the new track that points to the track identified by related_to. 

608 

609 Args: 

610 file_path (Union[FilePath, str]): Path to the file to add as track. 

611 related_track_id (Union[str, uuid.UUID]): ID of the related track. 

612 track_type (str): Track type. Defaults to "track". 

613 user_id (Union[str, UUID], optional): ID of the user adding the track. 

614 track_meta (dict, optional): Dictionary containing the track metadata. 

615 relationship_type (str): Type of the relationship. 

616 Defaults to "relationship". 

617 meta (dict): Dictionary containing metadata about 

618 the relationship. Defaults to {}. 

619 

620 Returns: 

621 NendoTrack: The track that was added to the Library 

622 """ 

623 raise NotImplementedError 

624 

625 @abstractmethod 

626 def add_related_track_from_signal( 

627 self, 

628 signal: np.ndarray, 

629 sr: int, 

630 related_track_id: Union[str, uuid.UUID], 

631 track_type: str = "track", 

632 user_id: Optional[uuid.UUID] = None, 

633 track_meta: Optional[Dict[str, Any]] = None, 

634 relationship_type: str = "relationship", 

635 meta: Optional[Dict[str, Any]] = None, 

636 ) -> NendoTrack: 

637 """Add signal as track that is related to another `NendoTrack`. 

638 

639 Add the track represented by the provided signal to the library and create a 

640 relationship in the new track that points to the track passed as related_to. 

641 

642 Args: 

643 signal (np.ndarray): Waveform of the track in numpy array form. 

644 sr (int): Sampling rate of the waveform. 

645 related_track_id (str | uuid.UUID): ID to which the relationship 

646 should point to. 

647 track_type (str): Track type. Defaults to "track". 

648 user_id (UUID, optional): ID of the user adding the track. 

649 track_meta (dict, optional): Dictionary containing the track metadata. 

650 relationship_type (str): Type of the relationship. 

651 Defaults to "relationship". 

652 meta (dict): Dictionary containing metadata about 

653 the relationship. Defaults to {}. 

654 

655 Returns: 

656 NendoTrack: The added track with the relationship. 

657 """ 

658 raise NotImplementedError 

659 

660 @abstractmethod 

661 def add_tracks( 

662 self, 

663 path: Union[DirectoryPath, str], 

664 track_type: str = "track", 

665 user_id: Optional[Union[str, uuid.UUID]] = None, 

666 copy_to_library: Optional[bool] = None, 

667 skip_duplicate: bool = True, 

668 ) -> NendoCollection: 

669 """Scan the provided path and upsert the information into the library. 

670 

671 Args: 

672 path (Union[DirectoryPath, str]): Path to the directory to scan. 

673 track_type (str): Track type. Defaults to "track". 

674 user_id (UUID, optional): The ID of the user adding the tracks. 

675 copy_to_library (Optional[bool], optional): Flag that specifies whether 

676 the file should be copied into the library directory. 

677 Defaults to None. 

678 skip_duplicate (Optional[bool], optional): Flag that specifies whether a 

679 file should be added that already exists in the library, based on its 

680 file checksum. Defaults to None. 

681 

682 Returns: 

683 collection (NendoCollection): The collection of tracks that were added to the Library 

684 """ 

685 raise NotImplementedError 

686 

687 @abstractmethod 

688 def update_track( 

689 self, 

690 track: NendoTrack, 

691 ) -> NendoTrack: 

692 """Updates the given collection by storing it to the database. 

693 

694 Args: 

695 track (NendoTrack): The track to be stored to the database. 

696 

697 Raises: 

698 NendoTrackNotFoundError: If the track passed to the function 

699 does not exist in the database. 

700 

701 Returns: 

702 NendoTrack: The updated track. 

703 """ 

704 raise NotImplementedError 

705 

706 @abstractmethod 

707 def add_plugin_data( 

708 self, 

709 track_id: Union[str, uuid.UUID], 

710 plugin_name: str, 

711 plugin_version: str, 

712 key: str, 

713 value: str, 

714 user_id: Optional[Union[str, uuid.UUID]] = None, 

715 replace: bool = False, 

716 ) -> NendoPluginData: 

717 """Add plugin data to a NendoTrack and persist changes into the DB. 

718 

719 Args: 

720 track_id (Union[str, uuid.UUID]): ID of the track to which 

721 the plugin data should be added. 

722 plugin_name (str): Name of the plugin. 

723 plugin_version (str): Version of the plugin. 

724 key (str): Key under which to save the data. 

725 value (str): Data to save. 

726 user_id (uuid4, optional): ID of user adding the plugin data. 

727 replace (bool, optional): Flag that determines whether 

728 the last existing data point for the given plugin name and -version 

729 is overwritten or not. Defaults to False. 

730 

731 Returns: 

732 NendoPluginData: The saved plugin data as a NendoPluginData object. 

733 """ 

734 

735 @abstractmethod 

736 def get_track(self, track_id: Any) -> NendoTrack: 

737 """Get a single track from the library by ID. 

738 

739 If no track with the given ID was found, return None. 

740 

741 Args: 

742 track_id (Any): The ID of the track to get 

743 

744 Returns: 

745 track (NendoTrack): The track with the given ID 

746 """ 

747 raise NotImplementedError 

748 

749 @abstractmethod 

750 @NendoPlugin.stream_output 

751 def get_tracks( 

752 self, 

753 user_id: Optional[Union[str, uuid.UUID]] = None, 

754 order_by: Optional[str] = None, 

755 order: str = "asc", 

756 limit: Optional[int] = None, 

757 offset: Optional[int] = None, 

758 ) -> Union[List, Iterator]: 

759 """Get tracks based on the given query parameters. 

760 

761 Args: 

762 user_id (Union[str, UUID], optional): ID of user getting the tracks. 

763 order_by (Optional[str]): Key used for ordering the results. 

764 order (Optional[str]): Order in which to retrieve results ("asc" or "desc"). 

765 limit (Optional[int]): Limit the number of returned results. 

766 offset (Optional[int]): Offset into the paginated results (requires limit). 

767 

768 Returns: 

769 Union[List, Iterator]: List or generator of tracks, depending on the 

770 configuration variable stream_mode 

771 """ 

772 raise NotImplementedError 

773 

774 @abstractmethod 

775 def get_related_tracks( 

776 self, 

777 track_id: Union[str, uuid.UUID], 

778 user_id: Optional[Union[str, uuid.UUID]] = None, 

779 order_by: Optional[str] = None, 

780 order: Optional[str] = "asc", 

781 limit: Optional[int] = None, 

782 offset: Optional[int] = None, 

783 ) -> Union[List, Iterator]: 

784 """Get tracks with a relationship to the track with track_id. 

785 

786 Args: 

787 track_id (str): ID of the track to be searched for. 

788 user_id (Union[str, UUID], optional): The user ID to filter for. 

789 order_by (Optional[str]): Key used for ordering the results. 

790 order (Optional[str]): Order in which to retrieve results ("asc" or "desc"). 

791 limit (Optional[int]): Limit the number of returned results. 

792 offset (Optional[int]): Offset into the paginated results (requires limit). 

793 

794 Returns: 

795 Union[List, Iterator]: List or generator of tracks, depending on the 

796 configuration variable stream_mode 

797 """ 

798 raise NotImplementedError 

799 

800 @abstractmethod 

801 def find_tracks( 

802 self, 

803 value: str, 

804 user_id: Optional[Union[str, uuid.UUID]] = None, 

805 order_by: Optional[str] = None, 

806 limit: Optional[int] = None, 

807 offset: Optional[int] = None, 

808 ) -> Union[List, Iterator]: 

809 """Find tracks by searching for a string through the resource metadata. 

810 

811 Args: 

812 value (str): The search value to filter by. 

813 user_id (Union[str, UUID], optional): The user ID to filter for. 

814 order_by (str, optional): Ordering. 

815 limit (str, optional): Pagination limit. 

816 offset (str, optional): Pagination offset. 

817 

818 Returns: 

819 Union[List, Iterator]: List or generator of tracks, depending on the 

820 configuration variable stream_mode 

821 """ 

822 raise NotImplementedError 

823 

824 @abstractmethod 

825 def filter_tracks( 

826 self, 

827 filters: Optional[dict] = None, 

828 resource_filters: Optional[Dict[str, Any]] = None, 

829 track_type: Optional[Union[str, List[str]]] = None, 

830 user_id: Optional[Union[str, uuid.UUID]] = None, 

831 collection_id: Optional[Union[str, uuid.UUID]] = None, 

832 plugin_names: Optional[List[str]] = None, 

833 order_by: Optional[str] = None, 

834 order: str = "asc", 

835 limit: Optional[int] = None, 

836 offset: Optional[int] = None, 

837 ) -> Union[List, Iterator]: 

838 """Obtain tracks from the db by filtering over plugin data. 

839 

840 Args: 

841 filters (Optional[dict]): Dictionary containing the filters to apply. 

842 Defaults to None. 

843 resource_filters (dict): Dictionary containing the keywords to search for 

844 over the track.resource.meta field. The dictionary's values 

845 should contain singular search tokens and the keys currently have no 

846 effect but might in the future. Defaults to {}. 

847 track_type (Union[str, List[str]], optional): Track type to filter for. 

848 Can be a singular type or a list of types. Defaults to None. 

849 user_id (Union[str, UUID], optional): The user ID to filter for. 

850 collection_id (Union[str, uuid.UUID], optional): Collection id to 

851 which the filtered tracks must have a relationship. Defaults to None. 

852 plugin_names (list, optional): List used for applying the filter only to 

853 data of certain plugins. If None, all plugin data related to the track 

854 is used for filtering. 

855 order_by (str, optional): Key used for ordering the results. 

856 order (str, optional): Ordering ("asc" vs "desc"). Defaults to "asc". 

857 limit (int, optional): Limit the number of returned results. 

858 offset (int, optional): Offset into the paginated results (requires limit). 

859 

860 Returns: 

861 Union[List, Iterator]: List or generator of tracks, depending on the 

862 configuration variable stream_mode 

863 """ 

864 raise NotImplementedError 

865 

866 @abstractmethod 

867 def remove_track( 

868 self, 

869 track_id: Union[str, uuid.UUID], 

870 remove_relationships: bool = False, 

871 remove_plugin_data: bool = False, 

872 remove_resources: bool = True, 

873 user_id: Optional[Union[str, uuid.UUID]] = None, 

874 ) -> bool: 

875 """Delete track from library by ID. 

876 

877 Args: 

878 track_id (Union[str, uuid.UUID]): The ID of the track to remove. 

879 remove_relationships (bool): 

880 If False prevent deletion if related tracks exist, 

881 if True delete relationships together with the object. 

882 remove_plugin_data (bool): 

883 If False prevent deletion if related plugin data exist, 

884 if True delete plugin data together with the object. 

885 remove_resources (bool): 

886 If False, keep the related resources, e.g. files, 

887 if True, delete the related resources. 

888 user_id (Union[str, UUID], optional): The ID of the user 

889 owning the track. 

890 

891 Returns: 

892 success (bool): True if removal was successful, False otherwise 

893 """ 

894 raise NotImplementedError 

895 

896 def export_track( 

897 self, 

898 track_id: Union[str, uuid.UUID], 

899 file_path: str, 

900 file_format: str = "wav", 

901 ) -> str: 

902 """Export the track to a file. 

903 

904 Args: 

905 track_id (Union[str, uuid.UUID]): The ID of the target track to export. 

906 file_path (str): Path to the exported file. Can be either a full 

907 file path or a directory path. If a directory path is given, 

908 a filename will be automatically generated and the file will be 

909 exported to the format specified as file_format. If a full file 

910 path is given, the format will be deduced from the path and the 

911 file_format parameter will be ignored. 

912 file_format (str, optional): Format of the exported track. Ignored if 

913 file_path is a full file path. Defaults to "wav". 

914 

915 Returns: 

916 str: The path to the exported file. 

917 """ 

918 raise NotImplementedError 

919 

920 # =============================== 

921 # 

922 # COLLECTION MANAGEMENT FUNCTIONS 

923 # 

924 # =============================== 

925 

926 @abstractmethod 

927 def add_collection( 

928 self, 

929 name: str, 

930 user_id: Optional[Union[str, uuid.UUID]] = None, 

931 track_ids: Optional[List[Union[str, uuid.UUID]]] = None, 

932 description: str = "", 

933 collection_type: str = "collection", 

934 meta: Optional[Dict[str, Any]] = None, 

935 ) -> NendoCollection: 

936 """Creates a new collection and saves it into the DB. 

937 

938 Args: 

939 track_ids (List[Union[str, uuid.UUID]]): List of track ids 

940 to be added to the collection. 

941 name (str): Name of the collection. 

942 user_id (UUID, optional): The ID of the user adding the collection. 

943 description (str): Description of the collection. 

944 collection_type (str): Type of the collection. Defaults to "collection". 

945 meta (Dict[str, Any]): Metadata of the collection. 

946 

947 Returns: 

948 schema.NendoCollection: The newly created NendoCollection object. 

949 """ 

950 raise NotImplementedError 

951 

952 @abstractmethod 

953 def add_related_collection( 

954 self, 

955 track_ids: List[Union[str, uuid.UUID]], 

956 collection_id: Union[str, uuid.UUID], 

957 name: str, 

958 description: str = "", 

959 user_id: Optional[Union[str, uuid.UUID]] = None, 

960 relationship_type: str = "relationship", 

961 meta: Optional[Dict[str, Any]] = None, 

962 ) -> NendoCollection: 

963 """Add a collection that is related to another `NendoCollection`. 

964 

965 Add a new collection with a relationship to and from the collection 

966 with the given collection_id. 

967 

968 Args: 

969 track_ids (List[Union[str, uuid.UUID]]): List of track ids. 

970 collection_id (Union[str, uuid.UUID]): Existing collection id. 

971 name (str): Name of the new related collection. 

972 description (str): Description of the new related collection. 

973 user_id (UUID, optional): The ID of the user adding the collection. 

974 relationship_type (str): Type of the relationship. 

975 meta (Dict[str, Any]): Meta of the new related collection. 

976 

977 Returns: 

978 schema.NendoCollection: The newly added NendoCollection object. 

979 """ 

980 raise NotImplementedError 

981 

982 @abstractmethod 

983 def add_track_to_collection( 

984 self, 

985 track_id: Union[str, uuid.UUID], 

986 collection_id: Union[str, uuid.UUID], 

987 position: Optional[int] = None, 

988 meta: Optional[Dict[str, Any]] = None, 

989 ) -> NendoCollection: 

990 """Creates a relationship from the track to the collection. 

991 

992 Args: 

993 collection_id (Union[str, uuid.UUID]): Collection id. 

994 track_id (Union[str, uuid.UUID]): Track id. 

995 

996 Returns: 

997 schema.NendoCollection: The updated NendoCollection object. 

998 """ 

999 raise NotImplementedError 

1000 

1001 @abstractmethod 

1002 def get_collection_tracks( 

1003 self, 

1004 collection_id: Union[str, uuid.UUID], 

1005 ) -> List[NendoTrack]: 

1006 """Get all tracks of a collection. 

1007 

1008 Args: 

1009 collection_id (Union[str, uuid.UUID]): Collection id. 

1010 

1011 Returns: 

1012 List[schema.NendoTrack]: List of tracks in the collection. 

1013 """ 

1014 raise NotImplementedError 

1015 

1016 @abstractmethod 

1017 def get_collection( 

1018 self, 

1019 collection_id: uuid.uuid4, 

1020 details: bool = True, 

1021 ) -> Union[NendoCollection, NendoCollectionSlim]: 

1022 """Get a collection by its ID. 

1023 

1024 Args: 

1025 collection_id (uuid.uuid4): ID of the target collection. 

1026 details (bool, optional): Flag that defines whether the result should 

1027 contain all fields or only a Defaults to True. 

1028 

1029 Returns: 

1030 Union[NendoCollection, NendoCollectionSlim]: Collection object, compact 

1031 version if the `details` flag has been set to False. 

1032 """ 

1033 raise NotImplementedError 

1034 

1035 @abstractmethod 

1036 @NendoPlugin.stream_output 

1037 def get_collections( 

1038 self, 

1039 user_id: Optional[Union[str, uuid.UUID]] = None, 

1040 order_by: Optional[str] = None, 

1041 order: Optional[str] = "asc", 

1042 limit: Optional[int] = None, 

1043 offset: Optional[int] = None, 

1044 ) -> Union[List, Iterator]: 

1045 """Get a list of collections. 

1046 

1047 Args: 

1048 user_id (Union[str, UUID], optional): The user ID to filter for. 

1049 order_by (Optional[str]): Key used for ordering the results. 

1050 order (Optional[str]): Order in which to retrieve results ("asc" or "desc"). 

1051 limit (Optional[int]): Limit the number of returned results. 

1052 offset (Optional[int]): Offset into the paginated results (requires limit). 

1053 

1054 Returns: 

1055 Union[List, Iterator]: List or generator of collections, depending on the 

1056 configuration variable stream_mode 

1057 """ 

1058 raise NotImplementedError 

1059 

1060 @abstractmethod 

1061 def find_collections( 

1062 self, 

1063 value: str = "", 

1064 user_id: Optional[Union[str, uuid.UUID]] = None, 

1065 order_by: Optional[str] = None, 

1066 order: Optional[str] = "asc", 

1067 limit: Optional[int] = None, 

1068 offset: Optional[int] = None, 

1069 ) -> Union[List, Iterator]: 

1070 """Find collections with a search term in the description or meta field. 

1071 

1072 Args: 

1073 value (str): Term to be searched for in the description and meta field. 

1074 user_id (Union[str, UUID], optional): The user ID to filter for. 

1075 order_by (Optional[str]): Key used for ordering the results. 

1076 order (Optional[str]): Order in which to retrieve results ("asc" or "desc"). 

1077 limit (Optional[int]): Limit the number of returned results. 

1078 offset (Optional[int]): Offset into the paginated results (requires limit). 

1079 

1080 Returns: 

1081 Union[List, Iterator]: List or generator of collections, depending on the 

1082 configuration variable stream_mode 

1083 """ 

1084 raise NotImplementedError 

1085 

1086 @abstractmethod 

1087 def get_related_collections( 

1088 self, 

1089 collection_id: Union[str, uuid.UUID], 

1090 user_id: Optional[Union[str, uuid.UUID]] = None, 

1091 order_by: Optional[str] = None, 

1092 order: Optional[str] = "asc", 

1093 limit: Optional[int] = None, 

1094 offset: Optional[int] = None, 

1095 ) -> Union[List, Iterator]: 

1096 """Get collections with a relationship to the collection with collection_id. 

1097 

1098 Args: 

1099 collection_id (str): ID of the collection to be searched for. 

1100 user_id (Union[str, UUID], optional): The user ID to filter for. 

1101 order_by (Optional[str]): Key used for ordering the results. 

1102 order (Optional[str]): Order in which to retrieve results ("asc" or "desc"). 

1103 limit (Optional[int]): Limit the number of returned results. 

1104 offset (Optional[int]): Offset into the paginated results (requires limit). 

1105 

1106 Returns: 

1107 Union[List, Iterator]: List or generator of collections, depending on the 

1108 configuration variable stream_mode 

1109 """ 

1110 raise NotImplementedError 

1111 

1112 @abstractmethod 

1113 def update_collection( 

1114 self, 

1115 collection: NendoCollection, 

1116 ) -> NendoCollection: 

1117 """Updates the given collection by storing it to the database. 

1118 

1119 Args: 

1120 collection (NendoCollection): The collection to store. 

1121 

1122 Raises: 

1123 NendoCollectionNotFoundError: If the collection with 

1124 the given ID was not found. 

1125 

1126 Returns: 

1127 NendoCollection: The updated collection. 

1128 """ 

1129 raise NotImplementedError 

1130 

1131 @abstractmethod 

1132 def remove_track_from_collection( 

1133 self, 

1134 track_id: Union[str, uuid.UUID], 

1135 collection_id: Union[str, uuid.UUID], 

1136 ) -> bool: 

1137 """Deletes a relationship from the track to the collection. 

1138 

1139 Args: 

1140 collection_id (Union[str, uuid.UUID]): Collection id. 

1141 track_id (Union[str, uuid.UUID]): Track id. 

1142 

1143 Returns: 

1144 success (bool): True if removal was successful, False otherwise. 

1145 """ 

1146 raise NotImplementedError 

1147 

1148 @abstractmethod 

1149 def remove_collection( 

1150 self, 

1151 collection_id: uuid.UUID, 

1152 remove_relationships: bool = False, 

1153 ) -> bool: 

1154 """Deletes the collection identified by `collection_id`. 

1155 

1156 Args: 

1157 collection_id (uuid.UUID): ID of the collection to remove. 

1158 remove_relationships (bool, optional): 

1159 If False prevent deletion if related tracks exist, 

1160 if True delete relationships together with the object. 

1161 Defaults to False. 

1162 

1163 Returns: 

1164 bool: True if deletion was successful, False otherwise. 

1165 """ 

1166 raise NotImplementedError 

1167 

1168 def export_collection( 

1169 self, 

1170 collection_id: Union[str, uuid.UUID], 

1171 export_path: str, 

1172 filename_suffix: str = "_nendo", 

1173 file_format: str = "wav", 

1174 ) -> List[str]: 

1175 """Export the track to a file. 

1176 

1177 Args: 

1178 collection_id (Union[str, uuid.UUID]): The ID of the target 

1179 collection to export. 

1180 export_path (str): Path to a directory into which the collection's tracks 

1181 should be exported. 

1182 filename_suffix (str): The suffix which should be appended to each 

1183 exported track's filename. 

1184 file_format (str, optional): Format of the exported track. Ignored if 

1185 file_path is a full file path. Defaults to "wav". 

1186 

1187 Returns: 

1188 List[str]: A list with all full paths to the exported files. 

1189 """ 

1190 raise NotImplementedError 

1191 

1192 # ========================= 

1193 # 

1194 # BLOB MANAGEMENT FUNCTIONS 

1195 # 

1196 # ========================= 

1197 

1198 @abstractmethod 

1199 def store_blob( 

1200 self, 

1201 file_path: Union[FilePath, str], 

1202 user_id: Optional[Union[str, uuid.UUID]] = None, 

1203 ) -> NendoBlob: 

1204 """Stores a blob of data. 

1205 

1206 Args: 

1207 file_path (Union[FilePath, str]): Path to the file to store as blob. 

1208 user_id (Optional[Union[str, uuid.UUID]], optional): ID of the user 

1209 who's storing the file to blob. 

1210 

1211 Returns: 

1212 schema.NendoBlob: The stored blob. 

1213 """ 

1214 raise NotImplementedError 

1215 

1216 @abstractmethod 

1217 def store_blob_from_bytes( 

1218 self, 

1219 data: bytes, 

1220 user_id: Optional[Union[str, uuid.UUID]] = None, 

1221 ) -> NendoBlob: 

1222 """Stores a data of type `bytes` to a blob. 

1223 

1224 Args: 

1225 data (bytes): The blob to store. 

1226 user_id (Optional[Union[str, uuid.UUID]], optional): ID of the user 

1227 who's storing the bytes to blob. 

1228 

1229 Returns: 

1230 schema.NendoBlob: The stored blob. 

1231 """ 

1232 raise NotImplementedError 

1233 

1234 @abstractmethod 

1235 def load_blob( 

1236 self, 

1237 blob_id: uuid.UUID, 

1238 user_id: Optional[Union[str, uuid.UUID]] = None, 

1239 ) -> NendoBlob: 

1240 """Loads a blob of data into memory. 

1241 

1242 Args: 

1243 blob_id (uuid.UUID): The UUID of the blob. 

1244 user_id (Optional[Union[str, uuid.UUID]], optional): ID of the user 

1245 who's loading the blob. 

1246 

1247 Returns: 

1248 schema.NendoBlob: The loaded blob. 

1249 """ 

1250 raise NotImplementedError 

1251 

1252 @abstractmethod 

1253 def remove_blob( 

1254 self, 

1255 blob_id: uuid.UUID, 

1256 remove_resources: bool = True, 

1257 user_id: Optional[uuid.UUID] = None, 

1258 ) -> bool: 

1259 """Deletes a blob of data. 

1260 

1261 Args: 

1262 blob_id (uuid.UUID): The UUID of the blob. 

1263 remove_resources (bool): If True, remove associated resources. 

1264 user_id (Optional[Union[str, uuid.UUID]], optional): ID of the user 

1265 who's removing the blob. 

1266 

1267 Returns: 

1268 success (bool): True if removal was successful, False otherwise 

1269 """ 

1270 raise NotImplementedError 

1271 

1272 # ================================== 

1273 # 

1274 # MISCELLANEOUS MANAGEMENT FUNCTIONS 

1275 # 

1276 # ================================== 

1277 

1278 def get_track_or_collection( 

1279 self, 

1280 target_id: Union[str, uuid.UUID], 

1281 ) -> Union[NendoTrack, NendoCollection]: 

1282 """Return a track or a collection based on the given target_id. 

1283 

1284 Args: 

1285 target_id (Union[str, uuid.UUID]): The target ID to obtain. 

1286 

1287 Returns: 

1288 Union[NendoTrack, NendoCollection]: The track or the collection. 

1289 """ 

1290 target_id = ensure_uuid(target_id) 

1291 collection = self.get_collection(target_id) 

1292 if collection is not None: 

1293 return collection 

1294 

1295 # assume the id is a track id 

1296 return self.get_track(target_id) 

1297 

1298 def verify(self, action: Optional[str] = None, user_id: str = "") -> None: 

1299 """Verify the library's integrity. 

1300 

1301 Args: 

1302 action (Optional[str], optional): Default action to choose when an 

1303 inconsistency is detected. Choose between (i)gnore and (r)emove. 

1304 """ 

1305 original_config = {} 

1306 try: 

1307 original_config["stream_mode"] = self.config.stream_mode 

1308 original_config["stream_chunk_size"] = self.config.stream_chunk_size 

1309 self.config.stream_mode = False 

1310 self.config.stream_chunk_size = 16 

1311 for track in self.get_tracks(): 

1312 if not self.storage_driver.file_exists( 

1313 file_name=track.resource.file_name, 

1314 user_id=user_id, 

1315 ): 

1316 action = ( 

1317 action 

1318 or input( 

1319 f"Inconsistency detected: {track.resource.src} " 

1320 "does not exist. Please choose an action:\n" 

1321 "(i) ignore - (r) remove", 

1322 ).lower() 

1323 ) 

1324 if action == "i": 

1325 self.logger.warning( 

1326 "Detected missing file " 

1327 f"{track.resource.src} but instructed " 

1328 "to ignore.", 

1329 ) 

1330 continue 

1331 if action == "r": 

1332 self.logger.info( 

1333 f"Removing track with ID {track.id} " 

1334 f"due to missing file {track.resource.src}", 

1335 ) 

1336 self.remove_track( 

1337 track_id=track.id, 

1338 remove_plugin_data=True, 

1339 remove_relationships=True, 

1340 remove_resources=False, 

1341 ) 

1342 for library_file in self.storage_driver.list_files(user_id=user_id): 

1343 file_without_ext = os.path.splitext(library_file)[0] 

1344 if len(self.find_tracks(value=file_without_ext)) == 0: 

1345 action = ( 

1346 action 

1347 or input( 

1348 f"Inconsistency detected: File {library_file} " 

1349 "cannot be fonud in database. Please choose an action:\n" 

1350 "(i) ignore - (r) remove", 

1351 ).lower() 

1352 ) 

1353 if action == "i": 

1354 self.logger.warning( 

1355 f"Detected orphaned file {library_file} " 

1356 f"but instructed to ignore.", 

1357 ) 

1358 continue 

1359 if action == "r": 

1360 self.logger.info(f"Removing orphaned file {library_file}") 

1361 self.storage_driver.remove_file( 

1362 file_name=library_file, 

1363 user_id=user_id, 

1364 ) 

1365 

1366 finally: 

1367 self.config.stream_mode = original_config["stream_mode"] 

1368 self.config.stream_chunk_size = original_config["stream_chunk_size"] 

1369 

1370 @abstractmethod 

1371 def reset( 

1372 self, 

1373 force: bool = False, 

1374 user_id: Optional[Union[str, uuid.UUID]] = None, 

1375 ) -> None: 

1376 """Reset the nendo library. 

1377 

1378 Erase all tracks, collections and relationships. 

1379 Ask before erasing. 

1380 

1381 Args: 

1382 force (bool, optional): Flag that specifies whether to ask the user for 

1383 confirmation of the operation. Default is to ask the user. 

1384 user_id (Optional[Union[str, uuid.UUID]], optional): ID of the user 

1385 who's resetting the library. If none is given, the configured 

1386 nendo default user will be used. 

1387 """ 

1388 raise NotImplementedError 

1389 

1390 def __str__(self): 

1391 output = f"{self.plugin_name}, version {self.plugin_version}:\n" 

1392 output += f"{len(self)} tracks" 

1393 return output 

1394 

1395 @property 

1396 def plugin_type(self) -> str: 

1397 """Return type of plugin.""" 

1398 return "LibraryPlugin"