Bump plexapi from 4.8.0 to 4.9.1 (#1627)

* Bump plexapi from 4.8.0 to 4.9.1

Bumps [plexapi](https://github.com/pkkid/python-plexapi) from 4.8.0 to 4.9.1.
- [Release notes](https://github.com/pkkid/python-plexapi/releases)
- [Commits](https://github.com/pkkid/python-plexapi/compare/4.8.0...4.9.1)

---
updated-dependencies:
- dependency-name: plexapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update plexapi==4.9.1

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>
This commit is contained in:
dependabot[bot] 2022-01-25 11:08:49 -08:00 committed by GitHub
parent a4ab5ab9be
commit 7f0abe0fe6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 254 additions and 76 deletions

View file

@ -8,6 +8,7 @@ from plexapi.exceptions import BadRequest
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin
from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin
from plexapi.playlist import Playlist
class Audio(PlexPartialObject): class Audio(PlexPartialObject):
@ -222,6 +223,11 @@ class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, S
filepaths += track.download(_savepath, keep_original_name, **kwargs) filepaths += track.download(_savepath, keep_original_name, **kwargs)
return filepaths return filepaths
def station(self):
""" Returns a :class:`~plexapi.playlist.Playlist` artist radio station or `None`. """
key = '%s?includeStations=1' % self.key
return next(iter(self.fetchItems(key, cls=Playlist, rtag="Stations")), None)
@utils.registerPlexObject @utils.registerPlexObject
class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin, class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,

View file

@ -3,7 +3,7 @@
# Library version # Library version
MAJOR_VERSION = 4 MAJOR_VERSION = 4
MINOR_VERSION = 8 MINOR_VERSION = 9
PATCH_VERSION = 0 PATCH_VERSION = 1
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}"

View file

@ -82,6 +82,28 @@ class Library(PlexObject):
except KeyError: except KeyError:
raise NotFound('Invalid library sectionID: %s' % sectionID) from None raise NotFound('Invalid library sectionID: %s' % sectionID) from None
def hubs(self, sectionID=None, identifier=None, **kwargs):
""" Returns a list of :class:`~plexapi.library.Hub` across all library sections.
Parameters:
sectionID (int or str or list, optional):
IDs of the sections to limit results or "playlists".
identifier (str or list, optional):
Names of identifiers to limit results.
Available on `Hub` instances as the `hubIdentifier` attribute.
Examples: 'home.continue' or 'home.ondeck'
"""
if sectionID:
if not isinstance(sectionID, list):
sectionID = [sectionID]
kwargs['contentDirectoryID'] = ",".join(map(str, sectionID))
if identifier:
if not isinstance(identifier, list):
identifier = [identifier]
kwargs['identifier'] = ",".join(identifier)
key = '/hubs%s' % utils.joinArgs(kwargs)
return self.fetchItems(key)
def all(self, **kwargs): def all(self, **kwargs):
""" Returns a list of all media from all library sections. """ Returns a list of all media from all library sections.
This may be a very large dataset to retrieve. This may be a very large dataset to retrieve.
@ -169,7 +191,7 @@ class Library(PlexObject):
name (str): Name of the library name (str): Name of the library
agent (str): Example com.plexapp.agents.imdb agent (str): Example com.plexapp.agents.imdb
type (str): movie, show, # check me type (str): movie, show, # check me
location (str): /path/to/files location (str or list): /path/to/files, ["/path/to/files", "/path/to/morefiles"]
language (str): Two letter language fx en language (str): Two letter language fx en
kwargs (dict): Advanced options should be passed as a dict. where the id is the key. kwargs (dict): Advanced options should be passed as a dict. where the id is the key.
@ -308,8 +330,16 @@ class Library(PlexObject):
40:South Africa, 41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad, 40:South Africa, 41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad,
46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela. 46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela.
""" """
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&location=%s' % ( if isinstance(location, str):
quote_plus(name), type, agent, quote_plus(scanner), language, quote_plus(location)) # noqa E126 location = [location]
locations = []
for path in location:
if not self._server.isBrowsable(path):
raise BadRequest('Path: %s does not exist.' % path)
locations.append(('location', path))
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&%s' % (
quote_plus(name), type, agent, quote_plus(scanner), language, urlencode(locations, doseq=True)) # noqa E126
if kwargs: if kwargs:
part += urlencode(kwargs) part += urlencode(kwargs)
return self._server.query(part, method=self._server._session.post) return self._server.query(part, method=self._server._session.post)
@ -486,16 +516,76 @@ class LibrarySection(PlexObject):
return self return self
def edit(self, agent=None, **kwargs): def edit(self, agent=None, **kwargs):
""" Edit a library (Note: agent is required). See :class:`~plexapi.library.Library` for example usage. """ Edit a library. See :class:`~plexapi.library.Library` for example usage.
Parameters: Parameters:
agent (str, optional): The library agent.
kwargs (dict): Dict of settings to edit. kwargs (dict): Dict of settings to edit.
""" """
if not agent: if not agent:
agent = self.agent agent = self.agent
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(kwargs))
locations = []
if kwargs.get('location'):
if isinstance(kwargs['location'], str):
kwargs['location'] = [kwargs['location']]
for path in kwargs.pop('location'):
if not self._server.isBrowsable(path):
raise BadRequest('Path: %s does not exist.' % path)
locations.append(('location', path))
params = list(kwargs.items()) + locations
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(params, doseq=True))
self._server.query(part, method=self._server._session.put) self._server.query(part, method=self._server._session.put)
def addLocations(self, location):
""" Add a location to a library.
Parameters:
location (str or list): A single folder path, list of paths.
Example:
.. code-block:: python
LibrarySection.addLocations('/path/1')
LibrarySection.addLocations(['/path/1', 'path/2', '/path/3'])
"""
locations = self.locations
if isinstance(location, str):
location = [location]
for path in location:
if not self._server.isBrowsable(path):
raise BadRequest('Path: %s does not exist.' % path)
locations.append(path)
self.edit(location=locations)
def removeLocations(self, location):
""" Remove a location from a library.
Parameters:
location (str or list): A single folder path, list of paths.
Example:
.. code-block:: python
LibrarySection.removeLocations('/path/1')
LibrarySection.removeLocations(['/path/1', 'path/2', '/path/3'])
"""
locations = self.locations
if isinstance(location, str):
location = [location]
for path in location:
if path in locations:
locations.remove(path)
else:
raise BadRequest('Path: %s does not exist in the library.' % location)
if len(locations) == 0:
raise BadRequest('You are unable to remove all locations from a library.')
self.edit(location=locations)
def get(self, title): def get(self, title):
""" Returns the media item with the specified title. """ Returns the media item with the specified title.
@ -510,9 +600,7 @@ class LibrarySection(PlexObject):
def getGuid(self, guid): def getGuid(self, guid):
""" Returns the media item with the specified external IMDB, TMDB, or TVDB ID. """ Returns the media item with the specified external IMDB, TMDB, or TVDB ID.
Note: This search uses a PlexAPI operator so performance may be slow. All items from the Note: Only available for the Plex Movie and Plex TV Series agents.
entire Plex library need to be retrieved for each guid search. It is recommended to create
your own lookup dictionary if you are searching for a lot of external guids.
Parameters: Parameters:
guid (str): The external guid of the item to return. guid (str): The external guid of the item to return.
@ -525,20 +613,23 @@ class LibrarySection(PlexObject):
.. code-block:: python .. code-block:: python
# This will retrieve all items in the entire library 3 times
result1 = library.getGuid('imdb://tt0944947') result1 = library.getGuid('imdb://tt0944947')
result2 = library.getGuid('tmdb://1399') result2 = library.getGuid('tmdb://1399')
result3 = library.getGuid('tvdb://121361') result3 = library.getGuid('tvdb://121361')
# This will only retrieve all items in the library once to create a lookup dictionary # Alternatively, create your own guid lookup dictionary for faster performance
guidLookup = {guid.id: item for item in library.all() for guid in item.guids} guidLookup = {guid.id: item for item in library.all() for guid in item.guids}
result1 = guidLookup['imdb://tt0944947'] result1 = guidLookup['imdb://tt0944947']
result2 = guidLookup['tmdb://1399'] result2 = guidLookup['tmdb://1399']
result3 = guidLookup['tvdb://121361'] result3 = guidLookup['tvdb://121361']
""" """
key = '/library/sections/%s/all?includeGuids=1' % self.key try:
return self.fetchItem(key, Guid__id__iexact=guid) dummy = self.search(maxresults=1)[0]
match = dummy.matches(agent=self.agent, title=guid.replace('://', '-'))
return self.search(guid=match[0].guid)[0]
except IndexError:
raise NotFound("Guid '%s' is not found in the library" % guid) from None
def all(self, libtype=None, **kwargs): def all(self, libtype=None, **kwargs):
""" Returns a list of all items from this library section. """ Returns a list of all items from this library section.
@ -556,13 +647,13 @@ class LibrarySection(PlexObject):
def hubs(self): def hubs(self):
""" Returns a list of available :class:`~plexapi.library.Hub` for this library section. """ Returns a list of available :class:`~plexapi.library.Hub` for this library section.
""" """
key = '/hubs/sections/%s' % self.key key = '/hubs/sections/%s?includeStations=1' % self.key
return self.fetchItems(key) return self.fetchItems(key)
def agents(self): def agents(self):
""" Returns a list of available :class:`~plexapi.media.Agent` for this library section. """ Returns a list of available :class:`~plexapi.media.Agent` for this library section.
""" """
return self._server.agents(utils.searchType(self.type)) return self._server.agents(self.type)
def settings(self): def settings(self):
""" Returns a list of all library settings. """ """ Returns a list of all library settings. """
@ -606,6 +697,36 @@ class LibrarySection(PlexObject):
self.edit(**data) self.edit(**data)
def _lockUnlockAllField(self, field, libtype=None, locked=True):
""" Lock or unlock a field for all items in the library. """
libtype = libtype or self.TYPE
args = {
'type': utils.searchType(libtype),
'%s.locked' % field: int(locked)
}
key = '/library/sections/%s/all%s' % (self.key, utils.joinArgs(args))
self._server.query(key, method=self._server._session.put)
def lockAllField(self, field, libtype=None):
""" Lock a field for all items in the library.
Parameters:
field (str): The field to lock (e.g. thumb, rating, collection).
libtype (str, optional): The library type to lock (movie, show, season, episode,
artist, album, track, photoalbum, photo). Default is the main library type.
"""
self._lockUnlockAllField(field, libtype=libtype, locked=True)
def unlockAllField(self, field, libtype=None):
""" Unlock a field for all items in the library.
Parameters:
field (str): The field to unlock (e.g. thumb, rating, collection).
libtype (str, optional): The library type to lock (movie, show, season, episode,
artist, album, track, photoalbum, photo). Default is the main library type.
"""
self._lockUnlockAllField(field, libtype=libtype, locked=False)
def timeline(self): def timeline(self):
""" Returns a timeline query for this library section. """ """ Returns a timeline query for this library section. """
key = '/library/sections/%s/timeline' % self.key key = '/library/sections/%s/timeline' % self.key
@ -1718,9 +1839,8 @@ class MusicSection(LibrarySection):
return self.fetchItems(key) return self.fetchItems(key)
def stations(self): def stations(self):
""" Returns a list of :class:`~plexapi.audio.Album` objects in this section. """ """ Returns a list of :class:`~plexapi.playlist.Playlist` stations in this section. """
key = '/hubs/sections/%s?includeStations=1' % self.key return next((hub.items for hub in self.hubs() if hub.context == 'hub.music.stations'), None)
return self.fetchItems(key, cls=Station)
def searchArtists(self, **kwargs): def searchArtists(self, **kwargs):
""" Search for an artist. See :func:`~plexapi.library.LibrarySection.search` for usage. """ """ Search for an artist. See :func:`~plexapi.library.LibrarySection.search` for usage. """
@ -1934,6 +2054,7 @@ class Hub(PlexObject):
context (str): The context of the hub. context (str): The context of the hub.
hubKey (str): API URL for these specific hub items. hubKey (str): API URL for these specific hub items.
hubIdentifier (str): The identifier of the hub. hubIdentifier (str): The identifier of the hub.
items (list): List of items in the hub.
key (str): API URL for the hub. key (str): API URL for the hub.
more (bool): True if there are more items to load (call reload() to fetch all items). more (bool): True if there are more items to load (call reload() to fetch all items).
size (int): The number of items in the hub. size (int): The number of items in the hub.
@ -2086,39 +2207,6 @@ class Place(HubMediaTag):
TAGTYPE = 400 TAGTYPE = 400
@utils.registerPlexObject
class Station(PlexObject):
""" Represents the Station area in the MusicSection.
Attributes:
TITLE (str): 'Stations'
TYPE (str): 'station'
hubIdentifier (str): Unknown.
size (int): Number of items found.
title (str): Title of this Hub.
type (str): Type of items in the Hub.
more (str): Unknown.
style (str): Unknown
items (str): List of items in the Hub.
"""
TITLE = 'Stations'
TYPE = 'station'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.hubIdentifier = data.attrib.get('hubIdentifier')
self.size = utils.cast(int, data.attrib.get('size'))
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
self.more = data.attrib.get('more')
self.style = data.attrib.get('style')
self.items = self.findItems(data)
def __len__(self):
return self.size
class FilteringType(PlexObject): class FilteringType(PlexObject):
""" Represents a single filtering Type object for a library. """ Represents a single filtering Type object for a library.

