mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 03:33:08 +00:00
style: lint all python files (#1228)
This commit is contained in:
parent
dba3369f55
commit
a6b6ebfbff
28 changed files with 131 additions and 105 deletions
10
.flake8
10
.flake8
|
@ -5,12 +5,14 @@
|
||||||
# E701: multiple statements on one line (colon)
|
# E701: multiple statements on one line (colon)
|
||||||
# E702: multiple statements on one line (semicolon)
|
# E702: multiple statements on one line (semicolon)
|
||||||
# E731: do not assign a lambda expression, use a def
|
# E731: do not assign a lambda expression, use a def
|
||||||
# W293: blank line contains whitespace
|
|
||||||
# W503: line break before binary operator
|
# W503: line break before binary operator
|
||||||
# W605: invalid escape sequence
|
# W605: invalid escape sequence
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore=E128,E701,E702,E731,W293,W503,W605
|
ignore=E128,E701,E702,E731,W503,W605
|
||||||
exclude=compat.py
|
exclude=compat.py,venv
|
||||||
|
per-file-ignores =
|
||||||
|
tests/payloads.py:E501
|
||||||
max-complexity = -1
|
max-complexity = -1
|
||||||
max-line-length = 125
|
# The GitHub editor is 127 chars wide
|
||||||
|
max-line-length = 127
|
||||||
show-source = True
|
show-source = True
|
||||||
|
|
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
|
@ -54,9 +54,13 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
flake8 plexapi --count --select=E9,F63,F7,F82 --show-source --statistics
|
echo "::group::flake8 pass 1"
|
||||||
|
flake8 --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||||
|
echo "::endgroup::"
|
||||||
# The GitHub editor is 127 chars wide
|
# The GitHub editor is 127 chars wide
|
||||||
flake8 plexapi --count --max-complexity=12 --max-line-length=127 --statistics
|
echo "::group::flake8 pass 2"
|
||||||
|
flake8 --count --max-complexity=12 --max-line-length=127 --statistics
|
||||||
|
echo "::endgroup::"
|
||||||
|
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
|
|
31
docs/conf.py
31
docs/conf.py
|
@ -12,12 +12,13 @@
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
import copy, sys
|
import copy
|
||||||
from os.path import abspath, dirname, join
|
from os.path import abspath, dirname, join
|
||||||
|
import sys
|
||||||
path = dirname(dirname(abspath(__file__)))
|
path = dirname(dirname(abspath(__file__)))
|
||||||
sys.path.append(path)
|
sys.path.append(path)
|
||||||
sys.path.append(join(path, 'plexapi'))
|
sys.path.append(join(path, 'plexapi'))
|
||||||
import plexapi
|
import plexapi # noqa: E402
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
|
@ -27,13 +28,17 @@ extensions = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# -- Monkey-patch docstring to not auto-link :ivars ------------------------
|
# -- Monkey-patch docstring to not auto-link :ivars ------------------------
|
||||||
from sphinx.domains.python import PythonDomain
|
from sphinx.domains.python import PythonDomain # noqa: E402
|
||||||
print('Monkey-patching PythonDomain.resolve_xref()')
|
print('Monkey-patching PythonDomain.resolve_xref()')
|
||||||
old_resolve_xref = copy.deepcopy(PythonDomain.resolve_xref)
|
old_resolve_xref = copy.deepcopy(PythonDomain.resolve_xref)
|
||||||
|
|
||||||
|
|
||||||
def new_resolve_xref(*args):
|
def new_resolve_xref(*args):
|
||||||
if '.' not in args[5]: # target
|
if '.' not in args[5]: # target
|
||||||
return None
|
return None
|
||||||
return old_resolve_xref(*args)
|
return old_resolve_xref(*args)
|
||||||
|
|
||||||
|
|
||||||
PythonDomain.resolve_xref = new_resolve_xref
|
PythonDomain.resolve_xref = new_resolve_xref
|
||||||
|
|
||||||
# -- Napoleon Settings -----------------------------------------------------
|
# -- Napoleon Settings -----------------------------------------------------
|
||||||
|
@ -79,7 +84,7 @@ author = 'M.Shepanski'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = plexapi.VERSION
|
version = plexapi.VERSION
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
#release = '2.0.2'
|
# release = '2.0.2'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -137,7 +142,7 @@ html_context = {'css_files': ['_static/custom.css']}
|
||||||
html_theme_options = {
|
html_theme_options = {
|
||||||
'collapse_navigation': False,
|
'collapse_navigation': False,
|
||||||
'display_version': False,
|
'display_version': False,
|
||||||
#'navigation_depth': 3,
|
# 'navigation_depth': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,17 +243,17 @@ htmlhelp_basename = 'PythonPlexAPIdoc'
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
# 'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# 'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
# 'preamble': '',
|
# 'preamble': '',
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
# Latex figure (float) alignment
|
||||||
# 'figure_align': 'htbp',
|
# 'figure_align': 'htbp',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
|
|
@ -919,7 +919,7 @@ class PlexSession(object):
|
||||||
|
|
||||||
def stop(self, reason=''):
|
def stop(self, reason=''):
|
||||||
""" Stop playback for the session.
|
""" Stop playback for the session.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
reason (str): Message displayed to the user for stopping playback.
|
reason (str): Message displayed to the user for stopping playback.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -400,7 +400,7 @@ class Collection(
|
||||||
@deprecated('use editTitle, editSortTitle, editContentRating, and editSummary instead')
|
@deprecated('use editTitle, editSortTitle, editContentRating, and editSummary instead')
|
||||||
def edit(self, title=None, titleSort=None, contentRating=None, summary=None, **kwargs):
|
def edit(self, title=None, titleSort=None, contentRating=None, summary=None, **kwargs):
|
||||||
""" Edit the collection.
|
""" Edit the collection.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
title (str, optional): The title of the collection.
|
title (str, optional): The title of the collection.
|
||||||
titleSort (str, optional): The sort title of the collection.
|
titleSort (str, optional): The sort title of the collection.
|
||||||
|
|
|
@ -542,7 +542,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
def addLocations(self, location):
|
def addLocations(self, location):
|
||||||
""" Add a location to a library.
|
""" Add a location to a library.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
location (str or list): A single folder path, list of paths.
|
location (str or list): A single folder path, list of paths.
|
||||||
|
|
||||||
|
@ -565,7 +565,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
def removeLocations(self, location):
|
def removeLocations(self, location):
|
||||||
""" Remove a location from a library.
|
""" Remove a location from a library.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
location (str or list): A single folder path, list of paths.
|
location (str or list): A single folder path, list of paths.
|
||||||
|
|
||||||
|
@ -744,7 +744,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
def lockAllField(self, field, libtype=None):
|
def lockAllField(self, field, libtype=None):
|
||||||
""" Lock a field for all items in the library.
|
""" Lock a field for all items in the library.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
field (str): The field to lock (e.g. thumb, rating, collection).
|
field (str): The field to lock (e.g. thumb, rating, collection).
|
||||||
libtype (str, optional): The library type to lock (movie, show, season, episode,
|
libtype (str, optional): The library type to lock (movie, show, season, episode,
|
||||||
|
@ -754,7 +754,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
def unlockAllField(self, field, libtype=None):
|
def unlockAllField(self, field, libtype=None):
|
||||||
""" Unlock a field for all items in the library.
|
""" Unlock a field for all items in the library.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
field (str): The field to unlock (e.g. thumb, rating, collection).
|
field (str): The field to unlock (e.g. thumb, rating, collection).
|
||||||
libtype (str, optional): The library type to lock (movie, show, season, episode,
|
libtype (str, optional): The library type to lock (movie, show, season, episode,
|
||||||
|
@ -847,7 +847,7 @@ class LibrarySection(PlexObject):
|
||||||
"""
|
"""
|
||||||
_key = ('/library/sections/{key}/{filter}?includeMeta=1&includeAdvanced=1'
|
_key = ('/library/sections/{key}/{filter}?includeMeta=1&includeAdvanced=1'
|
||||||
'&X-Plex-Container-Start=0&X-Plex-Container-Size=0')
|
'&X-Plex-Container-Start=0&X-Plex-Container-Size=0')
|
||||||
|
|
||||||
key = _key.format(key=self.key, filter='all')
|
key = _key.format(key=self.key, filter='all')
|
||||||
data = self._server.query(key)
|
data = self._server.query(key)
|
||||||
self._filterTypes = self.findItems(data, FilteringType, rtag='Meta')
|
self._filterTypes = self.findItems(data, FilteringType, rtag='Meta')
|
||||||
|
@ -894,7 +894,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
def getFieldType(self, fieldType):
|
def getFieldType(self, fieldType):
|
||||||
""" Returns a :class:`~plexapi.library.FilteringFieldType` for a specified fieldType.
|
""" Returns a :class:`~plexapi.library.FilteringFieldType` for a specified fieldType.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
||||||
subtitleLanguage, audioLanguage, resolution).
|
subtitleLanguage, audioLanguage, resolution).
|
||||||
|
@ -927,7 +927,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.getFilterType(libtype).filters
|
return self.getFilterType(libtype).filters
|
||||||
|
|
||||||
def listSorts(self, libtype=None):
|
def listSorts(self, libtype=None):
|
||||||
""" Returns a list of available :class:`~plexapi.library.FilteringSort` for a specified libtype.
|
""" Returns a list of available :class:`~plexapi.library.FilteringSort` for a specified libtype.
|
||||||
This is the list of options in the sorting dropdown menu
|
This is the list of options in the sorting dropdown menu
|
||||||
|
@ -970,7 +970,7 @@ class LibrarySection(PlexObject):
|
||||||
""" Returns a list of available :class:`~plexapi.library.FilteringOperator` for a specified fieldType.
|
""" Returns a list of available :class:`~plexapi.library.FilteringOperator` for a specified fieldType.
|
||||||
This is the list of options in the custom filter operator dropdown menu
|
This is the list of options in the custom filter operator dropdown menu
|
||||||
(`screenshot <../_static/images/LibrarySection.search.png>`__).
|
(`screenshot <../_static/images/LibrarySection.search.png>`__).
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
||||||
subtitleLanguage, audioLanguage, resolution).
|
subtitleLanguage, audioLanguage, resolution).
|
||||||
|
@ -992,7 +992,7 @@ class LibrarySection(PlexObject):
|
||||||
:class:`~plexapi.library.FilteringFilter` or filter field.
|
:class:`~plexapi.library.FilteringFilter` or filter field.
|
||||||
This is the list of available values for a custom filter
|
This is the list of available values for a custom filter
|
||||||
(`screenshot <../_static/images/LibrarySection.search.png>`__).
|
(`screenshot <../_static/images/LibrarySection.search.png>`__).
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
field (str): :class:`~plexapi.library.FilteringFilter` object,
|
field (str): :class:`~plexapi.library.FilteringFilter` object,
|
||||||
or the name of the field (genre, year, contentRating, etc.).
|
or the name of the field (genre, year, contentRating, etc.).
|
||||||
|
@ -1024,7 +1024,7 @@ class LibrarySection(PlexObject):
|
||||||
availableFilters = [f.filter for f in self.listFilters(libtype)]
|
availableFilters = [f.filter for f in self.listFilters(libtype)]
|
||||||
raise NotFound(f'Unknown filter field "{field}" for libtype "{libtype}". '
|
raise NotFound(f'Unknown filter field "{field}" for libtype "{libtype}". '
|
||||||
f'Available filters: {availableFilters}') from None
|
f'Available filters: {availableFilters}') from None
|
||||||
|
|
||||||
data = self._server.query(field.key)
|
data = self._server.query(field.key)
|
||||||
return self.findItems(data, FilterChoice)
|
return self.findItems(data, FilterChoice)
|
||||||
|
|
||||||
|
@ -1111,7 +1111,7 @@ class LibrarySection(PlexObject):
|
||||||
except (ValueError, AttributeError):
|
except (ValueError, AttributeError):
|
||||||
raise BadRequest(f'Invalid value "{value}" for filter field "{filterField.key}", '
|
raise BadRequest(f'Invalid value "{value}" for filter field "{filterField.key}", '
|
||||||
f'value should be type {fieldType.type}') from None
|
f'value should be type {fieldType.type}') from None
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _validateFieldValueDate(self, value):
|
def _validateFieldValueDate(self, value):
|
||||||
|
@ -1345,7 +1345,7 @@ class LibrarySection(PlexObject):
|
||||||
Tag type filter values can be a :class:`~plexapi.library.FilterChoice` object,
|
Tag type filter values can be a :class:`~plexapi.library.FilterChoice` object,
|
||||||
:class:`~plexapi.media.MediaTag` object, the exact name :attr:`MediaTag.tag` (*str*),
|
:class:`~plexapi.media.MediaTag` object, the exact name :attr:`MediaTag.tag` (*str*),
|
||||||
or the exact id :attr:`MediaTag.id` (*int*).
|
or the exact id :attr:`MediaTag.id` (*int*).
|
||||||
|
|
||||||
Date type filter values can be a ``datetime`` object, a relative date using a one of the
|
Date type filter values can be a ``datetime`` object, a relative date using a one of the
|
||||||
available date suffixes (e.g. ``30d``) (*str*), or a date in ``YYYY-MM-DD`` (*str*) format.
|
available date suffixes (e.g. ``30d``) (*str*), or a date in ``YYYY-MM-DD`` (*str*) format.
|
||||||
|
|
||||||
|
@ -1358,7 +1358,7 @@ class LibrarySection(PlexObject):
|
||||||
* ``w``: ``weeks``
|
* ``w``: ``weeks``
|
||||||
* ``mon``: ``months``
|
* ``mon``: ``months``
|
||||||
* ``y``: ``years``
|
* ``y``: ``years``
|
||||||
|
|
||||||
Multiple values can be ``OR`` together by providing a list of values.
|
Multiple values can be ``OR`` together by providing a list of values.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
@ -1686,10 +1686,10 @@ class LibrarySection(PlexObject):
|
||||||
""" Validates the specified items are from this library and of the same type. """
|
""" Validates the specified items are from this library and of the same type. """
|
||||||
if items is None or items == []:
|
if items is None or items == []:
|
||||||
raise BadRequest('No items specified.')
|
raise BadRequest('No items specified.')
|
||||||
|
|
||||||
if not isinstance(items, list):
|
if not isinstance(items, list):
|
||||||
items = [items]
|
items = [items]
|
||||||
|
|
||||||
itemType = items[0].type
|
itemType = items[0].type
|
||||||
for item in items:
|
for item in items:
|
||||||
if item.librarySectionID != self.key:
|
if item.librarySectionID != self.key:
|
||||||
|
@ -3102,6 +3102,7 @@ class FirstCharacter(PlexObject):
|
||||||
size (str): Total amount of library items starting with this character.
|
size (str): Total amount of library items starting with this character.
|
||||||
title (str): Character (#, !, A, B, C, ...).
|
title (str): Character (#, !, A, B, C, ...).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _loadData(self, data):
|
def _loadData(self, data):
|
||||||
""" Load attribute values from Plex XML response. """
|
""" Load attribute values from Plex XML response. """
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|
|
@ -1008,6 +1008,7 @@ class BaseResource(PlexObject):
|
||||||
selected (bool): True if the resource is currently selected.
|
selected (bool): True if the resource is currently selected.
|
||||||
thumb (str): The URL to retrieve the resource thumbnail.
|
thumb (str): The URL to retrieve the resource thumbnail.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _loadData(self, data):
|
def _loadData(self, data):
|
||||||
self._data = data
|
self._data = data
|
||||||
self.key = data.attrib.get('key')
|
self.key = data.attrib.get('key')
|
||||||
|
|
|
@ -39,7 +39,7 @@ class AdvancedSettingsMixin:
|
||||||
pref = preferences[settingID]
|
pref = preferences[settingID]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotFound(f'{value} not found in {list(preferences.keys())}')
|
raise NotFound(f'{value} not found in {list(preferences.keys())}')
|
||||||
|
|
||||||
enumValues = pref.enumValues
|
enumValues = pref.enumValues
|
||||||
if enumValues.get(value, enumValues.get(str(value))):
|
if enumValues.get(value, enumValues.get(str(value))):
|
||||||
data[settingID] = value
|
data[settingID] = value
|
||||||
|
@ -69,7 +69,7 @@ class SmartFilterMixin:
|
||||||
filters = {}
|
filters = {}
|
||||||
filterOp = 'and'
|
filterOp = 'and'
|
||||||
filterGroups = [[]]
|
filterGroups = [[]]
|
||||||
|
|
||||||
for key, value in parse_qsl(content.query):
|
for key, value in parse_qsl(content.query):
|
||||||
# Move = sign to key when operator is ==
|
# Move = sign to key when operator is ==
|
||||||
if value.startswith('='):
|
if value.startswith('='):
|
||||||
|
@ -96,11 +96,11 @@ class SmartFilterMixin:
|
||||||
filterGroups.pop()
|
filterGroups.pop()
|
||||||
else:
|
else:
|
||||||
filterGroups[-1].append({key: value})
|
filterGroups[-1].append({key: value})
|
||||||
|
|
||||||
if filterGroups:
|
if filterGroups:
|
||||||
filters['filters'] = self._formatFilterGroups(filterGroups.pop())
|
filters['filters'] = self._formatFilterGroups(filterGroups.pop())
|
||||||
return filters
|
return filters
|
||||||
|
|
||||||
def _formatFilterGroups(self, groups):
|
def _formatFilterGroups(self, groups):
|
||||||
""" Formats the filter groups into the advanced search rules. """
|
""" Formats the filter groups into the advanced search rules. """
|
||||||
if len(groups) == 1 and isinstance(groups[0], list):
|
if len(groups) == 1 and isinstance(groups[0], list):
|
||||||
|
@ -131,7 +131,7 @@ class SplitMergeMixin:
|
||||||
|
|
||||||
def merge(self, ratingKeys):
|
def merge(self, ratingKeys):
|
||||||
""" Merge other Plex objects into the current object.
|
""" Merge other Plex objects into the current object.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
ratingKeys (list): A list of rating keys to merge.
|
ratingKeys (list): A list of rating keys to merge.
|
||||||
"""
|
"""
|
||||||
|
@ -320,7 +320,7 @@ class RatingMixin:
|
||||||
|
|
||||||
class ArtUrlMixin:
|
class ArtUrlMixin:
|
||||||
""" Mixin for Plex objects that can have a background artwork url. """
|
""" Mixin for Plex objects that can have a background artwork url. """
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def artUrl(self):
|
def artUrl(self):
|
||||||
""" Return the art url for the Plex object. """
|
""" Return the art url for the Plex object. """
|
||||||
|
@ -349,7 +349,7 @@ class ArtMixin(ArtUrlMixin, ArtLockMixin):
|
||||||
|
|
||||||
def uploadArt(self, url=None, filepath=None):
|
def uploadArt(self, url=None, filepath=None):
|
||||||
""" Upload a background artwork from a url or filepath.
|
""" Upload a background artwork from a url or filepath.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
url (str): The full URL to the image to upload.
|
url (str): The full URL to the image to upload.
|
||||||
filepath (str): The full file path the the image to upload or file-like object.
|
filepath (str): The full file path the the image to upload or file-like object.
|
||||||
|
@ -365,7 +365,7 @@ class ArtMixin(ArtUrlMixin, ArtLockMixin):
|
||||||
|
|
||||||
def setArt(self, art):
|
def setArt(self, art):
|
||||||
""" Set the background artwork for a Plex object.
|
""" Set the background artwork for a Plex object.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
art (:class:`~plexapi.media.Art`): The art object to select.
|
art (:class:`~plexapi.media.Art`): The art object to select.
|
||||||
"""
|
"""
|
||||||
|
@ -425,7 +425,7 @@ class PosterMixin(PosterUrlMixin, PosterLockMixin):
|
||||||
|
|
||||||
def setPoster(self, poster):
|
def setPoster(self, poster):
|
||||||
""" Set the poster for a Plex object.
|
""" Set the poster for a Plex object.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
poster (:class:`~plexapi.media.Poster`): The poster object to select.
|
poster (:class:`~plexapi.media.Poster`): The poster object to select.
|
||||||
"""
|
"""
|
||||||
|
@ -491,11 +491,11 @@ class ThemeMixin(ThemeUrlMixin, ThemeLockMixin):
|
||||||
|
|
||||||
class EditFieldMixin:
|
class EditFieldMixin:
|
||||||
""" Mixin for editing Plex object fields. """
|
""" Mixin for editing Plex object fields. """
|
||||||
|
|
||||||
def editField(self, field, value, locked=True, **kwargs):
|
def editField(self, field, value, locked=True, **kwargs):
|
||||||
""" Edit the field of a Plex object. All field editing methods can be chained together.
|
""" Edit the field of a Plex object. All field editing methods can be chained together.
|
||||||
Also see :func:`~plexapi.base.PlexPartialObject.batchEdits` for batch editing fields.
|
Also see :func:`~plexapi.base.PlexPartialObject.batchEdits` for batch editing fields.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
field (str): The name of the field to edit.
|
field (str): The name of the field to edit.
|
||||||
value (str): The value to edit the field to.
|
value (str): The value to edit the field to.
|
||||||
|
|
|
@ -674,7 +674,7 @@ class MyPlexAccount(PlexObject):
|
||||||
if (invite.username and invite.email and invite.id and username.lower() in
|
if (invite.username and invite.email and invite.id and username.lower() in
|
||||||
(invite.username.lower(), invite.email.lower(), str(invite.id))):
|
(invite.username.lower(), invite.email.lower(), str(invite.id))):
|
||||||
return invite
|
return invite
|
||||||
|
|
||||||
raise NotFound(f'Unable to find invite {username}')
|
raise NotFound(f'Unable to find invite {username}')
|
||||||
|
|
||||||
def pendingInvites(self, includeSent=True, includeReceived=True):
|
def pendingInvites(self, includeSent=True, includeReceived=True):
|
||||||
|
@ -952,7 +952,7 @@ class MyPlexAccount(PlexObject):
|
||||||
"""
|
"""
|
||||||
if not isinstance(items, list):
|
if not isinstance(items, list):
|
||||||
items = [items]
|
items = [items]
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
if self.onWatchlist(item):
|
if self.onWatchlist(item):
|
||||||
raise BadRequest(f'"{item.title}" is already on the watchlist')
|
raise BadRequest(f'"{item.title}" is already on the watchlist')
|
||||||
|
@ -973,7 +973,7 @@ class MyPlexAccount(PlexObject):
|
||||||
"""
|
"""
|
||||||
if not isinstance(items, list):
|
if not isinstance(items, list):
|
||||||
items = [items]
|
items = [items]
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
if not self.onWatchlist(item):
|
if not self.onWatchlist(item):
|
||||||
raise BadRequest(f'"{item.title}" is not on the watchlist')
|
raise BadRequest(f'"{item.title}" is not on the watchlist')
|
||||||
|
@ -1944,7 +1944,7 @@ class AccountOptOut(PlexObject):
|
||||||
|
|
||||||
def optOutManaged(self):
|
def optOutManaged(self):
|
||||||
""" Sets the Online Media Source to "Disabled for Managed Users".
|
""" Sets the Online Media Source to "Disabled for Managed Users".
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:exc:`~plexapi.exceptions.BadRequest`: When trying to opt out music.
|
:exc:`~plexapi.exceptions.BadRequest`: When trying to opt out music.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -256,7 +256,7 @@ class Photo(
|
||||||
List<str> of file paths where the photo is found on disk.
|
List<str> of file paths where the photo is found on disk.
|
||||||
"""
|
"""
|
||||||
return [part.file for item in self.media for part in item.parts if part]
|
return [part.file for item in self.media for part in item.parts if part]
|
||||||
|
|
||||||
def sync(self, resolution, client=None, clientId=None, limit=None, title=None):
|
def sync(self, resolution, client=None, clientId=None, limit=None, title=None):
|
||||||
""" Add current photo as sync item for specified device.
|
""" Add current photo as sync item for specified device.
|
||||||
See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
|
See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
|
||||||
|
|
|
@ -155,7 +155,7 @@ class Playlist(
|
||||||
sectionKey = int(match.group(1))
|
sectionKey = int(match.group(1))
|
||||||
self._section = self._server.library.sectionByID(sectionKey)
|
self._section = self._server.library.sectionByID(sectionKey)
|
||||||
return self._section
|
return self._section
|
||||||
|
|
||||||
# Try to get the library section from the first item in the playlist
|
# Try to get the library section from the first item in the playlist
|
||||||
if self.items():
|
if self.items():
|
||||||
self._section = self.items()[0].section()
|
self._section = self.items()[0].section()
|
||||||
|
@ -314,7 +314,7 @@ class Playlist(
|
||||||
|
|
||||||
def edit(self, title=None, summary=None):
|
def edit(self, title=None, summary=None):
|
||||||
""" Edit the playlist.
|
""" Edit the playlist.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
title (str, optional): The title of the playlist.
|
title (str, optional): The title of the playlist.
|
||||||
summary (str, optional): The summary of the playlist.
|
summary (str, optional): The summary of the playlist.
|
||||||
|
@ -432,7 +432,7 @@ class Playlist(
|
||||||
|
|
||||||
def copyToUser(self, user):
|
def copyToUser(self, user):
|
||||||
""" Copy playlist to another user account.
|
""" Copy playlist to another user account.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user (:class:`~plexapi.myplex.MyPlexUser` or str): `MyPlexUser` object, username,
|
user (:class:`~plexapi.myplex.MyPlexUser` or str): `MyPlexUser` object, username,
|
||||||
email, or user id of the user to copy the playlist to.
|
email, or user id of the user to copy the playlist to.
|
||||||
|
|
|
@ -202,7 +202,7 @@ class PlexServer(PlexObject):
|
||||||
def claim(self, account):
|
def claim(self, account):
|
||||||
""" Claim the Plex server using a :class:`~plexapi.myplex.MyPlexAccount`.
|
""" Claim the Plex server using a :class:`~plexapi.myplex.MyPlexAccount`.
|
||||||
This will only work with an unclaimed server on localhost or the same subnet.
|
This will only work with an unclaimed server on localhost or the same subnet.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
account (:class:`~plexapi.myplex.MyPlexAccount`): The account used to
|
account (:class:`~plexapi.myplex.MyPlexAccount`): The account used to
|
||||||
claim the server.
|
claim the server.
|
||||||
|
@ -245,7 +245,7 @@ class PlexServer(PlexObject):
|
||||||
def switchUser(self, user, session=None, timeout=None):
|
def switchUser(self, user, session=None, timeout=None):
|
||||||
""" Returns a new :class:`~plexapi.server.PlexServer` object logged in as the given username.
|
""" Returns a new :class:`~plexapi.server.PlexServer` object logged in as the given username.
|
||||||
Note: Only the admin account can switch to other users.
|
Note: Only the admin account can switch to other users.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user (:class:`~plexapi.myplex.MyPlexUser` or str): `MyPlexUser` object, username,
|
user (:class:`~plexapi.myplex.MyPlexUser` or str): `MyPlexUser` object, username,
|
||||||
email, or user id of the user to log in to the server.
|
email, or user id of the user to log in to the server.
|
||||||
|
@ -590,7 +590,7 @@ class PlexServer(PlexObject):
|
||||||
def runButlerTask(self, task):
|
def runButlerTask(self, task):
|
||||||
""" Manually run a butler task immediately instead of waiting for the scheduled task to run.
|
""" Manually run a butler task immediately instead of waiting for the scheduled task to run.
|
||||||
Note: The butler task is run asynchronously. Check Plex Web to monitor activity.
|
Note: The butler task is run asynchronously. Check Plex Web to monitor activity.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
task (str): The name of the task to run. (e.g. 'BackupDatabase')
|
task (str): The name of the task to run. (e.g. 'BackupDatabase')
|
||||||
|
|
||||||
|
@ -666,7 +666,7 @@ class PlexServer(PlexObject):
|
||||||
args['librarySectionID'] = librarySectionID
|
args['librarySectionID'] = librarySectionID
|
||||||
if mindate:
|
if mindate:
|
||||||
args['viewedAt>'] = int(mindate.timestamp())
|
args['viewedAt>'] = int(mindate.timestamp())
|
||||||
|
|
||||||
key = f'/status/sessions/history/all{utils.joinArgs(args)}'
|
key = f'/status/sessions/history/all{utils.joinArgs(args)}'
|
||||||
return self.fetchItems(key, maxresults=maxresults)
|
return self.fetchItems(key, maxresults=maxresults)
|
||||||
|
|
||||||
|
@ -1258,7 +1258,7 @@ class StatisticsResources(PlexObject):
|
||||||
@utils.registerPlexObject
|
@utils.registerPlexObject
|
||||||
class ButlerTask(PlexObject):
|
class ButlerTask(PlexObject):
|
||||||
""" Represents a single scheduled butler task.
|
""" Represents a single scheduled butler task.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
TAG (str): 'ButlerTask'
|
TAG (str): 'ButlerTask'
|
||||||
description (str): The description of the task.
|
description (str): The description of the task.
|
||||||
|
@ -1291,7 +1291,7 @@ class Identity(PlexObject):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}:{self.machineIdentifier}>"
|
return f"<{self.__class__.__name__}:{self.machineIdentifier}>"
|
||||||
|
|
||||||
def _loadData(self, data):
|
def _loadData(self, data):
|
||||||
self._data = data
|
self._data = data
|
||||||
self.claimed = utils.cast(bool, data.attrib.get('claimed'))
|
self.claimed = utils.cast(bool, data.attrib.get('claimed'))
|
||||||
|
|
|
@ -23,7 +23,6 @@ you can set items to be synced to your app) you need to init some variables.
|
||||||
You have to fake platform/device/model because transcoding profiles are hardcoded in Plex, and you obviously have
|
You have to fake platform/device/model because transcoding profiles are hardcoded in Plex, and you obviously have
|
||||||
to explicitly specify that your app supports `sync-target`.
|
to explicitly specify that your app supports `sync-target`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import plexapi
|
import plexapi
|
||||||
|
|
|
@ -88,7 +88,7 @@ def test_history_PlexHistory(plex, movie):
|
||||||
movie.markPlayed()
|
movie.markPlayed()
|
||||||
history = plex.history()
|
history = plex.history()
|
||||||
assert len(history)
|
assert len(history)
|
||||||
|
|
||||||
hist = history[0]
|
hist = history[0]
|
||||||
assert hist.source() == movie
|
assert hist.source() == movie
|
||||||
assert hist.accountID
|
assert hist.accountID
|
||||||
|
@ -106,14 +106,20 @@ def test_history_User(account, shared_username):
|
||||||
user = account.user(shared_username)
|
user = account.user(shared_username)
|
||||||
history = user.history()
|
history = user.history()
|
||||||
|
|
||||||
|
assert isinstance(history, list)
|
||||||
|
|
||||||
|
|
||||||
def test_history_UserServer(account, shared_username, plex):
|
def test_history_UserServer(account, shared_username, plex):
|
||||||
userSharedServer = account.user(shared_username).server(plex.friendlyName)
|
userSharedServer = account.user(shared_username).server(plex.friendlyName)
|
||||||
history = userSharedServer.history()
|
history = userSharedServer.history()
|
||||||
|
|
||||||
|
assert isinstance(history, list)
|
||||||
|
|
||||||
|
|
||||||
def test_history_UserSection(account, shared_username, plex):
|
def test_history_UserSection(account, shared_username, plex):
|
||||||
userSharedServerSection = (
|
userSharedServerSection = (
|
||||||
account.user(shared_username).server(plex.friendlyName).section("Movies")
|
account.user(shared_username).server(plex.friendlyName).section("Movies")
|
||||||
)
|
)
|
||||||
history = userSharedServerSection.history()
|
history = userSharedServerSection.history()
|
||||||
|
|
||||||
|
assert isinstance(history, list)
|
||||||
|
|
|
@ -611,7 +611,7 @@ def test_library_MusicSection_search(music, artist):
|
||||||
album.removeMood("test_search", locked=False)
|
album.removeMood("test_search", locked=False)
|
||||||
album.removeCollection("test_search", locked=False)
|
album.removeCollection("test_search", locked=False)
|
||||||
album.removeLabel("test_search", locked=False)
|
album.removeLabel("test_search", locked=False)
|
||||||
|
|
||||||
track = album.track(track=1)
|
track = album.track(track=1)
|
||||||
track.addMood("test_search")
|
track.addMood("test_search")
|
||||||
_test_library_search(music, track)
|
_test_library_search(music, track)
|
||||||
|
@ -777,7 +777,7 @@ def test_library_search_exceptions(movies):
|
||||||
movies.search(sort="titleSort:bad")
|
movies.search(sort="titleSort:bad")
|
||||||
|
|
||||||
|
|
||||||
def _test_library_search(library, obj):
|
def _test_library_search(library, obj): # noqa: C901
|
||||||
# Create & operator
|
# Create & operator
|
||||||
AndOperator = namedtuple("AndOperator", ["key", "title"])
|
AndOperator = namedtuple("AndOperator", ["key", "title"])
|
||||||
andOp = AndOperator("&=", "and")
|
andOp = AndOperator("&=", "and")
|
||||||
|
|
|
@ -317,7 +317,7 @@ def _test_mixins_edit_theme(obj):
|
||||||
obj.unlockTheme()
|
obj.unlockTheme()
|
||||||
obj.reload()
|
obj.reload()
|
||||||
assert "theme" not in _fields()
|
assert "theme" not in _fields()
|
||||||
|
|
||||||
# Lock the theme
|
# Lock the theme
|
||||||
obj.lockTheme()
|
obj.lockTheme()
|
||||||
obj.reload()
|
obj.reload()
|
||||||
|
|
|
@ -28,6 +28,7 @@ def test_navigate_around_artist(account, plex):
|
||||||
print(f"Album: {album}")
|
print(f"Album: {album}")
|
||||||
print(f"Tracks: {tracks}...")
|
print(f"Tracks: {tracks}...")
|
||||||
print(f"Track: {track}")
|
print(f"Track: {track}")
|
||||||
|
assert isinstance(albums, list), "Unable to list artist albums."
|
||||||
assert artist.track("As Colourful as Ever") == track, "Unable to get artist track."
|
assert artist.track("As Colourful as Ever") == track, "Unable to get artist track."
|
||||||
assert album.track("As Colourful as Ever") == track, "Unable to get album track."
|
assert album.track("As Colourful as Ever") == track, "Unable to get album track."
|
||||||
assert album.artist() == artist, "album.artist() doesn't match expected artist."
|
assert album.artist() == artist, "album.artist() doesn't match expected artist."
|
||||||
|
|
|
@ -436,7 +436,7 @@ def test_server_system_devices(plex):
|
||||||
assert len(device.name) or device.name == ""
|
assert len(device.name) or device.name == ""
|
||||||
assert len(device.platform) or device.platform == ""
|
assert len(device.platform) or device.platform == ""
|
||||||
assert plex.systemDevice(device.id) == device
|
assert plex.systemDevice(device.id) == device
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.authenticated
|
@pytest.mark.authenticated
|
||||||
def test_server_dashboard_bandwidth(account_plexpass, plex):
|
def test_server_dashboard_bandwidth(account_plexpass, plex):
|
||||||
|
|
|
@ -383,7 +383,7 @@ def test_video_Movie_download(monkeydownload, tmpdir, movie):
|
||||||
def test_video_Movie_videoStreams(movie):
|
def test_video_Movie_videoStreams(movie):
|
||||||
assert movie.videoStreams()
|
assert movie.videoStreams()
|
||||||
|
|
||||||
|
|
||||||
def test_video_Movie_audioStreams(movie):
|
def test_video_Movie_audioStreams(movie):
|
||||||
assert movie.audioStreams()
|
assert movie.audioStreams()
|
||||||
|
|
||||||
|
@ -418,7 +418,7 @@ def test_video_Movie_upload_select_remove_subtitle(movie, subtitle):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
except:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -632,7 +632,7 @@ def test_video_Movie_batchEdits(movie):
|
||||||
assert movie.tagline == tagline
|
assert movie.tagline == tagline
|
||||||
assert movie.studio == studio
|
assert movie.studio == studio
|
||||||
assert not movie.fields
|
assert not movie.fields
|
||||||
|
|
||||||
with pytest.raises(BadRequest):
|
with pytest.raises(BadRequest):
|
||||||
movie.saveEdits()
|
movie.saveEdits()
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from plexapi.server import PlexServer
|
from plexapi.server import PlexServer
|
||||||
|
|
||||||
TAGS = {'keep5':5, 'keep10':10, 'keep15':15, 'keepSeason':'season'}
|
TAGS = {'keep5': 5, 'keep10': 10, 'keep15': 15, 'keepSeason': 'season'}
|
||||||
datestr = lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
datestr = lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ def keep_episodes(show, keep):
|
||||||
""" Delete all but last count episodes in show. """
|
""" Delete all but last count episodes in show. """
|
||||||
deleted = 0
|
deleted = 0
|
||||||
print('%s Cleaning %s to %s episodes.' % (datestr(), show.title, keep))
|
print('%s Cleaning %s to %s episodes.' % (datestr(), show.title, keep))
|
||||||
sort = lambda x:x.originallyAvailableAt or x.addedAt
|
sort = lambda x: x.originallyAvailableAt or x.addedAt
|
||||||
items = sorted(show.episodes(), key=sort, reverse=True)
|
items = sorted(show.episodes(), key=sort, reverse=True)
|
||||||
for episode in items[keep:]:
|
for episode in items[keep:]:
|
||||||
delete_episode(episode)
|
delete_episode(episode)
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
"""
|
"""
|
||||||
Backup and restore the watched status of Plex libraries to a json file.
|
Backup and restore the watched status of Plex libraries to a json file.
|
||||||
"""
|
"""
|
||||||
import argparse, json
|
import argparse
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import json
|
||||||
from plexapi import utils
|
from plexapi import utils
|
||||||
|
|
||||||
SECTIONS = ('movie', 'show')
|
SECTIONS = ('movie', 'show')
|
||||||
|
@ -32,7 +33,7 @@ def _item_key(item):
|
||||||
|
|
||||||
def _iter_sections(plex, opts):
|
def _iter_sections(plex, opts):
|
||||||
libraries = opts.libraries.split(',') if opts.libraries else []
|
libraries = opts.libraries.split(',') if opts.libraries else []
|
||||||
libraries = [l.strip().lower() for l in libraries]
|
libraries = [lib.strip().lower() for lib in libraries]
|
||||||
for section in plex.library.sections():
|
for section in plex.library.sections():
|
||||||
title = section.title.lower()
|
title = section.title.lower()
|
||||||
if section.type in SECTIONS and (not libraries or title in libraries):
|
if section.type in SECTIONS and (not libraries or title in libraries):
|
||||||
|
@ -76,11 +77,11 @@ def restore_watched(plex, opts):
|
||||||
skey = section.title.lower()
|
skey = section.title.lower()
|
||||||
for item in _iter_items(section):
|
for item in _iter_items(section):
|
||||||
ikey = _item_key(item)
|
ikey = _item_key(item)
|
||||||
sval = source.get(skey,{}).get(ikey)
|
sval = source.get(skey, {}).get(ikey)
|
||||||
if sval is None:
|
if sval is None:
|
||||||
raise SystemExit('%s not found' % ikey)
|
raise SystemExit('%s not found' % ikey)
|
||||||
if (sval is not None and item.isWatched != sval) and (not opts.watchedonly or sval):
|
if (sval is not None and item.isWatched != sval) and (not opts.watchedonly or sval):
|
||||||
differences[skey][ikey] = {'isWatched':sval, 'item':item}
|
differences[skey][ikey] = {'isWatched': sval, 'item': item}
|
||||||
print('Applying %s differences to destination' % len(differences))
|
print('Applying %s differences to destination' % len(differences))
|
||||||
import pprint; pprint.pprint(differences)
|
import pprint; pprint.pprint(differences)
|
||||||
|
|
||||||
|
@ -93,7 +94,8 @@ if __name__ == '__main__':
|
||||||
parser.add_argument('-u', '--username', default=CONFIG.get('auth.myplex_username'), help='Plex username')
|
parser.add_argument('-u', '--username', default=CONFIG.get('auth.myplex_username'), help='Plex username')
|
||||||
parser.add_argument('-p', '--password', default=CONFIG.get('auth.myplex_password'), help='Plex password')
|
parser.add_argument('-p', '--password', default=CONFIG.get('auth.myplex_password'), help='Plex password')
|
||||||
parser.add_argument('-s', '--servername', help='Plex server name')
|
parser.add_argument('-s', '--servername', help='Plex server name')
|
||||||
parser.add_argument('-w', '--watchedonly', default=False, action='store_true', help='Only backup or restore watched items.')
|
parser.add_argument('-w', '--watchedonly', default=False, action='store_true',
|
||||||
|
help='Only backup or restore watched items.')
|
||||||
parser.add_argument('-l', '--libraries', help='Only backup or restore the specified libraries (comma separated).')
|
parser.add_argument('-l', '--libraries', help='Only backup or restore the specified libraries (comma separated).')
|
||||||
opts = parser.parse_args()
|
opts = parser.parse_args()
|
||||||
account = utils.getMyPlexAccount(opts)
|
account = utils.getMyPlexAccount(opts)
|
||||||
|
|
|
@ -121,15 +121,15 @@ def setup_music(music_path, docker=False):
|
||||||
"Broke for free": {
|
"Broke for free": {
|
||||||
"Layers": [
|
"Layers": [
|
||||||
"1 - As Colorful As Ever.mp3",
|
"1 - As Colorful As Ever.mp3",
|
||||||
#"02 - Knock Knock.mp3",
|
# "02 - Knock Knock.mp3",
|
||||||
#"03 - Only Knows.mp3",
|
# "03 - Only Knows.mp3",
|
||||||
#"04 - If.mp3",
|
# "04 - If.mp3",
|
||||||
#"05 - Note Drop.mp3",
|
# "05 - Note Drop.mp3",
|
||||||
#"06 - Murmur.mp3",
|
# "06 - Murmur.mp3",
|
||||||
#"07 - Spellbound.mp3",
|
# "07 - Spellbound.mp3",
|
||||||
#"08 - The Collector.mp3",
|
# "08 - The Collector.mp3",
|
||||||
#"09 - Quit Bitching.mp3",
|
# "09 - Quit Bitching.mp3",
|
||||||
#"10 - A Year.mp3",
|
# "10 - A Year.mp3",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ def add_library_section(server, section):
|
||||||
raise SystemExit("Timeout adding section to Plex instance.")
|
raise SystemExit("Timeout adding section to Plex instance.")
|
||||||
|
|
||||||
|
|
||||||
def create_section(server, section, opts):
|
def create_section(server, section, opts): # noqa: C901
|
||||||
processed_media = 0
|
processed_media = 0
|
||||||
expected_media_count = section.pop("expected_media_count", 0)
|
expected_media_count = section.pop("expected_media_count", 0)
|
||||||
expected_media_type = (section["type"],)
|
expected_media_type = (section["type"],)
|
||||||
|
@ -337,7 +337,7 @@ def create_section(server, section, opts):
|
||||||
notifier.stop()
|
notifier.stop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # noqa: C901
|
||||||
default_ip = get_default_ip()
|
default_ip = get_default_ip()
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
# Authentication arguments
|
# Authentication arguments
|
||||||
|
|
|
@ -23,7 +23,7 @@ def search_for_item(url=None):
|
||||||
servers = [s for s in account.resources() if 'server' in s.provides]
|
servers = [s for s in account.resources() if 'server' in s.provides]
|
||||||
server = utils.choose('Choose a Server', servers, 'name').connect()
|
server = utils.choose('Choose a Server', servers, 'name').connect()
|
||||||
query = input('What are you looking for?: ')
|
query = input('What are you looking for?: ')
|
||||||
item = []
|
item = []
|
||||||
items = [i for i in server.search(query) if i.__class__ in VALID_TYPES]
|
items = [i for i in server.search(query) if i.__class__ in VALID_TYPES]
|
||||||
items = utils.choose('Choose result', items, lambda x: '(%s) %s' % (x.type.title(), x.title[0:60]))
|
items = utils.choose('Choose result', items, lambda x: '(%s) %s' % (x.type.title(), x.title[0:60]))
|
||||||
|
|
||||||
|
@ -63,10 +63,10 @@ def get_item_from_url(url):
|
||||||
server = servers[0].connect()
|
server = servers[0].connect()
|
||||||
return server.fetchItem(key)
|
return server.fetchItem(key)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Command line parser
|
# Command line parser
|
||||||
from plexapi import CONFIG
|
from plexapi import CONFIG
|
||||||
from tqdm import tqdm
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument('-u', '--username', help='Your Plex username',
|
parser.add_argument('-u', '--username', help='Your Plex username',
|
||||||
default=CONFIG.get('auth.myplex_username'))
|
default=CONFIG.get('auth.myplex_username'))
|
||||||
|
@ -84,4 +84,3 @@ if __name__ == '__main__':
|
||||||
url = item._server.url('%s?download=1' % part.key)
|
url = item._server.url('%s?download=1' % part.key)
|
||||||
filepath = utils.download(url, token=item._server._token, filename=filename, savepath=os.getcwd(),
|
filepath = utils.download(url, token=item._server._token, filename=filename, savepath=os.getcwd(),
|
||||||
session=item._server._session, showstatus=True)
|
session=item._server._session, showstatus=True)
|
||||||
#print(' %s' % filepath)
|
|
||||||
|
|
|
@ -6,7 +6,14 @@ items to build a collection of attributes on each media type. The resulting list
|
||||||
can be compared with the current object implementation in python-plexapi to track
|
can be compared with the current object implementation in python-plexapi to track
|
||||||
new attributes and deprecate old ones.
|
new attributes and deprecate old ones.
|
||||||
"""
|
"""
|
||||||
import argparse, copy, pickle, plexapi, os, re, sys, time
|
import argparse
|
||||||
|
import copy
|
||||||
|
import pickle
|
||||||
|
import plexapi
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
from os.path import abspath, dirname, join
|
from os.path import abspath, dirname, join
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -296,19 +303,19 @@ class PlexAttributes():
|
||||||
def _safe_connect(self, elem):
|
def _safe_connect(self, elem):
|
||||||
try:
|
try:
|
||||||
return elem.connect()
|
return elem.connect()
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _safe_reload(self, elem):
|
def _safe_reload(self, elem):
|
||||||
try:
|
try:
|
||||||
elem.reload()
|
elem.reload()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _(text, color):
|
def _(text, color):
|
||||||
FMTSTR = '\033[%dm%s\033[0m'
|
FMTSTR = '\033[%dm%s\033[0m'
|
||||||
COLORS = {'blue':34, 'cyan':36, 'green':32, 'grey':30, 'purple':35, 'red':31, 'white':37, 'yellow':33}
|
COLORS = {'blue': 34, 'cyan': 36, 'green': 32, 'grey': 30, 'purple': 35, 'red': 31, 'white': 37, 'yellow': 33}
|
||||||
return FMTSTR % (COLORS[color], text)
|
return FMTSTR % (COLORS[color], text)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from os.path import abspath, dirname, join
|
||||||
from plexapi import utils
|
from plexapi import utils
|
||||||
from plexapi.server import PlexServer
|
from plexapi.server import PlexServer
|
||||||
|
|
||||||
GROUPNAMES = {'butler':'Scheduled Task', 'dlna':'DLNA'}
|
GROUPNAMES = {'butler': 'Scheduled Task', 'dlna': 'DLNA'}
|
||||||
OUTPUT = join(dirname(dirname(abspath(__file__))), 'docs/settingslist.rst')
|
OUTPUT = join(dirname(dirname(abspath(__file__))), 'docs/settingslist.rst')
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ def _setting_group(setting):
|
||||||
return setting.group
|
return setting.group
|
||||||
|
|
||||||
|
|
||||||
def _write_settings(handle, groups, group):
|
def _write_settings(handle, groups, group): # noqa: C901
|
||||||
title = GROUPNAMES.get(group, group.title())
|
title = GROUPNAMES.get(group, group.title())
|
||||||
print('\n%s Settings\n%s' % (title, '-' * (len(title) + 9)))
|
print('\n%s Settings\n%s' % (title, '-' * (len(title) + 9)))
|
||||||
handle.write('%s Settings\n%s\n' % (title, '-' * (len(title) + 9)))
|
handle.write('%s Settings\n%s\n' % (title, '-' * (len(title) + 9)))
|
||||||
|
|
|
@ -47,7 +47,7 @@ def _list_devices(account, servers):
|
||||||
def _test_servers(servers):
|
def _test_servers(servers):
|
||||||
items, seen = [], set()
|
items, seen = [], set()
|
||||||
print('Finding Plex clients..')
|
print('Finding Plex clients..')
|
||||||
listargs = [[PlexServer, s, t, None, 5] for s,t in servers.items()]
|
listargs = [[PlexServer, s, t, None, 5] for s, t in servers.items()]
|
||||||
results = utils.threaded(_connect, listargs)
|
results = utils.threaded(_connect, listargs)
|
||||||
for url, token, plex, runtime in results:
|
for url, token, plex, runtime in results:
|
||||||
clients = plex.clients() if plex else []
|
clients = plex.clients() if plex else []
|
||||||
|
|
|
@ -41,17 +41,17 @@ def _iter_items(search):
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
datestr = lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S') # noqa
|
datestr = lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S') # noqa
|
||||||
print('{datestr} Starting plex-markwatched script..'.format(datestr=datestr()))
|
print(f'{datestr()} Starting plex-markwatched script..')
|
||||||
plex = PlexServer()
|
plex = PlexServer()
|
||||||
for section in plex.library.sections():
|
for section in plex.library.sections():
|
||||||
print('{datestr} Checking {section.title} for unwatched items..'.format(datestr=datestr()))
|
print(f'{datestr()} Checking {section.title} for unwatched items..')
|
||||||
for item in _iter_items(section.search(collection='markwatched')):
|
for item in _iter_items(section.search(collection='markwatched')):
|
||||||
if not item.isWatched:
|
if not item.isWatched:
|
||||||
print('{datestr} Marking {_get_title(item)} watched.'.format(datestr=datestr()))
|
print(f'{datestr()} Marking {_get_title(item)} watched.')
|
||||||
item.markWatched()
|
item.markWatched()
|
||||||
# Check all OnDeck items
|
# Check all OnDeck items
|
||||||
print('{datestr} Checking OnDeck for unwatched items..'.format(datestr=datestr()))
|
print(f'{datestr()} Checking OnDeck for unwatched items.')
|
||||||
for item in plex.library.onDeck():
|
for item in plex.library.onDeck():
|
||||||
if not item.isWatched and _has_markwatched_tag(item):
|
if not item.isWatched and _has_markwatched_tag(item):
|
||||||
print('{datestr} Marking {_get_title(item)} watched.'.format(datestr=datestr()))
|
print(f'{datestr()} Marking {_get_title(item)} watched.')
|
||||||
item.markWatched()
|
item.markWatched()
|
||||||
|
|
|
@ -90,10 +90,9 @@ def main():
|
||||||
if arguments.tag:
|
if arguments.tag:
|
||||||
subprocess.run(["git", "tag", str(bumped), "-m", f"Release {bumped}"])
|
subprocess.run(["git", "tag", str(bumped), "-m", f"Release {bumped}"])
|
||||||
|
|
||||||
|
|
||||||
def test_bump_version():
|
def test_bump_version():
|
||||||
"""Make sure it all works."""
|
"""Make sure it all works."""
|
||||||
import pytest
|
|
||||||
|
|
||||||
assert bump_version(Version("4.7.0"), "patch") == Version("4.7.1")
|
assert bump_version(Version("4.7.0"), "patch") == Version("4.7.1")
|
||||||
assert bump_version(Version("4.7.0"), "minor") == Version("4.8.0")
|
assert bump_version(Version("4.7.0"), "minor") == Version("4.8.0")
|
||||||
assert bump_version(Version("4.7.3"), "minor") == Version("4.8.0")
|
assert bump_version(Version("4.7.3"), "minor") == Version("4.8.0")
|
||||||
|
|
Loading…
Reference in a new issue