Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions plexapi/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,10 +849,7 @@ def _validateFieldValue(self, filterField, values, libtype=None):
if fieldType.type == 'boolean':
value = int(bool(value))
elif fieldType.type == 'date':
if isinstance(value, datetime):
value = int(value.timestamp())
else:
value = int(utils.toDatetime(value, '%Y-%m-%d').timestamp())
value = self._validateFieldValueDate(value)
elif fieldType.type == 'integer':
value = int(value)
elif fieldType.type == 'string':
Expand All @@ -863,12 +860,23 @@ def _validateFieldValue(self, filterField, values, libtype=None):
value = next((f.key for f in filterChoices
if matchValue in {f.key.lower(), f.title.lower()}), value)
results.append(str(value))
except ValueError:
except (ValueError, AttributeError):
raise BadRequest('Invalid value "%s" for filter field "%s", value should be type %s'
% (value, filterField.key, fieldType.type)) from None

return results

def _validateFieldValueDate(self, value):
""" Validates a filter date value. A filter date value can be a datetime object,
a relative date (e.g. -30d), or a date in YYYY-MM-DD format.
"""
if isinstance(value, datetime):
return int(value.timestamp())
elif re.match(r'^-?\d+(mon|[smhdwy])$', value):
return '-' + value.lstrip('-')
else:
return int(utils.toDatetime(value, '%Y-%m-%d').timestamp())

def _validateSortField(self, sort, libtype=None):
""" Validates a filter sort field is available for the library.
Returns the validated sort field string.
Expand Down Expand Up @@ -939,10 +947,7 @@ def search(self, title=None, sort=None, maxresults=None,
* See :func:`~plexapi.library.LibrarySection.listFilterChoices` to get a list of all available filter values.

The following filter fields are just some examples of the possible filters. The list is not exaustive,
and not all filters apply to all library types. For tag type filters, a :class:`~plexapi.media.MediaTag`
object, the exact name :attr:`MediaTag.tag` (*str*), or the exact id :attr:`MediaTag.id` (*int*) can be
provided. For date type filters, either a ``datetime`` object or a date in ``YYYY-MM-DD`` (*str*) format
can be provided. Multiple values can be ``OR`` together by providing a list of values.
and not all filters apply to all library types.

* **actor** (:class:`~plexapi.media.MediaTag`): Search for the name of an actor.
* **addedAt** (*datetime*): Search for items added before or after a date. See operators below.
Expand Down Expand Up @@ -970,6 +975,24 @@ def search(self, title=None, sort=None, maxresults=None,
* **writer** (:class:`~plexapi.media.MediaTag`): Search for the name of a writer.
* **year** (*int*): Search for a specific year.

Tag type filter values can be a :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.

Relative date suffixes:

* ``s``: ``seconds``
* ``m``: ``minutes``
* ``h``: ``hours``
* ``d``: ``days``
* ``w``: ``weeks``
* ``mon``: ``months``
* ``y``: ``years``

Multiple values can be ``OR`` together by providing a list of values.

Examples:

.. code-block:: python
Expand Down Expand Up @@ -1068,6 +1091,9 @@ def search(self, title=None, sort=None, maxresults=None,
# Title starts with Marvel and added before 2021-01-01
library.search(**{"title<": "Marvel", "addedAt<<": "2021-01-01"})

# Added in the last 30 days using relative dates
library.search(**{"addedAt>>": "30d"})

# Collection is James Bond and user rating is greater than 8
library.search(**{"collection": "James Bond", "userRating>>": 8})

Expand Down
44 changes: 22 additions & 22 deletions tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,28 +469,28 @@ def _test_library_search(library, obj):
searchValue = value - timedelta(days=1)
else:
searchValue = value

searchFilter = {field.key + operator.key[:-1]: searchValue}
results = library.search(libtype=obj.type, **searchFilter)

if operator.key.startswith("!") or operator.key.startswith(">>") and searchValue == 0:
assert obj not in results
else:
assert obj in results
_do_test_library_search(library, obj, field, operator, searchValue)

# Test search again using string tag and date
if field.type in {"tag", "date"}:
if field.type == "tag" and fieldAttr != 'contentRating':
if not isinstance(searchValue, list):
searchValue = [searchValue]
searchValue = [v.tag for v in searchValue]
elif field.type == "date":
searchValue = searchValue.strftime("%Y-%m-%d")

searchFilter = {field.key + operator.key[:-1]: searchValue}
results = library.search(libtype=obj.type, **searchFilter)

if operator.key.startswith("!") or operator.key.startswith(">>") and searchValue == 0:
assert obj not in results
else:
assert obj in results
if field.type == "tag" and fieldAttr != "contentRating":
if not isinstance(searchValue, list):
searchValue = [searchValue]
searchValue = [v.tag for v in searchValue]
_do_test_library_search(library, obj, field, operator, searchValue)

elif field.type == "date":
searchValue = searchValue.strftime("%Y-%m-%d")
_do_test_library_search(library, obj, field, operator, searchValue)
searchValue = "1s"
_do_test_library_search(library, obj, field, operator, searchValue)


def _do_test_library_search(library, obj, field, operator, searchValue):
searchFilter = {field.key + operator.key[:-1]: searchValue}
results = library.search(libtype=obj.type, **searchFilter)

if operator.key.startswith("!") or operator.key.startswith(">>") and (searchValue == 0 or searchValue == '1s'):
assert obj not in results
else:
assert obj in results