View file

@ -6,6 +6,7 @@ from urllib.parse import quote_plus
from plexapi import log, settings, utils from plexapi import log, settings, utils
from plexapi.base import PlexObject from plexapi.base import PlexObject
from plexapi.exceptions import BadRequest from plexapi.exceptions import BadRequest
from plexapi.utils import deprecated
@utils.registerPlexObject @utils.registerPlexObject
@ -1058,31 +1059,50 @@ class Agent(PlexObject):
self.hasAttribution = data.attrib.get('hasAttribution') self.hasAttribution = data.attrib.get('hasAttribution')
self.hasPrefs = data.attrib.get('hasPrefs') self.hasPrefs = data.attrib.get('hasPrefs')
self.identifier = data.attrib.get('identifier') self.identifier = data.attrib.get('identifier')
self.name = data.attrib.get('name')
self.primary = data.attrib.get('primary') self.primary = data.attrib.get('primary')
self.shortIdentifier = self.identifier.rsplit('.', 1)[1] self.shortIdentifier = self.identifier.rsplit('.', 1)[1]
if 'mediaType' in self._initpath:
self.name = data.attrib.get('name')
self.languageCode = []
for code in data:
self.languageCode += [code.attrib.get('code')]
else:
self.mediaTypes = [AgentMediaType(server=self._server, data=d) for d in data]
def _settings(self): if 'mediaType' in self._initpath:
self.languageCodes = self.listAttrs(data, 'code', etag='Language')
self.mediaTypes = []
else:
self.languageCodes = []
self.mediaTypes = self.findItems(data, cls=AgentMediaType)
@property
@deprecated('use "languageCodes" instead')
def languageCode(self):
return self.languageCodes
def settings(self):
key = '/:/plugins/%s/prefs' % self.identifier key = '/:/plugins/%s/prefs' % self.identifier
data = self._server.query(key) data = self._server.query(key)
return self.findItems(data, cls=settings.Setting) return self.findItems(data, cls=settings.Setting)
@deprecated('use "settings" instead')
def _settings(self):
return self.settings()
class AgentMediaType(Agent): class AgentMediaType(Agent):
""" Represents a single Agent MediaType.
Attributes:
TAG (str): 'MediaType'
"""
TAG = 'MediaType'
def __repr__(self): def __repr__(self):
uid = self._clean(self.firstAttr('name')) uid = self._clean(self.firstAttr('name'))
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p]) return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
def _loadData(self, data): def _loadData(self, data):
self.languageCodes = self.listAttrs(data, 'code', etag='Language')
self.mediaType = utils.cast(int, data.attrib.get('mediaType')) self.mediaType = utils.cast(int, data.attrib.get('mediaType'))
self.name = data.attrib.get('name') self.name = data.attrib.get('name')
self.languageCode = []
for code in data: @property
self.languageCode += [code.attrib.get('code')] @deprecated('use "languageCodes" instead')
def languageCode(self):
return self.languageCodes

