diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..bdc9f0e0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,98 @@ +name: Deploy package to PyPI + +on: + push: + branches: [ v0.3 ] + + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate_windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install '.[test]' + + - name: Run tests + run: | + pytest --junit-xml=test-results.xml + + - name: Report failed tests + if: always() + uses: pmeier/pytest-results-action@main + with: + path: test-results.xml + summary: true + display-options: fEXs + fail-on-empty: true + title: Test results + + validate_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install '.[test]' + + - name: Run tests + run: | + pytest --junit-xml=test-results.xml + + - name: Report failed tests + if: always() + uses: pmeier/pytest-results-action@main + with: + path: test-results.xml + summary: true + display-options: fEXs + fail-on-empty: true + title: Test results + + validate_mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install '.[test]' + + - name: Run tests + run: | + pytest --junit-xml=test-results.xml + + - name: Report failed tests + if: always() + uses: pmeier/pytest-results-action@main + with: + path: test-results.xml + summary: true + display-options: fEXs + fail-on-empty: true + title: Test results \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a4e534e4..270d1481 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,4 +1,4 @@ -name: documentation +name: Publish documentation on: push: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml deleted file mode 100644 index 80a1dc72..00000000 --- a/.github/workflows/validate.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: validate - -on: - push: - branches: [ v0.3 ] - - workflow_dispatch: - -permissions: - contents: read - -jobs: - test: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install '.[test]' - - - name: Run tests - run: | - pytest diff --git a/README.md b/README.md index cbd4c980..5ea60083 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![PyPI - Downloads](https://img.shields.io/pypi/dm/syncify)](https://pypi.org/project/syncify/) [![Contributors](https://img.shields.io/github/contributors/geo-martino/syncify)](https://github.com/geo-martino/syncify/graphs/contributors)
-[![GitHub - Deployment](https://github.com/geo-martino/syncify/workflows/validate/badge.svg)](https://github.com/geo-martino/syncify/actions/workflows/validate.yml) +[![GitHub - Deployment](https://github.com/geo-martino/syncify/workflows/deploy/badge.svg)](https://github.com/geo-martino/syncify/actions/workflows/deploy.yml) [![GitHub - Documentation](https://github.com/geo-martino/syncify/workflows/documentation/badge.svg)](https://github.com/geo-martino/syncify/actions/workflows/documentation.yml) ### A complete local and music streaming service (remote) library management tool. @@ -379,7 +379,7 @@ python -m pip install syncify ## Currently Supported - **Music Streaming Services**: `Spotify` -- **Audio filetypes**: `.flac` `.wma` `.m4a` `.mp3` +- **Audio filetypes**: `.mp3` `.m4a` `.wma` `.flac` - **Local playlist filetypes**: `.m3u` `.xautopf` - **Local Libraries**: `MusicBee` diff --git a/README.template.md b/README.template.md index 22529127..64f9ec52 100644 --- a/README.template.md +++ b/README.template.md @@ -5,7 +5,7 @@ [![PyPI - Downloads](https://img.shields.io/pypi/dm/syncify)](https://pypi.org/project/syncify/) [![Contributors](https://img.shields.io/github/contributors/geo-martino/syncify)](https://github.com/geo-martino/syncify/graphs/contributors)
-[![GitHub - Deployment](https://github.com/geo-martino/syncify/workflows/validate/badge.svg)](https://github.com/geo-martino/syncify/actions/workflows/validate.yml) +[![GitHub - Deployment](https://github.com/geo-martino/syncify/workflows/deploy/badge.svg)](https://github.com/geo-martino/syncify/actions/workflows/deploy.yml) [![GitHub - Documentation](https://github.com/geo-martino/syncify/workflows/documentation/badge.svg)](https://github.com/geo-martino/syncify/actions/workflows/documentation.yml) ### A complete local and music streaming service (remote) library management tool. diff --git a/pyproject.toml b/pyproject.toml index 492e470f..795a3309 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ test = [ dev = [ "syncify[test]", "grip~=4.6", + "jupyterlab~=4.0", ] docs = [ "sphinx~=7.2", diff --git a/src/syncify/__init__.py b/src/syncify/__init__.py index 172e4a40..6a156c74 100644 --- a/src/syncify/__init__.py +++ b/src/syncify/__init__.py @@ -1,11 +1,11 @@ -from os.path import basename, dirname - -PROGRAM_NAME = "Syncify" -__version__ = "0.3" -PROGRAM_OWNER_NAME = "George Martin Marino" -PROGRAM_OWNER_USER = "geo-martino" -PROGRAM_OWNER_EMAIL = f"gm.engineer+{PROGRAM_NAME.lower()}@pm.me" -PROGRAM_URL = f"https://github.com/{PROGRAM_OWNER_USER}/{PROGRAM_NAME.lower()}" - -MODULE_ROOT: str = basename(dirname(__file__)) -PACKAGE_ROOT: str = dirname(dirname(dirname(__file__))) +from os.path import basename, dirname + +PROGRAM_NAME = "Syncify" +__version__ = "0.3.0" +PROGRAM_OWNER_NAME = "George Martin Marino" +PROGRAM_OWNER_USER = "geo-martino" +PROGRAM_OWNER_EMAIL = f"gm.engineer+{PROGRAM_NAME.lower()}@pm.me" +PROGRAM_URL = f"https://github.com/{PROGRAM_OWNER_USER}/{PROGRAM_NAME.lower()}" + +MODULE_ROOT: str = basename(dirname(__file__)) +PACKAGE_ROOT: str = dirname(dirname(dirname(__file__))) diff --git a/src/syncify/local/base.py b/src/syncify/local/base.py index 76458a87..8a2d1828 100644 --- a/src/syncify/local/base.py +++ b/src/syncify/local/base.py @@ -8,6 +8,3 @@ class LocalItem(File, Item, metaclass=ABCMeta): """Generic base class for locally-stored items""" __attributes_classes__ = (File, Item) - - def __hash__(self): # TODO: why doesn't this get inherited correctly from File - return super().__hash__() diff --git a/src/syncify/local/collection.py b/src/syncify/local/collection.py index 6551a986..d008d034 100644 --- a/src/syncify/local/collection.py +++ b/src/syncify/local/collection.py @@ -180,11 +180,6 @@ def merge_tracks(self, tracks: Collection[Track], tags: UnitIterable[TagField] = for tag in tag_names: # merge on each tag if hasattr(track, tag): - if tag == "image_links": # TODO: find a better way to do this - track_in_collection[tag].clear() - track_in_collection[tag].update(track[tag]) - continue - track_in_collection[tag] = track[tag] if isinstance(self, Library): diff --git a/src/syncify/local/track/base/reader.py b/src/syncify/local/track/base/reader.py index 87f99f9d..c2d278df 100644 --- a/src/syncify/local/track/base/reader.py +++ b/src/syncify/local/track/base/reader.py @@ -260,6 +260,10 @@ def has_uri(self): def image_links(self): return self._image_links + @image_links.setter + def image_links(self, value: dict[str, str]): + self._image_links = value + @property def has_image(self): return self._has_image diff --git a/src/syncify/processors/compare.py b/src/syncify/processors/compare.py index 5e67742f..be9f0871 100644 --- a/src/syncify/processors/compare.py +++ b/src/syncify/processors/compare.py @@ -294,10 +294,10 @@ def _contains(self, value: Any | None, expected: Sequence[Any] | None) -> bool: def _does_not_contain(self, value: Any | None, expected: Sequence[Any] | None) -> bool: return not self._contains(value=value, expected=expected) - @dynamicprocessormethod - def _in_tag_hierarchy(self, value: Any | None, expected: Sequence[Any] | None) -> bool: - # TODO: what does this even mean - raise NotImplementedError + # no plans to ever implement this + # @dynamicprocessormethod + # def _in_tag_hierarchy(self, value: Any | None, expected: Sequence[Any] | None) -> bool: + # raise NotImplementedError @dynamicprocessormethod def _matches_reg_ex(self, value: Any | None, expected: Sequence[Any] | None) -> bool: diff --git a/src/syncify/shared/core/enum.py b/src/syncify/shared/core/enum.py index f2cbe2e5..7254cd80 100644 --- a/src/syncify/shared/core/enum.py +++ b/src/syncify/shared/core/enum.py @@ -121,7 +121,7 @@ class Fields(Field): # file properties PATH = 106 FOLDER = 179 - FILENAME = 52 + FILENAME = 3 EXT = 100 SIZE = 7 KIND = 4 diff --git a/src/syncify/shared/logger.py b/src/syncify/shared/logger.py index aa3e407c..64154c14 100644 --- a/src/syncify/shared/logger.py +++ b/src/syncify/shared/logger.py @@ -99,13 +99,7 @@ def get_progress_bar[T: Any]( cols = 120 # clear closed bars - # TODO: why does this need to run twice to actually clear all disabled bars - for bar in self._bars: - if bar.n >= bar.total or bar.disable: - self._bars.remove(bar) - for bar in self._bars: - if bar.n >= bar.total or bar.disable: - self._bars.remove(bar) + self._bars = [bar for bar in self._bars if bar.n < bar.total] # determine the level of bar to generate and whether to leave the bar based on current active count position = kwargs.get("position", abs(min(bar.pos for bar in self._bars)) + 1 if self._bars else 0) diff --git a/tests/local/track/test_track.py b/tests/local/track/test_track.py index debad1fa..917b3e16 100644 --- a/tests/local/track/test_track.py +++ b/tests/local/track/test_track.py @@ -406,7 +406,7 @@ def test_update_tags_with_replace(self, track: LocalTrack): @staticmethod def get_update_image_test_track(track: LocalTrack) -> tuple[LocalTrack, LocalTrack]: """Load track and modify its tags for update tags tests""" - track._image_links = {"cover_front": path_track_img} + track.image_links = {"cover front": path_track_img} return track, copy(track) def test_update_image_dry_run(self, track: LocalTrack): diff --git a/tests/local/track/utils.py b/tests/local/track/utils.py index 24630787..3a08640c 100644 --- a/tests/local/track/utils.py +++ b/tests/local/track/utils.py @@ -57,7 +57,7 @@ def random_track[T: LocalTrack](cls: type[T] | None = None) -> T: has_uri = choice([True, False]) track.uri = random_uri() if has_uri else remote_wrangler.unavailable_uri_dummy - track._image_links = {} + track.image_links = {} track.has_image = False filename_ext = f"{str(track.track_number).zfill(2)} - {track.title}" + choice(tuple(track.valid_extensions)) diff --git a/tests/shared/core/collection.py b/tests/shared/core/collection.py index eea5b132..c665dca4 100644 --- a/tests/shared/core/collection.py +++ b/tests/shared/core/collection.py @@ -243,7 +243,7 @@ def test_get_filtered_playlists_basic(library: Library): @staticmethod def test_get_filtered_playlists_on_tags(library: Library): # filters out tags - filter_names = [item.name for item in next(pl for pl in library.playlists.values())[:2]] + filter_names = [item.name for item in next(pl for pl in library.playlists.values() if len(pl) > 0)[:2]] filter_tags = {"name": [name.upper() + " " for name in filter_names]} expected_counts = {} for name, pl in library.playlists.items():