reader 3.24__tar.gz → 3.25__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.24 → reader-3.25}/CHANGES.rst +23 -0
- {reader-3.24/src/reader.egg-info → reader-3.25}/PKG-INFO +2 -1
- {reader-3.24 → reader-3.25}/README.rst +1 -0
- {reader-3.24 → reader-3.25}/docs/api.rst +6 -0
- {reader-3.24 → reader-3.25}/docs/conf.py +1 -1
- {reader-3.24 → reader-3.25}/docs/install.rst +8 -6
- {reader-3.24 → reader-3.25}/docs/internal.rst +29 -22
- {reader-3.24 → reader-3.25}/docs/plugins.rst +1 -0
- {reader-3.24 → reader-3.25}/pyproject.toml +1 -0
- {reader-3.24 → reader-3.25}/src/reader/__init__.py +1 -1
- {reader-3.24 → reader-3.25}/src/reader/_app/__init__.py +35 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/add_feed.html +9 -1
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/entries.html +58 -17
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/entry.html +1 -2
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/help.html +8 -2
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/layout_base.html +1 -1
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/macros.html +8 -0
- reader-3.24/src/reader/_parser/_lazy.py → reader-3.25/src/reader/_parser/__init__.py +481 -26
- {reader-3.24 → reader-3.25}/src/reader/_parser/feedparser.py +8 -2
- reader-3.25/src/reader/_parser/http.py +299 -0
- {reader-3.24 → reader-3.25}/src/reader/_parser/jsonfeed.py +6 -2
- {reader-3.24 → reader-3.25}/src/reader/_plugins/legacy/preview_feed_list.py +13 -8
- {reader-3.24 → reader-3.25}/src/reader/_plugins/sqlite_releases.py +4 -2
- {reader-3.24 → reader-3.25}/src/reader/_storage/_entries.py +1 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_html_utils.py +9 -4
- {reader-3.24 → reader-3.25}/src/reader/_storage/_schema.py +1 -1
- {reader-3.24 → reader-3.25}/src/reader/_update/base.py +14 -13
- {reader-3.24 → reader-3.25}/src/reader/_update/hooks.py +34 -20
- {reader-3.24 → reader-3.25}/src/reader/_utils.py +0 -15
- {reader-3.24 → reader-3.25}/src/reader/core.py +20 -16
- reader-3.25/src/reader/discover.py +143 -0
- reader-3.25/src/reader/plugins/autodiscover.py +91 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/ua_fallback.py +4 -2
- {reader-3.24 → reader-3.25/src/reader.egg-info}/PKG-INFO +2 -1
- {reader-3.24 → reader-3.25}/src/reader.egg-info/SOURCES.txt +4 -3
- {reader-3.24 → reader-3.25}/tests/fakeparser.py +1 -2
- {reader-3.24 → reader-3.25}/tests/test_cli.py +4 -1
- reader-3.25/tests/test_discover.py +48 -0
- {reader-3.24 → reader-3.25}/tests/test_html_utils.py +1 -1
- {reader-3.24 → reader-3.25}/tests/test_lazy_imports.py +1 -1
- {reader-3.24 → reader-3.25}/tests/test_parser.py +34 -17
- reader-3.25/tests/test_plugins_autodiscover.py +69 -0
- {reader-3.24 → reader-3.25}/tests/test_reader.py +2 -2
- {reader-3.24 → reader-3.25}/tests/test_reader_hooks.py +18 -1
- {reader-3.24 → reader-3.25}/tests/test_storage.py +17 -8
- reader-3.24/src/reader/_parser/__init__.py +0 -510
- reader-3.24/src/reader/_parser/http.py +0 -107
- reader-3.24/src/reader/_parser/requests/__init__.py +0 -176
- reader-3.24/src/reader/_parser/requests/_lazy.py +0 -162
- {reader-3.24 → reader-3.25}/LICENSE +0 -0
- {reader-3.24 → reader-3.25}/MANIFEST.in +0 -0
- {reader-3.24 → reader-3.25}/docs/app.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/changelog.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/cli.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/config.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/contributing.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/dev.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/guide.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/index.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/empty.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/entries-filters-light.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/entries.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/entry-image-light.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/entry.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/feed.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/feeds.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/help.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/import-result.png +0 -0
- {reader-3.24 → reader-3.25}/docs/screenshots/import-select.png +0 -0
- {reader-3.24 → reader-3.25}/docs/tutorial.rst +0 -0
- {reader-3.24 → reader-3.25}/docs/why.rst +0 -0
- {reader-3.24 → reader-3.25}/examples/config.toml +0 -0
- {reader-3.24 → reader-3.25}/examples/custom_headers.py +0 -0
- {reader-3.24 → reader-3.25}/examples/feed_slugs.py +0 -0
- {reader-3.24 → reader-3.25}/examples/parser_only.py +0 -0
- {reader-3.24 → reader-3.25}/examples/podcast.py +0 -0
- {reader-3.24 → reader-3.25}/examples/terminal.py +0 -0
- {reader-3.24 → reader-3.25}/run.sh +0 -0
- {reader-3.24 → reader-3.25}/scripts/backup.sh +0 -0
- {reader-3.24 → reader-3.25}/scripts/bench.py +0 -0
- {reader-3.24 → reader-3.25}/scripts/debug_storage_stats.py +0 -0
- {reader-3.24 → reader-3.25}/scripts/entry_dedupe_backtest.py +0 -0
- {reader-3.24 → reader-3.25}/scripts/jscontrols.html +0 -0
- {reader-3.24 → reader-3.25}/scripts/jscontrols.py +0 -0
- {reader-3.24 → reader-3.25}/scripts/lines.sh +0 -0
- {reader-3.24 → reader-3.25}/scripts/release.py +0 -0
- {reader-3.24 → reader-3.25}/scripts/vendor_app_stuff.py +0 -0
- {reader-3.24 → reader-3.25}/setup.cfg +0 -0
- {reader-3.24 → reader-3.25}/src/reader/__main__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/cli.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/ext.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/forms.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/api_thing.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/static/controls.js +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/static/style.css +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/add_entry.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/entries.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/entry.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/feeds.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/layout.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/macros.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/metadata.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/templates/tags.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/legacy/wsgi.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/style.css +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/theme.js +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/README +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/css/bootstrap.min.css +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/htmx-ext-response-targets@2.0.4/dist/response-targets.min.js +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/static/vendor/htmx.org@2.0.8/dist/htmx.min.js +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/change_feed_title.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/delete_feed.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/import_feeds.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_app/wsgi.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_cli.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_config_utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_hash_utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_logging.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_parser/_http_utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_parser/file.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/cli_status.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/legacy/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/legacy/enclosure_tags.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/legacy/share.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/legacy/templates/preview_feed_list.html +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_plugins/timer.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_base.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_changes.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_feeds.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_search.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_sqlite_utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_storage/_tags.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_types.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_update/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/exceptions.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/opml.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/_loader.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/entry_dedupe.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/mark_as_read.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/plugins/readtime.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/py.typed +0 -0
- {reader-3.24 → reader-3.25}/src/reader/types.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader/utils.py +0 -0
- {reader-3.24 → reader-3.25}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.24 → reader-3.25}/src/reader.egg-info/requires.txt +0 -0
- {reader-3.24 → reader-3.25}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.24 → reader-3.25}/tests/_plugins/__init__.py +0 -0
- {reader-3.24 → reader-3.25}/tests/_plugins/builtin.py +0 -0
- {reader-3.24 → reader-3.25}/tests/_plugins/error_during_import.py +0 -0
- {reader-3.24 → reader-3.25}/tests/_plugins/error_import_error.py +0 -0
- {reader-3.24 → reader-3.25}/tests/conftest.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/10.json +0 -0
- {reader-3.24 → reader-3.25}/tests/data/10.json.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/custom +0 -0
- {reader-3.24 → reader-3.25}/tests/data/empty.atom +0 -0
- {reader-3.24 → reader-3.25}/tests/data/empty.atom.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/empty.json +0 -0
- {reader-3.24 → reader-3.25}/tests/data/empty.json.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/empty.rss +0 -0
- {reader-3.24 → reader-3.25}/tests/data/empty.rss.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/full.atom +0 -0
- {reader-3.24 → reader-3.25}/tests/data/full.atom.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/full.json +0 -0
- {reader-3.24 → reader-3.25}/tests/data/full.json.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/full.rss +0 -0
- {reader-3.24 → reader-3.25}/tests/data/full.rss.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/invalid.json +0 -0
- {reader-3.24 → reader-3.25}/tests/data/invalid.json.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/relative.atom +0 -0
- {reader-3.24 → reader-3.25}/tests/data/relative.atom.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/relative.rss +0 -0
- {reader-3.24 → reader-3.25}/tests/data/relative.rss.py +0 -0
- {reader-3.24 → reader-3.25}/tests/data/sqlite_releases.html +0 -0
- {reader-3.24 → reader-3.25}/tests/data/unknown.json +0 -0
- {reader-3.24 → reader-3.25}/tests/data/unknown.json.py +0 -0
- {reader-3.24 → reader-3.25}/tests/fixtures.py +0 -0
- {reader-3.24 → reader-3.25}/tests/reader_methods.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test__types.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test__utils.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_app_legacy.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_app_wsgi.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_bench.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_changes.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_config_utils.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_exceptions.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_hash_utils.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_logging.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_opml.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_cli_status.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_entry_dedupe.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_loader.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_readtime.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_sqlite_releases.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_counts.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_deprecations.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_filter.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_integration.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_lifecycle.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_private.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_search.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_sort.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_update.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_reader_utils.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_search.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_sql_utils.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_sqlite_utils.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_tags.py +0 -0
- {reader-3.24 → reader-3.25}/tests/test_types.py +0 -0
- {reader-3.24 → reader-3.25}/tests/utils.py +0 -0
- {reader-3.24 → reader-3.25}/tox.ini +0 -0
|
@@ -6,6 +6,29 @@ Changelog
|
|
|
6
6
|
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.25
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2026-05-25
|
|
13
|
+
|
|
14
|
+
* Stable feed autodiscovery support. (:issue:`404`)
|
|
15
|
+
|
|
16
|
+
* Add :mod:`~reader.plugins.autodiscover` built-in plugin.
|
|
17
|
+
* Add :mod:`reader.discover` module providing low level discovery.
|
|
18
|
+
* In the web app, show :mod:`~reader.plugins.autodiscover` suggestions when adding a feed.
|
|
19
|
+
|
|
20
|
+
* Fix bugs that may cause the 3.24 database migration to fail. (:issue:`405`)
|
|
21
|
+
|
|
22
|
+
* Don't fail for ``John Doe (...) (email)`` RSS authors.
|
|
23
|
+
* Support missing :attr:`EntrySource` ``author`` field during migration.
|
|
24
|
+
|
|
25
|
+
Note there should be no integrity issues,
|
|
26
|
+
as migrations happen inside a transaction.
|
|
27
|
+
|
|
28
|
+
* Simplify parser lazy initialization (make it explicit and opt-in by plugins).
|
|
29
|
+
* Allow before/after update hook functions to take *fewer* arguments than documented.
|
|
30
|
+
|
|
31
|
+
|
|
9
32
|
Version 3.24
|
|
10
33
|
------------
|
|
11
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.25
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -114,6 +114,7 @@ you'd probably call it a fat model.
|
|
|
114
114
|
* full-text search articles
|
|
115
115
|
* get statistics on feed and user activity
|
|
116
116
|
* import / export feeds as OPML
|
|
117
|
+
* automatically discover feeds in web pages
|
|
117
118
|
* write plugins to extend its functionality
|
|
118
119
|
* skip all the low level stuff and focus on what makes your feed reader different
|
|
119
120
|
|
|
@@ -50,6 +50,7 @@ you'd probably call it a fat model.
|
|
|
50
50
|
* full-text search articles
|
|
51
51
|
* get statistics on feed and user activity
|
|
52
52
|
* import / export feeds as OPML
|
|
53
|
+
* automatically discover feeds in web pages
|
|
53
54
|
* write plugins to extend its functionality
|
|
54
55
|
* skip all the low level stuff and focus on what makes your feed reader different
|
|
55
56
|
|
|
@@ -13,13 +13,10 @@ Dependencies
|
|
|
13
13
|
|
|
14
14
|
These packages will be installed automatically when installing *reader*:
|
|
15
15
|
|
|
16
|
-
* `feedparser`_ parses
|
|
17
|
-
* `requests`_ retrieves feeds from the internet
|
|
18
|
-
it replaces feedparser's default use of :mod:`urllib`
|
|
19
|
-
to make it easier to write plugins.
|
|
16
|
+
* `feedparser`_ parses Atom and RSSfeeds.
|
|
17
|
+
* `requests`_ retrieves feeds from the internet.
|
|
20
18
|
* `werkzeug`_ provides HTTP utilities.
|
|
21
|
-
* `beautifulsoup4`_ is used to strip HTML tags
|
|
22
|
-
to the search index.
|
|
19
|
+
* `beautifulsoup4`_ is used to strip HTML tags and by various plugins.
|
|
23
20
|
* `structlog`_ is used for structured logging.
|
|
24
21
|
* `typing-extensions`_ is used for :mod:`typing` backports.
|
|
25
22
|
|
|
@@ -58,8 +55,13 @@ As of version |version|, *reader* has the following extras:
|
|
|
58
55
|
* Specific plugins may require additional dependencies;
|
|
59
56
|
refer to their documentation for details.
|
|
60
57
|
|
|
58
|
+
Beautiful Soup supports `multiple parsers`_,
|
|
59
|
+
and will use the best available one;
|
|
60
|
+
for speed, install `lxml`_.
|
|
61
61
|
|
|
62
62
|
.. _beautifulsoup4: https://www.crummy.com/software/BeautifulSoup/
|
|
63
|
+
.. _multiple parsers: https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser
|
|
64
|
+
.. _lxml: https://lxml.de/
|
|
63
65
|
.. _feedparser: https://feedparser.readthedocs.io/en/latest/
|
|
64
66
|
.. _requests: https://requests.readthedocs.io/
|
|
65
67
|
.. _werkzeug: https://werkzeug.palletsprojects.com/
|
|
@@ -31,16 +31,6 @@ Parser
|
|
|
31
31
|
:special-members: __call__
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
.. module:: reader._parser.requests
|
|
35
|
-
|
|
36
|
-
.. autoclass:: SessionFactory(...)
|
|
37
|
-
:members:
|
|
38
|
-
:special-members: __call__
|
|
39
|
-
|
|
40
|
-
.. autoclass:: SessionWrapper(...)
|
|
41
|
-
:members:
|
|
42
|
-
|
|
43
|
-
|
|
44
34
|
Protocols
|
|
45
35
|
~~~~~~~~~
|
|
46
36
|
|
|
@@ -79,18 +69,6 @@ Protocols
|
|
|
79
69
|
:show-inheritance:
|
|
80
70
|
|
|
81
71
|
|
|
82
|
-
.. module:: reader._parser.requests
|
|
83
|
-
:no-index:
|
|
84
|
-
|
|
85
|
-
.. autoclass:: RequestHook
|
|
86
|
-
:members:
|
|
87
|
-
:special-members: __call__
|
|
88
|
-
|
|
89
|
-
.. autoclass:: ResponseHook
|
|
90
|
-
:members:
|
|
91
|
-
:special-members: __call__
|
|
92
|
-
|
|
93
|
-
|
|
94
72
|
Data objects
|
|
95
73
|
~~~~~~~~~~~~
|
|
96
74
|
|
|
@@ -113,6 +91,35 @@ Data objects
|
|
|
113
91
|
:members:
|
|
114
92
|
|
|
115
93
|
|
|
94
|
+
Built-in retrievers and parsers
|
|
95
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
96
|
+
|
|
97
|
+
.. module:: reader._parser.http
|
|
98
|
+
|
|
99
|
+
.. autoclass:: HTTPRetriever
|
|
100
|
+
:members: request_hooks, response_hooks
|
|
101
|
+
|
|
102
|
+
.. autoclass:: RequestHook
|
|
103
|
+
:members:
|
|
104
|
+
:special-members: __call__
|
|
105
|
+
|
|
106
|
+
.. autoclass:: ResponseHook
|
|
107
|
+
:members:
|
|
108
|
+
:special-members: __call__
|
|
109
|
+
|
|
110
|
+
.. module:: reader._parser.file
|
|
111
|
+
|
|
112
|
+
.. autoclass:: FileRetriever
|
|
113
|
+
|
|
114
|
+
.. module:: reader._parser.feedparser
|
|
115
|
+
|
|
116
|
+
.. autoclass:: FeedparserParser
|
|
117
|
+
|
|
118
|
+
.. module:: reader._parser.jsonfeed
|
|
119
|
+
|
|
120
|
+
.. autoclass:: JSONFeedParser
|
|
121
|
+
|
|
122
|
+
|
|
116
123
|
.. _storage:
|
|
117
124
|
|
|
118
125
|
Storage
|
|
@@ -16,6 +16,7 @@ This is a list of built-in plugins that are considered stable.
|
|
|
16
16
|
See the :ref:`Plugins <plugins>` section of the user guide
|
|
17
17
|
for details on how built-in plugins are loaded.
|
|
18
18
|
|
|
19
|
+
.. automodule:: reader.plugins.autodiscover
|
|
19
20
|
.. automodule:: reader.plugins.enclosure_dedupe
|
|
20
21
|
.. automodule:: reader.plugins.entry_dedupe
|
|
21
22
|
.. automodule:: reader.plugins.mark_as_read
|
|
@@ -88,6 +88,15 @@ def entries():
|
|
|
88
88
|
if feed_url := form.feed.data:
|
|
89
89
|
feed = reader.get_feed(feed_url)
|
|
90
90
|
|
|
91
|
+
autodiscover = None
|
|
92
|
+
if feed and feed.last_exception and not feed.last_updated:
|
|
93
|
+
autodiscover_tag = reader.make_reader_reserved_name('autodiscover')
|
|
94
|
+
hostname = urlparse(feed.url).hostname
|
|
95
|
+
autodiscover = [
|
|
96
|
+
(link, hostname and urlparse(link['href']).hostname == hostname)
|
|
97
|
+
for link in reader.get_tag(feed, autodiscover_tag, None)
|
|
98
|
+
]
|
|
99
|
+
|
|
91
100
|
kwargs = dict(form.data)
|
|
92
101
|
|
|
93
102
|
if not (feed or kwargs.get('starting_after')):
|
|
@@ -106,6 +115,7 @@ def entries():
|
|
|
106
115
|
form=form,
|
|
107
116
|
entries=entries,
|
|
108
117
|
feed=feed,
|
|
118
|
+
autodiscover=autodiscover,
|
|
109
119
|
limit=limit,
|
|
110
120
|
)
|
|
111
121
|
|
|
@@ -277,6 +287,31 @@ def add_feed():
|
|
|
277
287
|
return render_template('add_feed.html', form=form)
|
|
278
288
|
|
|
279
289
|
|
|
290
|
+
@blueprint.route('/feeds/change', methods=['POST'])
|
|
291
|
+
def change_feed_url():
|
|
292
|
+
# NOTE: currently only for post-add autodiscover change,
|
|
293
|
+
# actual "change feed URL" UX needs design (e.g. needs preview)
|
|
294
|
+
|
|
295
|
+
reader = get_reader()
|
|
296
|
+
old = request.form['old']
|
|
297
|
+
new = request.form['new']
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
reader.change_feed_url(old, new)
|
|
301
|
+
except FeedExistsError:
|
|
302
|
+
flash(f"Feed {new} already exists.", 'secondary')
|
|
303
|
+
return redirect(url_for('.entries', feed=old), code=303)
|
|
304
|
+
else:
|
|
305
|
+
# TODO: updating should be out of band
|
|
306
|
+
try:
|
|
307
|
+
reader.update_feed(new)
|
|
308
|
+
except UpdateError:
|
|
309
|
+
pass
|
|
310
|
+
else:
|
|
311
|
+
flash("Changed and updated feed.", 'success')
|
|
312
|
+
return redirect(url_for('.entries', feed=new), code=303)
|
|
313
|
+
|
|
314
|
+
|
|
280
315
|
@blueprint.route('/feeds/import', methods=['GET', 'POST'])
|
|
281
316
|
def import_feeds():
|
|
282
317
|
reader = get_reader()
|
|
@@ -13,7 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
<h1>Add feed</h1>
|
|
15
15
|
|
|
16
|
-
<p>
|
|
16
|
+
<p>
|
|
17
|
+
Paste a feed or website URL:
|
|
18
|
+
<a
|
|
19
|
+
href="{{ url_for('.help', _anchor=help_topic) }}"
|
|
20
|
+
class="link-secondary d-inline-block px-2"
|
|
21
|
+
>
|
|
22
|
+
<i class="bi bi-question-lg" aria-label="help: adding feeds"></i>
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
17
25
|
|
|
18
26
|
<form action="{{ url_for(request.endpoint) }}" method="post" class="my-2 mb-4">
|
|
19
27
|
{#- TODO: aria stuff #}
|
|
@@ -18,6 +18,64 @@
|
|
|
18
18
|
|
|
19
19
|
{{ macros.flashed_messages() }}
|
|
20
20
|
|
|
21
|
+
{%- if feed and not feed.url.startswith('reader:') %}
|
|
22
|
+
<ul class="list-unstyled mt-3">
|
|
23
|
+
|
|
24
|
+
{%- if not feed.last_updated and not feed.last_exception %}
|
|
25
|
+
<li class="alert alert-secondary">
|
|
26
|
+
Feed not updated yet. <a href="">Refresh.</a>
|
|
27
|
+
</li>
|
|
28
|
+
{%- endif %}
|
|
29
|
+
|
|
30
|
+
{%- if feed.last_exception %}
|
|
31
|
+
|
|
32
|
+
{%- if autodiscover %}
|
|
33
|
+
<li class="alert alert-primary">
|
|
34
|
+
|
|
35
|
+
<p>
|
|
36
|
+
<strong>This doesn't look like a feed</strong>,
|
|
37
|
+
but it links to these feeds:
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<ul class="list-unstyled d-table">
|
|
41
|
+
{%- for link, official in autodiscover | sort(attribute=1, reverse=True) %}
|
|
42
|
+
<li class="d-table-row mb-1">
|
|
43
|
+
|
|
44
|
+
<span class="d-table-cell pe-4 pb-1">
|
|
45
|
+
{{ macros.link_object(link) }}
|
|
46
|
+
{%- if not official %}
|
|
47
|
+
<i class="bi bi-box-arrow-up-right ms-1" aria-label="external feed" title="external feed"></i>
|
|
48
|
+
{%- endif %}
|
|
49
|
+
</span>
|
|
50
|
+
|
|
51
|
+
<form
|
|
52
|
+
action="{{ url_for('.change_feed_url') }}"
|
|
53
|
+
method="post"
|
|
54
|
+
class="d-table-cell pb-1"
|
|
55
|
+
>
|
|
56
|
+
{{ macros.csrf_token_field() }}
|
|
57
|
+
<input type="hidden" name="old" value="{{ feed.url }}">
|
|
58
|
+
<input type="hidden" name="new" value="{{ link.href }}">
|
|
59
|
+
<input type="submit" class="btn btn-primary btn-sm" value="add">
|
|
60
|
+
</form>
|
|
61
|
+
|
|
62
|
+
</li>
|
|
63
|
+
{%- endfor %}
|
|
64
|
+
</ul>
|
|
65
|
+
|
|
66
|
+
</li>
|
|
67
|
+
{%- endif %}
|
|
68
|
+
|
|
69
|
+
<li class="alert alert-{{ 'danger' if not autodiscover else 'secondary' }}">
|
|
70
|
+
<strong>Update error</strong>
|
|
71
|
+
{%- if not feed.last_updated %} (first update){% endif %}:
|
|
72
|
+
{{ feed.last_exception.value_str }}
|
|
73
|
+
</li>
|
|
74
|
+
|
|
75
|
+
{%- endif %}
|
|
76
|
+
</ul>
|
|
77
|
+
{%- endif %}
|
|
78
|
+
|
|
21
79
|
|
|
22
80
|
{%- if not feed %}
|
|
23
81
|
<h1>Entries</h1>
|
|
@@ -34,23 +92,6 @@
|
|
|
34
92
|
|
|
35
93
|
{{ macros.subtitle(feed.subtitle) }}
|
|
36
94
|
|
|
37
|
-
{%- if feed and not feed.url.startswith('reader:') %}
|
|
38
|
-
<ul class="list-unstyled mt-3">
|
|
39
|
-
{%- if not feed.last_updated and not feed.last_exception %}
|
|
40
|
-
<li class="alert alert-secondary">
|
|
41
|
-
Feed not updated yet. <a href="">Refresh.</a>
|
|
42
|
-
</li>
|
|
43
|
-
{%- endif %}
|
|
44
|
-
{%- if feed.last_exception %}
|
|
45
|
-
<li class="alert alert-danger">
|
|
46
|
-
<strong>Update error</strong>
|
|
47
|
-
{%- if not feed.last_updated %} (first update){% endif %}:
|
|
48
|
-
{{ feed.last_exception.value_str }}
|
|
49
|
-
</li>
|
|
50
|
-
{%- endif %}
|
|
51
|
-
</ul>
|
|
52
|
-
{%- endif %}
|
|
53
|
-
|
|
54
95
|
<form
|
|
55
96
|
action="{{ url_for('.feed_actions', from='entries') }}"
|
|
56
97
|
method="post"
|
|
@@ -114,8 +114,7 @@
|
|
|
114
114
|
<ul class="card-text list-unstyled">
|
|
115
115
|
{% for enclosure in entry.enclosures %}
|
|
116
116
|
<li>
|
|
117
|
-
|
|
118
|
-
<small>{{ enclosure.type }}</small>
|
|
117
|
+
{{ macros.link_object(enclosure) }}
|
|
119
118
|
</li>
|
|
120
119
|
{% endfor %}
|
|
121
120
|
</ul>
|
|
@@ -39,11 +39,17 @@
|
|
|
39
39
|
<h3 id="add-feed">Adding feeds</h3>
|
|
40
40
|
|
|
41
41
|
<p>
|
|
42
|
-
To add a feed, paste
|
|
42
|
+
To add a feed, paste the feed or website URL on the
|
|
43
43
|
<a href="{{ url_for('.add_feed') }}">Add feed</a> page.
|
|
44
44
|
|
|
45
45
|
<p>
|
|
46
|
-
|
|
46
|
+
If you use a website URL,
|
|
47
|
+
<em>reader</em> will discover any standard feeds it links to
|
|
48
|
+
and suggest them to you.
|
|
49
|
+
|
|
50
|
+
<p>
|
|
51
|
+
If that doesn't work,
|
|
52
|
+
you can usually find if a website has a feed
|
|
47
53
|
by looking in its header or footer for this icon
|
|
48
54
|
<i class="bi bi-rss" aria-label="RSS logo"></i>,
|
|
49
55
|
or for links called <em>feed</em>, <em>RSS</em>, or <em>Atom</em>.
|
|
@@ -355,3 +355,11 @@
|
|
|
355
355
|
or learn more about <a href="{{ url_for('.help') }}">how to use <strong>reader</strong></a>.
|
|
356
356
|
</p>
|
|
357
357
|
{%- endmacro -%}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
{%- macro link_object(link) %}
|
|
361
|
+
{#- TODO: when links are unified, add a property for this #}
|
|
362
|
+
{%- set title = link.title or link.href.rpartition('?')[0].rstrip('/').rpartition('/')[2] %}
|
|
363
|
+
<a href="{{ link.href }}" target="_blank">{{ title }}</a>
|
|
364
|
+
{%- if link.type %} <small>{{ link.type }}</small>{% endif %}
|
|
365
|
+
{%- endmacro %}
|