plexutil 3.2.0__tar.gz → 3.2.2__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.0 → plexutil-3.2.2}/PKG-INFO +4 -8
  2. {plexutil-3.2.0 → plexutil-3.2.2}/README.md +2 -7
  3. {plexutil-3.2.0 → plexutil-3.2.2}/pyproject.toml +2 -1
  4. {plexutil-3.2.0 → plexutil-3.2.2}/requirements.txt +1 -0
  5. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/__main__.py +10 -3
  6. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/library.py +28 -0
  7. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/prompt.py +36 -3
  8. plexutil-3.2.2/src/plexutil/exception/server_connection_error.py +2 -0
  9. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/util/icons.py +4 -0
  10. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil.egg-info/PKG-INFO +4 -8
  11. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil.egg-info/SOURCES.txt +1 -0
  12. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil.egg-info/requires.txt +1 -0
  13. {plexutil-3.2.0 → plexutil-3.2.2}/.github/workflows/python-publish.yml +0 -0
  14. {plexutil-3.2.0 → plexutil-3.2.2}/.gitignore +0 -0
  15. {plexutil-3.2.0 → plexutil-3.2.2}/LICENSE +0 -0
  16. {plexutil-3.2.0 → plexutil-3.2.2}/MANIFEST.in +0 -0
  17. {plexutil-3.2.0 → plexutil-3.2.2}/git-hooks/commit-msg +0 -0
  18. {plexutil-3.2.0 → plexutil-3.2.2}/git-hooks/pre-commit +0 -0
  19. {plexutil-3.2.0 → plexutil-3.2.2}/init.sh +0 -0
  20. {plexutil-3.2.0 → plexutil-3.2.2}/ruff.toml +0 -0
  21. {plexutil-3.2.0 → plexutil-3.2.2}/setup.cfg +0 -0
  22. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/__init__.py +0 -0
  23. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/config/log_config.yaml +0 -0
  24. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/__init__.py +0 -0
  25. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/auth.py +0 -0
  26. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/library_factory.py +0 -0
  27. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/movie_library.py +0 -0
  28. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/music_library.py +0 -0
  29. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/music_playlist.py +0 -0
  30. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/core/tv_library.py +0 -0
  31. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/__init__.py +0 -0
  32. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/bootstrap_paths_dto.py +0 -0
  33. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/dropdown_item_dto.py +0 -0
  34. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/library_setting_dto.py +0 -0
  35. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/movie_dto.py +0 -0
  36. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/music_playlist_dto.py +0 -0
  37. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/song_dto.py +0 -0
  38. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/tv_episode_dto.py +0 -0
  39. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/dto/tv_series_dto.py +0 -0
  40. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/__init__.py +0 -0
  41. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/agent.py +0 -0
  42. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/file_type.py +0 -0
  43. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/language.py +0 -0
  44. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/library_setting.py +0 -0
  45. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/library_type.py +0 -0
  46. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/scanner.py +0 -0
  47. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/server_setting.py +0 -0
  48. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/enums/user_request.py +0 -0
  49. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/__init__.py +0 -0
  50. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/auth_error.py +0 -0
  51. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/bootstrap_error.py +0 -0
  52. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/database_connection_error.py +0 -0
  53. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/device_error.py +0 -0
  54. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/entity_not_found_error.py +0 -0
  55. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/library_illegal_state_error.py +0 -0
  56. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/library_op_error.py +0 -0
  57. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/library_poll_timeout_error.py +0 -0
  58. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/library_section_missing_error.py +0 -0
  59. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/library_unsupported_error.py +0 -0
  60. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/plex_media_missing_error.py +0 -0
  61. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/unexpected_argument_error.py +0 -0
  62. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/unexpected_naming_pattern_error.py +0 -0
  63. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/exception/user_error.py +0 -0
  64. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/graphical/__init__.py +0 -0
  65. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/graphical/selection_window.py +0 -0
  66. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/mapper/__init__.py +0 -0
  67. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/mapper/music_playlist_mapper.py +0 -0
  68. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/mapper/song_mapper.py +0 -0
  69. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/model/__init__.py +0 -0
  70. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/model/music_playlist_entity.py +0 -0
  71. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/model/song_entity.py +0 -0
  72. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/model/song_music_playlist_entity.py +0 -0
  73. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/plex_util_logger.py +0 -0
  74. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/service/__init__.py +0 -0
  75. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/service/db_manager.py +0 -0
  76. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/service/music_playlist_service.py +0 -0
  77. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/service/song_music_playlist_composite_service.py +0 -0
  78. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/service/song_service.py +0 -0
  79. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/static.py +0 -0
  80. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/util/__init__.py +0 -0
  81. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/util/file_importer.py +0 -0
  82. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/util/plex_ops.py +0 -0
  83. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil/util/query_builder.py +0 -0
  84. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil.egg-info/dependency_links.txt +0 -0
  85. {plexutil-3.2.0 → plexutil-3.2.2}/src/plexutil.egg-info/entry_points.txt +0 -0
  86. {plexutil-3.2.0 → plexutil-3.2.2}/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.0
