plexutil 3.2.1__tar.gz → 3.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. {plexutil-3.2.1 → plexutil-3.3.0}/PKG-INFO +1 -1
  2. {plexutil-3.2.1 → plexutil-3.3.0}/pyproject.toml +1 -1
  3. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/__main__.py +10 -0
  4. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/library.py +39 -0
  5. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/music_playlist.py +42 -5
  6. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil.egg-info/PKG-INFO +1 -1
  7. {plexutil-3.2.1 → plexutil-3.3.0}/.github/workflows/python-publish.yml +0 -0
  8. {plexutil-3.2.1 → plexutil-3.3.0}/.gitignore +0 -0
  9. {plexutil-3.2.1 → plexutil-3.3.0}/LICENSE +0 -0
  10. {plexutil-3.2.1 → plexutil-3.3.0}/MANIFEST.in +0 -0
  11. {plexutil-3.2.1 → plexutil-3.3.0}/README.md +0 -0
  12. {plexutil-3.2.1 → plexutil-3.3.0}/git-hooks/commit-msg +0 -0
  13. {plexutil-3.2.1 → plexutil-3.3.0}/git-hooks/pre-commit +0 -0
  14. {plexutil-3.2.1 → plexutil-3.3.0}/init.sh +0 -0
  15. {plexutil-3.2.1 → plexutil-3.3.0}/requirements.txt +0 -0
  16. {plexutil-3.2.1 → plexutil-3.3.0}/ruff.toml +0 -0
  17. {plexutil-3.2.1 → plexutil-3.3.0}/setup.cfg +0 -0
  18. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/__init__.py +0 -0
  19. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/config/log_config.yaml +0 -0
  20. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/__init__.py +0 -0
  21. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/auth.py +0 -0
  22. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/library_factory.py +0 -0
  23. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/movie_library.py +0 -0
  24. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/music_library.py +0 -0
  25. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/prompt.py +0 -0
  26. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/core/tv_library.py +0 -0
  27. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/__init__.py +0 -0
  28. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/bootstrap_paths_dto.py +0 -0
  29. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/dropdown_item_dto.py +0 -0
  30. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/library_setting_dto.py +0 -0
  31. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/movie_dto.py +0 -0
  32. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/music_playlist_dto.py +0 -0
  33. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/song_dto.py +0 -0
  34. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/tv_episode_dto.py +0 -0
  35. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/dto/tv_series_dto.py +0 -0
  36. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/__init__.py +0 -0
  37. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/agent.py +0 -0
  38. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/file_type.py +0 -0
  39. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/language.py +0 -0
  40. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/library_setting.py +0 -0
  41. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/library_type.py +0 -0
  42. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/scanner.py +0 -0
  43. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/server_setting.py +0 -0
  44. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/enums/user_request.py +0 -0
  45. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/__init__.py +0 -0
  46. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/auth_error.py +0 -0
  47. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/bootstrap_error.py +0 -0
  48. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/database_connection_error.py +0 -0
  49. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/device_error.py +0 -0
  50. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/entity_not_found_error.py +0 -0
  51. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/library_illegal_state_error.py +0 -0
  52. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/library_op_error.py +0 -0
  53. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/library_poll_timeout_error.py +0 -0
  54. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/library_section_missing_error.py +0 -0
  55. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/library_unsupported_error.py +0 -0
  56. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/plex_media_missing_error.py +0 -0
  57. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/server_connection_error.py +0 -0
  58. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/unexpected_argument_error.py +0 -0
  59. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/unexpected_naming_pattern_error.py +0 -0
  60. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/exception/user_error.py +0 -0
  61. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/graphical/__init__.py +0 -0
  62. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/graphical/selection_window.py +0 -0
  63. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/mapper/__init__.py +0 -0
  64. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/mapper/music_playlist_mapper.py +0 -0
  65. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/mapper/song_mapper.py +0 -0
  66. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/model/__init__.py +0 -0
  67. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/model/music_playlist_entity.py +0 -0
  68. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/model/song_entity.py +0 -0
  69. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/model/song_music_playlist_entity.py +0 -0
  70. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/plex_util_logger.py +0 -0
  71. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/service/__init__.py +0 -0
  72. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/service/db_manager.py +0 -0
  73. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/service/music_playlist_service.py +0 -0
  74. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/service/song_music_playlist_composite_service.py +0 -0
  75. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/service/song_service.py +0 -0
  76. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/static.py +0 -0
  77. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/util/__init__.py +0 -0
  78. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/util/file_importer.py +0 -0
  79. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/util/icons.py +0 -0
  80. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/util/plex_ops.py +0 -0
  81. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil/util/query_builder.py +0 -0
  82. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil.egg-info/SOURCES.txt +0 -0
  83. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil.egg-info/dependency_links.txt +0 -0
  84. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil.egg-info/entry_points.txt +0 -0
  85. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil.egg-info/requires.txt +0 -0
  86. {plexutil-3.2.1 → plexutil-3.3.0}/src/plexutil.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexutil
