Skip to content

Commit

Permalink
remove processor abstraction layer + update docstrings (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
geo-martino authored Apr 3, 2024
1 parent 2ea38ce commit 18af39c
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 98 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ Install through pip using one of the following commands:
musify.report
musify.types
musify.utils

genindex
44 changes: 23 additions & 21 deletions docs/release-history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,34 +43,35 @@ Added
* Introduced :py:class:`.MusifyItemSettable` class to allow distinction
between items that can have their properties set and those that can't
* Extend :py:class:`.FilterMatcher` with group_by tag functionality
* Now fully supports parsing of processors relating to XAutoPF objects with full I/O of settings
to/from their related XML files on disk.
* Now supports creating new XAutoPF files from scratch without the file needing to already exist.
* Now fully supports parsing of processors relating to :py:class:`.XAutoPF` objects with full I/O of settings
to/from their related XML files on disk
* Now supports creating new :py:class:`.XAutoPF` files from scratch without the file needing to already exist
For XML values not directly controlled by Musify, users can use the 'default_xml' class attribute
to control the initial default values applied in this scenario.
to control the initial default values applied in this scenario

Changed
-------

* Major refactoring and restructuring to all modules to improve modularity and add composition
* :py:meth:`.LocalLibrary.load_tracks` and :py:meth:`.LocalLibrary.load_playlists` now run concurrently.
* :py:meth:`.LocalLibrary.load_tracks` and :py:meth:`.LocalLibrary.load_playlists` now run concurrently
* Made :py:func:`.load_tracks` and :py:func:`.load_playlists` utility functions more DRY
* Move :py:meth:`.TagReader.load` from :py:class:`.LocalTrack` to super class :py:class:`.TagReader`
* :py:meth:`.SpotifyAPI.extend_items` now skips on responses that are already fully extended
* :py:meth:`.SpotifyArtist.load` now uses the base `load` method from :py:class:`.SpotifyCollectionLoader`
meaning it now takes full advantage of the item filtering this method offers.
meaning it now takes full advantage of the item filtering this method offers
As part of this, the base method was made more generic to accommodate all :py:class:`.SpotifyObject` types
* Renamed 'kind' property on :py:class:`.LocalTrack` to 'type' to avoid clashing property names
* :py:class:`.ItemMatcher`, :py:class:`.RemoteItemChecker`, and :py:class:`.RemoteItemSearcher` now accept
all MusifyItem types that may have their URI property set manually.
all MusifyItem types that may have their URI property set manually
* :py:class:`.ItemSorter` now shuffles randomly on unsupported types
+ prioritises fields settings over shuffle settings
* :py:meth:`.Comparer._in_range` now uses inclusive range i.e. ``a <= x <= b`` where ``x`` is the value to compare
and ``a`` and ``b`` are the limits. Previously used exclusive range i.e. ``a < x < b``
* Removed ``from_xml`` and ``to_xml`` methods from all :py:class:`.MusicBeeProcessor` subclasses.
Moved this logic to :py:class:`.XMLPlaylistParser` as distinct 'get' methods for each processor type.
Moved this logic to :py:class:`.XMLPlaylistParser` as distinct 'get' methods for each processor type
* Moved loading of XML file logic from :py:class:`.XAutoPF` to :py:class:`.XMLPlaylistParser`.
:py:class:`.XMLPlaylistParser` is now solely responsible for all XML parsing and handling for XAutoPF files
:py:class:`.XMLPlaylistParser` is now solely responsible for all XML parsing and handling
for :py:class:`.XAutoPF` files

Fixed
-----
Expand All @@ -81,6 +82,7 @@ Removed
-------

* Redundant ShuffleBy enum and related arguments from :py:class:`.ItemSorter`
* `ItemProcessor` and `MusicBeeProcessor` abstraction layers. No longer needed after some refactoring

Documentation
-------------
Expand All @@ -94,7 +96,7 @@ Changed
-------

* :py:class:`.ItemSorter` now accepts ``shuffle_weight`` between -1 and 1 instead of 0 and 1.
This parameter's logic has not yet been implemented so no changes to functionality have been made yet.
This parameter's logic has not yet been implemented so no changes to functionality have been made yet
* Move :py:meth:`.get_filepaths` from :py:class:`.LocalTrack` to super class :py:class:`.File`

Documentation
Expand All @@ -109,13 +111,13 @@ Fixed
* Tweaked assignment of description of IDv3 comment tags for :py:class:`.MP3`
* :py:func:`.align_string` function now handles combining unicode characters properly for fixed-width fonts
* :py:meth:`.LocalTrack.get_filepaths` on LocalTrack no longer returns paths from ``$RECYCLE.BIN`` folders.
These are deleted files and were causing the package to crash when trying to load them.
These are deleted files and were causing the package to crash when trying to load them
* :py:meth:`.PrettyPrinter.json` and :py:meth:`.PrettyPrinter._to_str` converts attribute keys to string
to ensure safe json/str/repr output
* :py:class:`.FilterMatcher` and :py:class:`.FilterComparers` now correctly import conditions from XML playlist files.
Previously, these filters could not import nested match conditions from files.
Changes to logic also made to :py:meth:`.Comparer.from_xml` to accommodate.
* :py:class:`.XMLLibraryParser` now handles empty arrays correctly. Previously would crash.
Changes to logic also made to :py:meth:`.Comparer.from_xml` to accommodate
* :py:class:`.XMLLibraryParser` now handles empty arrays correctly. Previously would crash
* Fixed :py:class:`.Comparer` dynamic process method alternate names for ``in_the_last`` and ``not_in_the_last``

Removed
Expand All @@ -137,29 +139,29 @@ Changed

* Generating folders for a :py:class:`.LocalLibrary` now uses folder names
as relative to the library folders of the :py:class:`.LocalLibrary`.
This now supports nested folder structures better.
* Writing date tags to :py:class:`.LocalTrack` now supports partial dates of only YYYY-MM.
* Writing date tags to :py:class:`.LocalTrack` skips writing year, month, day tags if date tag already written.
This now supports nested folder structures better
* Writing date tags to :py:class:`.LocalTrack` now supports partial dates of only YYYY-MM
* Writing date tags to :py:class:`.LocalTrack` skips writing year, month, day tags if date tag already written

Removed
-------

* set_compilation_tags method removed from :py:class:`.LocalFolder`.
This contained author specific logic and was not appropriate for general use.
This contained author specific logic and was not appropriate for general use

Fixed
-----

* ConnectionError catch in :py:class:`.RequestHandler` now handles correctly
* Added safe characters and replacements for path conversion in MusicBee :py:class:`.XMLLibraryParser`.
Now converts path to expected XML format correctly.
* :py:class:`.FilterMatcher` now handles '&' character correctly.
Now converts path to expected XML format correctly
* :py:class:`.FilterMatcher` now handles '&' character correctly
* :py:class:`.SpotifyAPI` now only requests batches of up to 20 items when getting albums.
Now matches Spotify Web API specifications better.
Now matches Spotify Web API specifications better
* Loading of logging yaml config uses UTF-8 encoding now
* Removed dependency on pytest-lazy-fixture.
Package is `broken for pytest >8.0 <https://github.com/TvoroG/pytest-lazy-fixture/issues/65>`_.
Replaced functionality with forked version of code.
Replaced functionality with forked version of code


0.7.6
Expand Down
23 changes: 13 additions & 10 deletions musify/api/authorise.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,21 @@ class APIAuthoriser:
}
:param name: The name of the API service being accessed.
:param auth_args: The parameters to be passed to the requests.post() function for initial token authorisation.
See description for possible example values.
:param user_args: Parameters to be passed to the requests.post() function
for requesting user authorised access to API services.
:param auth_args: The parameters to be passed to the
`requests.post() <https://requests.readthedocs.io/en/latest/api/#requests.post>`_
function for initial token authorisation. See description for possible example values.
:param user_args: The parameters to be passed to the
`requests.post() <https://requests.readthedocs.io/en/latest/api/#requests.post>`_
function for requesting user authorised access to API services.
The code response from this request is then added to the authorisation request args
to grant user authorisation to the API.
See description for possible example values.
:param refresh_args: Parameters to be passed to the requests.post() function
for refreshing an expired token when a refresh_token is present.
to grant user authorisation to the API. See description for possible example values.
:param refresh_args: The parameters to be passed to the
`requests.post() <https://requests.readthedocs.io/en/latest/api/#requests.post>`_
function for refreshing an expired token when a refresh_token is present.
See description for possible example values.
:param test_args: Parameters to be passed to the requests.get() function for testing validity of the token.
Must be set in conjunction with test_condition to work.
:param test_args: The parameters to be passed to the
`requests.get() <https://requests.readthedocs.io/en/latest/api/#requests.get>`_
function for testing validity of the token. Must be set in conjunction with test_condition to work.
See description for possible example values.
:param test_condition: Callable function for testing the response from the given
test_args. e.g. ``lambda r: "error" not in r``
Expand Down
10 changes: 5 additions & 5 deletions musify/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def map(cls, enum: Self) -> list[Self]:


