reader 3.17__tar.gz → 3.18__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.17 → reader-3.18}/CHANGES.rst +19 -0
- {reader-3.17/src/reader.egg-info → reader-3.18}/PKG-INFO +4 -4
- {reader-3.17 → reader-3.18}/docs/api.rst +17 -0
- {reader-3.17 → reader-3.18}/pyproject.toml +2 -3
- {reader-3.17 → reader-3.18}/src/reader/__init__.py +4 -1
- {reader-3.17 → reader-3.18}/src/reader/_parser/__init__.py +2 -7
- {reader-3.17 → reader-3.18}/src/reader/_parser/_lazy.py +1 -2
- {reader-3.17 → reader-3.18}/src/reader/_parser/requests/_lazy.py +1 -2
- {reader-3.17 → reader-3.18}/src/reader/_plugins/cli_status.py +2 -2
- {reader-3.17 → reader-3.18}/src/reader/_storage/_base.py +1 -1
- {reader-3.17 → reader-3.18}/src/reader/_storage/_entries.py +6 -6
- {reader-3.17 → reader-3.18}/src/reader/_storage/_feeds.py +6 -4
- {reader-3.17 → reader-3.18}/src/reader/_storage/_search.py +12 -11
- {reader-3.17 → reader-3.18}/src/reader/_storage/_sql_utils.py +2 -3
- {reader-3.17 → reader-3.18}/src/reader/_storage/_sqlite_utils.py +11 -2
- {reader-3.17 → reader-3.18}/src/reader/_types.py +3 -7
- {reader-3.17 → reader-3.18}/src/reader/_update.py +1 -2
- {reader-3.17 → reader-3.18}/src/reader/core.py +39 -87
- {reader-3.17 → reader-3.18}/src/reader/exceptions.py +15 -81
- {reader-3.17 → reader-3.18}/src/reader/types.py +74 -22
- {reader-3.17 → reader-3.18/src/reader.egg-info}/PKG-INFO +4 -4
- {reader-3.17 → reader-3.18}/tests/data/full.atom.py +5 -11
- {reader-3.17 → reader-3.18}/tests/data/full.json.py +3 -3
- {reader-3.17 → reader-3.18}/tests/data/full.rss.py +3 -3
- {reader-3.17 → reader-3.18}/tests/test_plugins_cli_status.py +1 -0
- {reader-3.17 → reader-3.18}/tests/test_reader.py +5 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_hooks.py +14 -4
- {reader-3.17 → reader-3.18}/tests/test_reader_update.py +17 -0
- {reader-3.17 → reader-3.18}/tox.ini +3 -3
- {reader-3.17 → reader-3.18}/LICENSE +0 -0
- {reader-3.17 → reader-3.18}/MANIFEST.in +0 -0
- {reader-3.17 → reader-3.18}/README.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/.DS_Store +0 -0
- {reader-3.17 → reader-3.18}/docs/Makefile +0 -0
- {reader-3.17 → reader-3.18}/docs/app.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/changelog.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/cli.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/conf.py +0 -0
- {reader-3.17 → reader-3.18}/docs/config.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/contributing.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/dev-app.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/dev.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/guide.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/images/redesign-01.png +0 -0
- {reader-3.17 → reader-3.18}/docs/index.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/install.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/internal.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/make.bat +0 -0
- {reader-3.17 → reader-3.18}/docs/plugins.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/dillo.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/entries-feed.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/entries-v2-dark.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/entries-v2-filters-light.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/entries.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/entry-one.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/entry-two.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/feeds.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/lynx.png +0 -0
- {reader-3.17 → reader-3.18}/docs/screenshots/search.png +0 -0
- {reader-3.17 → reader-3.18}/docs/tutorial.rst +0 -0
- {reader-3.17 → reader-3.18}/docs/why.rst +0 -0
- {reader-3.17 → reader-3.18}/examples/config.yaml +0 -0
- {reader-3.17 → reader-3.18}/examples/custom_headers.py +0 -0
- {reader-3.17 → reader-3.18}/examples/feed_slugs.py +0 -0
- {reader-3.17 → reader-3.18}/examples/parser_only.py +0 -0
- {reader-3.17 → reader-3.18}/examples/podcast.py +0 -0
- {reader-3.17 → reader-3.18}/examples/terminal.py +0 -0
- {reader-3.17 → reader-3.18}/run.sh +0 -0
- {reader-3.17 → reader-3.18}/scripts/.coverage +0 -0
- {reader-3.17 → reader-3.18}/scripts/backup.sh +0 -0
- {reader-3.17 → reader-3.18}/scripts/bench.py +0 -0
- {reader-3.17 → reader-3.18}/scripts/debug_storage_stats.py +0 -0
- {reader-3.17 → reader-3.18}/scripts/generate_import_all.py +0 -0
- {reader-3.17 → reader-3.18}/scripts/jscontrols.html +0 -0
- {reader-3.17 → reader-3.18}/scripts/jscontrols.py +0 -0
- {reader-3.17 → reader-3.18}/scripts/lines.sh +0 -0
- {reader-3.17 → reader-3.18}/scripts/release.py +0 -0
- {reader-3.17 → reader-3.18}/setup.cfg +0 -0
- {reader-3.17 → reader-3.18}/src/reader/__main__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/api_thing.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/cli.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/static/controls.js +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/static/style.css +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/add_entry.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/entries.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/entry.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/macros.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/metadata.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/templates/tags.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/forms.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/static/style.css +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/static/theme.js +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/templates/v2/entries.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/templates/v2/layout.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/v2/templates/v2/macros.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_app/wsgi.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_cli.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_config.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_hash_utils.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/_http_utils.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/feedparser.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/file.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/http.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/jsonfeed.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_parser/requests/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/enclosure_tags.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/preview_feed_list.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/share.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/sqlite_releases.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/templates/preview_feed_list.html +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_plugins/timer.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_storage/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_storage/_changes.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_storage/_html_utils.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_storage/_schema.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_storage/_tags.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_utils.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/plugins/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/plugins/entry_dedupe.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/plugins/mark_as_read.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/plugins/readtime.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/plugins/ua_fallback.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader/py.typed +0 -0
- {reader-3.17 → reader-3.18}/src/reader/utils.py +0 -0
- {reader-3.17 → reader-3.18}/src/reader.egg-info/SOURCES.txt +0 -0
- {reader-3.17 → reader-3.18}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.17 → reader-3.18}/src/reader.egg-info/requires.txt +0 -0
- {reader-3.17 → reader-3.18}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.17 → reader-3.18}/tests/conftest.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/10.json +0 -0
- {reader-3.17 → reader-3.18}/tests/data/10.json.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/custom +0 -0
- {reader-3.17 → reader-3.18}/tests/data/empty.atom +0 -0
- {reader-3.17 → reader-3.18}/tests/data/empty.atom.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/empty.json +0 -0
- {reader-3.17 → reader-3.18}/tests/data/empty.json.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/empty.rss +0 -0
- {reader-3.17 → reader-3.18}/tests/data/empty.rss.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/full.atom +0 -0
- {reader-3.17 → reader-3.18}/tests/data/full.json +0 -0
- {reader-3.17 → reader-3.18}/tests/data/full.rss +0 -0
- {reader-3.17 → reader-3.18}/tests/data/invalid.json +0 -0
- {reader-3.17 → reader-3.18}/tests/data/invalid.json.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/relative.atom +0 -0
- {reader-3.17 → reader-3.18}/tests/data/relative.atom.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/relative.rss +0 -0
- {reader-3.17 → reader-3.18}/tests/data/relative.rss.py +0 -0
- {reader-3.17 → reader-3.18}/tests/data/sqlite_releases.html +0 -0
- {reader-3.17 → reader-3.18}/tests/data/unknown.json +0 -0
- {reader-3.17 → reader-3.18}/tests/data/unknown.json.py +0 -0
- {reader-3.17 → reader-3.18}/tests/fakeparser.py +0 -0
- {reader-3.17 → reader-3.18}/tests/reader_methods.py +0 -0
- {reader-3.17 → reader-3.18}/tests/reader_test_plugins/__init__.py +0 -0
- {reader-3.17 → reader-3.18}/tests/reader_test_plugins/good.py +0 -0
- {reader-3.17 → reader-3.18}/tests/reader_test_plugins/init_error.py +0 -0
- {reader-3.17 → reader-3.18}/tests/reader_test_plugins/missing_dependency.py +0 -0
- {reader-3.17 → reader-3.18}/tests/reader_test_plugins/missing_entry_point.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test__types.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test__utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_app.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_app_wsgi.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_bench.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_changes.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_cli.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_config.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_exceptions.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_hash_utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_html_utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_lazy_imports.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_parser.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_entry_dedupe.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_readtime.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_sqlite_releases.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_context.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_counts.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_deprecations.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_filter.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_integration.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_plugins.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_private.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_search.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_sort.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_reader_utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_search.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_sql_utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_sqlite_utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_storage.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_tags.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_test_utils.py +0 -0
- {reader-3.17 → reader-3.18}/tests/test_types.py +0 -0
- {reader-3.17 → reader-3.18}/tests/utils.py +0 -0
|
@@ -6,6 +6,25 @@ Changelog
|
|
|
6
6
|
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.18
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2025-05-04
|
|
13
|
+
|
|
14
|
+
* Drop Python 3.10 support. (:issue:`354`)
|
|
15
|
+
|
|
16
|
+
* Add the ``overwrite`` argument to :meth:`~Reader.add_entry`.
|
|
17
|
+
* Add :class:`FeedSort`, :class:`EntrySort`, and :class:`EntrySearchSort` enums.
|
|
18
|
+
(:issue:`354`)
|
|
19
|
+
|
|
20
|
+
* Fix bug causing :meth:`.Reader.update_feeds`
|
|
21
|
+
to not honor ``updates_enabled=True`` when ``scheduled=True``.
|
|
22
|
+
(:issue:`365`)
|
|
23
|
+
|
|
24
|
+
* In the :mod:`~reader._plugins.cli_status` plugin,
|
|
25
|
+
overwrite the entry so it doesn't appear briefly as unread.
|
|
26
|
+
|
|
27
|
+
|
|
9
28
|
Version 3.17
|
|
10
29
|
------------
|
|
11
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.18
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -19,7 +19,6 @@ Classifier: Operating System :: OS Independent
|
|
|
19
19
|
Classifier: Programming Language :: Python
|
|
20
20
|
Classifier: Programming Language :: Python :: 3
|
|
21
21
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -32,7 +31,7 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
|
32
31
|
Classifier: Topic :: Software Development :: Libraries
|
|
33
32
|
Classifier: Topic :: Utilities
|
|
34
33
|
Classifier: Typing :: Typed
|
|
35
|
-
Requires-Python: >=3.
|
|
34
|
+
Requires-Python: >=3.11
|
|
36
35
|
Description-Content-Type: text/x-rst
|
|
37
36
|
License-File: LICENSE
|
|
38
37
|
Requires-Dist: typing-extensions>=4
|
|
@@ -90,6 +89,7 @@ Requires-Dist: tox; extra == "dev"
|
|
|
90
89
|
Requires-Dist: pre-commit; extra == "dev"
|
|
91
90
|
Requires-Dist: build; extra == "dev"
|
|
92
91
|
Requires-Dist: twine; extra == "dev"
|
|
92
|
+
Dynamic: license-file
|
|
93
93
|
|
|
94
94
|
.. begin-intro
|
|
95
95
|
|
|
@@ -171,6 +171,23 @@ The class hierarchy for :mod:`reader` exceptions is:
|
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
Enumerations
|
|
175
|
+
------------
|
|
176
|
+
|
|
177
|
+
.. autoclass:: reader.FeedSort(value)
|
|
178
|
+
:show-inheritance:
|
|
179
|
+
:members:
|
|
180
|
+
|
|
181
|
+
.. autoclass:: reader.EntrySort(value)
|
|
182
|
+
:show-inheritance:
|
|
183
|
+
:members:
|
|
184
|
+
|
|
185
|
+
.. autoclass:: reader.EntrySearchSort(value)
|
|
186
|
+
:show-inheritance:
|
|
187
|
+
:members:
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
174
191
|
.. _type aliases:
|
|
175
192
|
|
|
176
193
|
Type aliases
|
|
@@ -23,7 +23,6 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python",
|
|
24
24
|
"Programming Language :: Python :: 3",
|
|
25
25
|
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
-
"Programming Language :: Python :: 3.10",
|
|
27
26
|
"Programming Language :: Python :: 3.11",
|
|
28
27
|
"Programming Language :: Python :: 3.12",
|
|
29
28
|
"Programming Language :: Python :: 3.13",
|
|
@@ -37,7 +36,7 @@ classifiers = [
|
|
|
37
36
|
"Topic :: Utilities",
|
|
38
37
|
"Typing :: Typed",
|
|
39
38
|
]
|
|
40
|
-
requires-python = ">=3.
|
|
39
|
+
requires-python = ">=3.11"
|
|
41
40
|
dependencies = [
|
|
42
41
|
"typing-extensions>=4",
|
|
43
42
|
"feedparser>=6",
|
|
@@ -189,7 +188,7 @@ ignore_errors = true
|
|
|
189
188
|
|
|
190
189
|
[tool.isort]
|
|
191
190
|
profile = "black"
|
|
192
|
-
py_version =
|
|
191
|
+
py_version = 311
|
|
193
192
|
src_paths = ["src", "tests"]
|
|
194
193
|
extend_skip = ["examples", "src/reader/_vendor"]
|
|
195
194
|
skip_gitignore = true
|
|
@@ -37,7 +37,7 @@ Print the titles of the unread entries::
|
|
|
37
37
|
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
__version__ = '3.
|
|
40
|
+
__version__ = '3.18'
|
|
41
41
|
|
|
42
42
|
# isort: off
|
|
43
43
|
|
|
@@ -58,6 +58,9 @@ from .types import (
|
|
|
58
58
|
FeedCounts as FeedCounts,
|
|
59
59
|
EntryCounts as EntryCounts,
|
|
60
60
|
EntrySearchCounts as EntrySearchCounts,
|
|
61
|
+
FeedSort as FeedSort,
|
|
62
|
+
EntrySort as EntrySort,
|
|
63
|
+
EntrySearchSort as EntrySearchSort,
|
|
61
64
|
UpdateResult as UpdateResult,
|
|
62
65
|
UpdatedFeed as UpdatedFeed,
|
|
63
66
|
EntryUpdateStatus as EntryUpdateStatus,
|
|
@@ -239,13 +239,9 @@ class NotModified(RetrieveError):
|
|
|
239
239
|
_default_message = "not modified"
|
|
240
240
|
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
class RetrieveResult(_namedtuple_compat, Generic[F, T, E]):
|
|
242
|
+
class RetrieveResult(NamedTuple, Generic[F, T, E]):
|
|
244
243
|
"""The result of retrieving a feed, regardless of the outcome."""
|
|
245
244
|
|
|
246
|
-
# should be a NamedTuple, but the typing one became generic only in 3.11,
|
|
247
|
-
# and we don't want to depend on typing_extensions at runtime
|
|
248
|
-
|
|
249
245
|
#: The feed (a :class:`FeedArgument`, usually a :class:`FeedForUpdate`).
|
|
250
246
|
feed: F
|
|
251
247
|
|
|
@@ -339,8 +335,7 @@ class FeedForUpdateRetrieverType(RetrieverType[T_co], Protocol): # pragma: no c
|
|
|
339
335
|
"""
|
|
340
336
|
|
|
341
337
|
|
|
342
|
-
|
|
343
|
-
class ParseResult(_namedtuple_compat, Generic[F, E]):
|
|
338
|
+
class ParseResult(NamedTuple, Generic[F, E]):
|
|
344
339
|
"""The result of retrieving and parsing a feed, regardless of the outcome."""
|
|
345
340
|
|
|
346
341
|
#: The feed (a :class:`FeedArgument`, usually a :class:`.FeedForUpdate`).
|
|
@@ -4,6 +4,7 @@ from collections.abc import Sequence
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from dataclasses import field
|
|
6
6
|
from typing import Any
|
|
7
|
+
from typing import Self
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
from typing import TypeVar
|
|
9
10
|
|
|
@@ -14,8 +15,6 @@ from . import TimeoutType
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING: # pragma: no cover
|
|
17
|
-
from typing_extensions import Self
|
|
18
|
-
|
|
19
18
|
from . import Headers
|
|
20
19
|
from . import RequestHook
|
|
21
20
|
from . import ResponseHook
|
|
@@ -73,7 +73,6 @@ def save_output(reader, now, config, command_path, output):
|
|
|
73
73
|
|
|
74
74
|
try:
|
|
75
75
|
old_content = reader.get_entry((feed_url, entry_id)).content[0].value
|
|
76
|
-
reader.delete_entry((feed_url, entry_id))
|
|
77
76
|
except EntryNotFoundError:
|
|
78
77
|
old_content = ''
|
|
79
78
|
|
|
@@ -91,7 +90,8 @@ def save_output(reader, now, config, command_path, output):
|
|
|
91
90
|
title=title,
|
|
92
91
|
updated=now,
|
|
93
92
|
content=[dict(type='text/plain', value=''.join(parts))],
|
|
94
|
-
)
|
|
93
|
+
),
|
|
94
|
+
overwrite=True,
|
|
95
95
|
)
|
|
96
96
|
reader.mark_entry_as_read((feed_url, entry_id))
|
|
97
97
|
|
|
@@ -41,7 +41,7 @@ if debug: # pragma: no cover
|
|
|
41
41
|
stmt = data.pop('stmt', None)
|
|
42
42
|
print('STORAGE_DEBUG', json.dumps(data), file=sys.stderr)
|
|
43
43
|
if stmt:
|
|
44
|
-
print(stmt)
|
|
44
|
+
print(stmt, file=sys.stderr)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
wrap_exceptions = partial(_sqlite_utils.wrap_exceptions, StorageError)
|
|
@@ -55,7 +55,7 @@ class EntriesMixin(StorageBase):
|
|
|
55
55
|
def get_entries(
|
|
56
56
|
self,
|
|
57
57
|
filter: EntryFilter = EntryFilter(), # noqa: B008
|
|
58
|
-
sort: EntrySort =
|
|
58
|
+
sort: EntrySort = EntrySort.RECENT,
|
|
59
59
|
limit: int | None = None,
|
|
60
60
|
starting_after: tuple[str, str] | None = None,
|
|
61
61
|
) -> Iterable[Entry]:
|
|
@@ -64,7 +64,7 @@ class EntriesMixin(StorageBase):
|
|
|
64
64
|
partial(get_entries_query, filter, sort),
|
|
65
65
|
row_factory=entry_factory,
|
|
66
66
|
)
|
|
67
|
-
if sort !=
|
|
67
|
+
if sort != EntrySort.RANDOM:
|
|
68
68
|
last = self.get_entry_last(sort, starting_after) if starting_after else None
|
|
69
69
|
return paginated_query(limit, last)
|
|
70
70
|
else:
|
|
@@ -571,7 +571,7 @@ RECENT_SORT_KEY = SortKey(
|
|
|
571
571
|
desc=True,
|
|
572
572
|
)
|
|
573
573
|
|
|
574
|
-
ENTRY_SORT_KEYS = {
|
|
574
|
+
ENTRY_SORT_KEYS = {EntrySort.RECENT: RECENT_SORT_KEY}
|
|
575
575
|
|
|
576
576
|
|
|
577
577
|
def entries_recent_sort(
|
|
@@ -597,9 +597,9 @@ def entries_random_sort(query: Query) -> None:
|
|
|
597
597
|
query.ORDER_BY("random()")
|
|
598
598
|
|
|
599
599
|
|
|
600
|
-
ENTRIES_SORT: dict[
|
|
601
|
-
|
|
602
|
-
|
|
600
|
+
ENTRIES_SORT: dict[EntrySort, Callable[[Query], None]] = {
|
|
601
|
+
EntrySort.RECENT: entries_recent_sort,
|
|
602
|
+
EntrySort.RANDOM: entries_random_sort,
|
|
603
603
|
}
|
|
604
604
|
|
|
605
605
|
|
|
@@ -107,7 +107,7 @@ class FeedsMixin(StorageBase):
|
|
|
107
107
|
def get_feeds(
|
|
108
108
|
self,
|
|
109
109
|
filter: FeedFilter = FeedFilter(), # noqa: B008
|
|
110
|
-
sort: FeedSort =
|
|
110
|
+
sort: FeedSort = FeedSort.TITLE,
|
|
111
111
|
limit: int | None = None,
|
|
112
112
|
starting_after: str | None = None,
|
|
113
113
|
) -> Iterable[Feed]:
|
|
@@ -335,8 +335,10 @@ def feed_factory(row: tuple[Any, ...]) -> Feed:
|
|
|
335
335
|
FEED_SORT_KEYS = {
|
|
336
336
|
# values must be non-null, see #203 for details.
|
|
337
337
|
# url at the end makes the order deterministic.
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
FeedSort.TITLE: SortKey(
|
|
339
|
+
("kinda_title", "lower(coalesce(user_title, title, ''))"), "url"
|
|
340
|
+
),
|
|
341
|
+
FeedSort.ADDED: SortKey("added", "url", desc=True),
|
|
340
342
|
}
|
|
341
343
|
|
|
342
344
|
|
|
@@ -358,7 +360,7 @@ def feed_filter(query: Query, filter: FeedFilter) -> dict[str, Any]:
|
|
|
358
360
|
if new is not None:
|
|
359
361
|
query.WHERE(f"last_retrieved is {'' if new else 'NOT'} NULL")
|
|
360
362
|
if update_after is not None:
|
|
361
|
-
query.WHERE("update_after is NULL or update_after <= :update_after")
|
|
363
|
+
query.WHERE("(update_after is NULL or update_after <= :update_after)")
|
|
362
364
|
context.update(update_after=adapt_datetime(update_after))
|
|
363
365
|
|
|
364
366
|
return context
|
|
@@ -28,8 +28,9 @@ from ..exceptions import SearchError
|
|
|
28
28
|
from ..exceptions import SearchNotEnabledError
|
|
29
29
|
from ..types import EntrySearchCounts
|
|
30
30
|
from ..types import EntrySearchResult
|
|
31
|
+
from ..types import EntrySearchSort
|
|
32
|
+
from ..types import EntrySort
|
|
31
33
|
from ..types import HighlightedString
|
|
32
|
-
from ..types import SearchSortOrder
|
|
33
34
|
from . import _entries
|
|
34
35
|
from . import _html_utils
|
|
35
36
|
from . import _sqlite_utils
|
|
@@ -425,7 +426,7 @@ class Search:
|
|
|
425
426
|
self,
|
|
426
427
|
query: str,
|
|
427
428
|
filter: EntryFilter = EntryFilter(), # noqa: B008
|
|
428
|
-
sort:
|
|
429
|
+
sort: EntrySearchSort = EntrySearchSort.RELEVANT,
|
|
429
430
|
limit: int | None = None,
|
|
430
431
|
starting_after: tuple[str, str] | None = None,
|
|
431
432
|
) -> Iterable[EntrySearchResult]:
|
|
@@ -457,13 +458,13 @@ class Search:
|
|
|
457
458
|
)
|
|
458
459
|
|
|
459
460
|
# TODO: dupe of at least Storage.get_entries(), maybe deduplicate
|
|
460
|
-
if sort !=
|
|
461
|
+
if sort != EntrySearchSort.RANDOM:
|
|
461
462
|
last = None
|
|
462
463
|
if starting_after:
|
|
463
|
-
if sort ==
|
|
464
|
+
if sort == EntrySearchSort.RELEVANT:
|
|
464
465
|
last = self.search_entry_last(query, starting_after)
|
|
465
|
-
elif sort ==
|
|
466
|
-
last = self.storage.get_entry_last(
|
|
466
|
+
elif sort == EntrySearchSort.RECENT:
|
|
467
|
+
last = self.storage.get_entry_last(EntrySort.RECENT, starting_after)
|
|
467
468
|
else:
|
|
468
469
|
assert False, "shouldn't get here" # noqa: B011; # pragma: no cover
|
|
469
470
|
|
|
@@ -527,7 +528,7 @@ class Search:
|
|
|
527
528
|
|
|
528
529
|
|
|
529
530
|
def make_search_entries_query(
|
|
530
|
-
filter: EntryFilter, sort:
|
|
531
|
+
filter: EntryFilter, sort: EntrySearchSort
|
|
531
532
|
) -> tuple[Query, dict[str, Any]]:
|
|
532
533
|
search = (
|
|
533
534
|
Query()
|
|
@@ -585,12 +586,12 @@ def relevant_sort(query: Query) -> None:
|
|
|
585
586
|
)
|
|
586
587
|
|
|
587
588
|
|
|
588
|
-
SEARCH_ENTRIES_SORT: dict[
|
|
589
|
-
|
|
590
|
-
|
|
589
|
+
SEARCH_ENTRIES_SORT: dict[EntrySearchSort, Callable[[Query], None]] = {
|
|
590
|
+
EntrySearchSort.RELEVANT: relevant_sort,
|
|
591
|
+
EntrySearchSort.RECENT: partial(
|
|
591
592
|
_entries.entries_recent_sort, keyword='HAVING', id_prefix='search._'
|
|
592
593
|
),
|
|
593
|
-
|
|
594
|
+
EntrySearchSort.RANDOM: _entries.entries_random_sort,
|
|
594
595
|
}
|
|
595
596
|
|
|
596
597
|
|
|
@@ -34,6 +34,7 @@ from collections.abc import Iterable
|
|
|
34
34
|
from dataclasses import dataclass
|
|
35
35
|
from typing import Any
|
|
36
36
|
from typing import NamedTuple
|
|
37
|
+
from typing import Self
|
|
37
38
|
from typing import TYPE_CHECKING
|
|
38
39
|
from typing import TypeVar
|
|
39
40
|
from typing import Union
|
|
@@ -42,8 +43,6 @@ from typing import Union
|
|
|
42
43
|
if TYPE_CHECKING: # pragma: no cover
|
|
43
44
|
import sqlite3
|
|
44
45
|
|
|
45
|
-
from typing_extensions import Self
|
|
46
|
-
|
|
47
46
|
|
|
48
47
|
_T = TypeVar('_T')
|
|
49
48
|
_U = TypeVar('_U')
|
|
@@ -197,7 +196,7 @@ class BaseQuery:
|
|
|
197
196
|
|
|
198
197
|
yield '\n'
|
|
199
198
|
|
|
200
|
-
_indent = functools.partial(textwrap.indent, prefix=' ')
|
|
199
|
+
_indent = staticmethod(functools.partial(textwrap.indent, prefix=' '))
|
|
201
200
|
|
|
202
201
|
|
|
203
202
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -95,8 +95,17 @@ def wrap_exceptions(
|
|
|
95
95
|
can only be fixed by the user (e.g. access permission denied), and aren't
|
|
96
96
|
domain-related (those should have other custom exceptions).
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
We intentionally rely on the error message, because:
|
|
99
|
+
|
|
100
|
+
* the DB-API exceptions are somewhat fuzzy in their meaning
|
|
101
|
+
* we can't access the SQLite result code prior to Python 3.11
|
|
102
|
+
* even having the primary result code (added in 3.11):
|
|
103
|
+
* it's generic SQLITE_ERROR for a lot of errors we care about
|
|
104
|
+
("no such ...", "... already exists", invalid FTS5 queries)
|
|
105
|
+
* we need the table name so we don't accidentally shadow bugs
|
|
106
|
+
* we need the extended result code for the same reason
|
|
107
|
+
(e.g. SQLITE_CONSTRAINT vs SQLITE_CONSTRAINT_FOREIGNKEY)
|
|
108
|
+
* ProgrammingError usually does not have an error code
|
|
100
109
|
|
|
101
110
|
Full discussion at https://github.com/lemon24/reader/issues/21
|
|
102
111
|
|
|
@@ -20,7 +20,7 @@ from typing import NamedTuple
|
|
|
20
20
|
from typing import overload
|
|
21
21
|
from typing import Protocol
|
|
22
22
|
from typing import runtime_checkable
|
|
23
|
-
from typing import
|
|
23
|
+
from typing import Self
|
|
24
24
|
from typing import TypeVar
|
|
25
25
|
from typing import Union
|
|
26
26
|
|
|
@@ -40,6 +40,7 @@ from .types import EntryCounts
|
|
|
40
40
|
from .types import EntryInput
|
|
41
41
|
from .types import EntrySearchCounts
|
|
42
42
|
from .types import EntrySearchResult
|
|
43
|
+
from .types import EntrySearchSort
|
|
43
44
|
from .types import EntrySort
|
|
44
45
|
from .types import EntrySource
|
|
45
46
|
from .types import ExceptionInfo
|
|
@@ -51,15 +52,10 @@ from .types import JSONType
|
|
|
51
52
|
from .types import MISSING
|
|
52
53
|
from .types import MissingType
|
|
53
54
|
from .types import ResourceId
|
|
54
|
-
from .types import SearchSortOrder
|
|
55
55
|
from .types import TagFilterInput
|
|
56
56
|
from .types import TristateFilterInput
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
if TYPE_CHECKING: # pragma: no cover
|
|
60
|
-
from typing_extensions import Self
|
|
61
|
-
|
|
62
|
-
|
|
63
59
|
log = logging.getLogger("reader")
|
|
64
60
|
|
|
65
61
|
# Private API
|
|
@@ -1268,7 +1264,7 @@ class SearchType(Protocol): # pragma: no cover
|
|
|
1268
1264
|
query: str,
|
|
1269
1265
|
/,
|
|
1270
1266
|
filter: EntryFilter,
|
|
1271
|
-
sort:
|
|
1267
|
+
sort: EntrySearchSort,
|
|
1272
1268
|
limit: int | None,
|
|
1273
1269
|
starting_after: tuple[str, str] | None,
|
|
1274
1270
|
) -> Iterable[EntrySearchResult]:
|
|
@@ -430,8 +430,7 @@ class Pipeline:
|
|
|
430
430
|
config: UpdateConfig,
|
|
431
431
|
result: ParseResult[FeedForUpdate, ParseError],
|
|
432
432
|
) -> tuple[str, UpdatedFeed | None | Exception]:
|
|
433
|
-
feed = result
|
|
434
|
-
value = result.value
|
|
433
|
+
feed, value, _ = result
|
|
435
434
|
|
|
436
435
|
# TODO: don't duplicate code from update()
|
|
437
436
|
# TODO: the feed tag value should come from get_feeds_for_update()
|