-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add reader.utils.archive_entries(). #290
- Loading branch information
Showing
8 changed files
with
166 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"""Too specific to be in core, too small to have dedicated modules.""" | ||
|
||
from collections.abc import Collection | ||
from urllib.parse import urlencode | ||
|
||
from . import EntryExistsError | ||
from . import FeedExistsError | ||
from . import Reader | ||
from .types import _entry_argument | ||
from .types import EntryInput | ||
|
||
|
||
def archive_entries( | ||
reader: Reader, | ||
entries: Collection[EntryInput], | ||
/, | ||
feed_url: str = 'reader:archived', | ||
feed_user_title: str | None = 'Archived', | ||
) -> None: | ||
"""Copy a list of entries to a special "archived" feed. | ||
Entries that are already in the archived feed will be overwritten. | ||
The original entries will remain unchanged. | ||
Args: | ||
reader (Reader): A reader instance. | ||
entries (list(tuple(str, str) or Entry)): Entries to be archived. | ||
feed_url (str): | ||
The URL of the archived feed. | ||
If the feed does not exist, it will be created. | ||
feed_user_title (str or None): | ||
:attr:`~.Feed.user_title` for the archived feed. | ||
Raises: | ||
EntryExistsError: If any of the entries does not exist. | ||
StorageError | ||
.. versionadded:: 3.16 | ||
""" | ||
entry_ids = [_entry_argument(e) for e in entries] | ||
|
||
try: | ||
reader.add_feed(feed_url, allow_invalid_url=True) | ||
reader.disable_feed_updates(feed_url) | ||
except FeedExistsError: | ||
pass | ||
reader.set_feed_user_title(feed_url, feed_user_title) | ||
|
||
for src in entry_ids: | ||
dst = feed_url, _make_archived_entry_id(feed_url, src) | ||
try: | ||
reader.copy_entry(src, dst) | ||
except EntryExistsError: | ||
reader.delete_entry(dst) | ||
reader.copy_entry(src, dst) | ||
|
||
# TODO: ideally, archiving may redirect to a view of the archived entries | ||
# | ||
# this can be achieved in one of the following ways: | ||
# | ||
# * filter by the archived entry ids | ||
# * get_entries(entries=...) does not exist | ||
# * if there are a lot of entries, the query string may be to big | ||
# * filter by entry source – get_entries(source=...) | ||
# * this will not include entries that already have a source | ||
# * idem for original_feed_url | ||
# * filter by entry id prefix – reader:archived?feed=...& | ||
# * get_entries(entry_id_prefix=...) does not exist | ||
# * by far the most correct | ||
# | ||
# until we figure this out, leaving return type to None | ||
|
||
|
||
def _make_archived_entry_id(feed_url: str, entry: tuple[str, str]) -> str: | ||
query = urlencode({'feed': entry[0], 'entry': entry[1]}) | ||
return f"{feed_url}?{query}" |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from unittest.mock import Mock | ||
|
||
import pytest | ||
|
||
from fakeparser import Parser | ||
from reader import EntryNotFoundError | ||
from reader.utils import archive_entries | ||
|
||
|
||
def test_archive_entries(reader): | ||
reader._parser = parser = Parser() | ||
reader.copy_entry = Mock(wraps=reader.copy_entry) | ||
|
||
feed = parser.feed(1) | ||
one = parser.entry(1, 'one', title='one') | ||
two = parser.entry(1, '&?:/', title='not URL safe') | ||
reader.add_feed(feed) | ||
reader.update_feeds() | ||
|
||
# archive an entry, archived does not exist | ||
|
||
reader.copy_entry.reset_mock() | ||
archive_entries(reader, [one]) | ||
|
||
assert len(reader.copy_entry.call_args_list) == 1 | ||
assert {e.resource_id + (e.title,) for e in reader.get_entries()} == { | ||
('1', 'one', 'one'), | ||
('1', '&?:/', 'not URL safe'), | ||
('reader:archived', 'reader:archived?feed=1&entry=one', 'one'), | ||
} | ||
archived = reader.get_feed('reader:archived') | ||
assert archived.updates_enabled is False | ||
assert archived.user_title == 'Archived' | ||
|
||
# archive two entries (one already archived), archived exists | ||
|
||
one = parser.entry(1, 'one', title='new one') | ||
reader.update_feeds() | ||
|
||
reader.copy_entry.reset_mock() | ||
archive_entries(reader, [one, two]) | ||
|
||
# 3 because one is copied (exists error), deleted, and then copied again | ||
assert len(reader.copy_entry.call_args_list) == 3 | ||
assert {e.resource_id + (e.title,) for e in reader.get_entries()} == { | ||
('1', 'one', 'new one'), | ||
('1', '&?:/', 'not URL safe'), | ||
('reader:archived', 'reader:archived?feed=1&entry=one', 'new one'), | ||
( | ||
'reader:archived', | ||
'reader:archived?feed=1&entry=%26%3F%3A%2F', | ||
'not URL safe', | ||
), | ||
} | ||
|
||
# archive inexistent entry | ||
|
||
with pytest.raises(EntryNotFoundError): | ||
archive_entries(reader, [('1', 'inexistent')]) |