class TrackField(TrackFieldMixin):
"""Represent all currently supported fields for objects of type :py:class:`Track`"""
"""Represents all currently supported fields for objects of type :py:class:`Track`"""
ALL = TagFields.ALL.value

TITLE = TagFields.TITLE.value
Expand Down Expand Up @@ -49,7 +49,7 @@ class TrackField(TrackFieldMixin):


class PlaylistField(Field):
"""Represent all currently supported fields for objects of type :py:class:`Playlist`"""
"""Represents all currently supported fields for objects of type :py:class:`Playlist`"""
ALL = Fields.ALL.value

# tags/core properties
Expand All @@ -66,7 +66,7 @@ class PlaylistField(Field):


class FolderField(Field):
"""Represent all currently supported fields for objects of type :py:class:`Folder`"""
"""Represents all currently supported fields for objects of type :py:class:`Folder`"""
ALL = Fields.ALL.value

# tags/core properties
Expand All @@ -81,7 +81,7 @@ class FolderField(Field):


class AlbumField(Field):
"""Represent all currently supported fields for objects of type :py:class:`Album`"""
"""Represents all currently supported fields for objects of type :py:class:`Album`"""
ALL = Fields.ALL.value

# tags/core properties
Expand All @@ -102,7 +102,7 @@ class AlbumField(Field):


