Skip to content

Commit

Permalink
Allow filtering entries by the entry source. #267
Browse files Browse the repository at this point in the history
lemon24 committed Dec 2, 2024
1 parent a29fc9f commit 5c4f5c6
Showing 5 changed files with 54 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ Unreleased
* Add :attr:`Entry.feed_resolved_title` and :attr:`Feed.resolved_title` properties.
* The ``feed`` search column now indexes :attr:`Entry.feed_resolved_title`,
instead of feed :attr:`~Feed.user_title` or :attr:`~Feed.title`.
* Allow filtering entries by the entry source.

* Fix :meth:`~Reader.enable_search` / :meth:`~Reader.update_search`
not working when the search database is missing but change tracking is enabled
8 changes: 7 additions & 1 deletion src/reader/_storage/_entries.py
Original file line number Diff line number Diff line change
@@ -518,7 +518,9 @@ def entry_filter(
query: Query, filter: EntryFilter, keyword: str = 'WHERE'
) -> dict[str, Any]:
add = getattr(query, keyword)
feed_url, entry_id, read, important, has_enclosures, tags, feed_tags = filter
feed_url, entry_id, read, important, has_enclosures, source_url, tags, feed_tags = (
filter
)

context = {}

@@ -544,6 +546,10 @@ def entry_filter(
"""
)

if source_url:
add("json_extract(entries.source, '$.url') = :source_url")
context['source_url'] = source_url

context.update(entry_tags_filter(query, tags, keyword=keyword))
context.update(feed_tags_filter(query, feed_tags, 'entries.feed', keyword=keyword))

5 changes: 5 additions & 0 deletions src/reader/_types.py
Original file line number Diff line number Diff line change
@@ -514,6 +514,7 @@ class EntryFilter(NamedTuple):
read: bool | None = None
important: TristateFilter = 'any'
has_enclosures: bool | None = None
source: str | None = None
tags: TagFilter = ()
feed_tags: TagFilter = ()

@@ -525,6 +526,7 @@ def from_args(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
source: FeedInput | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
) -> Self:
@@ -543,6 +545,8 @@ def from_args(
if has_enclosures not in (None, False, True):
raise ValueError("has_enclosures should be one of (None, False, True)")

source_url = _feed_argument(source) if source is not None else None

tag_filter = tag_filter_argument(tags)
feed_tag_filter = tag_filter_argument(feed_tags, 'feed_tags')

@@ -552,6 +556,7 @@ def from_args(
read,
important_filter,
has_enclosures,
source_url,
tag_filter,
feed_tag_filter,
)
44 changes: 36 additions & 8 deletions src/reader/core.py
Original file line number Diff line number Diff line change
@@ -1091,6 +1091,7 @@ def get_entries(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
source: FeedInput | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
sort: EntrySort = 'recent',
@@ -1131,7 +1132,8 @@ def get_entries(
.. versionadded:: 1.2
Args:
feed (str or tuple(str) or Feed or None): Only return the entries for this feed.
feed (str or tuple(str) or Feed or None):
Only return the entries for this feed.
entry (tuple(str, str) or Entry or None):
Only return the entry with this (feed URL, entry id) tuple.
read (bool or None): Only return (un)read entries.
@@ -1141,6 +1143,8 @@ def get_entries(
:data:`~reader.types.TristateFilterInput` string filters.
has_enclosures (bool or None): Only return entries that (don't)
have enclosures.
source (str or tuple(str) or Feed or None):
Only return the entries for this source.
tags (None or bool or list(str or bool or list(str or bool))):
Only return entries matching these tags;
see :data:`~reader.types.TagFilterInput` for details.
@@ -1177,13 +1181,16 @@ def get_entries(
.. versionadded:: 3.11
The ``tags`` keyword argument.
.. versionadded:: 3.16
The ``source`` keyword argument.
"""

# If we ever implement pagination, consider following the guidance in
# https://specs.openstack.org/openstack/api-wg/guidelines/pagination_filter_sort.html

filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, tags, feed_tags
feed, entry, read, important, has_enclosures, source, tags, feed_tags
)

if sort not in ('recent', 'random'):
@@ -1252,13 +1259,15 @@ def get_entry_counts(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
source: FeedInput | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
) -> EntryCounts:
"""Count all or some of the entries.
Args:
feed (str or tuple(str) or Feed or None): Only count the entries for this feed.
feed (str or tuple(str) or Feed or None):
Only count the entries for this feed.
entry (tuple(str, str) or Entry or None):
Only count the entry with this (feed URL, entry id) tuple.
read (bool or None): Only count (un)read entries.
@@ -1268,6 +1277,8 @@ def get_entry_counts(
:data:`~reader.types.TristateFilterInput` string filters.
has_enclosures (bool or None): Only count entries that (don't)
have enclosures.
source (str or tuple(str) or Feed or None):
Only count the entries for this source.
tags (None or bool or list(str or bool or list(str or bool))):
Only count entries matching these tags;
see :data:`~reader.types.TagFilterInput` for details.
@@ -1289,10 +1300,13 @@ def get_entry_counts(
.. versionadded:: 3.11
The ``tags`` keyword argument.
.. versionadded:: 3.16
The ``source`` keyword argument.
"""

filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, tags, feed_tags
feed, entry, read, important, has_enclosures, source, tags, feed_tags
)
now = self._now()
return self._storage.get_entry_counts(now, filter)
@@ -1657,6 +1671,7 @@ def search_entries(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
source: FeedInput | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
sort: SearchSortOrder = 'relevant',
@@ -1723,7 +1738,8 @@ def search_entries(
Args:
query (str): The search query.
feed (str or tuple(str) or Feed or None): Only search the entries for this feed.
feed (str or tuple(str) or Feed or None):
Only search the entries for this feed.
entry (tuple(str, str) or Entry or None):
Only search for the entry with this (feed URL, entry id) tuple.
read (bool or None): Only search (un)read entries.
@@ -1733,6 +1749,8 @@ def search_entries(
:data:`~reader.types.TristateFilterInput` string filters.
has_enclosures (bool or None): Only search entries that (don't)
have enclosures.
source (str or tuple(str) or Feed or None):
Only search the entries for this source.
tags (None or bool or list(str or bool or list(str or bool))):
Only search entries matching these tags;
see :data:`~reader.types.TagFilterInput` for details.
@@ -1775,9 +1793,12 @@ def search_entries(
.. versionadded:: 3.11
The ``tags`` keyword argument.
.. versionadded:: 3.16
The ``source`` keyword argument.
"""
filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, tags, feed_tags
feed, entry, read, important, has_enclosures, source, tags, feed_tags
)

if sort not in ('relevant', 'recent', 'random'):
@@ -1803,6 +1824,7 @@ def search_entry_counts(
read: bool | None = None,
important: TristateFilterInput = None,
has_enclosures: bool | None = None,
source: FeedInput | None = None,
tags: TagFilterInput = None,
feed_tags: TagFilterInput = None,
) -> EntrySearchCounts:
@@ -1814,7 +1836,8 @@ def search_entry_counts(
Args:
query (str): The search query.
feed (str or tuple(str) or Feed or None): Only count the entries for this feed.
feed (str or tuple(str) or Feed or None):
Only count the entries for this feed.
entry (tuple(str, str) or Entry or None):
Only count the entry with this (feed URL, entry id) tuple.
read (bool or None or str):
@@ -1824,6 +1847,8 @@ def search_entry_counts(
important (bool or None): Only count (un)important entries.
has_enclosures (bool or None): Only count entries that (don't)
have enclosures.
source (str or tuple(str) or Feed or None):
Only count the entries for this source.
tags (None or bool or list(str or bool or list(str or bool))):
Only count entries matching these tags;
see :data:`~reader.types.TagFilterInput` for details.
@@ -1851,10 +1876,13 @@ def search_entry_counts(
.. versionadded:: 3.11
The ``tags`` keyword argument.
.. versionadded:: 3.16
The ``source`` keyword argument.
"""

filter = EntryFilter.from_args(
feed, entry, read, important, has_enclosures, tags, feed_tags
feed, entry, read, important, has_enclosures, source, tags, feed_tags
)
now = self._now()
return self._search.search_entry_counts(query, now, filter)
6 changes: 5 additions & 1 deletion tests/test_reader_filter.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
from fakeparser import Parser
from reader import Enclosure
from reader import Entry
from reader import EntrySource
from reader import Feed
from reader import make_reader
from reader_methods import get_feeds
@@ -152,7 +153,7 @@ def reader_entries():
one_three = parser.entry(1, 3) # important
one_four = parser.entry(1, 4, enclosures=[Enclosure('http://e2')])
two = parser.feed(2)
two_one = parser.entry(2, 1)
two_one = parser.entry(2, 1, source=EntrySource(url='source'))

reader.add_feed(one.url)
reader.add_feed(two.url)
@@ -188,6 +189,8 @@ def reader_entries():
(dict(entry=('1', '1, 2')), {(1, 2)}),
(dict(entry=Entry('1, 2', feed=Feed('1'))), {(1, 2)}),
(dict(entry=('inexistent', 'also-inexistent')), set()),
(dict(source='source'), {(2, 1)}),
(dict(source=Feed('source')), {(2, 1)}),
],
)
@rename_argument('reader', 'reader_entries')
@@ -205,6 +208,7 @@ def test_entries(reader, get_entries, kwargs, expected):
dict(has_enclosures=object()),
dict(feed=object()),
dict(entry=object()),
dict(source=object()),
],
)
@rename_argument('reader', 'reader_entries')

0 comments on commit 5c4f5c6

Please sign in to comment.