Source code for rekordbox_set_list_manager.services.project_io

"""Save and load .setmgr project files."""

from pathlib import Path

from pydantic import BaseModel, ValidationError

from rekordbox_set_list_manager.models.project import Project
from rekordbox_set_list_manager.utils.constants import PROJECT_FILE_EXTENSION

_FORMAT_VERSION = "1"


[docs] class ProjectIOError(Exception): """Raised when a project file cannot be read or written."""
class _ProjectFile(BaseModel): """On-disk envelope that wraps a Project with format versioning.""" version: str = _FORMAT_VERSION project: Project
[docs] def save_project(project: Project, path: Path) -> None: """Serialise *project* and write it to *path*. The path should use the :data:`~rekordbox_set_list_manager.utils.constants .PROJECT_FILE_EXTENSION` suffix, but this is not enforced so callers can write to temp paths during saves. Parameters ---------- project : Project The project to serialise. path : Path Destination file path to write. """ envelope = _ProjectFile(project=project) try: path.write_text(envelope.model_dump_json(indent=2), encoding="utf-8") except OSError as exc: raise ProjectIOError(f"Could not write project to {path}: {exc}") from exc
[docs] def load_project(path: Path) -> Project: """Read a .setmgr file from *path* and deserialise it into a :class:`Project`. Parameters ---------- path : Path Path to the ``.setmgr`` project file. Returns ------- Project The deserialised project. Raises ------ ProjectIOError If the file cannot be read or parsed. """ if not path.exists(): raise ProjectIOError(f"Project file not found: {path}") suffix = path.suffix.lower() if suffix != PROJECT_FILE_EXTENSION: raise ProjectIOError( f"Unexpected file extension '{suffix}'; expected '{PROJECT_FILE_EXTENSION}'" ) try: raw = path.read_text(encoding="utf-8") except OSError as exc: raise ProjectIOError(f"Could not read project file {path}: {exc}") from exc try: envelope = _ProjectFile.model_validate_json(raw) except ValidationError as exc: raise ProjectIOError(f"Invalid project file {path}: {exc}") from exc if envelope.version != _FORMAT_VERSION: raise ProjectIOError( f"Unsupported project file version '{envelope.version}' (expected '{_FORMAT_VERSION}')" ) return envelope.project