class ArtistField(Field):
"""Represent all currently supported fields for objects of type :py:class:`Artist`"""
"""Represents all currently supported fields for objects of type :py:class:`Artist`"""
ALL = Fields.ALL.value

# tags/core properties
Expand Down
17 changes: 12 additions & 5 deletions musify/libraries/local/playlist/xautopf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from musify.libraries.local.playlist.base import LocalPlaylist
from musify.libraries.local.track import LocalTrack
from musify.processors.compare import Comparer
from musify.processors.exception import ItemSorterError
from musify.processors.exception import SorterProcessorError
from musify.processors.filter import FilterDefinedList, FilterComparers
from musify.processors.filter_matcher import FilterMatcher
from musify.processors.limit import ItemLimiter, LimitType
Expand Down Expand Up @@ -78,6 +78,10 @@ class XAutoPF(LocalPlaylist[AutoMatcher]):
__slots__ = ("_parser",)

valid_extensions = frozenset({".xautopf"})

#: The initial values to use as the base XML when creating a new XAutoPF file from scratch.
#: Certain settings in the 'Source' key relating to processors that this program recognises
#: will be set on initialisation. Hence, any default values assigned to these keys will be overridden.
default_xml = {
"SmartPlaylist": {
"@SaveStaticCopy": "False",
Expand Down Expand Up @@ -183,7 +187,7 @@ class XMLPlaylistParser(File, PrettyPrinter):
__slots__ = ("_path", "path_mapper", "xml",)

# noinspection SpellCheckingInspection
#: Map of MusicBee field name to Field enum
#: Map of MusicBee field key name to Field enum
name_field_map = {
"None": None,
"Title": Fields.TITLE,
Expand Down Expand Up @@ -220,6 +224,7 @@ class XMLPlaylistParser(File, PrettyPrinter):
"FileLastPlayed": Fields.LAST_PLAYED,
"FilePlayCount": Fields.PLAY_COUNT,
}
#: Map of Field enum to MusicBee field key name
field_name_map = {field: name for name, field in name_field_map.items()}

#: Settings for custom sort codes.
Expand All @@ -233,7 +238,9 @@ class XMLPlaylistParser(File, PrettyPrinter):
# TODO: implement field_code 78 - manual order according to the order of tracks found
# in the MusicBee library file for a given playlist.
}
#: The default/manual sort code
default_sort = 78
#: The default setting to use for the GroupBy setting
default_group_by = "track"

@property
Expand Down Expand Up @@ -265,8 +272,8 @@ def description(self, value: str | None):

def __init__(self, path: str, path_mapper: PathMapper = PathMapper()):
self._path = path
#: Maps paths stored in the playlist file.
self.path_mapper = path_mapper

#: A map representation of the loaded XML playlist data
self.xml: dict[str, Any] = {}

Expand Down Expand Up @@ -529,7 +536,7 @@ def get_sorter(self) -> ItemSorter | None:
elif "DefinedSort" in self.xml_source:
fields = [field]
else:
raise ItemSorterError("Sort type in XML not recognised")
raise SorterProcessorError("Sort type in XML not recognised")

shuffle_mode_value = self._pascal_to_snake(self.xml_smart_playlist["@ShuffleMode"])
if not fields and shuffle_mode_value != "none":
Expand Down Expand Up @@ -560,7 +567,7 @@ def parse_sorter(self, sorter: ItemSorter | None = None) -> None:
return

if len(sorter.sort_fields) > 1:
raise ItemSorterError(
raise SorterProcessorError(
"Cannot generate an XML representation of a mapping of many sort fields unless they have "
"a defined sort ID. To parse these fields to XML, define this map in the 'defined_sort' "
f"class attribute of this parser | {sorter.sort_fields}"
Expand Down
2 changes: 1 addition & 1 deletion musify/libraries/local/track/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class LocalTrackField(TrackFieldMixin):
"""Represent all currently supported fields for objects of type :py:class:`LocalTrack`"""
"""Represents all currently supported fields for objects of type :py:class:`LocalTrack`"""
ALL = TagFields.ALL.value

TITLE = TagFields.TITLE.value
Expand Down
2 changes: 1 addition & 1 deletion musify/libraries/remote/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def get_items(
use_cache: bool = True,
) -> list[dict[str, Any]]:
"""
``GET: /{kind}s`` - Get information for given ``values``.
``GET`` - Get information for given ``values``.
``values`` may be:
* A string representing a URL/URI/ID.
Expand Down
23 changes: 5 additions & 18 deletions musify/processors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ def _format_help_text(options: Mapping[str, str], header: MutableSequence[str] |
return "\n\t".join(help_text) + '\n'


class ItemProcessor(Processor, metaclass=ABCMeta):
"""Base object for processing :py:class:`MusifyItem` objects"""


class MusicBeeProcessor(ItemProcessor, metaclass=ABCMeta):
"""Base object for processing :py:class:`MusifyItem` objects on MusicBee settings"""

@classmethod
def _processor_method_fmt(cls, name: str) -> str:
"""A custom formatter to apply to the dynamic processor name"""
return "_" + cls._pascal_to_snake(name)


# noinspection PyPep8Naming,SpellCheckingInspection
class dynamicprocessormethod:
"""
Expand Down Expand Up @@ -115,6 +102,11 @@ def processor_methods(self) -> frozenset[str]:
"""String representation of the current processor name of this object"""
return frozenset(self._processor_method_fmt(name) for name in self.__processormethods__)

@classmethod
def _processor_method_fmt(cls, name: str) -> str:
"""A custom formatter to apply to the dynamic processor name"""
return name

def __new__(cls, *_, **__):
processor_methods = list(cls.__processormethods__)

Expand All @@ -138,11 +130,6 @@ def __new__(cls, *_, **__):
def __init__(self):
self._processor_name: str | None = None

@classmethod
def _processor_method_fmt(cls, name: str) -> str:
"""A custom formatter to apply to the dynamic processor name"""
return name

def _set_processor_name(self, value: str | None, fail_on_empty: bool = True):
"""Verifies and sets the condition name"""
if value is None:
Expand Down
8 changes: 6 additions & 2 deletions musify/processors/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

from musify.core.base import MusifyItem
from musify.core.enum import Field
from musify.processors.base import DynamicProcessor, MusicBeeProcessor, dynamicprocessormethod
from musify.processors.base import DynamicProcessor, dynamicprocessormethod
from musify.processors.exception import ComparerError
from musify.processors.time import TimeMapper
from musify.types import UnitSequence
from musify.utils import to_collection


class Comparer(MusicBeeProcessor, DynamicProcessor):
class Comparer(DynamicProcessor):
"""
Compares an item or object with another item, object or a given set of expected values to find a match.
Expand All @@ -31,6 +31,10 @@ class Comparer(MusicBeeProcessor, DynamicProcessor):

__slots__ = ("_expected", "_converted", "field")

@classmethod
def _processor_method_fmt(cls, name: str) -> str:
return "_" + cls._pascal_to_snake(name)

@property
def condition(self) -> str:
"""String representation of the current condition name of this object"""
Expand Down
4 changes: 2 additions & 2 deletions musify/processors/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
from musify.core.enum import Field, Fields
from musify.exception import MusifyEnumError
from musify.libraries.core.collection import MusifyCollection
from musify.processors.base import InputProcessor, ItemProcessor
from musify.processors.base import InputProcessor
from musify.types import UnitIterable
from musify.utils import to_collection


class ItemDownloadHelper(InputProcessor, ItemProcessor):
class ItemDownloadHelper(InputProcessor):
"""
Runs operations for helping the user to download items from given collections.
Expand Down
Loading

0 comments on commit 18af39c

Please sign in to comment.