View file

@ -29,7 +29,11 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
icon (str): Icon URI string for smart playlists. icon (str): Icon URI string for smart playlists.
key (str): API URL (/playlist/<ratingkey>). key (str): API URL (/playlist/<ratingkey>).
leafCount (int): Number of items in the playlist view. leafCount (int): Number of items in the playlist view.
librarySectionID (int): Library section identifier (radio only)
librarySectionKey (str): Library section key (radio only)
librarySectionTitle (str): Library section title (radio only)
playlistType (str): 'audio', 'video', or 'photo' playlistType (str): 'audio', 'video', or 'photo'
radio (bool): If this playlist represents a radio station
ratingKey (int): Unique key identifying the playlist. ratingKey (int): Unique key identifying the playlist.
smart (bool): True if the playlist is a smart playlist. smart (bool): True if the playlist is a smart playlist.
summary (str): Summary of the playlist. summary (str): Summary of the playlist.
@ -54,7 +58,11 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
self.icon = data.attrib.get('icon') self.icon = data.attrib.get('icon')
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50 self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
self.leafCount = utils.cast(int, data.attrib.get('leafCount')) self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
self.librarySectionID = utils.cast(int, data.attrib.get('librarySectionID'))
self.librarySectionKey = data.attrib.get('librarySectionKey')
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.playlistType = data.attrib.get('playlistType') self.playlistType = data.attrib.get('playlistType')
self.radio = utils.cast(bool, data.attrib.get('radio', 0))
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.smart = utils.cast(bool, data.attrib.get('smart')) self.smart = utils.cast(bool, data.attrib.get('smart'))
self.summary = data.attrib.get('summary') self.summary = data.attrib.get('summary')
@ -169,6 +177,8 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
def items(self): def items(self):
""" Returns a list of all items in the playlist. """ """ Returns a list of all items in the playlist. """
if self.radio:
return []
if self._items is None: if self._items is None:
key = '%s/items' % self.key key = '%s/items' % self.key
items = self.fetchItems(key) items = self.fetchItems(key)