3
- Version: 3.2.1
3
+ Version: 3.3.0
4
4
  Author-email: Carlos Florez <carlos@florez.co.uk>
5
5
  Maintainer-email: Carlos Florez <carlos@florez.co.uk>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plexutil"
7
- version = "3.2.1"
7
+ version = "3.3.0"
8
8
  requires-python = ">=3.11"
9
9
  authors = [
10
10
  {name = "Carlos Florez", email = "carlos@florez.co.uk"}
@@ -7,6 +7,7 @@ from plexutil.core.auth import Auth
7
7
  from plexutil.core.library_factory import LibraryFactory
8
8
  from plexutil.core.prompt import Prompt
9
9
  from plexutil.enums.user_request import UserRequest
10
+ from plexutil.exception.auth_error import AuthError
10
11
  from plexutil.exception.bootstrap_error import BootstrapError
11
12
  from plexutil.exception.library_illegal_state_error import (
12
13
  LibraryIllegalStateError,
@@ -71,6 +72,15 @@ def main() -> None:
71
72
  PlexUtilLogger.get_logger().exception(description)
72
73
  raise
73
74
 
75
+ except AuthError as e:
76
+ sys.tracebacklimit = 0
77
+ description = (
78
+ f"\n{Icons.BANNER_LEFT}Authentication "
79
+ f"Error{Icons.BANNER_RIGHT}\n{e!s}"
80
+ )
81
+ PlexUtilLogger.get_logger().error(description)
82
+ sys.exit(1)
83
+
74
84
  except UserError as e:
75
85
  sys.tracebacklimit = 0
76
86
  description = (
@@ -5,6 +5,8 @@ from abc import ABC, abstractmethod
5
5
  from pathlib import Path
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from yaspin import yaspin
9
+
8
10
  from plexutil.core.prompt import Prompt
9
11
  from plexutil.enums.agent import Agent
10
12
  from plexutil.enums.language import Language
@@ -116,14 +118,51 @@ class Library(ABC):
116
118
 
117
119
  @abstractmethod
118
120
  def update(self) -> None:
121
+ """
122
+ Updates and Refreshes a Library, including each of its media items
123
+
124
+ Returns:
125
+ None: This method does not return a value
126
+ """
127
+ start = time.time()
119
128
  section = self.get_section()
120
129
  section.update()
121
130
  section.refresh()
122
131
  [media.refresh() for media in self.query()]
123
132
 
133
+ is_updating = any(
134
+ "updating" in x.title.lower() or "scanning" in x.title.lower()
135
+ for x in self.plex_server.activities
136
+ )
137
+ while is_updating:
138
+ with yaspin(text="Updating", color="yellow") as spinner:
139
+ elapsed = time.time() - start
140
+ spinner.write(
141
+ f"# of items: {len(self.query())!s} [{elapsed:.2f}s]"
142
+ )
143
+ is_updating = any(
144
+ "updating" in x.title.lower()
145
+ or "scanning" in x.title.lower()
146
+ for x in self.plex_server.activities
147
+ )
148
+ if not is_updating:
149
+ spinner.ok(f"{Icons.SUCCESS} Updated")
150
+
151
+ elapsed = time.time() - start
152
+ description = (
153
+ f"\nFinished update in {elapsed:.2f}s\n"
154
+ f"# of items: {len(self.query())!s}"
155
+ )
156
+ PlexUtilLogger.get_console_logger().info(description)
157
+
124
158
  @abstractmethod
125
159
  def modify(self, is_modify_media: bool = False) -> None:
160
+ """
161
+ Modifies a Library
126
162
 
163
+ Returns:
164
+ None: This method does not return a value
165
+ """
127
166
  if is_modify_media:
128
167
  selected_media = self.display_media(expect_input=True)
129
168
 
@@ -47,6 +47,7 @@ class MusicPlaylist(Library):
47
47
  ) -> None:
48
48
  super().__init__(
49
49
  supported_requests=[
50
+ UserRequest.CREATE,
50
51
  UserRequest.DELETE,
51
52
  UserRequest.DISPLAY,
52
53
  UserRequest.UPLOAD,
@@ -85,8 +86,14 @@ class MusicPlaylist(Library):
85
86
  f"{Icons.WARNING} Song not found: {song!s} | Skipping..."
86
87
  )
87
88
  PlexUtilLogger.get_logger().warning(description)
88
- playlist = self.get_section().playlist(self.playlist_name)
89
- playlist.addItems(tracks)
89
+
90
+ if self.user_request is UserRequest.CREATE:
91
+ self.get_section().createPlaylist(
92
+ title=self.playlist_name, items=tracks
93
+ )
94
+ else:
95
+ playlist = self.get_section().playlist(self.playlist_name)
96
+ playlist.addItems(tracks)
90
97
 
91
98
  def remove_item(self) -> None:
92
99
  playlist = self.get_section().playlist(self.playlist_name)
@@ -117,11 +124,40 @@ class MusicPlaylist(Library):
117
124
  playlist.removeItems(tracks)
118
125
 
119
126
  def create(self) -> None:
120
- raise NotImplementedError
127
+
128
+ op_type = "Create Playlist"
129
+
130
+ self.display(expect_input=True)
131
+
132
+ text = Prompt.confirm_text(
133
+ "Playlist Name",
134
+ "Playlist requires a name",
135
+ "What should the playlist name be",
136
+ )
137
+ if not text:
138
+ raise LibraryOpError(
139
+ op_type=op_type,
140
+ library_type=self.library_type,
141
+ description="No name supplied?",
142
+ )
143
+ self.playlist_name = text[0]
144
+ exists = any(
145
+ x.name == self.playlist_name for x in self.__get_all_playlists()
146
+ )
147
+ if exists:
148
+ description = (
149
+ f"{self.playlist_name} already exists in Library ({self.name})"
150
+ )
151
+ raise LibraryOpError(
152
+ op_type=op_type,
153
+ library_type=self.library_type,
154
+ description=description,
155
+ )
156
+
157
+ self.add_item()
121
158
 
122
159
  def update(self) -> None:
123
- # None of MusicPlaylist operations benefit from a refresh/reload
124
- return
160
+ raise NotImplementedError
125
161
 
126
162
  def display_media(self, expect_input: bool = False) -> Track:
127
163
  raise NotImplementedError
@@ -158,6 +194,7 @@ class MusicPlaylist(Library):
158
194
  if (
159
195
  self.user_request is UserRequest.DOWNLOAD
160
196
  or self.user_request is UserRequest.UPLOAD
197
+ or self.user_request is UserRequest.CREATE
161
198
  ):
162
199
  return
163
200
  dropdown = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexutil
3
- Version: 3.2.1
3
+ Version: 3.3.0
4
4
  Author-email: Carlos Florez <carlos@florez.co.uk>
5
5
  Maintainer-email: Carlos Florez <carlos@florez.co.uk>
6
6
  License: MIT License
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes