mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-24 20:53:09 +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)
|
||||
# E702: multiple statements on one line (semicolon)
|
||||
# E731: do not assign a lambda expression, use a def
|
||||
# W293: blank line contains whitespace
|
||||
# W503: line break before binary operator
|
||||
# W605: invalid escape sequence
|
||||
[flake8]
|
||||
ignore=E128,E701,E702,E731,W293,W503,W605
|
||||
exclude=compat.py
|
||||
ignore=E128,E701,E702,E731,W503,W605
|
||||
exclude=compat.py,venv
|
||||
per-file-ignores =
|
||||
tests/payloads.py:E501
|
||||
max-complexity = -1
|
||||
max-line-length = 125
|
||||
# The GitHub editor is 127 chars wide
|
||||
max-line-length = 127
|
||||
show-source = True
|
||||
|
|
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
|
@ -54,9 +54,13 @@ jobs:
|
|||
run: |
|
||||
. venv/bin/activate
|
||||
# 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
|
||||
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:
|
||||
|
|
31
docs/conf.py
31
docs/conf.py
|
@ -12,12 +12,13 @@
|
|||
# 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
|
||||
# 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
|
||||
import sys
|
||||
path = dirname(dirname(abspath(__file__)))
|
||||
sys.path.append(path)
|
||||
sys.path.append(join(path, 'plexapi'))
|
||||
import plexapi
|
||||
import plexapi # noqa: E402
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
|
@ -27,13 +28,17 @@ extensions = [
|
|||
]
|
||||
|
||||
# -- 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()')
|
||||
old_resolve_xref = copy.deepcopy(PythonDomain.resolve_xref)
|
||||
|
||||
|
||||
def new_resolve_xref(*args):
|
||||
if '.' not in args[5]: # target
|
||||
return None
|
||||
return old_resolve_xref(*args)
|
||||
|
||||
|
||||
PythonDomain.resolve_xref = new_resolve_xref
|
||||
|
||||
# -- Napoleon Settings -----------------------------------------------------
|
||||
|
@ -79,7 +84,7 @@ author = 'M.Shepanski'
|
|||
# The short X.Y version.
|
||||
version = plexapi.VERSION
|
||||
# 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
|
||||
# for a list of supported languages.
|
||||
|
@ -137,7 +142,7 @@ html_context = {'css_files': ['_static/custom.css']}
|
|||
html_theme_options = {
|
||||
'collapse_navigation': False,
|
||||
'display_version': False,
|
||||
#'navigation_depth': 3,
|
||||
# 'navigation_depth': 3,
|
||||
}
|
||||
|
||||
|
||||
|
@ -238,17 +243,17 @@ htmlhelp_basename = 'PythonPlexAPIdoc'
|
|||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
# 'figure_align': 'htbp',
|
||||
# Latex figure (float) alignment
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
|
|
|
@ -919,7 +919,7 @@ class PlexSession(object):
|
|||
|
||||
def stop(self, reason=''):
|
||||
""" Stop playback for the session.
|
||||
|
||||
|
||||
Parameters:
|
||||
reason (str): Message displayed to the user for stopping playback.
|
||||
"""
|
||||
|
|
|
@ -400,7 +400,7 @@ class Collection(
|
|||
@deprecated('use editTitle, editSortTitle, editContentRating, and editSummary instead')
|
||||
def edit(self, title=None, titleSort=None, contentRating=None, summary=None, **kwargs):
|
||||
""" Edit the collection.
|
||||
|
||||
|
||||
Parameters:
|
||||
title (str, optional): The title of the collection.
|
||||
titleSort (str, optional): The sort title of the collection.
|
||||
|
|
|
@ -542,7 +542,7 @@ class LibrarySection(PlexObject):
|
|||
|
||||
def addLocations(self, location):
|
||||
""" Add a location to a library.
|
||||
|
||||
|
||||
Parameters:
|
||||
location (str or list): A single folder path, list of paths.
|
||||
|
||||
|
@ -565,7 +565,7 @@ class LibrarySection(PlexObject):
|
|||
|
||||
def removeLocations(self, location):
|
||||
""" Remove a location from a library.
|
||||
|
||||
|
||||
Parameters:
|
||||
location (str or list): A single folder path, list of paths.
|
||||
|
||||
|
@ -744,7 +744,7 @@ class LibrarySection(PlexObject):
|
|||
|
||||
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,
|
||||
|
@ -754,7 +754,7 @@ class LibrarySection(PlexObject):
|
|||
|
||||
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,
|
||||
|
@ -847,7 +847,7 @@ class LibrarySection(PlexObject):
|
|||
"""
|
||||
_key = ('/library/sections/{key}/{filter}?includeMeta=1&includeAdvanced=1'
|
||||
'&X-Plex-Container-Start=0&X-Plex-Container-Size=0')
|
||||
|
||||
|
||||
key = _key.format(key=self.key, filter='all')
|
||||
data = self._server.query(key)
|
||||
self._filterTypes = self.findItems(data, FilteringType, rtag='Meta')
|
||||
|
@ -894,7 +894,7 @@ class LibrarySection(PlexObject):
|
|||
|
||||
def getFieldType(self, fieldType):
|
||||
""" Returns a :class:`~plexapi.library.FilteringFieldType` for a specified fieldType.
|
||||
|
||||
|
||||
Parameters:
|
||||
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
||||
subtitleLanguage, audioLanguage, resolution).
|
||||
|
@ -927,7 +927,7 @@ class LibrarySection(PlexObject):
|
|||
|
||||
"""
|
||||
return self.getFilterType(libtype).filters
|
||||
|
||||
|
||||
def listSorts(self, libtype=None):
|
||||
""" Returns a list of available :class:`~plexapi.library.FilteringSort` for a specified libtype.
|
||||
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.
|
||||
This is the list of options in the custom filter operator dropdown menu
|
||||
(`screenshot <../_static/images/LibrarySection.search.png>`__).
|
||||
|
||||
|
||||
Parameters:
|
||||
fieldType (str): The data type for the field (tag, integer, string, boolean, date,
|
||||
subtitleLanguage, audioLanguage, resolution).
|
||||
|
@ -992,7 +992,7 @@ class LibrarySection(PlexObject):
|
|||
:class:`~plexapi.library.FilteringFilter` or filter field.
|
||||
This is the list of available values for a custom filter
|
||||
(`screenshot <../_static/images/LibrarySection.search.png>`__).
|
||||
|
||||
|
||||
Parameters:
|
||||
field (str): :class:`~plexapi.library.FilteringFilter` object,
|
||||
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)]
|
||||
raise NotFound(f'Unknown filter field "{field}" for libtype "{libtype}". '
|
||||
f'Available filters: {availableFilters}') from None
|
||||
|
||||
|
||||
data = self._server.query(field.key)
|
||||
return self.findItems(data, FilterChoice)
|
||||
|
||||
|
@ -1111,7 +1111,7 @@ class LibrarySection(PlexObject):
|
|||
except (ValueError, AttributeError):
|
||||
raise BadRequest(f'Invalid value "{value}" for filter field "{filterField.key}", '
|
||||
f'value should be type {fieldType.type}') from None
|
||||
|
||||
|
||||
return results
|
||||
|
||||
def _validateFieldValueDate(self, value):
|
||||
|
@ -1345,7 +1345,7 @@ class LibrarySection(PlexObject):
|
|||
Tag type filter values can be a :class:`~plexapi.library.FilterChoice` object,
|
||||
:class:`~plexapi.media.MediaTag` object, the exact name :attr:`MediaTag.tag` (*str*),
|
||||
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
|
||||
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``
|
||||
* ``mon``: ``months``
|
||||
* ``y``: ``years``
|
||||
|
||||
|
||||
Multiple values can be ``OR`` together by providing a list of values.
|
||||
|
||||
Examples:
|
||||
|
@ -1686,10 +1686,10 @@ class LibrarySection(PlexObject):
|
|||
""" Validates the specified items are from this library and of the same type. """
|
||||
if items is None or items == []:
|
||||
raise BadRequest('No items specified.')
|
||||
|
||||
|
||||
if not isinstance(items, list):
|
||||
items = [items]
|
||||
|
||||
|
||||
itemType = items[0].type
|
||||
for item in items:
|
||||
if item.librarySectionID != self.key:
|
||||
|
@ -3102,6 +3102,7 @@ class FirstCharacter(PlexObject):
|
|||
size (str): Total amount of library items starting with this character.
|
||||
title (str): Character (#, !, A, B, C, ...).
|
||||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
self._data = data
|
||||
|
|
|
@ -1008,6 +1008,7 @@ class BaseResource(PlexObject):
|
|||
selected (bool): True if the resource is currently selected.
|
||||
thumb (str): The URL to retrieve the resource thumbnail.
|
||||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
self._data = data
|
||||
self.key = data.attrib.get('key')
|
||||
|
|
|
@ -39,7 +39,7 @@ class AdvancedSettingsMixin:
|
|||
pref = preferences[settingID]
|
||||
except KeyError:
|
||||
raise NotFound(f'{value} not found in {list(preferences.keys())}')
|
||||
|
||||
|
||||
enumValues = pref.enumValues
|
||||
if enumValues.get(value, enumValues.get(str(value))):
|
||||
data[settingID] = value
|
||||
|
@ -69,7 +69,7 @@ class SmartFilterMixin:
|
|||
filters = {}
|
||||
filterOp = 'and'
|
||||
filterGroups = [[]]
|
||||
|
||||
|
||||
for key, value in parse_qsl(content.query):
|
||||
# Move = sign to key when operator is ==
|
||||
if value.startswith('='):
|
||||
|
@ -96,11 +96,11 @@ class SmartFilterMixin:
|
|||
filterGroups.pop()
|
||||
else:
|
||||
filterGroups[-1].append({key: value})
|
||||
|
||||
|
||||
if filterGroups:
|
||||
filters['filters'] = self._formatFilterGroups(filterGroups.pop())
|
||||
return filters
|
||||
|
||||
|
||||
def _formatFilterGroups(self, groups):
|
||||
""" Formats the filter groups into the advanced search rules. """
|
||||
if len(groups) == 1 and isinstance(groups[0], list):
|
||||
|
@ -131,7 +131,7 @@ class SplitMergeMixin:
|
|||
|
||||
def merge(self, ratingKeys):
|
||||
""" Merge other Plex objects into the current object.
|
||||
|
||||
|
||||
Parameters:
|
||||
ratingKeys (list): A list of rating keys to merge.
|
||||
"""
|
||||
|
@ -320,7 +320,7 @@ class RatingMixin:
|
|||
|
||||
class ArtUrlMixin:
|
||||
""" Mixin for Plex objects that can have a background artwork url. """
|
||||
|
||||
|
||||
@property
|
||||
def artUrl(self):
|
||||
""" Return the art url for the Plex object. """
|
||||
|
@ -349,7 +349,7 @@ class ArtMixin(ArtUrlMixin, ArtLockMixin):
|
|||
|
||||
def uploadArt(self, url=None, filepath=None):
|
||||
""" Upload a background artwork from a url or filepath.
|
||||
|
||||
|
||||
Parameters:
|
||||
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.
|
||||
|
@ -365,7 +365,7 @@ class ArtMixin(ArtUrlMixin, ArtLockMixin):
|
|||
|
||||
def setArt(self, art):
|
||||
""" Set the background artwork for a Plex object.
|
||||
|
||||
|
||||
Parameters:
|
||||
art (:class:`~plexapi.media.Art`): The art object to select.
|
||||
"""
|
||||
|
@ -425,7 +425,7 @@ class PosterMixin(PosterUrlMixin, PosterLockMixin):
|
|||
|
||||
def setPoster(self, poster):
|
||||
""" Set the poster for a Plex object.
|
||||
|
||||
|
||||
Parameters:
|
||||
poster (:class:`~plexapi.media.Poster`): The poster object to select.
|
||||
"""
|
||||
|
@ -491,11 +491,11 @@ class ThemeMixin(ThemeUrlMixin, ThemeLockMixin):
|
|||
|
||||
class EditFieldMixin:
|
||||
""" Mixin for editing Plex object fields. """
|
||||
|
||||
|
||||
def editField(self, field, value, locked=True, **kwargs):
|
||||
""" 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.
|
||||
|
||||
|
||||
Parameters:
|
||||
field (str): The name of the field to edit.
|
||||
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
|
||||
(invite.username.lower(), invite.email.lower(), str(invite.id))):
|
||||
return invite
|
||||
|
||||
|
||||
raise NotFound(f'Unable to find invite {username}')
|
||||
|
||||
def pendingInvites(self, includeSent=True, includeReceived=True):
|
||||
|
@ -952,7 +952,7 @@ class MyPlexAccount(PlexObject):
|
|||
"""
|
||||
if not isinstance(items, list):
|
||||
items = [items]
|
||||
|
||||
|
||||
for item in items:
|
||||
if self.onWatchlist(item):
|
||||
raise BadRequest(f'"{item.title}" is already on the watchlist')
|
||||
|
@ -973,7 +973,7 @@ class MyPlexAccount(PlexObject):
|
|||
"""
|
||||
if not isinstance(items, list):
|
||||
items = [items]
|
||||
|
||||
|
||||
for item in items:
|
||||
if not self.onWatchlist(item):
|
||||
raise BadRequest(f'"{item.title}" is not on the watchlist')
|
||||
|
@ -1944,7 +1944,7 @@ class AccountOptOut(PlexObject):
|
|||
|
||||
def optOutManaged(self):
|
||||
""" Sets the Online Media Source to "Disabled for Managed Users".
|
||||
|
||||
|
||||
Raises:
|
||||
: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.
|
||||
"""
|
||||
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):
|
||||
""" Add current photo as sync item for specified device.
|
||||
See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
|
||||
|
|
|
@ -155,7 +155,7 @@ class Playlist(
|
|||
sectionKey = int(match.group(1))
|
||||
self._section = self._server.library.sectionByID(sectionKey)
|
||||
return self._section
|
||||
|
||||
|
||||
# Try to get the library section from the first item in the playlist
|
||||
if self.items():
|
||||
self._section = self.items()[0].section()
|
||||
|
@ -314,7 +314,7 @@ class Playlist(
|
|||
|
||||
def edit(self, title=None, summary=None):
|
||||
""" Edit the playlist.
|
||||
|
||||
|
||||
Parameters:
|
||||
title (str, optional): The title of the playlist.
|
||||
summary (str, optional): The summary of the playlist.
|
||||
|
@ -432,7 +432,7 @@ class Playlist(
|
|||
|
||||
def copyToUser(self, user):
|
||||
""" Copy playlist to another user account.
|
||||
|
||||
|
||||
Parameters:
|
||||
user (:class:`~plexapi.myplex.MyPlexUser` or str): `MyPlexUser` object, username,
|
||||
email, or user id of the user to copy the playlist to.
|
||||
|
|
|
@ -202,7 +202,7 @@ class PlexServer(PlexObject):
|
|||
def claim(self, account):
|
||||
""" Claim the Plex server using a :class:`~plexapi.myplex.MyPlexAccount`.
|
||||
This will only work with an unclaimed server on localhost or the same subnet.
|
||||
|
||||
|
||||
Parameters:
|
||||
account (:class:`~plexapi.myplex.MyPlexAccount`): The account used to
|
||||
claim the server.
|
||||
|
@ -245,7 +245,7 @@ class PlexServer(PlexObject):
|
|||
def switchUser(self, user, session=None, timeout=None):
|
||||
""" Returns a new :class:`~plexapi.server.PlexServer` object logged in as the given username.
|
||||
Note: Only the admin account can switch to other users.
|
||||
|
||||
|
||||
Parameters:
|
||||
user (:class:`~plexapi.myplex.MyPlexUser` or str): `MyPlexUser` object, username,
|
||||
email, or user id of the user to log in to the server.
|
||||
|
@ -590,7 +590,7 @@ class PlexServer(PlexObject):
|
|||
def runButlerTask(self, task):
|
||||
""" 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.
|
||||
|
||||
|
||||
Parameters:
|
||||
task (str): The name of the task to run. (e.g. 'BackupDatabase')
|
||||
|
||||
|
@ -666,7 +666,7 @@ class PlexServer(PlexObject):
|
|||
args['librarySectionID'] = librarySectionID
|
||||
if mindate:
|
||||
args['viewedAt>'] = int(mindate.timestamp())
|
||||
|
||||
|
||||
key = f'/status/sessions/history/all{utils.joinArgs(args)}'
|
||||
return self.fetchItems(key, maxresults=maxresults)
|
||||
|
||||
|
@ -1258,7 +1258,7 @@ class StatisticsResources(PlexObject):
|
|||
@utils.registerPlexObject
|
||||
class ButlerTask(PlexObject):
|
||||
""" Represents a single scheduled butler task.
|
||||
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'ButlerTask'
|
||||
description (str): The description of the task.
|
||||
|
@ -1291,7 +1291,7 @@ class Identity(PlexObject):
|
|||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}:{self.machineIdentifier}>"
|
||||
|
||||
|
||||
def _loadData(self, data):
|
||||
self._data = data
|
||||
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
|
||||
to explicitly specify that your app supports `sync-target`.
|
||||
"""
|
||||
|
||||
import requests
|
||||
|
||||
import plexapi
|
||||
|
|
|
@ -88,7 +88,7 @@ def test_history_PlexHistory(plex, movie):
|
|||
movie.markPlayed()
|
||||
history = plex.history()
|
||||
assert len(history)
|
||||
|
||||
|
||||
hist = history[0]
|
||||
assert hist.source() == movie
|
||||
assert hist.accountID
|
||||
|
@ -106,14 +106,20 @@ def test_history_User(account, shared_username):
|
|||
user = account.user(shared_username)
|
||||
history = user.history()
|
||||
|
||||
assert isinstance(history, list)
|
||||
|
||||
|
||||
def test_history_UserServer(account, shared_username, plex):
|
||||
userSharedServer = account.user(shared_username).server(plex.friendlyName)
|
||||
history = userSharedServer.history()
|
||||
|
||||
assert isinstance(history, list)
|
||||
|
||||
|
||||
def test_history_UserSection(account, shared_username, plex):
|
||||
userSharedServerSection = (
|
||||
account.user(shared_username).server(plex.friendlyName).section("Movies")
|
||||
)
|
||||
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.removeCollection("test_search", locked=False)
|
||||
album.removeLabel("test_search", locked=False)
|
||||
|
||||
|
||||
track = album.track(track=1)
|
||||
track.addMood("test_search")
|
||||
_test_library_search(music, track)
|
||||
|
@ -777,7 +777,7 @@ def test_library_search_exceptions(movies):
|
|||
movies.search(sort="titleSort:bad")
|
||||
|
||||
|
||||
def _test_library_search(library, obj):
|
||||
def _test_library_search(library, obj): # noqa: C901
|
||||
# Create & operator
|
||||
AndOperator = namedtuple("AndOperator", ["key", "title"])
|
||||
andOp = AndOperator("&=", "and")
|
||||
|
|
|
@ -317,7 +317,7 @@ def _test_mixins_edit_theme(obj):
|
|||
obj.unlockTheme()
|
||||
obj.reload()
|
||||
assert "theme" not in _fields()
|
||||
|
||||
|
||||
# Lock the theme
|
||||
obj.lockTheme()
|
||||
obj.reload()
|
||||
|
|
|
@ -28,6 +28,7 @@ def test_navigate_around_artist(account, plex):
|
|||
print(f"Album: {album}")
|
||||
print(f"Tracks: {tracks}...")
|
||||
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 album.track("As Colourful as Ever") == track, "Unable to get album track."
|
||||
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.platform) or device.platform == ""
|
||||
assert plex.systemDevice(device.id) == device
|
||||
|
||||
|
||||
|
||||
@pytest.mark.authenticated
|
||||
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):
|
||||
assert movie.videoStreams()
|
||||
|
||||
|
||||
|
||||
def test_video_Movie_audioStreams(movie):
|
||||
assert movie.audioStreams()
|
||||
|
||||
|
@ -418,7 +418,7 @@ def test_video_Movie_upload_select_remove_subtitle(movie, subtitle):
|
|||
|
||||
try:
|
||||
os.remove(filepath)
|
||||
except:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
|
@ -632,7 +632,7 @@ def test_video_Movie_batchEdits(movie):
|
|||
assert movie.tagline == tagline
|
||||
assert movie.studio == studio
|
||||
assert not movie.fields
|
||||
|
||||
|
||||
with pytest.raises(BadRequest):
|
||||
movie.saveEdits()
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import os
|
|||
from datetime import datetime
|
||||
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')
|
||||
|
||||
|
||||
|
@ -40,7 +40,7 @@ def keep_episodes(show, keep):
|
|||
""" Delete all but last count episodes in show. """
|
||||
deleted = 0
|
||||
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)
|
||||
for episode in items[keep:]:
|
||||
delete_episode(episode)
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
"""
|
||||
Backup and restore the watched status of Plex libraries to a json file.
|
||||
"""
|
||||
import argparse, json
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
import json
|
||||
from plexapi import utils
|
||||
|
||||
SECTIONS = ('movie', 'show')
|
||||
|
@ -32,7 +33,7 @@ def _item_key(item):
|
|||
|
||||
def _iter_sections(plex, opts):
|
||||
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():
|
||||
title = section.title.lower()
|
||||
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()
|
||||
for item in _iter_items(section):
|
||||
ikey = _item_key(item)
|
||||
sval = source.get(skey,{}).get(ikey)
|
||||
sval = source.get(skey, {}).get(ikey)
|
||||
if sval is None:
|
||||
raise SystemExit('%s not found' % ikey)
|
||||
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))
|
||||
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('-p', '--password', default=CONFIG.get('auth.myplex_password'), help='Plex password')
|
||||
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).')
|
||||
opts = parser.parse_args()
|
||||
account = utils.getMyPlexAccount(opts)
|
||||
|
|
|
@ -121,15 +121,15 @@ def setup_music(music_path, docker=False):
|
|||
"Broke for free": {
|
||||
"Layers": [
|
||||
"1 - As Colorful As Ever.mp3",
|
||||
#"02 - Knock Knock.mp3",
|
||||
#"03 - Only Knows.mp3",
|
||||
#"04 - If.mp3",
|
||||
#"05 - Note Drop.mp3",
|
||||
#"06 - Murmur.mp3",
|
||||
#"07 - Spellbound.mp3",
|
||||
#"08 - The Collector.mp3",
|
||||
#"09 - Quit Bitching.mp3",
|
||||
#"10 - A Year.mp3",
|
||||
# "02 - Knock Knock.mp3",
|
||||
# "03 - Only Knows.mp3",
|
||||
# "04 - If.mp3",
|
||||
# "05 - Note Drop.mp3",
|
||||
# "06 - Murmur.mp3",
|
||||
# "07 - Spellbound.mp3",
|
||||
# "08 - The Collector.mp3",
|
||||
# "09 - Quit Bitching.mp3",
|
||||
# "10 - A Year.mp3",
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -279,7 +279,7 @@ def add_library_section(server, section):
|
|||
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
|
||||
expected_media_count = section.pop("expected_media_count", 0)
|
||||
expected_media_type = (section["type"],)
|
||||
|
@ -337,7 +337,7 @@ def create_section(server, section, opts):
|
|||
notifier.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__": # noqa: C901
|
||||
default_ip = get_default_ip()
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
# Authentication arguments
|
||||
|
|
|
@ -23,7 +23,7 @@ def search_for_item(url=None):
|
|||
servers = [s for s in account.resources() if 'server' in s.provides]
|
||||
server = utils.choose('Choose a Server', servers, 'name').connect()
|
||||
query = input('What are you looking for?: ')
|
||||
item = []
|
||||
item = []
|
||||
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]))
|
||||
|
||||
|
@ -63,10 +63,10 @@ def get_item_from_url(url):
|
|||
server = servers[0].connect()
|
||||
return server.fetchItem(key)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Command line parser
|
||||
from plexapi import CONFIG
|
||||
from tqdm import tqdm
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('-u', '--username', help='Your Plex username',
|
||||
default=CONFIG.get('auth.myplex_username'))
|
||||
|
@ -84,4 +84,3 @@ if __name__ == '__main__':
|
|||
url = item._server.url('%s?download=1' % part.key)
|
||||
filepath = utils.download(url, token=item._server._token, filename=filename, savepath=os.getcwd(),
|
||||
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
|
||||
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 collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
@ -296,19 +303,19 @@ class PlexAttributes():
|
|||
def _safe_connect(self, elem):
|
||||
try:
|
||||
return elem.connect()
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _safe_reload(self, elem):
|
||||
try:
|
||||
elem.reload()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _(text, color):
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from os.path import abspath, dirname, join
|
|||
from plexapi import utils
|
||||
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')
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ def _setting_group(setting):
|
|||
return setting.group
|
||||
|
||||
|
||||
def _write_settings(handle, groups, group):
|
||||
def _write_settings(handle, groups, group): # noqa: C901
|
||||
title = GROUPNAMES.get(group, group.title())
|
||||
print('\n%s Settings\n%s' % (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):
|
||||
items, seen = [], set()
|
||||
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)
|
||||
for url, token, plex, runtime in results:
|
||||
clients = plex.clients() if plex else []
|
||||
|
|
|
@ -41,17 +41,17 @@ def _iter_items(search):
|
|||
|
||||
if __name__ == '__main__':
|
||||
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()
|
||||
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')):
|
||||
if not item.isWatched:
|
||||
print('{datestr} Marking {_get_title(item)} watched.'.format(datestr=datestr()))
|
||||
print(f'{datestr()} Marking {_get_title(item)} watched.')
|
||||
item.markWatched()
|
||||
# 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():
|
||||
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()
|
||||
|
|
|
@ -90,10 +90,9 @@ def main():
|
|||
if arguments.tag:
|
||||
subprocess.run(["git", "tag", str(bumped), "-m", f"Release {bumped}"])
|
||||
|
||||
|
||||
def test_bump_version():
|
||||
"""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"), "minor") == Version("4.8.0")
|
||||
assert bump_version(Version("4.7.3"), "minor") == Version("4.8.0")
|
||||
|
|
Loading…
Reference in a new issue