View file

@ -175,8 +175,11 @@ class PlayQueue(PlexObject):
args["uri"] = "library:///directory/{uri_args}".format(uri_args=uri_args) args["uri"] = "library:///directory/{uri_args}".format(uri_args=uri_args)
args["type"] = items[0].listType args["type"] = items[0].listType
elif items.type == "playlist": elif items.type == "playlist":
args["playlistID"] = items.ratingKey
args["type"] = items.playlistType args["type"] = items.playlistType
if items.radio:
args["uri"] = f"server://{server.machineIdentifier}/{server.library.identifier}{items.key}"
else:
args["playlistID"] = items.ratingKey
else: else:
uuid = items.section().uuid uuid = items.section().uuid
args["type"] = items.listType args["type"] = items.listType
@ -192,6 +195,42 @@ class PlayQueue(PlexObject):
c._server = server c._server = server
return c return c
@classmethod
def fromStationKey(cls, server, key):
"""Create and return a new :class:`~plexapi.playqueue.PlayQueue`.
This is a convenience method to create a `PlayQueue` for
radio stations when only the `key` string is available.
Parameters:
server (:class:`~plexapi.server.PlexServer`): Server you are connected to.
key (str): A station key as provided by :func:`~plexapi.library.LibrarySection.hubs()`
or :func:`~plexapi.audio.Artist.station()`
Example:
.. code-block:: python
from plexapi.playqueue import PlayQueue
music = server.library.section("Music")
artist = music.get("Artist Name")
station = artist.station()
key = station.key # "/library/metadata/12855/station/8bd39616-dbdb-459e-b8da-f46d0b170af4?type=10"
pq = PlayQueue.fromStationKey(server, key)
client = server.clients()[0]
client.playMedia(pq)
"""
args = {
"type": "audio",
"uri": f"server://{server.machineIdentifier}/{server.library.identifier}{key}"
}
path = f"/playQueues{utils.joinArgs(args)}"
data = server.query(path, method=server._session.post)
c = cls(server, data, initpath=path)
c.playQueueType = args["type"]
c._server = server
return c
def addItem(self, item, playNext=False, refresh=True): def addItem(self, item, playNext=False, refresh=True):
""" """
Append the provided item to the "Up Next" section of the PlayQueue. Append the provided item to the "Up Next" section of the PlayQueue.

