6
6
import warnings
7
7
from collections import defaultdict
8
8
from datetime import datetime
9
- from functools import cached_property
10
9
from urllib .parse import parse_qs , quote_plus , urlencode , urlparse
11
10
12
11
from plexapi import log , media , utils
@@ -44,9 +43,8 @@ def _loadData(self, data):
44
43
self .mediaTagVersion = data .attrib .get ('mediaTagVersion' )
45
44
self .title1 = data .attrib .get ('title1' )
46
45
self .title2 = data .attrib .get ('title2' )
47
- self ._sectionsByID = {} # cached sections by key
48
- self ._sectionsByTitle = {} # cached sections by title
49
46
47
+ @cached_data_property
50
48
def _loadSections (self ):
51
49
""" Loads and caches all the library sections. """
52
50
key = '/library/sections'
@@ -64,15 +62,23 @@ def _loadSections(self):
64
62
sectionsByID [section .key ] = section
65
63
sectionsByTitle [section .title .lower ().strip ()].append (section )
66
64
67
- self ._sectionsByID = sectionsByID
68
- self ._sectionsByTitle = dict (sectionsByTitle )
65
+ return sectionsByID , dict (sectionsByTitle )
66
+
67
+ @property
68
+ def _sectionsByID (self ):
69
+ """ Returns a dictionary of all library sections by ID. """
70
+ return self ._loadSections [0 ]
71
+
72
+ @property
73
+ def _sectionsByTitle (self ):
74
+ """ Returns a dictionary of all library sections by title. """
75
+ return self ._loadSections [1 ]
69
76
70
77
def sections (self ):
71
78
""" Returns a list of all media sections in this library. Library sections may be any of
72
79
:class:`~plexapi.library.MovieSection`, :class:`~plexapi.library.ShowSection`,
73
80
:class:`~plexapi.library.MusicSection`, :class:`~plexapi.library.PhotoSection`.
74
81
"""
75
- self ._loadSections ()
76
82
return list (self ._sectionsByID .values ())
77
83
78
84
def section (self , title ):
@@ -87,8 +93,6 @@ def section(self, title):
87
93
:exc:`~plexapi.exceptions.NotFound`: The library section title is not found on the server.
88
94
"""
89
95
normalized_title = title .lower ().strip ()
90
- if not self ._sectionsByTitle or normalized_title not in self ._sectionsByTitle :
91
- self ._loadSections ()
92
96
try :
93
97
sections = self ._sectionsByTitle [normalized_title ]
94
98
except KeyError :
@@ -110,8 +114,6 @@ def sectionByID(self, sectionID):
110
114
Raises:
111
115
:exc:`~plexapi.exceptions.NotFound`: The library section ID is not found on the server.
112
116
"""
113
- if not self ._sectionsByID or sectionID not in self ._sectionsByID :
114
- self ._loadSections ()
115
117
try :
116
118
return self ._sectionsByID [sectionID ]
117
119
except KeyError :
@@ -385,7 +387,9 @@ def add(self, name='', type='', agent='', scanner='', location='', language='en-
385
387
if kwargs :
386
388
prefs_params = {f'prefs[{ k } ]' : v for k , v in kwargs .items ()}
387
389
part += f'&{ urlencode (prefs_params )} '
388
- return self ._server .query (part , method = self ._server ._session .post )
390
+ data = self ._server .query (part , method = self ._server ._session .post )
391
+ self ._invalidateCachedProperties ()
392
+ return data
389
393
390
394
def history (self , maxresults = None , mindate = None ):
391
395
""" Get Play History for all library Sections for the owner.
@@ -448,35 +452,25 @@ def _loadData(self, data):
448
452
self .type = data .attrib .get ('type' )
449
453
self .updatedAt = utils .toDatetime (data .attrib .get ('updatedAt' ))
450
454
self .uuid = data .attrib .get ('uuid' )
451
- # Private attrs as we don't want a reload.
452
- self ._filterTypes = None
453
- self ._fieldTypes = None
454
- self ._totalViewSize = None
455
- self ._totalDuration = None
456
- self ._totalStorage = None
457
455
458
456
@cached_data_property
459
457
def locations (self ):
460
458
return self .listAttrs (self ._data , 'path' , etag = 'Location' )
461
459
462
- @cached_property
460
+ @cached_data_property
463
461
def totalSize (self ):
464
462
""" Returns the total number of items in the library for the default library type. """
465
463
return self .totalViewSize (includeCollections = False )
466
464
467
465
@property
468
466
def totalDuration (self ):
469
467
""" Returns the total duration (in milliseconds) of items in the library. """
470
- if self ._totalDuration is None :
471
- self ._getTotalDurationStorage ()
472
- return self ._totalDuration
468
+ return self ._getTotalDurationStorage [0 ]
473
469
474
470
@property
475
471
def totalStorage (self ):
476
472
""" Returns the total storage (in bytes) of items in the library. """
477
- if self ._totalStorage is None :
478
- self ._getTotalDurationStorage ()
479
- return self ._totalStorage
473
+ return self ._getTotalDurationStorage [1 ]
480
474
481
475
def __getattribute__ (self , attr ):
482
476
# Intercept to call EditFieldMixin and EditTagMixin methods
@@ -492,6 +486,7 @@ def __getattribute__(self, attr):
492
486
)
493
487
return value
494
488
489
+ @cached_data_property
495
490
def _getTotalDurationStorage (self ):
496
491
""" Queries the Plex server for the total library duration and storage and caches the values. """
497
492
data = self ._server .query ('/media/providers?includeStorage=1' )
@@ -502,8 +497,10 @@ def _getTotalDurationStorage(self):
502
497
)
503
498
directory = next (iter (data .findall (xpath )), None )
504
499
if directory :
505
- self ._totalDuration = utils .cast (int , directory .attrib .get ('durationTotal' ))
506
- self ._totalStorage = utils .cast (int , directory .attrib .get ('storageTotal' ))
500
+ totalDuration = utils .cast (int , directory .attrib .get ('durationTotal' ))
501
+ totalStorage = utils .cast (int , directory .attrib .get ('storageTotal' ))
502
+ return totalDuration , totalStorage
503
+ return None , None
507
504
508
505
def totalViewSize (self , libtype = None , includeCollections = True ):
509
506
""" Returns the total number of items in the library for a specified libtype.
@@ -534,7 +531,9 @@ def totalViewSize(self, libtype=None, includeCollections=True):
534
531
def delete (self ):
535
532
""" Delete a library section. """
536
533
try :
537
- return self ._server .query (f'/library/sections/{ self .key } ' , method = self ._server ._session .delete )
534
+ data = self ._server .query (f'/library/sections/{ self .key } ' , method = self ._server ._session .delete )
535
+ self ._server .library ._invalidateCachedProperties ()
536
+ return data
538
537
except BadRequest : # pragma: no cover
539
538
msg = f'Failed to delete library { self .key } '
540
539
msg += 'You may need to allow this permission in your Plex settings.'
@@ -874,6 +873,7 @@ def deleteMediaPreviews(self):
874
873
self ._server .query (key , method = self ._server ._session .delete )
875
874
return self
876
875
876
+ @cached_data_property
877
877
def _loadFilters (self ):
878
878
""" Retrieves and caches the list of :class:`~plexapi.library.FilteringType` and
879
879
list of :class:`~plexapi.library.FilteringFieldType` for this library section.
@@ -883,23 +883,23 @@ def _loadFilters(self):
883
883
884
884
key = _key .format (key = self .key , filter = 'all' )
885
885
data = self ._server .query (key )
886
- self . _filterTypes = self .findItems (data , FilteringType , rtag = 'Meta' )
887
- self . _fieldTypes = self .findItems (data , FilteringFieldType , rtag = 'Meta' )
886
+ filterTypes = self .findItems (data , FilteringType , rtag = 'Meta' )
887
+ fieldTypes = self .findItems (data , FilteringFieldType , rtag = 'Meta' )
888
888
889
889
if self .TYPE != 'photo' : # No collections for photo library
890
890
key = _key .format (key = self .key , filter = 'collections' )
891
891
data = self ._server .query (key )
892
- self . _filterTypes .extend (self .findItems (data , FilteringType , rtag = 'Meta' ))
892
+ filterTypes .extend (self .findItems (data , FilteringType , rtag = 'Meta' ))
893
893
894
894
# Manually add guid field type, only allowing "is" operator
895
895
guidFieldType = '<FieldType type="guid"><Operator key="=" title="is"/></FieldType>'
896
- self ._fieldTypes .append (self ._manuallyLoadXML (guidFieldType , FilteringFieldType ))
896
+ fieldTypes .append (self ._manuallyLoadXML (guidFieldType , FilteringFieldType ))
897
+
898
+ return filterTypes , fieldTypes
897
899
898
900
def filterTypes (self ):
899
901
""" Returns a list of available :class:`~plexapi.library.FilteringType` for this library section. """
900
- if self ._filterTypes is None :
901
- self ._loadFilters ()
902
- return self ._filterTypes
902
+ return self ._loadFilters [0 ]
903
903
904
904
def getFilterType (self , libtype = None ):
905
905
""" Returns a :class:`~plexapi.library.FilteringType` for a specified libtype.
@@ -921,9 +921,7 @@ def getFilterType(self, libtype=None):
921
921
922
922
def fieldTypes (self ):
923
923
""" Returns a list of available :class:`~plexapi.library.FilteringFieldType` for this library section. """
924
- if self ._fieldTypes is None :
925
- self ._loadFilters ()
926
- return self ._fieldTypes
924
+ return self ._loadFilters [1 ]
927
925
928
926
def getFieldType (self , fieldType ):
929
927
""" Returns a :class:`~plexapi.library.FilteringFieldType` for a specified fieldType.
@@ -1972,7 +1970,7 @@ def albums(self):
1972
1970
1973
1971
def stations (self ):
1974
1972
""" Returns a list of :class:`~plexapi.playlist.Playlist` stations in this section. """
1975
- return next ((hub .items for hub in self .hubs () if hub .context == 'hub.music.stations' ), None )
1973
+ return next ((hub ._partialItems for hub in self .hubs () if hub .context == 'hub.music.stations' ), None )
1976
1974
1977
1975
def searchArtists (self , ** kwargs ):
1978
1976
""" Search for an artist. See :func:`~plexapi.library.LibrarySection.search` for usage. """
@@ -2232,26 +2230,38 @@ def _loadData(self, data):
2232
2230
self .style = data .attrib .get ('style' )
2233
2231
self .title = data .attrib .get ('title' )
2234
2232
self .type = data .attrib .get ('type' )
2235
- self ._section = None # cache for self.section
2233
+
2234
+ def __len__ (self ):
2235
+ return self .size
2236
2236
2237
2237
@cached_data_property
2238
- def items (self ):
2238
+ def _partialItems (self ):
2239
+ """ Cache for partial items. """
2240
+ return self .findItems (self ._data )
2241
+
2242
+ @cached_data_property
2243
+ def _items (self ):
2244
+ """ Cache for items. """
2239
2245
if self .more and self .key : # If there are more items to load, fetch them
2240
2246
items = self .fetchItems (self .key )
2241
2247
self .more = False
2242
2248
self .size = len (items )
2243
2249
return items
2244
2250
# Otherwise, all the data is in the initial _data XML response
2245
- return self .findItems ( self . _data )
2251
+ return self ._partialItems
2246
2252
2247
- def __len__ (self ):
2248
- return self .size
2253
+ def items (self ):
2254
+ """ Returns a list of all items in the hub. """
2255
+ return self ._items
2256
+
2257
+ @cached_data_property
2258
+ def _section (self ):
2259
+ """ Cache for section. """
2260
+ return self ._server .library .sectionByID (self .librarySectionID )
2249
2261
2250
2262
def section (self ):
2251
2263
""" Returns the :class:`~plexapi.library.LibrarySection` this hub belongs to.
2252
2264
"""
2253
- if self ._section is None :
2254
- self ._section = self ._server .library .sectionByID (self .librarySectionID )
2255
2265
return self ._section
2256
2266
2257
2267
def _reload (self , ** kwargs ):
0 commit comments