Skip to content

Commit bbe3e8e

Browse files
authored
Improve reload performance (#1451)
* Change default includes to false * Update isFullObject() check * Fix checkFiles in tests
1 parent d560467 commit bbe3e8e

File tree

5 files changed

+59
-44
lines changed

5 files changed

+59
-44
lines changed

plexapi/base.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Generic, Iterable, List, Optional, TypeVar, Union
44
import weakref
55
from functools import cached_property
6-
from urllib.parse import urlencode
6+
from urllib.parse import parse_qsl, urlencode, urlparse
77
from xml.etree import ElementTree
88
from xml.etree.ElementTree import Element
99

@@ -391,31 +391,38 @@ def reload(self, key=None, **kwargs):
391391
392392
Parameters:
393393
key (string, optional): Override the key to reload.
394-
**kwargs (dict): A dictionary of XML include parameters to exclude or override.
395-
All parameters are included by default with the option to override each parameter
396-
or disable each parameter individually by setting it to False or 0.
394+
**kwargs (dict): A dictionary of XML include parameters to include/exclude or override.
397395
See :class:`~plexapi.base.PlexPartialObject` for all the available include parameters.
396+
Set parameter to True to include and False to exclude.
398397
399398
Example:
400399
401400
.. code-block:: python
402401
403402
from plexapi.server import PlexServer
404403
plex = PlexServer('http://localhost:32400', token='xxxxxxxxxxxxxxxxxxxx')
404+
405+
# Search results are partial objects.
405406
movie = plex.library.section('Movies').get('Cars')
407+
movie.isPartialObject() # Returns True
406408
407-
# Partial reload of the movie without the `checkFiles` parameter.
408-
# Excluding `checkFiles` will prevent the Plex server from reading the
409-
# file to check if the file still exists and is accessible.
409+
# Partial reload of the movie without a default include parameter.
410410
# The movie object will remain as a partial object.
411-
movie.reload(checkFiles=False)
411+
movie.reload(includeMarkers=False)
412412
movie.isPartialObject() # Returns True
413413
414-
# Full reload of the movie with all include parameters.
414+
# Full reload of the movie with all default include parameters.
415415
# The movie object will be a full object.
416416
movie.reload()
417417
movie.isFullObject() # Returns True
418418
419+
# Full reload of the movie with all default and extra include parameter.
420+
# Including `checkFiles` will tell the Plex server to check if the file
421+
# still exists and is accessible.
422+
# The movie object will be a full object.
423+
movie.reload(checkFiles=True)
424+
movie.isFullObject() # Returns True
425+
419426
"""
420427
return self._reload(key=key, **kwargs)
421428

@@ -505,25 +512,25 @@ class PlexPartialObject(PlexObject):
505512
automatically and update itself.
506513
"""
507514
_INCLUDES = {
508-
'checkFiles': 1,
509-
'includeAllConcerts': 1,
515+
'checkFiles': 0,
516+
'includeAllConcerts': 0,
510517
'includeBandwidths': 1,
511518
'includeChapters': 1,
512-
'includeChildren': 1,
513-
'includeConcerts': 1,
514-
'includeExternalMedia': 1,
515-
'includeExtras': 1,
519+
'includeChildren': 0,
520+
'includeConcerts': 0,
521+
'includeExternalMedia': 0,
522+
'includeExtras': 0,
516523
'includeFields': 'thumbBlurHash,artBlurHash',
517524
'includeGeolocation': 1,
518525
'includeLoudnessRamps': 1,
519526
'includeMarkers': 1,
520-
'includeOnDeck': 1,
521-
'includePopularLeaves': 1,
522-
'includePreferences': 1,
523-
'includeRelated': 1,
524-
'includeRelatedCount': 1,
525-
'includeReviews': 1,
526-
'includeStations': 1,
527+
'includeOnDeck': 0,
528+
'includePopularLeaves': 0,
529+
'includePreferences': 0,
530+
'includeRelated': 0,
531+
'includeRelatedCount': 0,
532+
'includeReviews': 0,
533+
'includeStations': 0,
527534
}
528535
_EXCLUDES = {
529536
'excludeElements': (
@@ -592,7 +599,11 @@ def isFullObject(self):
592599
search result for a movie often only contain a portion of the attributes a full
593600
object (main url) for that movie would contain.
594601
"""
595-
return not self.key or (self._details_key or self.key) == self._initpath
602+
parsed_key = urlparse(self._details_key or self.key)
603+
parsed_initpath = urlparse(self._initpath)
604+
query_key = set(parse_qsl(parsed_key.query))
605+
query_init = set(parse_qsl(parsed_initpath.query))
606+
return not self.key or (parsed_key.path == parsed_initpath.path and query_key <= query_init)
596607

597608
def isPartialObject(self):
598609
""" Returns True if this is not a full object. """

plexapi/media.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,16 @@ class MediaPart(PlexObject):
106106
Attributes:
107107
TAG (str): 'Part'
108108
accessible (bool): True if the file is accessible.
109+
Requires reloading the media with ``checkFiles=True``.
110+
Refer to :func:`~plexapi.base.PlexObject.reload`.
109111
audioProfile (str): The audio profile of the file.
110112
container (str): The container type of the file (ex: avi).
111113
decision (str): Unknown.
112114
deepAnalysisVersion (int): The Plex deep analysis version for the file.
113115
duration (int): The duration of the file in milliseconds.
114116
exists (bool): True if the file exists.
117+
Requires reloading the media with ``checkFiles=True``.
118+
Refer to :func:`~plexapi.base.PlexObject.reload`.
115119
file (str): The path to this file on disk (ex: /media/Movies/Cars (2006)/Cars (2006).mkv)
116120
has64bitOffsets (bool): True if the file has 64 bit offsets.
117121
hasThumbnail (bool): True if the file (track) has an embedded thumbnail.

plexapi/mixins.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class AdvancedSettingsMixin:
1414

1515
def preferences(self):
1616
""" Returns a list of :class:`~plexapi.settings.Preferences` objects. """
17-
data = self._server.query(self._details_key)
18-
return self.findItems(data, settings.Preferences, rtag='Preferences')
17+
key = f'{self.key}?includePreferences=1'
18+
return self.fetchItems(key, cls=settings.Preferences, rtag='Preferences')
1919

2020
def preference(self, pref):
2121
""" Returns a :class:`~plexapi.settings.Preferences` object for the specified pref.
@@ -240,8 +240,7 @@ def matches(self, agent=None, title=None, year=None, language=None):
240240
params['agent'] = utils.getAgentIdentifier(self.section(), agent)
241241

242242
key = key + '?' + urlencode(params)
243-
data = self._server.query(key, method=self._server._session.get)
244-
return self.findItems(data, initpath=key)
243+
return self.fetchItems(key, cls=media.SearchResult)
245244

246245
def fixMatch(self, searchResult=None, auto=False, agent=None):
247246
""" Use match result to update show metadata.
@@ -278,8 +277,8 @@ class ExtrasMixin:
278277
def extras(self):
279278
""" Returns a list of :class:`~plexapi.video.Extra` objects. """
280279
from plexapi.video import Extra
281-
data = self._server.query(self._details_key)
282-
return self.findItems(data, Extra, rtag='Extras')
280+
key = f'{self.key}/extras'
281+
return self.fetchItems(key, cls=Extra)
283282

284283

285284
class HubsMixin:
@@ -289,8 +288,7 @@ def hubs(self):
289288
""" Returns a list of :class:`~plexapi.library.Hub` objects. """
290289
from plexapi.library import Hub
291290
key = f'{self.key}/related'
292-
data = self._server.query(key)
293-
return self.findItems(data, Hub)
291+
return self.fetchItems(key, cls=Hub)
294292

295293

296294
class PlayedUnplayedMixin:

plexapi/video.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -456,8 +456,8 @@ def _prettyfilename(self):
456456

457457
def reviews(self):
458458
""" Returns a list of :class:`~plexapi.media.Review` objects. """
459-
data = self._server.query(self._details_key)
460-
return self.findItems(data, media.Review, rtag='Video')
459+
key = f'{self.key}?includeReviews=1'
460+
return self.fetchItems(key, cls=media.Review, rtag='Video')
461461

462462
def editions(self):
463463
""" Returns a list of :class:`~plexapi.video.Movie` objects
@@ -614,8 +614,8 @@ def onDeck(self):
614614
""" Returns show's On Deck :class:`~plexapi.video.Video` object or `None`.
615615
If show is unwatched, return will likely be the first episode.
616616
"""
617-
data = self._server.query(self._details_key)
618-
return next(iter(self.findItems(data, rtag='OnDeck')), None)
617+
key = f'{self.key}?includeOnDeck=1'
618+
return next(iter(self.fetchItems(key, cls=Episode, rtag='OnDeck')), None)
619619

620620
def season(self, title=None, season=None):
621621
""" Returns the season with the specified title or number.
@@ -796,8 +796,8 @@ def onDeck(self):
796796
""" Returns season's On Deck :class:`~plexapi.video.Video` object or `None`.
797797
Will only return a match if the show's On Deck episode is in this season.
798798
"""
799-
data = self._server.query(self._details_key)
800-
return next(iter(self.findItems(data, rtag='OnDeck')), None)
799+
key = f'{self.key}?includeOnDeck=1'
800+
return next(iter(self.fetchItems(key, cls=Episode, rtag='OnDeck')), None)
801801

802802
def episode(self, title=None, episode=None):
803803
""" Returns the episode with the given title or number.

tests/test_video.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,13 @@ def test_video_Movie_attrs(movies):
217217
assert utils.is_int(video.width, gte=400)
218218
# Part
219219
part = media.parts[0]
220-
assert part.accessible
220+
assert part.accessible is None
221221
assert part.audioProfile == "lc"
222222
assert part.container in utils.CONTAINERS
223223
assert part.decision is None
224224
assert part.deepAnalysisVersion is None or utils.is_int(part.deepAnalysisVersion)
225225
assert utils.is_int(part.duration, gte=160000)
226-
assert part.exists
226+
assert part.exists is None
227227
assert len(part.file) >= 10
228228
assert part.has64bitOffsets is False
229229
assert part.hasPreviewThumbnails is False
@@ -323,10 +323,12 @@ def test_video_Movie_getStreamURL(movie, account):
323323
def test_video_Movie_isFullObject_and_reload(plex):
324324
movie = plex.library.section("Movies").get("Sita Sings the Blues")
325325
assert movie.isFullObject() is False
326-
movie.reload(checkFiles=False)
326+
movie.reload(includeChapters=False)
327327
assert movie.isFullObject() is False
328328
movie.reload()
329329
assert movie.isFullObject() is True
330+
movie.reload(includeExtras=True)
331+
assert movie.isFullObject() is True
330332
movie_via_search = plex.library.search(movie.title)[0]
331333
assert movie_via_search.isFullObject() is False
332334
movie_via_search.reload()
@@ -1285,8 +1287,8 @@ def test_video_Episode_attrs(episode):
12851287
assert len(part.key) >= 10
12861288
assert part._server._baseurl == utils.SERVER_BASEURL
12871289
assert utils.is_int(part.size, gte=18184197)
1288-
assert part.exists
1289-
assert part.accessible
1290+
assert part.exists is None
1291+
assert part.accessible is None
12901292

12911293

12921294
def test_video_Episode_watched(tvshows):
@@ -1434,13 +1436,13 @@ def test_that_reload_return_the_same_object(plex):
14341436
def test_video_exists_accessible(movie, episode):
14351437
assert movie.media[0].parts[0].exists is None
14361438
assert movie.media[0].parts[0].accessible is None
1437-
movie.reload()
1439+
movie.reload(checkFiles=True)
14381440
assert movie.media[0].parts[0].exists is True
14391441
assert movie.media[0].parts[0].accessible is True
14401442

14411443
assert episode.media[0].parts[0].exists is None
14421444
assert episode.media[0].parts[0].accessible is None
1443-
episode.reload()
1445+
episode.reload(checkFiles=True)
14441446
assert episode.media[0].parts[0].exists is True
14451447
assert episode.media[0].parts[0].accessible is True
14461448

0 commit comments

Comments
 (0)