Skip to content

Release 0.7.0

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Jul 21:05

This is a pretty big one! This release introduces a major internal refactor, replacing loosely structured identifiers with strongly typed models, modernizing service discovery, and simplifying the core architecture. While most of these changes are internal, they provide a stronger foundation for future development and improve the library's extensibility and maintainability.

The release also introduces SyncedPlaylist, a new API for bidirectional, conflict-free playlist synchronization across multiple services, alongside new examples, quality-of-life improvements, and bug fixes. Due to the architectural changes, this release includes several breaking changes for developers extending or integrating with plistsync.

Breaking Changes

  • Refactored track identifier handling across the codebase by replacing service-specific TrackIds dictionaries with strongly typed, immutable TrackID dataclasses for each service (e.g. Spotify, Plex).
  • Refactored playlist identifier handling across the codebase by replacing service-specific PlaylistIds dictionaries with strongly typed, immutable PlaylistID dataclasses for each service (e.g. Spotify, Plex).
    • Removed obsolete playlist ID extraction utilities that are no longer required under the new identifier system.
    • Updated all playlist operations and internal synchronization flows to use the new typed identifier model consistently.

This improves type safety, clarity, and extensibility for playlist identification and lookup operations. All playlist-related APIs and synchronization logic now operate on explicit identifier types instead of loosely structured dictionaries.

  • Replaced the import-based ServiceRegistry with entry-point based service discovery via ServiceLoader (ServiceLoader.get(name) / ServiceLoader.all()). Services are now discovered through the plistsync.services entry-point group without importing all service modules, and services with missing optional dependencies are skipped gracefully.
  • Removed the track_cls, library_cls, playlist_cls and playlist_id_cls attributes from Service implementations; registered classes are resolved through the Registry instead.
  • OfflinePlaylist now takes a typed PlaylistID (id=...) instead of an id_serial string.

Added

  • Added typed Service marker class with a module-inferred name property and registry-backed accessors (library(), tracks(), track_ids(), playlist_ids()), enabling single-handle access to all a service's core types. (#95)
  • Added generic Registry base class that auto-registers concrete implementations by service name at class-definition time. Track, Library, TrackID, and PlaylistID are now registry roots, making service classes discoverable simply by importing their module.
  • Added SerialID base class unifying the identifier contract (parse/serial) with a service-derived namespace prefix() that is enforced on construction. TrackID.from_serial() and PlaylistID.from_serial() resolve a serial string to the correct service's identifier class via ServiceLoader.
  • Added a migration example to fully copy a playlist from an arbitrary service to another. (#60)
  • Added SyncedPlaylist class for bidirectional playlist synchronization across an arbitrary number of services. This class encapsulates the logic for detecting differences, applying updates, and maintaining a consistent state across all linked services. (#100)
    • Conflict-free state replication via a CRDT (Fugue): each linked playlist acts as a replica, so changes made directly on a service (outside plistsync) merge cleanly without conflicts.
    • Phased sync() pipeline: refresh() pulls the current state from each service, merge() reconciles external additions/removals/reorders into the internal collection, enrich() cross-links tracks between services and backfills missing identifiers (e.g. ISRCs), and push() writes the resolved state back to every linked playlist.
    • Track association is availability-aware: tracks that cannot be resolved on a given service are skipped for that service instead of being dropped from the internal collection.
    • Added SyncedPlaylistID, a UUID-based playlist identifier for synced playlists.
    • Added serialization for SyncedPlaylists, save_to and load_from methods (#104)

Fixed

  • When syncing playlists to traktor, we now insert missing tracks into the library collection. This avoids those track to disappear from the playlist when launching Traktor after the sync. (#54)
  • Adjusted spotify api layer for deprecations

Changes

  • Introduced a OfflineTrack class mirroring the idea behind the OfflinePlaylist. This allows us to preserve track information even after deletion from the remote service, and to keep the Playlist class hierarchy cleanly separated between in-memory and service-synced implementations.

📦 PyPI: https://pypi.org/project/plistsync/0.7.0/