3
+ Version: 3.2.2
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
@@ -42,6 +42,7 @@ Requires-Dist: peewee==3.18.3
42
42
  Requires-Dist: colorlog==6.10.1
43
43
  Requires-Dist: pyjwt[crypto]==2.10.1
44
44
  Requires-Dist: ttkthemes==3.3.0
45
+ Requires-Dist: yaspin==3.4.0
45
46
  Dynamic: license-file
46
47
 
47
48
  # Plexutil
@@ -110,15 +111,10 @@ plexutil upload
110
111
  > [!WARNING]
111
112
  > This feature requires a graphical session (X11 or Wayland) <br>
112
113
 
113
- To add songs to an existing music playlist
114
+ To add songs to an existing music playlist or to remove songs from an existing music playlist
114
115
  ```bash
115
- plexutil add_to_playlist
116
+ plexutil modify
116
117
  ```
117
- To remove songs from an existing music playlist
118
- ```bash
119
- plexutil remove_from_playlist
120
- ```
121
-
122
118
 
123
119
  ## Development
124
120
  > [!NOTE]
@@ -64,15 +64,10 @@ plexutil upload
64
64
  > [!WARNING]
65
65
  > This feature requires a graphical session (X11 or Wayland) <br>
66
66
 
67
- To add songs to an existing music playlist
67
+ To add songs to an existing music playlist or to remove songs from an existing music playlist
68
68
  ```bash
69
- plexutil add_to_playlist
69
+ plexutil modify
70
70
  ```
71
- To remove songs from an existing music playlist
72
- ```bash
73
- plexutil remove_from_playlist
74
- ```
75
-
76
71
 
77
72
  ## Development
78
73
  > [!NOTE]
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plexutil"
7
- version = "3.2.0"
7
+ version = "3.2.2"
8
8
  requires-python = ">=3.11"
9
9
  authors = [
10
10
  {name = "Carlos Florez", email = "carlos@florez.co.uk"}
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "colorlog==6.10.1",
32
32
  "pyjwt[crypto]==2.10.1",
33
33
  "ttkthemes==3.3.0",
34
+ "yaspin==3.4.0",
34
35
  ]
35
36
 
36
37
  [project.scripts]
@@ -8,3 +8,4 @@ peewee==3.18.3
8
8
  colorlog==6.10.1
9
9
  pyjwt[crypto]==2.10.1
10
10
  ttkthemes==3.3.0