View file

@ -3,6 +3,7 @@ from urllib.parse import urlencode
from xml.etree import ElementTree from xml.etree import ElementTree
import requests import requests
import os
from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_CONTAINER_SIZE, log, from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_CONTAINER_SIZE, log,
logfilter) logfilter)
from plexapi import utils from plexapi import utils
@ -228,10 +229,10 @@ class PlexServer(PlexObject):
return activities return activities
def agents(self, mediaType=None): def agents(self, mediaType=None):
""" Returns the :class:`~plexapi.media.Agent` objects this server has available. """ """ Returns a list of :class:`~plexapi.media.Agent` objects this server has available. """
key = '/system/agents' key = '/system/agents'
if mediaType: if mediaType:
key += '?mediaType=%s' % mediaType key += '?mediaType=%s' % utils.searchType(mediaType)
return self.fetchItems(key) return self.fetchItems(key)
def createToken(self, type='delegation', scope='all'): def createToken(self, type='delegation', scope='all'):
@ -384,6 +385,18 @@ class PlexServer(PlexObject):
for path, paths, files in self.walk(_path): for path, paths, files in self.walk(_path):
yield path, paths, files yield path, paths, files
def isBrowsable(self, path):
""" Returns True if the Plex server can browse the given path.
Parameters:
path (:class:`~plexapi.library.Path` or str): Full path to browse.
"""
if isinstance(path, Path):
path = path.path
path = os.path.normpath(path)
paths = [p.path for p in self.browse(os.path.dirname(path), includeFiles=False)]
return path in paths
def clients(self): def clients(self):
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """ """ Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
items = [] items = []

