reader 3.12__tar.gz → 3.14__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.14}/CHANGES.rst +88 -1
- {reader-3.12/src/reader.egg-info → reader-3.14}/PKG-INFO +6 -5
- {reader-3.12 → reader-3.14}/docs/api.rst +3 -0
- {reader-3.12 → reader-3.14}/docs/cli.rst +4 -2
- {reader-3.12 → reader-3.14}/docs/conf.py +3 -0
- {reader-3.12 → reader-3.14}/docs/dev.rst +15 -3
- {reader-3.12 → reader-3.14}/docs/guide.rst +114 -49
- {reader-3.12 → reader-3.14}/docs/install.rst +1 -3
- {reader-3.12 → reader-3.14}/docs/internal.rst +11 -3
- reader-3.14/examples/custom_headers.py +50 -0
- {reader-3.12 → reader-3.14}/examples/parser_only.py +2 -1
- {reader-3.12 → reader-3.14}/examples/terminal.py +1 -0
- {reader-3.12 → reader-3.14}/pyproject.toml +21 -7
- {reader-3.12 → reader-3.14}/scripts/bench.py +3 -2
- {reader-3.12 → reader-3.14}/scripts/debug_storage_stats.py +2 -1
- {reader-3.12 → reader-3.14}/scripts/generate_import_all.py +1 -0
- {reader-3.12 → reader-3.14}/scripts/jscontrols.py +3 -1
- {reader-3.12 → reader-3.14}/src/reader/__init__.py +3 -1
- {reader-3.12 → reader-3.14}/src/reader/__main__.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/__init__.py +27 -8
- {reader-3.12 → reader-3.14}/src/reader/_app/api_thing.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/cli.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/wsgi.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_cli.py +15 -5
- {reader-3.12 → reader-3.14}/src/reader/_config.py +3 -1
- {reader-3.12 → reader-3.14}/src/reader/_hash_utils.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_parser/__init__.py +2 -7
- {reader-3.12 → reader-3.14}/src/reader/_parser/_http_utils.py +2 -0
- {reader-3.12 → reader-3.14}/src/reader/_parser/_lazy.py +25 -26
- {reader-3.12 → reader-3.14}/src/reader/_parser/_url_utils.py +16 -4
- {reader-3.12 → reader-3.14}/src/reader/_parser/feedparser.py +1 -1
- {reader-3.12 → reader-3.14}/src/reader/_parser/file.py +1 -2
- {reader-3.12 → reader-3.14}/src/reader/_parser/http.py +1 -2
- {reader-3.12 → reader-3.14}/src/reader/_parser/requests/__init__.py +5 -3
- {reader-3.12 → reader-3.14}/src/reader/_plugins/cli_status.py +70 -46
- reader-3.14/src/reader/_plugins/enclosure_tags.py +166 -0
- {reader-3.12 → reader-3.14}/src/reader/_plugins/preview_feed_list.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_plugins/share.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_plugins/sqlite_releases.py +2 -0
- {reader-3.12 → reader-3.14}/src/reader/_plugins/timer.py +2 -1
- {reader-3.12 → reader-3.14}/src/reader/_storage/_base.py +6 -4
- {reader-3.12 → reader-3.14}/src/reader/_storage/_changes.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_storage/_entries.py +34 -22
- {reader-3.12 → reader-3.14}/src/reader/_storage/_feeds.py +46 -87
- {reader-3.12 → reader-3.14}/src/reader/_storage/_html_utils.py +2 -0
- {reader-3.12 → reader-3.14}/src/reader/_storage/_schema.py +11 -2
- {reader-3.12 → reader-3.14}/src/reader/_storage/_search.py +36 -99
- {reader-3.12 → reader-3.14}/src/reader/_storage/_sql_utils.py +3 -0
- {reader-3.12 → reader-3.14}/src/reader/_storage/_sqlite_utils.py +5 -3
- {reader-3.12 → reader-3.14}/src/reader/_storage/_tags.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/_types.py +56 -35
- {reader-3.12 → reader-3.14}/src/reader/_update.py +196 -155
- {reader-3.12 → reader-3.14}/src/reader/_utils.py +3 -1
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/__init__.py +29 -10
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/api.py +106 -135
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/__init__.py +3 -2
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/asctime.py +26 -17
- reader-3.14/src/reader/_vendor/feedparser/datetimes/greek.py +90 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/hungarian.py +19 -25
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/iso8601.py +62 -52
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/korean.py +35 -24
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/perforce.py +21 -4
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/rfc822.py +52 -23
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +36 -22
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/encodings.py +215 -147
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/exceptions.py +6 -6
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/html.py +93 -84
- reader-3.14/src/reader/_vendor/feedparser/http.py +78 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/mixin.py +330 -275
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/_base.py +210 -166
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/admin.py +10 -10
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/cc.py +20 -19
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/dc.py +21 -17
- reader-3.14/src/reader/_vendor/feedparser/namespaces/georss.py +683 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/itunes.py +25 -21
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/mediarss.py +37 -36
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/psc.py +13 -13
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/parsers/json.py +54 -52
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/parsers/loose.py +27 -29
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/parsers/strict.py +34 -28
- reader-3.14/src/reader/_vendor/feedparser/sanitizer.py +974 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/sgml.py +17 -17
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/urls.py +91 -72
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/util.py +40 -40
- {reader-3.12 → reader-3.14}/src/reader/core.py +64 -8
- {reader-3.12 → reader-3.14}/src/reader/exceptions.py +3 -2
- {reader-3.12 → reader-3.14}/src/reader/plugins/__init__.py +2 -0
- {reader-3.12 → reader-3.14}/src/reader/plugins/enclosure_dedupe.py +1 -0
- {reader-3.12 → reader-3.14}/src/reader/plugins/entry_dedupe.py +45 -9
- {reader-3.12 → reader-3.14}/src/reader/plugins/mark_as_read.py +29 -0
- {reader-3.12 → reader-3.14}/src/reader/plugins/readtime.py +2 -0
- {reader-3.12 → reader-3.14}/src/reader/plugins/ua_fallback.py +2 -0
- {reader-3.12 → reader-3.14}/src/reader/types.py +72 -22
- {reader-3.12 → reader-3.14/src/reader.egg-info}/PKG-INFO +6 -5
- {reader-3.12 → reader-3.14}/src/reader.egg-info/SOURCES.txt +2 -0
- {reader-3.12 → reader-3.14}/src/reader.egg-info/requires.txt +3 -3
- {reader-3.12 → reader-3.14}/tests/conftest.py +7 -24
- {reader-3.12 → reader-3.14}/tests/test_app.py +3 -2
- {reader-3.12 → reader-3.14}/tests/test_bench.py +4 -7
- {reader-3.12 → reader-3.14}/tests/test_cli.py +18 -2
- {reader-3.12 → reader-3.14}/tests/test_lazy_imports.py +2 -1
- {reader-3.12 → reader-3.14}/tests/test_parser.py +7 -4
- reader-3.14/tests/test_plugins_cli_status.py +105 -0
- {reader-3.12 → reader-3.14}/tests/test_plugins_enclosure_dedupe.py +1 -2
- {reader-3.12 → reader-3.14}/tests/test_plugins_entry_dedupe.py +41 -7
- {reader-3.12 → reader-3.14}/tests/test_plugins_mark_as_read.py +47 -0
- {reader-3.12 → reader-3.14}/tests/test_reader.py +432 -67
- {reader-3.12 → reader-3.14}/tests/test_reader_context.py +3 -3
- {reader-3.12 → reader-3.14}/tests/test_reader_counts.py +44 -6
- {reader-3.12 → reader-3.14}/tests/test_reader_deprecations.py +1 -0
- {reader-3.12 → reader-3.14}/tests/test_reader_filter.py +7 -4
- {reader-3.12 → reader-3.14}/tests/test_reader_integration.py +27 -4
- {reader-3.12 → reader-3.14}/tests/test_reader_search.py +8 -8
- {reader-3.12 → reader-3.14}/tests/test_reader_sort.py +1 -0
- reader-3.14/tests/test_reader_update.py +163 -0
- {reader-3.12 → reader-3.14}/tests/test_search.py +3 -4
- {reader-3.12 → reader-3.14}/tests/test_storage.py +11 -14
- {reader-3.12 → reader-3.14}/tests/test_test_utils.py +5 -1
- {reader-3.12 → reader-3.14}/tests/test_utils.py +1 -0
- {reader-3.12 → reader-3.14}/tests/utils.py +9 -4
- {reader-3.12 → reader-3.14}/tox.ini +3 -3
- reader-3.12/src/reader/_plugins/enclosure_tags.py +0 -117
- reader-3.12/src/reader/_vendor/feedparser/datetimes/greek.py +0 -86
- reader-3.12/src/reader/_vendor/feedparser/http.py +0 -227
- reader-3.12/src/reader/_vendor/feedparser/namespaces/georss.py +0 -276
- reader-3.12/src/reader/_vendor/feedparser/sanitizer.py +0 -950
- reader-3.12/tests/test_plugins_cli_status.py +0 -26
- {reader-3.12 → reader-3.14}/LICENSE +0 -0
- {reader-3.12 → reader-3.14}/MANIFEST.in +0 -0
- {reader-3.12 → reader-3.14}/README.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/Makefile +0 -0
- {reader-3.12 → reader-3.14}/docs/app.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/changelog.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/config.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/contributing.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/dev-app.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/images/redesign-01.png +0 -0
- {reader-3.12 → reader-3.14}/docs/index.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/make.bat +0 -0
- {reader-3.12 → reader-3.14}/docs/plugins.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/dillo.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/entries-feed.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/entries.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/entry-one.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/entry-two.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/feeds.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/lynx.png +0 -0
- {reader-3.12 → reader-3.14}/docs/screenshots/search.png +0 -0
- {reader-3.12 → reader-3.14}/docs/tutorial.rst +0 -0
- {reader-3.12 → reader-3.14}/docs/why.rst +0 -0
- {reader-3.12 → reader-3.14}/examples/config.yaml +0 -0
- {reader-3.12 → reader-3.14}/examples/podcast.py +0 -0
- {reader-3.12 → reader-3.14}/run.sh +0 -0
- {reader-3.12 → reader-3.14}/scripts/.coverage +0 -0
- {reader-3.12 → reader-3.14}/scripts/backup.sh +0 -0
- {reader-3.12 → reader-3.14}/scripts/jscontrols.html +0 -0
- {reader-3.12 → reader-3.14}/scripts/lines.sh +0 -0
- {reader-3.12 → reader-3.14}/scripts/release.py +0 -0
- {reader-3.12 → reader-3.14}/setup.cfg +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/static/controls.js +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/static/style.css +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/add_entry.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/entries.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/entry.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/macros.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/metadata.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_app/templates/tags.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_parser/jsonfeed.py +1 -1
- {reader-3.12 → reader-3.14}/src/reader/_parser/requests/_lazy.py +2 -2
- {reader-3.12 → reader-3.14}/src/reader/_plugins/__init__.py +1 -1
- {reader-3.12 → reader-3.14}/src/reader/_plugins/templates/preview_feed_list.html +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_storage/__init__.py +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.12 → reader-3.14}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.12 → reader-3.14}/src/reader/py.typed +0 -0
- {reader-3.12 → reader-3.14}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.12 → reader-3.14}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.12 → reader-3.14}/tests/data/10.json +0 -0
- {reader-3.12 → reader-3.14}/tests/data/10.json.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/custom +0 -0
- {reader-3.12 → reader-3.14}/tests/data/empty.atom +0 -0
- {reader-3.12 → reader-3.14}/tests/data/empty.atom.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/empty.json +0 -0
- {reader-3.12 → reader-3.14}/tests/data/empty.json.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/empty.rss +0 -0
- {reader-3.12 → reader-3.14}/tests/data/empty.rss.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/full.atom +0 -0
- {reader-3.12 → reader-3.14}/tests/data/full.atom.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/full.json +0 -0
- {reader-3.12 → reader-3.14}/tests/data/full.json.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/full.rss +0 -0
- {reader-3.12 → reader-3.14}/tests/data/full.rss.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/invalid.json +0 -0
- {reader-3.12 → reader-3.14}/tests/data/invalid.json.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/relative.atom +0 -0
- {reader-3.12 → reader-3.14}/tests/data/relative.atom.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/relative.rss +0 -0
- {reader-3.12 → reader-3.14}/tests/data/relative.rss.py +0 -0
- {reader-3.12 → reader-3.14}/tests/data/sqlite_releases.html +0 -0
- {reader-3.12 → reader-3.14}/tests/data/unknown.json +0 -0
- {reader-3.12 → reader-3.14}/tests/data/unknown.json.py +0 -0
- {reader-3.12 → reader-3.14}/tests/fakeparser.py +0 -0
- {reader-3.12 → reader-3.14}/tests/reader_methods.py +0 -0
- {reader-3.12 → reader-3.14}/tests/reader_test_plugins/__init__.py +0 -0
- {reader-3.12 → reader-3.14}/tests/reader_test_plugins/good.py +0 -0
- {reader-3.12 → reader-3.14}/tests/reader_test_plugins/init_error.py +0 -0
- {reader-3.12 → reader-3.14}/tests/reader_test_plugins/missing_dependency.py +0 -0
- {reader-3.12 → reader-3.14}/tests/reader_test_plugins/missing_entry_point.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test__types.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_app_wsgi.py +1 -1
- {reader-3.12 → reader-3.14}/tests/test_changes.py +1 -1
- {reader-3.12 → reader-3.14}/tests/test_config.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_exceptions.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_hash_utils.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_html_utils.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_plugins_preview_feed_list.py +2 -2
- {reader-3.12 → reader-3.14}/tests/test_plugins_readtime.py +3 -3
- {reader-3.12 → reader-3.14}/tests/test_plugins_sqlite_releases.py +1 -1
- {reader-3.12 → reader-3.14}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_reader_hooks.py +4 -4
- {reader-3.12 → reader-3.14}/tests/test_reader_plugins.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_reader_private.py +3 -3
- {reader-3.12 → reader-3.14}/tests/test_sql_utils.py +0 -0
- {reader-3.12 → reader-3.14}/tests/test_sqlite_utils.py +1 -1
- {reader-3.12 → reader-3.14}/tests/test_tags.py +2 -2
- {reader-3.12 → reader-3.14}/tests/test_types.py +0 -0
|
@@ -6,6 +6,93 @@ Changelog
|
|
|
6
6
|
:noindex:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.14
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2024-07-23
|
|
13
|
+
|
|
14
|
+
* Add :attr:`~EntryCounts.unimportant` attribute to
|
|
15
|
+
:class:`EntryCounts` and :class:`EntrySearchCounts`.
|
|
16
|
+
Thanks to `chenthur`_ for the pull request.
|
|
17
|
+
(:issue:`283`)
|
|
18
|
+
* Fix bug in the :mod:`~reader.plugins.entry_dedupe` plugin causing entries
|
|
19
|
+
to flip-flop if there were multiple *new* duplicates of the same issue
|
|
20
|
+
(on the first update, one entry remains, on the second update, the other);
|
|
21
|
+
related to the bug fixed in `version 3.2 <Version 3.2_>`_.
|
|
22
|
+
(:issue:`340`)
|
|
23
|
+
|
|
24
|
+
* :mod:`~reader._plugins.enclosure_tags` plugin improvements. (:issue:`344`)
|
|
25
|
+
|
|
26
|
+
* Set genre to `Podcast` if the feed has any tag containing "podcast".
|
|
27
|
+
* Rewrite tags on the fly,
|
|
28
|
+
instead of downloading the entire file before sending it to the user;
|
|
29
|
+
allows browsers to display accurate download progress.
|
|
30
|
+
* Prefer feed user title to feed title if available.
|
|
31
|
+
* Use feed title as artist, instead of author.
|
|
32
|
+
|
|
33
|
+
* Support Python 3.13. (:issue:`341`)
|
|
34
|
+
* Update vendored `feedparser`_ to the ``develop`` branch as of 2024-06-26.
|
|
35
|
+
Needed because upstream removed dependency on stdlib module ``cgi``
|
|
36
|
+
(removed in Python 3.13), but the `version 2.9 <Version 2.9_>`_
|
|
37
|
+
memory usage reduction was not released upstream yet.
|
|
38
|
+
(:issue:`341`)
|
|
39
|
+
|
|
40
|
+
.. _chenthur: https://github.com/chenthur
|
|
41
|
+
.. _feedparser: https://feedparser.readthedocs.io/en/latest/
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Version 3.13
|
|
45
|
+
------------
|
|
46
|
+
|
|
47
|
+
Released 2024-06-19
|
|
48
|
+
|
|
49
|
+
* Allow different feed update intervals; see :ref:`scheduled` for details.
|
|
50
|
+
(:issue:`332`)
|
|
51
|
+
|
|
52
|
+
* Add ``scheduled`` argument to :meth:`~Reader.update_feeds()`,
|
|
53
|
+
:meth:`~Reader.get_feeds()`, etc.
|
|
54
|
+
|
|
55
|
+
* Add :attr:`Feed.update_after` and :attr:`~Feed.last_retrieved` attributes.
|
|
56
|
+
|
|
57
|
+
* The ``new`` filter of :meth:`~Reader.update_feeds()` etc. considers
|
|
58
|
+
a feed new if it was never retrieved (:attr:`~Feed.last_retrieved`),
|
|
59
|
+
not if it was never updated successfully (:attr:`~Feed.last_updated`).
|
|
60
|
+
|
|
61
|
+
* Update an entry whenever its :attr:`~Entry.updated` changes
|
|
62
|
+
(don't require it to be newer).
|
|
63
|
+
|
|
64
|
+
* Fix bug introduced in `version 3.12 <Version 3.12_>`_ causing an assertion error
|
|
65
|
+
when there are multiple entries with the same id in the same feed,
|
|
66
|
+
or when parallel :meth:`~Reader.update_feeds` calls add the same entry.
|
|
67
|
+
The fix restores the pre-3.12 first-entry-wins / last-write-wins behavior.
|
|
68
|
+
Thanks to `Joakim Hellsén`_ for reporting and helping debug this issue.
|
|
69
|
+
(:issue:`335`)
|
|
70
|
+
|
|
71
|
+
* Fix assertion error when an entry is deleted while being updated.
|
|
72
|
+
|
|
73
|
+
* Allow re-running the :mod:`~reader.plugins.mark_as_read` plugin for existing entries.
|
|
74
|
+
Thanks to `Michael Han`_ for the pull request.
|
|
75
|
+
(:issue:`317`)
|
|
76
|
+
|
|
77
|
+
* Other changes for scheduled updates. (:issue:`332`)
|
|
78
|
+
|
|
79
|
+
* Add ``--scheduled`` flag to the ``update`` command.
|
|
80
|
+
* The :mod:`~reader._plugins.cli_status` plugin now
|
|
81
|
+
records the output of multiple runs instead of just the last one,
|
|
82
|
+
with output from the same hour grouped in a single entry,
|
|
83
|
+
up to 24 entries/hours.
|
|
84
|
+
* Group mutually-exclusive attributes of :class:`~.FeedUpdateIntent`
|
|
85
|
+
into a new :attr:`~.FeedUpdateIntent.value` union attribute.
|
|
86
|
+
|
|
87
|
+
* New and improved :ref:`update` user guide section.
|
|
88
|
+
* Rename ``update --new-only`` CLI flag to ``--new``;
|
|
89
|
+
``--new-only`` remains available as an alias.
|
|
90
|
+
(:issue:`334`)
|
|
91
|
+
|
|
92
|
+
.. _Joakim Hellsén: https://github.com/TheLovinator1
|
|
93
|
+
.. _Michael Han: https://github.com/Sakunam
|
|
94
|
+
|
|
95
|
+
|
|
9
96
|
Version 3.12
|
|
10
97
|
------------
|
|
11
98
|
|
|
@@ -289,7 +376,7 @@ Released 2022-09-14
|
|
|
289
376
|
became optional.
|
|
290
377
|
(:issue:`96`)
|
|
291
378
|
* Fix bug in :mod:`~reader.plugins.entry_dedupe` causing updates to fail
|
|
292
|
-
if there were multiple *new* duplicates of the same
|
|
379
|
+
if there were multiple *new* duplicates of the same entry.
|
|
293
380
|
(:issue:`292`)
|
|
294
381
|
* Fix bug in :mod:`~reader.plugins.readtime`
|
|
295
382
|
and :mod:`~reader.plugins.mark_as_read` causing updates to fail
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.14
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
27
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
28
|
Classifier: Topic :: Internet
|
|
@@ -61,15 +62,15 @@ Provides-Extra: tests
|
|
|
61
62
|
Requires-Dist: pytest>=4; extra == "tests"
|
|
62
63
|
Requires-Dist: pytest-randomly; extra == "tests"
|
|
63
64
|
Requires-Dist: pytest-subtests; extra == "tests"
|
|
64
|
-
Requires-Dist:
|
|
65
|
+
Requires-Dist: pytest-rerunfailures; extra == "tests"
|
|
65
66
|
Requires-Dist: coverage; extra == "tests"
|
|
66
67
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
67
68
|
Requires-Dist: requests-mock; extra == "tests"
|
|
68
|
-
Requires-Dist: mechanicalsoup; (implementation_name != "pypy"
|
|
69
|
+
Requires-Dist: mechanicalsoup; (implementation_name != "pypy" and python_version <= "3.12") and extra == "tests"
|
|
69
70
|
Requires-Dist: requests-wsgi-adapter; extra == "tests"
|
|
70
|
-
Requires-Dist: lxml; (implementation_name != "pypy"
|
|
71
|
+
Requires-Dist: lxml; (implementation_name != "pypy" and python_version <= "3.12") and extra == "tests"
|
|
71
72
|
Requires-Dist: html5lib; extra == "tests"
|
|
72
|
-
Requires-Dist: numpy; (implementation_name != "pypy" and os_name == "posix") and extra == "tests"
|
|
73
|
+
Requires-Dist: numpy; (implementation_name != "pypy" and os_name == "posix" and python_version <= "3.12") and extra == "tests"
|
|
73
74
|
Requires-Dist: mypy; implementation_name != "pypy" and extra == "tests"
|
|
74
75
|
Requires-Dist: types-requests; extra == "tests"
|
|
75
76
|
Requires-Dist: types-beautifulsoup4; 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,20 @@ 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
|
|
724
|
+
* Discussion of unifying "on-line" dedupe (after an entry is added/updated),
|
|
725
|
+
and "on-demand" dedupe (backfill): :issue:`340`.
|
|
718
726
|
|
|
719
|
-
|
|
727
|
+
However, it is also possible for a feed to have two entries with the same id
|
|
728
|
+
– yes, even though in most (if not all) formats,
|
|
729
|
+
the id is meant to be *universally unique*!
|
|
730
|
+
As of 3.13, we do not support multiple entries with the same id
|
|
731
|
+
(the first entry wins); see :issue:`335` for thoughts on this.
|
|
720
732
|
|
|
721
733
|
|
|
722
734
|
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:
|
|
@@ -202,6 +202,9 @@ Data objects
|
|
|
202
202
|
.. autoclass:: FeedUpdateIntent
|
|
203
203
|
:members:
|
|
204
204
|
|
|
205
|
+
.. autoclass:: FeedToUpdate
|
|
206
|
+
:members:
|
|
207
|
+
|
|
205
208
|
.. autoclass:: EntryUpdateIntent
|
|
206
209
|
:members:
|
|
207
210
|
|
|
@@ -216,9 +219,14 @@ Type aliases
|
|
|
216
219
|
Recipes
|
|
217
220
|
-------
|
|
218
221
|
|
|
219
|
-
.. include:: ../examples/
|
|
222
|
+
.. include:: ../examples/custom_headers.py
|
|
220
223
|
:start-after: """
|
|
221
|
-
:end-before: """
|
|
224
|
+
:end-before: """
|
|
225
|
+
.. literalinclude:: ../examples/custom_headers.py
|
|
226
|
+
:start-at: import
|
|
222
227
|
|
|
228
|
+
.. include:: ../examples/parser_only.py
|
|
229
|
+
:start-after: """
|
|
230
|
+
:end-before: """
|
|
223
231
|
.. literalinclude:: ../examples/parser_only.py
|
|
224
|
-
:start-
|
|
232
|
+
:start-at: import
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adding custom headers when retrieving feeds
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Example of adding custom request headers with :attr:`.SessionFactory.request_hooks`:
|
|
6
|
+
|
|
7
|
+
.. code-block:: console
|
|
8
|
+
|
|
9
|
+
$ python examples/custom_headers.py
|
|
10
|
+
updating...
|
|
11
|
+
server: Hello, world!
|
|
12
|
+
updated!
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# fmt: off
|
|
17
|
+
# flake8: noqa
|
|
18
|
+
|
|
19
|
+
import http.server
|
|
20
|
+
import threading
|
|
21
|
+
from reader import make_reader
|
|
22
|
+
|
|
23
|
+
# start a background server that logs the received header
|
|
24
|
+
|
|
25
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
26
|
+
def log_message(self, *_): pass
|
|
27
|
+
def do_GET(self):
|
|
28
|
+
print("server:", self.headers.get('my-header'))
|
|
29
|
+
self.send_error(304)
|
|
30
|
+
|
|
31
|
+
server = http.server.HTTPServer(('localhost', 8080), Handler)
|
|
32
|
+
threading.Thread(target=server.handle_request).start()
|
|
33
|
+
|
|
34
|
+
# create a reader object
|
|
35
|
+
|
|
36
|
+
reader = make_reader(':memory:')
|
|
37
|
+
reader.add_feed('http://localhost:8080')
|
|
38
|
+
|
|
39
|
+
# set up a hook that adds the header to each request
|
|
40
|
+
|
|
41
|
+
def hook(session, request, **kwargs):
|
|
42
|
+
request.headers.setdefault('my-header', 'Hello, world!')
|
|
43
|
+
|
|
44
|
+
reader._parser.session_factory.request_hooks.append(hook)
|
|
45
|
+
|
|
46
|
+
# updating the feed sends the modified request to the server
|
|
47
|
+
|
|
48
|
+
print("updating...")
|
|
49
|
+
reader.update_feeds()
|
|
50
|
+
print("updated!")
|
|
@@ -11,7 +11,8 @@ retrieved asynchronously with `HTTPX <https://www.python-httpx.org/>`_:
|
|
|
11
11
|
death and gravity
|
|
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
|
+
"""
|
|
15
|
+
|
|
15
16
|
import asyncio
|
|
16
17
|
import io
|
|
17
18
|
import httpx
|
|
@@ -26,6 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: 3.10",
|
|
27
27
|
"Programming Language :: Python :: 3.11",
|
|
28
28
|
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
29
30
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
30
31
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
31
32
|
"Topic :: Internet",
|
|
@@ -102,19 +103,20 @@ tests = [
|
|
|
102
103
|
"pytest>=4",
|
|
103
104
|
"pytest-randomly",
|
|
104
105
|
"pytest-subtests",
|
|
105
|
-
"
|
|
106
|
+
"pytest-rerunfailures",
|
|
106
107
|
"coverage",
|
|
107
108
|
"pytest-cov",
|
|
108
109
|
"requests-mock",
|
|
109
110
|
# mechanicalsoup hard-depends on lxml (see below)
|
|
110
|
-
'mechanicalsoup; (implementation_name != "pypy"
|
|
111
|
+
'mechanicalsoup; (implementation_name != "pypy" and python_version <= "3.12")',
|
|
111
112
|
"requests-wsgi-adapter",
|
|
112
113
|
# we want to test search with all known bs4 parsers.
|
|
113
|
-
# lxml does not have
|
|
114
|
-
|
|
114
|
+
# lxml usually does not have recent PyPy wheels.
|
|
115
|
+
# lxml usually does not have pre-relase CPython wheels.
|
|
116
|
+
'lxml; (implementation_name != "pypy" and python_version <= "3.12")',
|
|
115
117
|
"html5lib",
|
|
116
118
|
# for bench.py
|
|
117
|
-
'numpy; (implementation_name != "pypy" and os_name == "posix")',
|
|
119
|
+
'numpy; (implementation_name != "pypy" and os_name == "posix" and python_version <= "3.12")',
|
|
118
120
|
# mypy does not work on pypy (yet).
|
|
119
121
|
'mypy; implementation_name != "pypy"',
|
|
120
122
|
"types-requests",
|
|
@@ -148,14 +150,15 @@ dev = [
|
|
|
148
150
|
version = {attr = "reader.__version__"}
|
|
149
151
|
|
|
150
152
|
[tool.pytest.ini_options]
|
|
153
|
+
addopts = "--strict-markers"
|
|
151
154
|
markers = [
|
|
152
155
|
"slow: mark a test as slow.",
|
|
153
156
|
"requires_lxml: mark a test to only run in places where we have lxml.",
|
|
154
157
|
"apptest: mark a test as a web app test (skipped sometimes).",
|
|
158
|
+
"noautoclose: skip 'autoclose' autouse fixture for a test."
|
|
155
159
|
]
|
|
156
|
-
addopts = "--no-success-flaky-report"
|
|
157
160
|
filterwarnings = [
|
|
158
|
-
"ignore:No parser was explicitly specified::reader._html_utils",
|
|
161
|
+
"ignore:No parser was explicitly specified::reader._storage._html_utils",
|
|
159
162
|
]
|
|
160
163
|
testpaths = ["tests"]
|
|
161
164
|
|
|
@@ -180,3 +183,14 @@ module = [
|
|
|
180
183
|
"reader._vendor.feedparser.*",
|
|
181
184
|
]
|
|
182
185
|
ignore_errors = true
|
|
186
|
+
|
|
187
|
+
[tool.isort]
|
|
188
|
+
profile = "black"
|
|
189
|
+
py_version = 310
|
|
190
|
+
src_paths = ["src", "tests"]
|
|
191
|
+
extend_skip = ["examples", "src/reader/_vendor"]
|
|
192
|
+
skip_gitignore = true
|
|
193
|
+
filter_files = true
|
|
194
|
+
force_single_line = true
|
|
195
|
+
order_by_type = false
|
|
196
|
+
lines_after_imports = 2
|