11
+ yaspin==3.4.0
@@ -18,6 +18,7 @@ from plexutil.exception.library_poll_timeout_error import (
18
18
  from plexutil.exception.library_section_missing_error import (
19
19
  LibrarySectionMissingError,
20
20
  )
21
+ from plexutil.exception.server_connection_error import ServerConnectionError
21
22
  from plexutil.exception.unexpected_argument_error import (
22
23
  UnexpectedArgumentError,
23
24
  )
@@ -46,9 +47,7 @@ def main() -> None:
46
47
  bootstrap_paths_dto.token_dir.unlink(missing_ok=True)
47
48
  plex_resources = Auth.get_resources(bootstrap_paths_dto)
48
49
 
49
- plex_server = Prompt.confirm_server(
50
- plex_resources=plex_resources
51
- ).connect()
50
+ plex_server = Prompt.confirm_server(plex_resources=plex_resources)
52
51
 
53
52
  if user_request is UserRequest.SETTINGS:
54
53
  PlexOps.set_server_settings(plex_server=plex_server)
@@ -80,6 +79,14 @@ def main() -> None:
80
79
  PlexUtilLogger.get_logger().error(description)
81
80
  sys.exit(1)
82
81
 
82
+ except ServerConnectionError as e:
83
+ sys.tracebacklimit = 0
84
+ description = (
85
+ f"\n{Icons.BANNER_LEFT}Connection Error{Icons.BANNER_RIGHT}\n{e!s}"
86
+ )
87
+ PlexUtilLogger.get_logger().error(description)
88
+ sys.exit(1)
89
+
83
90
  except LibraryIllegalStateError as e:
84
91
  sys.tracebacklimit = 0
85
92
  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,11 +118,37 @@ class Library(ABC):
116
118
 
117
119
  @abstractmethod
118
120
  def update(self) -> None:
121
+ start = time.time()
119
122
  section = self.get_section()
120
123
  section.update()
121
124
  section.refresh()
122
125
  [media.refresh() for media in self.query()]
123
126
 
127
+ is_updating = any(
128
+ "updating" in x.title.lower() or "scanning" in x.title.lower()
129
+ for x in self.plex_server.activities
130
+ )
131
+ while is_updating:
132
+ with yaspin(text="Updating", color="yellow") as spinner:
133
+ elapsed = time.time() - start
134
+ spinner.write(
135
+ f"# of items: {len(self.query())!s} [{elapsed:.2f}s]"
136
+ )
137
+ is_updating = any(
138
+ "updating" in x.title.lower()
139
+ or "scanning" in x.title.lower()
140
+ for x in self.plex_server.activities
141
+ )
142
+ if not is_updating:
143
+ spinner.ok(f"{Icons.SUCCESS} Updated")
144
+
145
+ elapsed = time.time() - start
146
+ description = (
147
+ f"\nFinished update in {elapsed:.2f}s\n"
148
+ f"# of items: {len(self.query())!s}"
149
+ )
150
+ PlexUtilLogger.get_console_logger().info(description)
151
+
124
152
  @abstractmethod
125
153
  def modify(self, is_modify_media: bool = False) -> None:
126
154
 
@@ -8,7 +8,11 @@ from argparse import RawTextHelpFormatter
8
8
  from importlib.metadata import PackageNotFoundError, version
9
9
  from typing import TYPE_CHECKING, cast
10
10
 
11
+ from plexapi.exceptions import NotFound
12
+ from yaspin import yaspin
13
+
11
14
  from plexutil.exception.device_error import DeviceError
15
+ from plexutil.exception.server_connection_error import ServerConnectionError
12
16
  from plexutil.graphical.selection_window import SelectionWindow
13
17
 
14
18
  if TYPE_CHECKING:
@@ -20,6 +24,7 @@ if TYPE_CHECKING:
20
24
  ShowSection,
21
25
  )
22
26
  from plexapi.myplex import MyPlexResource
27
+ from plexapi.server import PlexServer
23
28
  from plexapi.video import Movie, Show
24
29
 
25
30
  from plexutil.core.library import Library
@@ -383,7 +388,7 @@ class Prompt(Static):
383
388
  ).value
384
389
 
385
390
  @staticmethod
386
- def confirm_server(plex_resources: list[MyPlexResource]) -> MyPlexResource:
391
+ def confirm_server(plex_resources: list[MyPlexResource]) -> PlexServer:
387
392
  """
388
393
  Prompts user for a Plex Media Server selection
389
394
 
@@ -392,7 +397,9 @@ class Prompt(Static):
392
397
  anything other than a Plex Media Server is filtered
393
398
 
394
399
  Returns:
395
- MyPlexResource: The chosen Plex Media Server
400
+ PlexServer: The chosen Plex Media Server
401
+ Raises:
402
+ ServerConnectionError: If unable to connect
396
403
  """
397
404
  is_default = True
398
405
  dropdown = []
@@ -406,12 +413,38 @@ class Prompt(Static):
406
413
  is_default = False
