reader 3.12__tar.gz → 3.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reader-3.12 → reader-3.13}/CHANGES.rst +52 -0
- {reader-3.12/src/reader.egg-info → reader-3.13}/PKG-INFO +2 -2
- {reader-3.12 → reader-3.13}/docs/api.rst +3 -0
- {reader-3.12 → reader-3.13}/docs/cli.rst +4 -2
- {reader-3.12 → reader-3.13}/docs/conf.py +3 -0
- {reader-3.12 → reader-3.13}/docs/dev.rst +13 -3
- {reader-3.12 → reader-3.13}/docs/guide.rst +114 -49
- {reader-3.12 → reader-3.13}/docs/install.rst +1 -3
- {reader-3.12 → reader-3.13}/docs/internal.rst +3 -0
- {reader-3.12 → reader-3.13}/examples/parser_only.py +1 -0
- {reader-3.12 → reader-3.13}/examples/terminal.py +1 -0
- {reader-3.12 → reader-3.13}/pyproject.toml +15 -3
- {reader-3.12 → reader-3.13}/scripts/bench.py +3 -2
- {reader-3.12 → reader-3.13}/scripts/debug_storage_stats.py +2 -1
- {reader-3.12 → reader-3.13}/scripts/generate_import_all.py +1 -0
- {reader-3.12 → reader-3.13}/scripts/jscontrols.py +3 -1
- {reader-3.12 → reader-3.13}/src/reader/__init__.py +3 -1
- {reader-3.12 → reader-3.13}/src/reader/__main__.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/__init__.py +8 -5
- {reader-3.12 → reader-3.13}/src/reader/_app/api_thing.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/cli.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/wsgi.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_cli.py +15 -5
- {reader-3.12 → reader-3.13}/src/reader/_config.py +3 -1
- {reader-3.12 → reader-3.13}/src/reader/_hash_utils.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_parser/__init__.py +2 -7
- {reader-3.12 → reader-3.13}/src/reader/_parser/_http_utils.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/_parser/_lazy.py +5 -6
- {reader-3.12 → reader-3.13}/src/reader/_parser/_url_utils.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_parser/file.py +1 -2
- {reader-3.12 → reader-3.13}/src/reader/_parser/http.py +1 -2
- {reader-3.12 → reader-3.13}/src/reader/_parser/requests/__init__.py +5 -3
- {reader-3.12 → reader-3.13}/src/reader/_plugins/cli_status.py +70 -46
- {reader-3.12 → reader-3.13}/src/reader/_plugins/enclosure_tags.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/_plugins/preview_feed_list.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_plugins/share.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_plugins/sqlite_releases.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/_plugins/timer.py +2 -1
- {reader-3.12 → reader-3.13}/src/reader/_storage/_base.py +6 -4
- {reader-3.12 → reader-3.13}/src/reader/_storage/_changes.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_storage/_entries.py +31 -18
- {reader-3.12 → reader-3.13}/src/reader/_storage/_feeds.py +46 -87
- {reader-3.12 → reader-3.13}/src/reader/_storage/_html_utils.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/_storage/_schema.py +11 -2
- {reader-3.12 → reader-3.13}/src/reader/_storage/_search.py +32 -95
- {reader-3.12 → reader-3.13}/src/reader/_storage/_sql_utils.py +3 -0
- {reader-3.12 → reader-3.13}/src/reader/_storage/_sqlite_utils.py +3 -2
- {reader-3.12 → reader-3.13}/src/reader/_storage/_tags.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/_types.py +56 -35
- {reader-3.12 → reader-3.13}/src/reader/_update.py +193 -149
- {reader-3.12 → reader-3.13}/src/reader/core.py +64 -8
- {reader-3.12 → reader-3.13}/src/reader/exceptions.py +3 -2
- {reader-3.12 → reader-3.13}/src/reader/plugins/__init__.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/plugins/enclosure_dedupe.py +1 -0
- {reader-3.12 → reader-3.13}/src/reader/plugins/entry_dedupe.py +4 -1
- {reader-3.12 → reader-3.13}/src/reader/plugins/mark_as_read.py +29 -0
- {reader-3.12 → reader-3.13}/src/reader/plugins/readtime.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/plugins/ua_fallback.py +2 -0
- {reader-3.12 → reader-3.13}/src/reader/types.py +60 -22
- {reader-3.12 → reader-3.13/src/reader.egg-info}/PKG-INFO +2 -2
- {reader-3.12 → reader-3.13}/src/reader.egg-info/SOURCES.txt +1 -0
- {reader-3.12 → reader-3.13}/src/reader.egg-info/requires.txt +1 -1
- {reader-3.12 → reader-3.13}/tests/conftest.py +3 -19
- {reader-3.12 → reader-3.13}/tests/test_app.py +3 -2
- {reader-3.12 → reader-3.13}/tests/test_bench.py +2 -1
- {reader-3.12 → reader-3.13}/tests/test_cli.py +18 -2
- {reader-3.12 → reader-3.13}/tests/test_lazy_imports.py +2 -1
- {reader-3.12 → reader-3.13}/tests/test_parser.py +7 -4
- reader-3.13/tests/test_plugins_cli_status.py +105 -0
- {reader-3.12 → reader-3.13}/tests/test_plugins_enclosure_dedupe.py +1 -2
- {reader-3.12 → reader-3.13}/tests/test_plugins_mark_as_read.py +47 -0
- {reader-3.12 → reader-3.13}/tests/test_reader.py +432 -67
- {reader-3.12 → reader-3.13}/tests/test_reader_context.py +3 -3
- {reader-3.12 → reader-3.13}/tests/test_reader_deprecations.py +1 -0
- {reader-3.12 → reader-3.13}/tests/test_reader_filter.py +7 -4
- {reader-3.12 → reader-3.13}/tests/test_reader_integration.py +6 -3
- {reader-3.12 → reader-3.13}/tests/test_reader_sort.py +1 -0
- reader-3.13/tests/test_reader_update.py +163 -0
- {reader-3.12 → reader-3.13}/tests/test_search.py +3 -4
- {reader-3.12 → reader-3.13}/tests/test_storage.py +11 -14
- {reader-3.12 → reader-3.13}/tests/test_test_utils.py +5 -1
- {reader-3.12 → reader-3.13}/tests/test_utils.py +1 -0
- reader-3.12/tests/test_plugins_cli_status.py +0 -26
- {reader-3.12 → reader-3.13}/LICENSE +0 -0
- {reader-3.12 → reader-3.13}/MANIFEST.in +0 -0
- {reader-3.12 → reader-3.13}/README.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/Makefile +0 -0
- {reader-3.12 → reader-3.13}/docs/app.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/changelog.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/config.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/contributing.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/dev-app.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/images/redesign-01.png +0 -0
- {reader-3.12 → reader-3.13}/docs/index.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/make.bat +0 -0
- {reader-3.12 → reader-3.13}/docs/plugins.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/dillo.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/entries-feed.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/entries.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/entry-one.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/entry-two.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/feeds.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/lynx.png +0 -0
- {reader-3.12 → reader-3.13}/docs/screenshots/search.png +0 -0
- {reader-3.12 → reader-3.13}/docs/tutorial.rst +0 -0
- {reader-3.12 → reader-3.13}/docs/why.rst +0 -0
- {reader-3.12 → reader-3.13}/examples/config.yaml +0 -0
- {reader-3.12 → reader-3.13}/examples/podcast.py +0 -0
- {reader-3.12 → reader-3.13}/run.sh +0 -0
- {reader-3.12 → reader-3.13}/scripts/.coverage +0 -0
- {reader-3.12 → reader-3.13}/scripts/backup.sh +0 -0
- {reader-3.12 → reader-3.13}/scripts/jscontrols.html +0 -0
- {reader-3.12 → reader-3.13}/scripts/lines.sh +0 -0
- {reader-3.12 → reader-3.13}/scripts/release.py +0 -0
- {reader-3.12 → reader-3.13}/setup.cfg +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/static/controls.js +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/static/style.css +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/add_entry.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/entries.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/entry.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/macros.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/metadata.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_app/templates/tags.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_parser/feedparser.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_parser/jsonfeed.py +1 -1
- {reader-3.12 → reader-3.13}/src/reader/_parser/requests/_lazy.py +2 -2
- {reader-3.12 → reader-3.13}/src/reader/_plugins/__init__.py +1 -1
- {reader-3.12 → reader-3.13}/src/reader/_plugins/templates/preview_feed_list.html +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_storage/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_utils.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.12 → reader-3.13}/src/reader/py.typed +0 -0
- {reader-3.12 → reader-3.13}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.12 → reader-3.13}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.12 → reader-3.13}/tests/data/10.json +0 -0
- {reader-3.12 → reader-3.13}/tests/data/10.json.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/custom +0 -0
- {reader-3.12 → reader-3.13}/tests/data/empty.atom +0 -0
- {reader-3.12 → reader-3.13}/tests/data/empty.atom.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/empty.json +0 -0
- {reader-3.12 → reader-3.13}/tests/data/empty.json.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/empty.rss +0 -0
- {reader-3.12 → reader-3.13}/tests/data/empty.rss.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/full.atom +0 -0
- {reader-3.12 → reader-3.13}/tests/data/full.atom.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/full.json +0 -0
- {reader-3.12 → reader-3.13}/tests/data/full.json.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/full.rss +0 -0
- {reader-3.12 → reader-3.13}/tests/data/full.rss.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/invalid.json +0 -0
- {reader-3.12 → reader-3.13}/tests/data/invalid.json.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/relative.atom +0 -0
- {reader-3.12 → reader-3.13}/tests/data/relative.atom.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/relative.rss +0 -0
- {reader-3.12 → reader-3.13}/tests/data/relative.rss.py +0 -0
- {reader-3.12 → reader-3.13}/tests/data/sqlite_releases.html +0 -0
- {reader-3.12 → reader-3.13}/tests/data/unknown.json +0 -0
- {reader-3.12 → reader-3.13}/tests/data/unknown.json.py +0 -0
- {reader-3.12 → reader-3.13}/tests/fakeparser.py +0 -0
- {reader-3.12 → reader-3.13}/tests/reader_methods.py +0 -0
- {reader-3.12 → reader-3.13}/tests/reader_test_plugins/__init__.py +0 -0
- {reader-3.12 → reader-3.13}/tests/reader_test_plugins/good.py +0 -0
- {reader-3.12 → reader-3.13}/tests/reader_test_plugins/init_error.py +0 -0
- {reader-3.12 → reader-3.13}/tests/reader_test_plugins/missing_dependency.py +0 -0
- {reader-3.12 → reader-3.13}/tests/reader_test_plugins/missing_entry_point.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test__types.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_app_wsgi.py +1 -1
- {reader-3.12 → reader-3.13}/tests/test_changes.py +1 -1
- {reader-3.12 → reader-3.13}/tests/test_config.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_exceptions.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_hash_utils.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_html_utils.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_plugins_entry_dedupe.py +2 -2
- {reader-3.12 → reader-3.13}/tests/test_plugins_preview_feed_list.py +2 -2
- {reader-3.12 → reader-3.13}/tests/test_plugins_readtime.py +3 -3
- {reader-3.12 → reader-3.13}/tests/test_plugins_sqlite_releases.py +1 -1
- {reader-3.12 → reader-3.13}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_reader_counts.py +3 -3
- {reader-3.12 → reader-3.13}/tests/test_reader_hooks.py +4 -4
- {reader-3.12 → reader-3.13}/tests/test_reader_plugins.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_reader_private.py +3 -3
- {reader-3.12 → reader-3.13}/tests/test_reader_search.py +5 -5
- {reader-3.12 → reader-3.13}/tests/test_sql_utils.py +0 -0
- {reader-3.12 → reader-3.13}/tests/test_sqlite_utils.py +1 -1
- {reader-3.12 → reader-3.13}/tests/test_tags.py +2 -2
- {reader-3.12 → reader-3.13}/tests/test_types.py +0 -0
- {reader-3.12 → reader-3.13}/tests/utils.py +0 -0
- {reader-3.12 → reader-3.13}/tox.ini +0 -0
|
@@ -6,6 +6,58 @@ Changelog
|
|
|
6
6
|
:noindex:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.13
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2024-06-19
|
|
13
|
+
|
|
14
|
+
* Allow different feed update intervals; see :ref:`scheduled` for details.
|
|
15
|
+
(:issue:`332`)
|
|
16
|
+
|
|
17
|
+
* Add ``scheduled`` argument to :meth:`~Reader.update_feeds()`,
|
|
18
|
+
:meth:`~Reader.get_feeds()`, etc.
|
|
19
|
+
|
|
20
|
+
* Add :attr:`Feed.update_after` and :attr:`~Feed.last_retrieved` attributes.
|
|
21
|
+
|
|
22
|
+
* The ``new`` filter of :meth:`~Reader.update_feeds()` etc. considers
|
|
23
|
+
a feed new if it was never retrieved (:attr:`~Feed.last_retrieved`),
|
|
24
|
+
not if it was never updated successfully (:attr:`~Feed.last_updated`).
|
|
25
|
+
|
|
26
|
+
* Update an entry whenever its :attr:`~Entry.updated` changes
|
|
27
|
+
(don't require it to be newer).
|
|
28
|
+
|
|
29
|
+
* Fix bug introduced in `version 3.12 <Version 3.12_>`_ causing an assertion error
|
|
30
|
+
when there are multiple entries with the same id in the same feed,
|
|
31
|
+
or when parallel :meth:`~Reader.update_feeds` calls add the same entry.
|
|
32
|
+
The fix restores the pre-3.12 first-entry-wins / last-write-wins behavior.
|
|
33
|
+
Thanks to `Joakim Hellsén`_ for reporting and helping debug this issue.
|
|
34
|
+
(:issue:`335`)
|
|
35
|
+
|
|
36
|
+
* Fix assertion error when an entry is deleted while being updated.
|
|
37
|
+
|
|
38
|
+
* Allow re-running the :mod:`~reader.plugins.mark_as_read` plugin for existing entries.
|
|
39
|
+
Thanks to `Michael Han`_ for the pull request.
|
|
40
|
+
(:issue:`317`)
|
|
41
|
+
|
|
42
|
+
* Other changes for scheduled updates. (:issue:`332`)
|
|
43
|
+
|
|
44
|
+
* Add ``--scheduled`` flag to the ``update`` command.
|
|
45
|
+
* The :mod:`~reader._plugins.cli_status` plugin now
|
|
46
|
+
records the output of multiple runs instead of just the last one,
|
|
47
|
+
with output from the same hour grouped in a single entry,
|
|
48
|
+
up to 24 entries/hours.
|
|
49
|
+
* Group mutually-exclusive attributes of :class:`~.FeedUpdateIntent`
|
|
50
|
+
into a new :attr:`~.FeedUpdateIntent.value` union attribute.
|
|
51
|
+
|
|
52
|
+
* New and improved :ref:`update` user guide section.
|
|
53
|
+
* Rename ``update --new-only`` CLI flag to ``--new``;
|
|
54
|
+
``--new-only`` remains available as an alias.
|
|
55
|
+
(:issue:`334`)
|
|
56
|
+
|
|
57
|
+
.. _Joakim Hellsén: https://github.com/TheLovinator1
|
|
58
|
+
.. _Michael Han: https://github.com/Sakunam
|
|
59
|
+
|
|
60
|
+
|
|
9
61
|
Version 3.12
|
|
10
62
|
------------
|
|
11
63
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -61,7 +61,7 @@ Provides-Extra: tests
|
|
|
61
61
|
Requires-Dist: pytest>=4; extra == "tests"
|
|
62
62
|
Requires-Dist: pytest-randomly; extra == "tests"
|
|
63
63
|
Requires-Dist: pytest-subtests; extra == "tests"
|
|
64
|
-
Requires-Dist:
|
|
64
|
+
Requires-Dist: pytest-rerunfailures; extra == "tests"
|
|
65
65
|
Requires-Dist: coverage; extra == "tests"
|
|
66
66
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
67
67
|
Requires-Dist: requests-mock; extra == "tests"
|
|
@@ -45,6 +45,8 @@ Serve the web application locally (at http://localhost:8080/):
|
|
|
45
45
|
python -m reader serve
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
.. _cli-update:
|
|
49
|
+
|
|
48
50
|
Updating feeds
|
|
49
51
|
--------------
|
|
50
52
|
|
|
@@ -56,13 +58,13 @@ hour). Note that *reader* uses the ETag and Last-Modified headers, so, if
|
|
|
56
58
|
supported by the the server, feeds will only be downloaded if they changed.
|
|
57
59
|
|
|
58
60
|
To avoid waiting too much for a new feed to be updated, you can run
|
|
59
|
-
``update --new
|
|
61
|
+
``update --new`` more often (e.g. every minute); this will update
|
|
60
62
|
only newly-added feeds. This is also a good time to update the search index.
|
|
61
63
|
|
|
62
64
|
You can achieve this using cron::
|
|
63
65
|
|
|
64
66
|
42 * * * * reader update -v 2>&1 >>"/tmp/$LOGNAME.reader.update.hourly.log"
|
|
65
|
-
* * * * * reader update -v --new
|
|
67
|
+
* * * * * reader update -v --new 2>&1 >>"/tmp/$LOGNAME.reader.update.new.log"; reader search update 2>&1 >>"/tmp/$LOGNAME.reader.search.update.log"
|
|
66
68
|
|
|
67
69
|
If you are running *reader* on a personal computer, it might also be convenient
|
|
68
70
|
to run ``update`` once immediately after boot::
|
|
@@ -6,6 +6,7 @@ import unittest.mock
|
|
|
6
6
|
import packaging.version
|
|
7
7
|
from setuptools.config.pyprojecttoml import read_configuration
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
sys.path.insert(0, os.path.abspath('../src'))
|
|
10
11
|
|
|
11
12
|
# mock some things "by hand", so we can import reader below without any dependencies
|
|
@@ -23,6 +24,7 @@ for name in [
|
|
|
23
24
|
|
|
24
25
|
import reader
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
extensions = [
|
|
27
29
|
'sphinx_rtd_theme',
|
|
28
30
|
'sphinx.ext.autodoc',
|
|
@@ -155,6 +157,7 @@ texinfo_documents = [
|
|
|
155
157
|
|
|
156
158
|
|
|
157
159
|
import pkgutil
|
|
160
|
+
|
|
158
161
|
from docutils import nodes
|
|
159
162
|
from sphinx.util.docutils import SphinxDirective
|
|
160
163
|
|
|
@@ -600,6 +600,8 @@ Updating entries based on a hash of their content (regardless of ``updated``):
|
|
|
600
600
|
|
|
601
601
|
Decision to ignore feed.updated when updating feeds: :issue:`231`.
|
|
602
602
|
|
|
603
|
+
:ref:`scheduled` (initial design + postponed features): :issue:`322`.
|
|
604
|
+
|
|
603
605
|
|
|
604
606
|
.. _deleting entries:
|
|
605
607
|
|
|
@@ -713,10 +715,18 @@ OPML support
|
|
|
713
715
|
Thoughts on dynamic lists of feeds: :issue:`165#issuecomment-905893420`.
|
|
714
716
|
|
|
715
717
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
+
Duplicate entries
|
|
719
|
+
~~~~~~~~~~~~~~~~~
|
|
720
|
+
|
|
721
|
+
Duplicate entries are mainly handled by the :mod:`reader.entry_dedupe` plugin.
|
|
722
|
+
|
|
723
|
+
* Using MinHash to speed up similarity checks (maybe): https://gist.github.com/lemon24/b9af5ade919713406bda9603847d32e5
|
|
718
724
|
|
|
719
|
-
|
|
725
|
+
However, it is also possible for a feed to have two entries with the same id
|
|
726
|
+
– yes, even though in most (if not all) formats,
|
|
727
|
+
the id is meant to be *universally unique*!
|
|
728
|
+
As of 3.13, we do not support multiple entries with the same id
|
|
729
|
+
(the first entry wins); see :issue:`335` for thoughts on this.
|
|
720
730
|
|
|
721
731
|
|
|
722
732
|
REST API
|
|
@@ -36,8 +36,7 @@ The default (and currently only) storage uses SQLite,
|
|
|
36
36
|
so the path behaves like the ``database`` argument of :func:`sqlite3.connect`:
|
|
37
37
|
|
|
38
38
|
* If the database does not exist, it will be created automatically.
|
|
39
|
-
* You can pass ``
|
|
40
|
-
the data will disappear when the reader is closed.
|
|
39
|
+
* You can pass ``':memory:'`` to use a :ref:`temporary database <temp>`.
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
.. _lifecycle:
|
|
@@ -93,11 +92,18 @@ or if the thread was not created through the :mod:`threading` module
|
|
|
93
92
|
when garbage-collected).
|
|
94
93
|
|
|
95
94
|
|
|
95
|
+
.. _temp:
|
|
96
|
+
|
|
96
97
|
Temporary databases
|
|
97
98
|
~~~~~~~~~~~~~~~~~~~
|
|
98
99
|
|
|
100
|
+
With the default SQLite storage,
|
|
101
|
+
you can use an `in-memory`_ (or `temporary`_) database
|
|
102
|
+
by using ``':memory:'`` (or ``''``) as the database path;
|
|
103
|
+
the data will disappear when the reader is closed.
|
|
104
|
+
|
|
99
105
|
To maximize the usefulness of temporary databases,
|
|
100
|
-
the
|
|
106
|
+
the connection is closed (and the data discarded)
|
|
101
107
|
only when calling :meth:`~Reader.close`,
|
|
102
108
|
not when using the reader as a context manager.
|
|
103
109
|
The reader cannot be reused after calling :meth:`~Reader.close`.
|
|
@@ -127,6 +133,10 @@ since each connection would be to a *different* database::
|
|
|
127
133
|
reader.exceptions.StorageError: usage error: cannot use a private database from threads other than the creating thread
|
|
128
134
|
|
|
129
135
|
|
|
136
|
+
.. _in-memory: https://sqlite.org/inmemorydb.html
|
|
137
|
+
.. _temporary: https://sqlite.org/inmemorydb.html#temp_db
|
|
138
|
+
|
|
139
|
+
|
|
130
140
|
.. _backups:
|
|
131
141
|
|
|
132
142
|
Back-ups
|
|
@@ -207,42 +217,96 @@ use :meth:`~Reader.delete_feed`::
|
|
|
207
217
|
Updating feeds
|
|
208
218
|
--------------
|
|
209
219
|
|
|
210
|
-
To retrieve the latest version of a feed, along with any new entries,
|
|
211
|
-
it must be updated.
|
|
212
220
|
You can update all the feeds by using the :meth:`~Reader.update_feeds` method::
|
|
213
221
|
|
|
214
222
|
>>> reader.update_feeds()
|
|
215
223
|
>>> reader.get_feed(feed)
|
|
216
224
|
Feed(url='http://www.hellointernet.fm/podcast?format=rss', updated=datetime.datetime(2020, 2, 28, 9, 34, 2, tzinfo=datetime.timezone.utc), title='Hello Internet', ...)
|
|
217
225
|
|
|
218
|
-
|
|
219
226
|
To retrive feeds in parallel, use the ``workers`` flag::
|
|
220
227
|
|
|
221
228
|
>>> reader.update_feeds(workers=10)
|
|
222
229
|
|
|
223
|
-
|
|
224
|
-
You can also update a specific feed using :meth:`~Reader.update_feed`::
|
|
230
|
+
You can update a single feed using :meth:`~Reader.update_feed`::
|
|
225
231
|
|
|
226
232
|
>>> reader.update_feed("http://www.hellointernet.fm/podcast?format=rss")
|
|
233
|
+
UpdatedFeed(url='http://www.hellointernet.fm/podcast?format=rss', new=100, modified=0, unmodified=0)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
Saving bandwidth
|
|
237
|
+
~~~~~~~~~~~~~~~~
|
|
238
|
+
|
|
239
|
+
If supported by the server,
|
|
240
|
+
*reader* uses the `ETag and Last-Modified headers`_
|
|
241
|
+
to get the entire content of a feed only if it changed.
|
|
242
|
+
|
|
243
|
+
.. important::
|
|
244
|
+
|
|
245
|
+
If you prevent *reader* from saving feed state between updates
|
|
246
|
+
(e.g. by using a :ref:`temporary database <temp>`,
|
|
247
|
+
or by deleting the database or feeds every time),
|
|
248
|
+
you will repeatedly download feeds that have not changed.
|
|
249
|
+
This wastes your bandwidth and the publisher's bandwidth,
|
|
250
|
+
and the publisher may ban you from accessing their server.
|
|
251
|
+
|
|
252
|
+
Even so, you should not update feeds *too* often;
|
|
253
|
+
every hour seems reasonable.
|
|
254
|
+
To update newly-added feeds as soon as they are added,
|
|
255
|
+
you can call :meth:`update_feeds(new=True) <Reader.update_feeds>`
|
|
256
|
+
more often (e.g. every minute).
|
|
257
|
+
|
|
258
|
+
.. seealso::
|
|
259
|
+
|
|
260
|
+
The :ref:`cli-update` section of :doc:`cli`
|
|
261
|
+
for an example of how to do this using cron.
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
.. _ETag and Last-Modified headers: https://feedparser.readthedocs.io/en/latest/http-etag.html
|
|
265
|
+
|
|
227
266
|
|
|
228
|
-
|
|
229
|
-
to only retrieve feeds if they changed
|
|
230
|
-
(`details <https://feedparser.readthedocs.io/en/latest/http-etag.html>`_).
|
|
231
|
-
Even so, you should not update feeds *too* often,
|
|
232
|
-
to avoid wasting the feed publisher's resources,
|
|
233
|
-
and potentially getting banned;
|
|
234
|
-
every 30 minutes seems reasonable.
|
|
267
|
+
.. _scheduled:
|
|
235
268
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
you can call this more often (e.g. every minute)::
|
|
269
|
+
Scheduled updates
|
|
270
|
+
~~~~~~~~~~~~~~~~~
|
|
239
271
|
|
|
240
|
-
|
|
272
|
+
Because different feeds need to be updated at different rates,
|
|
273
|
+
*reader* also provides a mechanism for scheduling updates.
|
|
241
274
|
|
|
275
|
+
Each feed has an update interval that, on every update,
|
|
276
|
+
determines when the feed should be updated next.
|
|
277
|
+
Running :meth:`update_feeds(scheduled=True) <Reader.update_feeds>`
|
|
278
|
+
updates only the feeds that should be updated at or before the current time.
|
|
279
|
+
|
|
280
|
+
The global and per-feed update interval can be **configured by the user**
|
|
281
|
+
via the ``.reader.update`` global/feed tag;
|
|
282
|
+
the default interval is of one hour;
|
|
283
|
+
see :data:`~reader.types.UpdateConfig` for the schema.
|
|
284
|
+
In addition to the interval, the user can specify a jitter;
|
|
285
|
+
for an interval of 24 hours, a jitter of 0.25 means
|
|
286
|
+
the update will occur any time in the first 6 hours of the interval.
|
|
287
|
+
|
|
288
|
+
.. note::
|
|
289
|
+
|
|
290
|
+
As of |version|, there is no way to specify a minimum update interval.
|
|
291
|
+
If you want feeds to be updated no more often than e.g. every hour,
|
|
292
|
+
you have to run :meth:`update_feeds(scheduled=True) <Reader.update_feeds>`
|
|
293
|
+
no more often than every hour.
|
|
294
|
+
Please :ref:`open an issue <issues>` if you need a minimum update interval.
|
|
295
|
+
|
|
296
|
+
In a future version of *reader*,
|
|
297
|
+
the same mechanism will be used to handle
|
|
298
|
+
HTTP 429 Too Many Requests; see :issue:`307` for details.
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
.. versionadded:: 3.13
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
Update status
|
|
305
|
+
~~~~~~~~~~~~~
|
|
242
306
|
|
|
243
307
|
If you need the status of each feed as it gets updated
|
|
244
308
|
(for instance, to update a progress bar),
|
|
245
|
-
you can use :meth:`~Reader.update_feeds_iter` instead
|
|
309
|
+
you can use :meth:`~Reader.update_feeds_iter` instead of :meth:`~Reader.update_feeds`,
|
|
246
310
|
and get a (url, updated feed or none or exception) pair for each feed::
|
|
247
311
|
|
|
248
312
|
>>> for url, value in reader.update_feeds_iter():
|
|
@@ -257,9 +321,14 @@ and get a (url, updated feed or none or exception) pair for each feed::
|
|
|
257
321
|
https://www.relay.fm/cortex/feed not modified
|
|
258
322
|
|
|
259
323
|
|
|
324
|
+
Regardless of the update method used,
|
|
325
|
+
:attr:`Feed.last_retrieved`, :attr:`~Feed.last_updated`,
|
|
326
|
+
and :attr:`~Feed.last_exception` will be set accordingly
|
|
327
|
+
(also see :ref:`errors`).
|
|
260
328
|
|
|
261
|
-
|
|
262
|
-
|
|
329
|
+
|
|
330
|
+
Disabling updates
|
|
331
|
+
~~~~~~~~~~~~~~~~~
|
|
263
332
|
|
|
264
333
|
Sometimes, it is useful to skip a feed when using :meth:`~Reader.update_feeds`;
|
|
265
334
|
for example, the feed does not exist anymore,
|
|
@@ -282,30 +351,22 @@ Getting feeds
|
|
|
282
351
|
-------------
|
|
283
352
|
|
|
284
353
|
As seen in the previous sections,
|
|
285
|
-
:meth:`~Reader.get_feed` returns a :class:`Feed` object
|
|
286
|
-
with more information about a feed::
|
|
354
|
+
:meth:`~Reader.get_feed` returns a :class:`Feed` object::
|
|
287
355
|
|
|
288
|
-
>>> from prettyprinter import pprint, install_extras;
|
|
289
|
-
>>> install_extras(include=['dataclasses'])
|
|
290
356
|
>>> feed = reader.get_feed(feed)
|
|
291
357
|
>>> pprint(feed)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
updated=datetime.datetime(
|
|
295
|
-
year=2020,
|
|
296
|
-
month=2,
|
|
297
|
-
day=28,
|
|
298
|
-
hour=9,
|
|
299
|
-
minute=34,
|
|
300
|
-
second=2,
|
|
301
|
-
tzinfo=datetime.timezone.utc
|
|
302
|
-
),
|
|
358
|
+
Feed(url='http://www.hellointernet.fm/podcast?format=rss',
|
|
359
|
+
updated=datetime.datetime(2020, 2, 28, 9, 34, 2, tzinfo=datetime.timezone.utc),
|
|
303
360
|
title='Hello Internet',
|
|
304
361
|
link='http://www.hellointernet.fm/',
|
|
305
362
|
author='CGP Grey',
|
|
363
|
+
subtitle='CGP Grey and Brady Haran talk about YouTube, life, work, whatever.',
|
|
364
|
+
version='rss20',
|
|
365
|
+
user_title=None,
|
|
306
366
|
added=datetime.datetime(2020, 10, 12, tzinfo=datetime.timezone.utc),
|
|
307
|
-
last_updated=datetime.datetime(2020, 10, 12, tzinfo=datetime.timezone.utc)
|
|
308
|
-
|
|
367
|
+
last_updated=datetime.datetime(2020, 10, 12, tzinfo=datetime.timezone.utc),
|
|
368
|
+
last_exception=None,
|
|
369
|
+
updates_enabled=True)
|
|
309
370
|
|
|
310
371
|
To get all the feeds, use the :meth:`~Reader.get_feeds` method::
|
|
311
372
|
|
|
@@ -355,7 +416,7 @@ You can get all the entries, most-recent first,
|
|
|
355
416
|
by using :meth:`~Reader.get_entries()`,
|
|
356
417
|
which generates :class:`Entry` objects::
|
|
357
418
|
|
|
358
|
-
>>> for entry
|
|
419
|
+
>>> for entry in reader.get_entries(limit=10):
|
|
359
420
|
... print(entry.feed.title, '-', entry.title)
|
|
360
421
|
...
|
|
361
422
|
Cortex - 106: Clear and Boring
|
|
@@ -370,12 +431,11 @@ Here is an example of getting entries for a single feed::
|
|
|
370
431
|
|
|
371
432
|
>>> feed.title
|
|
372
433
|
'Hello Internet'
|
|
373
|
-
>>>
|
|
374
|
-
|
|
375
|
-
... print(entry.feed.title, '-', entry.title)
|
|
434
|
+
>>> for entry in reader.get_entries(feed=feed, limit=2):
|
|
435
|
+
... print(entry.title)
|
|
376
436
|
...
|
|
377
|
-
|
|
378
|
-
|
|
437
|
+
H.I. #136: Dog Bingo
|
|
438
|
+
H.I. #135: Place Your Bets
|
|
379
439
|
|
|
380
440
|
|
|
381
441
|
|
|
@@ -385,9 +445,11 @@ Entry flags
|
|
|
385
445
|
Entries can be marked as :attr:`~Entry.read` or :attr:`~Entry.important`.
|
|
386
446
|
These flags can be used for filtering::
|
|
387
447
|
|
|
388
|
-
>>> reader.
|
|
389
|
-
>>>
|
|
390
|
-
|
|
448
|
+
>>> entry = next(reader.get_entries(feed=feed))
|
|
449
|
+
>>> entry.title
|
|
450
|
+
'H.I. #136: Dog Bingo'
|
|
451
|
+
>>> reader.mark_entry_as_read(entry)
|
|
452
|
+
>>> for entry in reader.get_entries(feed=feed, read=False, limit=2):
|
|
391
453
|
... print(entry.title)
|
|
392
454
|
...
|
|
393
455
|
H.I. #135: Place Your Bets
|
|
@@ -766,7 +828,8 @@ This applies to the following names:
|
|
|
766
828
|
* tag keys
|
|
767
829
|
* the top-level keys of dict tag values
|
|
768
830
|
|
|
769
|
-
Currently,
|
|
831
|
+
Currently, the only *reader*-reserved names
|
|
832
|
+
are used by `Scheduled updates`_ and by :ref:`built-in plugins`;
|
|
770
833
|
new ones will be documented here.
|
|
771
834
|
|
|
772
835
|
The prefixes can be changed using
|
|
@@ -894,6 +957,8 @@ depending on their type attribute or feedparser defaults:
|
|
|
894
957
|
|
|
895
958
|
|
|
896
959
|
|
|
960
|
+
.. _errors:
|
|
961
|
+
|
|
897
962
|
Errors and exceptions
|
|
898
963
|
---------------------
|
|
899
964
|
|
|
@@ -24,9 +24,7 @@ These packages will be installed automatically when installing *reader*:
|
|
|
24
24
|
* `typing-extensions`_ is used for :mod:`typing` backports.
|
|
25
25
|
|
|
26
26
|
*reader* also depends on the :mod:`sqlite3` standard library module
|
|
27
|
-
(at least SQLite 3.
|
|
28
|
-
To use the :ref:`full-text search <fts>` functionality,
|
|
29
|
-
at least SQLite 3.18 with the `FTS5`_ extension is required.
|
|
27
|
+
(at least SQLite 3.18 with the `JSON1`_ and `FTS5`_ extensions).
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
.. _optional dependencies:
|
|
@@ -12,6 +12,7 @@ retrieved asynchronously with `HTTPX <https://www.python-httpx.org/>`_:
|
|
|
12
12
|
Has your password been pwned? Or, how I almost failed to search a 37 GB text file in under 1 millisecond (in Python)
|
|
13
13
|
|
|
14
14
|
""" # docstring-end
|
|
15
|
+
|
|
15
16
|
import asyncio
|
|
16
17
|
import io
|
|
17
18
|
import httpx
|
|
@@ -102,7 +102,7 @@ tests = [
|
|
|
102
102
|
"pytest>=4",
|
|
103
103
|
"pytest-randomly",
|
|
104
104
|
"pytest-subtests",
|
|
105
|
-
"
|
|
105
|
+
"pytest-rerunfailures",
|
|
106
106
|
"coverage",
|
|
107
107
|
"pytest-cov",
|
|
108
108
|
"requests-mock",
|
|
@@ -148,14 +148,15 @@ dev = [
|
|
|
148
148
|
version = {attr = "reader.__version__"}
|
|
149
149
|
|
|
150
150
|
[tool.pytest.ini_options]
|
|
151
|
+
addopts = "--strict-markers"
|
|
151
152
|
markers = [
|
|
152
153
|
"slow: mark a test as slow.",
|
|
153
154
|
"requires_lxml: mark a test to only run in places where we have lxml.",
|
|
154
155
|
"apptest: mark a test as a web app test (skipped sometimes).",
|
|
156
|
+
"noautoclose: skip 'autoclose' autouse fixture for a test."
|
|
155
157
|
]
|
|
156
|
-
addopts = "--no-success-flaky-report"
|
|
157
158
|
filterwarnings = [
|
|
158
|
-
"ignore:No parser was explicitly specified::reader._html_utils",
|
|
159
|
+
"ignore:No parser was explicitly specified::reader._storage._html_utils",
|
|
159
160
|
]
|
|
160
161
|
testpaths = ["tests"]
|
|
161
162
|
|
|
@@ -180,3 +181,14 @@ module = [
|
|
|
180
181
|
"reader._vendor.feedparser.*",
|
|
181
182
|
]
|
|
182
183
|
ignore_errors = true
|
|
184
|
+
|
|
185
|
+
[tool.isort]
|
|
186
|
+
profile = "black"
|
|
187
|
+
py_version = 310
|
|
188
|
+
src_paths = ["src", "tests"]
|
|
189
|
+
extend_skip = ["examples", "src/reader/_vendor"]
|
|
190
|
+
skip_gitignore = true
|
|
191
|
+
filter_files = true
|
|
192
|
+
force_single_line = true
|
|
193
|
+
order_by_type = false
|
|
194
|
+
lines_after_imports = 2
|
|
@@ -14,14 +14,15 @@ from functools import partial
|
|
|
14
14
|
|
|
15
15
|
import click
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
root_dir = os.path.dirname(__file__)
|
|
18
19
|
sys.path.insert(0, os.path.join(root_dir, '../src'))
|
|
19
20
|
sys.path.insert(0, os.path.join(root_dir, '../tests'))
|
|
20
21
|
|
|
21
22
|
from fakeparser import Parser
|
|
22
|
-
|
|
23
23
|
from reader import make_reader
|
|
24
|
-
from reader._app import create_app
|
|
24
|
+
from reader._app import create_app
|
|
25
|
+
from reader._app import get_reader
|
|
25
26
|
from reader._config import make_reader_config
|
|
26
27
|
|
|
27
28
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Extract query timings from (stderr) log output of:
|
|
3
3
|
|
|
4
|
-
reader --debug-storage [update | update --new
|
|
4
|
+
reader --debug-storage [update | update --new | search update] --vv
|
|
5
5
|
|
|
6
6
|
Note that the timings are not entirely accurate:
|
|
7
7
|
|
|
@@ -16,6 +16,7 @@ and how often they time out.
|
|
|
16
16
|
Initially made for #175 and #178.
|
|
17
17
|
|
|
18
18
|
"""
|
|
19
|
+
|
|
19
20
|
import json
|
|
20
21
|
import re
|
|
21
22
|
import sys
|
|
@@ -8,10 +8,12 @@ from flask import jsonify
|
|
|
8
8
|
from flask import redirect
|
|
9
9
|
from flask import request
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
root_dir = os.path.dirname(__file__)
|
|
12
13
|
sys.path.insert(0, os.path.join(root_dir, '../src'))
|
|
13
14
|
|
|
14
|
-
from reader._app.api_thing import
|
|
15
|
+
from reader._app.api_thing import APIError
|
|
16
|
+
from reader._app.api_thing import APIThing
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
app = Flask(
|
|
@@ -28,8 +28,6 @@ from flask import stream_with_context
|
|
|
28
28
|
from flask import url_for
|
|
29
29
|
|
|
30
30
|
import reader
|
|
31
|
-
from .api_thing import APIError
|
|
32
|
-
from .api_thing import APIThing
|
|
33
31
|
from reader import Content
|
|
34
32
|
from reader import Entry
|
|
35
33
|
from reader import EntrySearchResult
|
|
@@ -40,6 +38,9 @@ from reader._plugins import Loader
|
|
|
40
38
|
from reader.types import _get_entry_content
|
|
41
39
|
from reader.types import TristateFilterInput
|
|
42
40
|
|
|
41
|
+
from .api_thing import APIError
|
|
42
|
+
from .api_thing import APIThing
|
|
43
|
+
|
|
43
44
|
|
|
44
45
|
blueprint = Blueprint('reader', __name__)
|
|
45
46
|
|
|
@@ -491,9 +492,11 @@ def feeds():
|
|
|
491
492
|
(
|
|
492
493
|
feed,
|
|
493
494
|
list(reader.get_tag_keys(feed)),
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
(
|
|
496
|
+
reader.get_entry_counts(feed=feed)
|
|
497
|
+
if with_counts or sort in FEED_SORT_FANCY
|
|
498
|
+
else None
|
|
499
|
+
),
|
|
497
500
|
)
|
|
498
501
|
for feed in feeds
|
|
499
502
|
)
|