View file

@ -113,17 +113,19 @@ class Setting(PlexObject):
def _loadData(self, data): def _loadData(self, data):
""" Load attribute values from Plex XML response. """ """ Load attribute values from Plex XML response. """
self._setValue = None self.type = data.attrib.get('type')
self.advanced = utils.cast(bool, data.attrib.get('advanced'))
self.default = self._cast(data.attrib.get('default'))
self.enumValues = self._getEnumValues(data)
self.group = data.attrib.get('group')
self.hidden = utils.cast(bool, data.attrib.get('hidden'))
self.id = data.attrib.get('id') self.id = data.attrib.get('id')
self.label = data.attrib.get('label') self.label = data.attrib.get('label')
self.option = data.attrib.get('option')
self.secure = utils.cast(bool, data.attrib.get('secure'))
self.summary = data.attrib.get('summary') self.summary = data.attrib.get('summary')
self.type = data.attrib.get('type')
self.default = self._cast(data.attrib.get('default'))
self.value = self._cast(data.attrib.get('value')) self.value = self._cast(data.attrib.get('value'))
self.hidden = utils.cast(bool, data.attrib.get('hidden')) self._setValue = None
self.advanced = utils.cast(bool, data.attrib.get('advanced'))
self.group = data.attrib.get('group')
self.enumValues = self._getEnumValues(data)
def _cast(self, value): def _cast(self, value):
""" Cast the specific value to the type of this setting. """ """ Cast the specific value to the type of this setting. """
@ -132,8 +134,8 @@ class Setting(PlexObject):
return value return value
def _getEnumValues(self, data): def _getEnumValues(self, data):
""" Returns a list of dictionary of valis value for this setting. """ """ Returns a list or dictionary of values for this setting. """
enumstr = data.attrib.get('enumValues') enumstr = data.attrib.get('enumValues') or data.attrib.get('values')
if not enumstr: if not enumstr:
return None return None
if ':' in enumstr: if ':' in enumstr:

View file

@ -27,7 +27,7 @@ musicbrainzngs==0.7.1
oauthlib==3.1.1 oauthlib==3.1.1
packaging==21.3 packaging==21.3
paho-mqtt==1.6.1 paho-mqtt==1.6.1
plexapi==4.8.0 plexapi==4.9.1
portend==3.1.0 portend==3.1.0
profilehooks==1.12.0 profilehooks==1.12.0
PyJWT==2.3.0 PyJWT==2.3.0