407
414
  dropdown.append(item)
408
415
 
409
- return Prompt.draw_dropdown(
416
+ plex_resource = Prompt.draw_dropdown(
410
417
  title="Available Servers",
411
418
  description="Choose a server to connect to",
412
419
  dropdown=dropdown,
413
420
  ).value
414
421
 
422
+ with yaspin(text="Connecting", color="yellow") as spinner:
423
+ try:
424
+ plex_server = plex_resource.connect()
425
+ except NotFound as e:
426
+ spinner.fail(f"{Icons.FAILURE} Connection Failure")
427
+ description = (
428
+ f"Failed to connect to: {plex_resource.name} "
429
+ f"({plex_resource.device})\n"
430
+ f"Reason: [Unavailable/Not Found]"
431
+ )
432
+ raise ServerConnectionError(description) from e
433
+ except Exception as e:
434
+ spinner.fail(f"{Icons.FAILURE} Connection Failure")
435
+ description = (
436
+ f"Failed to connect to: {plex_resource.name} "
437
+ f"({plex_resource.device})\n"
438
+ f"Reason: [Unknown]"
439
+ )
440
+ raise ServerConnectionError(description) from e
441
+
442
+ spinner.ok(f"{Icons.SUCCESS} Connected")
443
+
444
+ description = f"Connected to: {plex_server}"
445
+ PlexUtilLogger.get_logger().debug(description)
446
+ return plex_server
447
+
415
448
  @staticmethod
416
449
  def confirm_plex_media(
417
450
  title: str,
@@ -0,0 +1,2 @@
1
+ class ServerConnectionError(Exception):
2
+ pass
@@ -23,3 +23,7 @@ class Icons(Static):
23
23
  "► " if sys.stdout.encoding.lower().startswith("utf") else "> "
24
24
  )
25
25
  STAR = "●" if sys.stdout.encoding.lower().startswith("utf") else "*"
26
+ SUCCESS = (
27
+ "🟢" if sys.stdout.encoding.lower().startswith("utf") else "SUCCESS"
28
+ )
29
+ FAILURE = "🔴" if sys.stdout.encoding.lower().startswith("utf") else "FAIL"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexutil
3
- Version: 3.2.0
3
+ Version: 3.2.2
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
@@ -42,6 +42,7 @@ Requires-Dist: peewee==3.18.3
42
42
  Requires-Dist: colorlog==6.10.1
43
43
  Requires-Dist: pyjwt[crypto]==2.10.1
44
44
  Requires-Dist: ttkthemes==3.3.0
45
+ Requires-Dist: yaspin==3.4.0
45
46
  Dynamic: license-file
46
47
 
47
48
  # Plexutil
@@ -110,15 +111,10 @@ plexutil upload
110
111
  > [!WARNING]
111
112
  > This feature requires a graphical session (X11 or Wayland) <br>
112
113
 
113
- To add songs to an existing music playlist
114
+ To add songs to an existing music playlist or to remove songs from an existing music playlist
114
115
  ```bash
115
- plexutil add_to_playlist
116
+ plexutil modify
116
117
  ```
117
- To remove songs from an existing music playlist
118
- ```bash
119
- plexutil remove_from_playlist
120
- ```
121
-
122
118
 
123
119
  ## Development
124
120
  > [!NOTE]
@@ -59,6 +59,7 @@ src/plexutil/exception/library_poll_timeout_error.py
59
59
  src/plexutil/exception/library_section_missing_error.py
60
60
  src/plexutil/exception/library_unsupported_error.py
61
61
  src/plexutil/exception/plex_media_missing_error.py
62
+ src/plexutil/exception/server_connection_error.py
62
63
  src/plexutil/exception/unexpected_argument_error.py
63
64
  src/plexutil/exception/unexpected_naming_pattern_error.py
64
65
  src/plexutil/exception/user_error.py
@@ -7,6 +7,7 @@ peewee==3.18.3
7
7
  colorlog==6.10.1
8
8
  pyjwt[crypto]==2.10.1
9
9
  ttkthemes==3.3.0
10
+ yaspin==3.4.0
10
11
 
11
12
  [:sys_platform == "win32"]
12
13
  pywin32==311
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes