11# -*- coding: utf-8 -*-
22import re
3+ from typing import TYPE_CHECKING , Generic , Iterable , List , Optional , TypeVar , Union
34import weakref
45from functools import cached_property
56from urllib .parse import urlencode
67from xml .etree import ElementTree
8+ from xml .etree .ElementTree import Element
79
810from plexapi import CONFIG , X_PLEX_CONTAINER_SIZE , log , utils
911from plexapi .exceptions import BadRequest , NotFound , UnknownType , Unsupported
1012
13+ if TYPE_CHECKING :
14+ from plexapi .server import PlexServer
15+
16+ PlexObjectT = TypeVar ("PlexObjectT" , bound = 'PlexObject' )
17+ MediaContainerT = TypeVar ("MediaContainerT" , bound = "MediaContainer" )
18+
1119USER_DONT_RELOAD_FOR_KEYS = set ()
1220_DONT_RELOAD_FOR_KEYS = {'key' }
1321OPERATORS = {
@@ -253,8 +261,7 @@ def fetchItems(self, ekey, cls=None, container_start=None, container_size=None,
253261 if maxresults is not None :
254262 container_size = min (container_size , maxresults )
255263
256- results = []
257- subresults = []
264+ results = MediaContainer [cls ](self ._server , Element ('MediaContainer' ), initpath = ekey )
258265 headers = {}
259266
260267 while True :
@@ -332,7 +339,7 @@ def findItems(self, data, cls=None, initpath=None, rtag=None, **kwargs):
332339 if rtag :
333340 data = next (utils .iterXMLBFS (data , rtag ), [])
334341 # loop through all data elements to find matches
335- items = []
342+ items = MediaContainer [ cls ]( self . _server , data , initpath = initpath ) if data . tag == 'MediaContainer' else []
336343 for elem in data :
337344 if self ._checkAttrs (elem , ** kwargs ):
338345 item = self ._buildItemOrNone (elem , cls , initpath )
@@ -1011,7 +1018,11 @@ def delete(self):
10111018 return self ._server .query (self .historyKey , method = self ._server ._session .delete )
10121019
10131020
1014- class MediaContainer (PlexObject ):
1021+ class MediaContainer (
1022+ Generic [PlexObjectT ],
1023+ List [PlexObjectT ],
1024+ PlexObject ,
1025+ ):
10151026 """ Represents a single MediaContainer.
10161027
10171028 Attributes:
@@ -1024,11 +1035,71 @@ class MediaContainer(PlexObject):
10241035 librarySectionUUID (str): :class:`~plexapi.library.LibrarySection` UUID.
10251036 mediaTagPrefix (str): "/system/bundle/media/flags/"
10261037 mediaTagVersion (int): Unknown
1038+ offset (int): The offset of current results.
10271039 size (int): The number of items in the hub.
1040+ totalSize (int): The total number of items for the query.
10281041
10291042 """
10301043 TAG = 'MediaContainer'
10311044
1045+ def __init__ (
1046+ self ,
1047+ server : "PlexServer" ,
1048+ data : Element ,
1049+ * args : PlexObjectT ,
1050+ initpath : Optional [str ] = None ,
1051+ parent : Optional [PlexObject ] = None ,
1052+ ) -> None :
1053+ # super calls Generic.__init__ which calls list.__init__ eventually
1054+ super ().__init__ (* args )
1055+ PlexObject .__init__ (self , server , data , initpath , parent )
1056+
1057+ def extend (
1058+ self : MediaContainerT ,
1059+ __iterable : Union [Iterable [PlexObjectT ], MediaContainerT ],
1060+ ) -> None :
1061+ curr_size = self .size if self .size is not None else len (self )
1062+ super ().extend (__iterable )
1063+ # update size, totalSize, and offset
1064+ if not isinstance (__iterable , MediaContainer ):
1065+ return
1066+
1067+ # prefer the totalSize of the new iterable even if it is smaller
1068+ self .totalSize = (
1069+ __iterable .totalSize
1070+ if __iterable .totalSize is not None
1071+ else self .totalSize
1072+ ) # ideally both should be equal
1073+
1074+ # the size of the new iterable is added to the current size
1075+ self .size = curr_size + (
1076+ __iterable .size if __iterable .size is not None else len (__iterable )
1077+ )
1078+
1079+ # the offset is the minimum of the two, prefering older values
1080+ if self .offset is not None and __iterable .offset is not None :
1081+ self .offset = min (self .offset , __iterable .offset )
1082+ else :
1083+ self .offset = (
1084+ self .offset if self .offset is not None else __iterable .offset
1085+ )
1086+
1087+ # for all other attributes, overwrite with the new iterable's values if previously None
1088+ for key in (
1089+ "allowSync" ,
1090+ "augmentationKey" ,
1091+ "identifier" ,
1092+ "librarySectionID" ,
1093+ "librarySectionTitle" ,
1094+ "librarySectionUUID" ,
1095+ "mediaTagPrefix" ,
1096+ "mediaTagVersion" ,
1097+ ):
1098+ if (not hasattr (self , key )) or (getattr (self , key ) is None ):
1099+ if not hasattr (__iterable , key ):
1100+ continue
1101+ setattr (self , key , getattr (__iterable , key ))
1102+
10321103 def _loadData (self , data ):
10331104 self ._data = data
10341105 self .allowSync = utils .cast (int , data .attrib .get ('allowSync' ))
@@ -1039,4 +1110,6 @@ def _loadData(self, data):
10391110 self .librarySectionUUID = data .attrib .get ('librarySectionUUID' )
10401111 self .mediaTagPrefix = data .attrib .get ('mediaTagPrefix' )
10411112 self .mediaTagVersion = data .attrib .get ('mediaTagVersion' )
1113+ self .offset = utils .cast (int , data .attrib .get ("offset" ))
10421114 self .size = utils .cast (int , data .attrib .get ('size' ))
1115+ self .totalSize = utils .cast (int , data .attrib .get ("totalSize" ))
0 commit comments