reader 3.14__tar.gz → 3.16__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.14 → reader-3.16}/CHANGES.rst +92 -1
- {reader-3.14/src/reader.egg-info → reader-3.16}/PKG-INFO +1 -1
- {reader-3.14 → reader-3.16}/docs/api.rst +13 -1
- {reader-3.14 → reader-3.16}/docs/dev.rst +36 -14
- {reader-3.14 → reader-3.16}/docs/guide.rst +28 -20
- {reader-3.14 → reader-3.16}/docs/install.rst +14 -0
- {reader-3.14 → reader-3.16}/docs/internal.rst +34 -18
- {reader-3.14 → reader-3.16}/docs/plugins.rst +18 -1
- {reader-3.14 → reader-3.16}/docs/tutorial.rst +1 -1
- reader-3.16/examples/feed_slugs.py +95 -0
- {reader-3.14 → reader-3.16}/scripts/backup.sh +9 -6
- {reader-3.14 → reader-3.16}/src/reader/__init__.py +2 -1
- {reader-3.14 → reader-3.16}/src/reader/_app/__init__.py +15 -19
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/entries.html +4 -1
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/entry.html +1 -1
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/macros.html +2 -8
- {reader-3.14 → reader-3.16}/src/reader/_cli.py +13 -20
- {reader-3.14 → reader-3.16}/src/reader/_parser/__init__.py +187 -83
- {reader-3.14 → reader-3.16}/src/reader/_parser/_http_utils.py +1 -0
- {reader-3.14 → reader-3.16}/src/reader/_parser/_lazy.py +166 -137
- {reader-3.14 → reader-3.16}/src/reader/_parser/feedparser.py +29 -3
- {reader-3.14 → reader-3.16}/src/reader/_parser/file.py +2 -6
- reader-3.16/src/reader/_parser/http.py +107 -0
- {reader-3.14 → reader-3.16}/src/reader/_parser/jsonfeed.py +1 -1
- {reader-3.14 → reader-3.16}/src/reader/_parser/requests/__init__.py +2 -0
- {reader-3.14 → reader-3.16}/src/reader/_parser/requests/_lazy.py +26 -6
- {reader-3.14 → reader-3.16}/src/reader/_storage/_base.py +13 -12
- {reader-3.14 → reader-3.16}/src/reader/_storage/_entries.py +34 -3
- {reader-3.14 → reader-3.16}/src/reader/_storage/_feeds.py +10 -9
- {reader-3.14 → reader-3.16}/src/reader/_storage/_schema.py +57 -3
- {reader-3.14 → reader-3.16}/src/reader/_storage/_search.py +50 -13
- {reader-3.14 → reader-3.16}/src/reader/_storage/_sqlite_utils.py +10 -1
- {reader-3.14 → reader-3.16}/src/reader/_storage/_tags.py +158 -52
- {reader-3.14 → reader-3.16}/src/reader/_types.py +108 -82
- {reader-3.14 → reader-3.16}/src/reader/_update.py +57 -54
- {reader-3.14 → reader-3.16}/src/reader/_utils.py +16 -3
- {reader-3.14 → reader-3.16}/src/reader/core.py +126 -44
- {reader-3.14 → reader-3.16}/src/reader/exceptions.py +6 -10
- {reader-3.14 → reader-3.16}/src/reader/plugins/__init__.py +1 -1
- {reader-3.14 → reader-3.16}/src/reader/plugins/entry_dedupe.py +1 -1
- {reader-3.14 → reader-3.16}/src/reader/plugins/mark_as_read.py +1 -1
- {reader-3.14 → reader-3.16}/src/reader/plugins/readtime.py +1 -1
- {reader-3.14 → reader-3.16}/src/reader/types.py +70 -5
- reader-3.16/src/reader/utils.py +78 -0
- {reader-3.14 → reader-3.16/src/reader.egg-info}/PKG-INFO +1 -1
- {reader-3.14 → reader-3.16}/src/reader.egg-info/SOURCES.txt +4 -1
- {reader-3.14 → reader-3.16}/tests/data/full.atom +38 -0
- {reader-3.14 → reader-3.16}/tests/data/full.atom.py +31 -0
- {reader-3.14 → reader-3.16}/tests/data/full.rss +14 -0
- {reader-3.14 → reader-3.16}/tests/data/full.rss.py +16 -0
- {reader-3.14 → reader-3.16}/tests/data/relative.rss +1 -1
- {reader-3.14 → reader-3.16}/tests/data/relative.rss.py +1 -1
- {reader-3.14 → reader-3.16}/tests/fakeparser.py +15 -16
- {reader-3.14 → reader-3.16}/tests/test__types.py +5 -2
- {reader-3.14 → reader-3.16}/tests/test_bench.py +1 -0
- {reader-3.14 → reader-3.16}/tests/test_cli.py +23 -8
- {reader-3.14 → reader-3.16}/tests/test_lazy_imports.py +11 -1
- {reader-3.14 → reader-3.16}/tests/test_parser.py +220 -89
- {reader-3.14 → reader-3.16}/tests/test_plugins_sqlite_releases.py +4 -2
- {reader-3.14 → reader-3.16}/tests/test_reader.py +161 -7
- {reader-3.14 → reader-3.16}/tests/test_reader_filter.py +5 -1
- {reader-3.14 → reader-3.16}/tests/test_reader_integration.py +5 -5
- {reader-3.14 → reader-3.16}/tests/test_reader_private.py +26 -20
- {reader-3.14 → reader-3.16}/tests/test_reader_search.py +90 -0
- reader-3.16/tests/test_reader_utils.py +59 -0
- {reader-3.14 → reader-3.16}/tests/test_storage.py +1 -6
- reader-3.14/src/reader/_parser/http.py +0 -108
- {reader-3.14 → reader-3.16}/LICENSE +0 -0
- {reader-3.14 → reader-3.16}/MANIFEST.in +0 -0
- {reader-3.14 → reader-3.16}/README.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/Makefile +0 -0
- {reader-3.14 → reader-3.16}/docs/app.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/changelog.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/cli.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/conf.py +0 -0
- {reader-3.14 → reader-3.16}/docs/config.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/contributing.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/dev-app.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/images/redesign-01.png +0 -0
- {reader-3.14 → reader-3.16}/docs/index.rst +0 -0
- {reader-3.14 → reader-3.16}/docs/make.bat +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/dillo.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/entries-feed.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/entries.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/entry-one.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/entry-two.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/feeds.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/lynx.png +0 -0
- {reader-3.14 → reader-3.16}/docs/screenshots/search.png +0 -0
- {reader-3.14 → reader-3.16}/docs/why.rst +0 -0
- {reader-3.14 → reader-3.16}/examples/config.yaml +0 -0
- {reader-3.14 → reader-3.16}/examples/custom_headers.py +0 -0
- {reader-3.14 → reader-3.16}/examples/parser_only.py +0 -0
- {reader-3.14 → reader-3.16}/examples/podcast.py +0 -0
- {reader-3.14 → reader-3.16}/examples/terminal.py +0 -0
- {reader-3.14 → reader-3.16}/pyproject.toml +0 -0
- {reader-3.14 → reader-3.16}/run.sh +0 -0
- {reader-3.14 → reader-3.16}/scripts/.coverage +0 -0
- {reader-3.14 → reader-3.16}/scripts/bench.py +0 -0
- {reader-3.14 → reader-3.16}/scripts/debug_storage_stats.py +0 -0
- {reader-3.14 → reader-3.16}/scripts/generate_import_all.py +0 -0
- {reader-3.14 → reader-3.16}/scripts/jscontrols.html +0 -0
- {reader-3.14 → reader-3.16}/scripts/jscontrols.py +0 -0
- {reader-3.14 → reader-3.16}/scripts/lines.sh +0 -0
- {reader-3.14 → reader-3.16}/scripts/release.py +0 -0
- {reader-3.14 → reader-3.16}/setup.cfg +0 -0
- {reader-3.14 → reader-3.16}/src/reader/__main__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/api_thing.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/cli.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/static/controls.js +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/static/style.css +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/add_entry.html +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/metadata.html +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/templates/tags.html +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_app/wsgi.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_config.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_hash_utils.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/cli_status.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/enclosure_tags.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/preview_feed_list.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/share.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/sqlite_releases.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/templates/preview_feed_list.html +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_plugins/timer.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_storage/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_storage/_changes.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_storage/_html_utils.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/plugins/ua_fallback.py +0 -0
- {reader-3.14 → reader-3.16}/src/reader/py.typed +0 -0
- {reader-3.14 → reader-3.16}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.14 → reader-3.16}/src/reader.egg-info/requires.txt +0 -0
- {reader-3.14 → reader-3.16}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.14 → reader-3.16}/tests/conftest.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/10.json +0 -0
- {reader-3.14 → reader-3.16}/tests/data/10.json.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/custom +0 -0
- {reader-3.14 → reader-3.16}/tests/data/empty.atom +0 -0
- {reader-3.14 → reader-3.16}/tests/data/empty.atom.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/empty.json +0 -0
- {reader-3.14 → reader-3.16}/tests/data/empty.json.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/empty.rss +0 -0
- {reader-3.14 → reader-3.16}/tests/data/empty.rss.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/full.json +0 -0
- {reader-3.14 → reader-3.16}/tests/data/full.json.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/invalid.json +0 -0
- {reader-3.14 → reader-3.16}/tests/data/invalid.json.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/relative.atom +0 -0
- {reader-3.14 → reader-3.16}/tests/data/relative.atom.py +0 -0
- {reader-3.14 → reader-3.16}/tests/data/sqlite_releases.html +0 -0
- {reader-3.14 → reader-3.16}/tests/data/unknown.json +0 -0
- {reader-3.14 → reader-3.16}/tests/data/unknown.json.py +0 -0
- {reader-3.14 → reader-3.16}/tests/reader_methods.py +0 -0
- {reader-3.14 → reader-3.16}/tests/reader_test_plugins/__init__.py +0 -0
- {reader-3.14 → reader-3.16}/tests/reader_test_plugins/good.py +0 -0
- {reader-3.14 → reader-3.16}/tests/reader_test_plugins/init_error.py +0 -0
- {reader-3.14 → reader-3.16}/tests/reader_test_plugins/missing_dependency.py +0 -0
- {reader-3.14 → reader-3.16}/tests/reader_test_plugins/missing_entry_point.py +0 -0
- /reader-3.14/tests/test_utils.py → /reader-3.16/tests/test__utils.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_app.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_app_wsgi.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_changes.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_config.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_exceptions.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_hash_utils.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_html_utils.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_cli_status.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_entry_dedupe.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_readtime.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_context.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_counts.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_deprecations.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_hooks.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_plugins.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_sort.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_reader_update.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_search.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_sql_utils.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_sqlite_utils.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_tags.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_test_utils.py +0 -0
- {reader-3.14 → reader-3.16}/tests/test_types.py +0 -0
- {reader-3.14 → reader-3.16}/tests/utils.py +0 -0
- {reader-3.14 → reader-3.16}/tox.ini +0 -0
|
@@ -3,7 +3,96 @@ Changelog
|
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
5
|
.. module:: reader
|
|
6
|
-
:
|
|
6
|
+
:no-index:
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Version 3.16
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2024-12-08
|
|
13
|
+
|
|
14
|
+
* Parse and store :attr:`Entry.source` for Atom and RSS feeds. (:issue:`267`)
|
|
15
|
+
|
|
16
|
+
* Add :attr:`Entry.feed_resolved_title` and :attr:`Feed.resolved_title` properties.
|
|
17
|
+
* The ``feed`` search column now indexes :attr:`Entry.feed_resolved_title`,
|
|
18
|
+
instead of feed :attr:`~Feed.user_title` or :attr:`~Feed.title`.
|
|
19
|
+
* Allow filtering entries by the entry source.
|
|
20
|
+
|
|
21
|
+
* Add :func:`reader.utils.archive_entries` for
|
|
22
|
+
copying entries to an "archived" feed.
|
|
23
|
+
(:issue:`290`)
|
|
24
|
+
|
|
25
|
+
* Add :meth:`~Reader.copy_entry`.
|
|
26
|
+
* Allow archiving entries from the web app.
|
|
27
|
+
|
|
28
|
+
* Fix bug causing :class:`Reader` operations
|
|
29
|
+
from a thread other than the one that created the instance
|
|
30
|
+
to happen with foreign key constraint enforcement disabled
|
|
31
|
+
(e.g. deleting a feed from another thread would not delete its entries).
|
|
32
|
+
|
|
33
|
+
This bug exists since using :class:`Reader` instances from other threads
|
|
34
|
+
became allowed in `2.15 <Version 2.15_>`_.
|
|
35
|
+
|
|
36
|
+
Serving the web application with ``python -m reader serve``
|
|
37
|
+
is known to be affected.
|
|
38
|
+
Serving it with uWSGI without threads (the default)
|
|
39
|
+
should not be affected.
|
|
40
|
+
|
|
41
|
+
.. attention::
|
|
42
|
+
|
|
43
|
+
**Your database may be in an inconsistent state because of this bug.**
|
|
44
|
+
|
|
45
|
+
It is recommended you run `PRAGMA foreign_key_check`_ on your database.
|
|
46
|
+
|
|
47
|
+
If you are upgrading from a version prior to 3.16
|
|
48
|
+
(i.e. were not using a pre-release version of *reader*),
|
|
49
|
+
the migration will do so for you.
|
|
50
|
+
If there are inconsistencies, you will get this error::
|
|
51
|
+
|
|
52
|
+
StorageError: integrity error: after migrating to version 43:
|
|
53
|
+
integrity error: FOREIGN KEY constraint failed
|
|
54
|
+
|
|
55
|
+
* Fix :meth:`~Reader.enable_search` / :meth:`~Reader.update_search`
|
|
56
|
+
not working when the search database is missing but change tracking is enabled
|
|
57
|
+
(e.g. when restoring the main database from backup).
|
|
58
|
+
(:issue:`362`)
|
|
59
|
+
|
|
60
|
+
.. _PRAGMA foreign_key_check: https://www.sqlite.org/pragma.html#pragma_foreign_key_check
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Version 3.15
|
|
64
|
+
------------
|
|
65
|
+
|
|
66
|
+
Released 2024-11-11
|
|
67
|
+
|
|
68
|
+
* During :ref:`scheduled updates <scheduled>`,
|
|
69
|
+
honor the Retry-After HTTP header sent with
|
|
70
|
+
429 Too Many Requests or 503 Service Unavailable responses.
|
|
71
|
+
(:issue:`307`)
|
|
72
|
+
* Optimize OR-only :meth:`~Reader.get_feeds()` and :meth:`~Reader.get_entries()`
|
|
73
|
+
tag filters (e.g. ``[['one', ...]]``). (:issue:`359`)
|
|
74
|
+
* Expose :data:`reader.core.DEFAULT_RESERVED_NAME_SCHEME`. (:issue:`287`)
|
|
75
|
+
|
|
76
|
+
* Update the (unstable) parser API to expose HTTP information to the updater.
|
|
77
|
+
(:issue:`307`)
|
|
78
|
+
|
|
79
|
+
* The :class:`.RetrieverType` protocol used by retrievers changed
|
|
80
|
+
(new return type, allow storing arbitrary caching data via
|
|
81
|
+
:attr:`~.RetrievedFeed.caching_info`).
|
|
82
|
+
* The ``HTTPAcceptParserType`` was renamed to :class:`.AcceptParserType`.
|
|
83
|
+
|
|
84
|
+
* Allow :ref:`using the installed feedparser <no-vendored-feedparser>`
|
|
85
|
+
instead of the one vendored by *reader*.
|
|
86
|
+
This is useful for working around issues in the vendored feedparser,
|
|
87
|
+
like the libxml2 <=2.13.3 incompatibility reported in :issue:`350`;
|
|
88
|
+
thanks to `Maks Verver`_ for reporting,
|
|
89
|
+
root causing, and following up with both dependencies.
|
|
90
|
+
|
|
91
|
+
* Fix a number of brittle / broken tests.
|
|
92
|
+
Thanks to `Maks Verver`_ for the issues and fixes.
|
|
93
|
+
(:issue:`348`, :issue:`349`, :issue:`355`)
|
|
94
|
+
|
|
95
|
+
.. _Maks Verver: https://github.com/maksverver
|
|
7
96
|
|
|
8
97
|
|
|
9
98
|
Version 3.14
|
|
@@ -650,6 +739,8 @@ Released 2022-03-12
|
|
|
650
739
|
Fix broken error flashing.
|
|
651
740
|
|
|
652
741
|
|
|
742
|
+
.. _version 2.9:
|
|
743
|
+
|
|
653
744
|
Version 2.9
|
|
654
745
|
-----------
|
|
655
746
|
|
|
@@ -16,7 +16,7 @@ Most of *reader*'s functionality can be accessed through a :class:`Reader` insta
|
|
|
16
16
|
|
|
17
17
|
Split this into sub-sections.
|
|
18
18
|
|
|
19
|
-
.. autofunction:: make_reader
|
|
19
|
+
.. autofunction:: make_reader
|
|
20
20
|
|
|
21
21
|
.. autoclass:: Reader(...)
|
|
22
22
|
:members:
|
|
@@ -40,6 +40,9 @@ Data objects
|
|
|
40
40
|
.. autoclass:: Enclosure
|
|
41
41
|
:members:
|
|
42
42
|
|
|
43
|
+
.. autoclass:: EntrySource
|
|
44
|
+
:members:
|
|
45
|
+
|
|
43
46
|
.. autoclass:: EntrySearchResult
|
|
44
47
|
:members:
|
|
45
48
|
|
|
@@ -184,4 +187,13 @@ Type aliases
|
|
|
184
187
|
Constants
|
|
185
188
|
---------
|
|
186
189
|
|
|
190
|
+
.. autodata:: reader.core.DEFAULT_RESERVED_NAME_SCHEME
|
|
191
|
+
|
|
187
192
|
.. autodata:: reader.plugins.DEFAULT_PLUGINS
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
Utilities
|
|
197
|
+
---------
|
|
198
|
+
|
|
199
|
+
.. autofunction:: reader.utils.archive_entries
|
|
@@ -3,7 +3,7 @@ Development
|
|
|
3
3
|
===========
|
|
4
4
|
|
|
5
5
|
.. module:: reader
|
|
6
|
-
:
|
|
6
|
+
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
Development should follow a problem-solution_ approach.
|
|
@@ -34,6 +34,9 @@ but I will prioritize supporting :doc:`contributors <contributing>`
|
|
|
34
34
|
|
|
35
35
|
* OPML support, :issue:`165`
|
|
36
36
|
* :ref:`deleting entries <deleting entries>`
|
|
37
|
+
|
|
38
|
+
* archiving important entries of deleted feeds, :issue:`290`
|
|
39
|
+
|
|
37
40
|
* :ref:`feed interaction statistics <counts api>`
|
|
38
41
|
|
|
39
42
|
* security
|
|
@@ -69,7 +72,6 @@ but I will prioritize supporting :doc:`contributors <contributing>`
|
|
|
69
72
|
|
|
70
73
|
* add more fields to data objects
|
|
71
74
|
|
|
72
|
-
* entry source, :issue:`276`
|
|
73
75
|
* extra data, as an escape hatch, :issue:`277`
|
|
74
76
|
|
|
75
77
|
* :ref:`multiple storage implementations <multiple storage implementations>`
|
|
@@ -378,8 +380,14 @@ Requests session plugins:
|
|
|
378
380
|
Retriever / parser split:
|
|
379
381
|
|
|
380
382
|
* :issue:`205#issuecomment-766321855`
|
|
381
|
-
* split exception hierarchy (not implemented as of 3.
|
|
383
|
+
* split exception hierarchy (not implemented as of 3.15): :issue:`218#issuecomment-1687094315`
|
|
384
|
+
* API overview as of 3.14, meant to show the dataflow: :issue:`307#issuecomment-2266647310`
|
|
385
|
+
* ~ideal API (mostly implemented in 3.15): :issue:`307#issuecomment-2281797898`
|
|
382
386
|
|
|
387
|
+
* exposes HTTP information (so it can be used by the updater)
|
|
388
|
+
* introduces ~internal RetrieveError and NotModified :exc:`ParseError` subclasses;
|
|
389
|
+
notably, this doesn't really follow the split exception hierarchy mentioned above,
|
|
390
|
+
and is only meant to surface HTTP information in error cases
|
|
383
391
|
|
|
384
392
|
Alternative feed parsers:
|
|
385
393
|
|
|
@@ -398,17 +406,13 @@ Lessons learned from the :ref:`Twitter` plugin:
|
|
|
398
406
|
:meth:`~reader._parser.ParserType` being generic.
|
|
399
407
|
|
|
400
408
|
* It is useful for a Retriever to store arbitrary caching data;
|
|
401
|
-
the plugin (mis)used
|
|
402
|
-
:attr:`~reader._parser.RetrieveResult.http_etag`
|
|
409
|
+
the plugin (mis)used ``RetrieveResult.http_etag``
|
|
403
410
|
to store the (integer) id of the newest tweet in the thread.
|
|
404
411
|
|
|
405
|
-
|
|
406
|
-
"arbitrary caching data" attribute;
|
|
407
|
-
also see `this comment <https://github.com/lemon24/reader/blob/0ada24bf4e65c69c3028641bada8eaeabbe02754/src/reader/_parser.py#L569>`_.
|
|
412
|
+
Update: This was formalized as :attr:`.RetrievedFeed.caching_info` in 3.15.
|
|
408
413
|
|
|
409
414
|
* It is useful for a Retriever to pass arbitrary data to itself;
|
|
410
|
-
the plugin (mis)used
|
|
411
|
-
:attr:`~reader._types.FeedForUpdate.http_etag` to pass from
|
|
415
|
+
the plugin (mis)used ``FeedForUpdate.http_etag`` to pass from
|
|
412
416
|
:meth:`~reader._parser.FeedForUpdateRetrieverType.process_feed_for_update`
|
|
413
417
|
to :meth:`~reader._parser.RetrieverType.__call__`:
|
|
414
418
|
|
|
@@ -510,6 +514,16 @@ From the initial issue:
|
|
|
510
514
|
* use cases: :issue:`149#issuecomment-700066794`
|
|
511
515
|
* initial requirements: :issue:`149#issuecomment-700532183`
|
|
512
516
|
|
|
517
|
+
Splitting a feed into two feeds is discussed in :issue:`221`.
|
|
518
|
+
The main takeaway at the time (2021) was that
|
|
519
|
+
not all entry data can be retrieved through the storage API,
|
|
520
|
+
so it's not fully possible to round-trip an entry.
|
|
521
|
+
Based on a quick 2024 survey,
|
|
522
|
+
the only affected attributes
|
|
523
|
+
are :attr:`~.EntryUpdateIntent.feed_order` (can't get; not critical)
|
|
524
|
+
and :attr:`~Entry.original_feed_url` (can't set; will be required for
|
|
525
|
+
archiving important entries of deleted feeds, :issue:`290`).
|
|
526
|
+
|
|
513
527
|
|
|
514
528
|
Resource tags / metadata
|
|
515
529
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -684,6 +698,14 @@ Update hook error handling:
|
|
|
684
698
|
* expected behavior: :issue:`218#issuecomment-1595691410`, :issue:`218#issuecomment-1666823222`
|
|
685
699
|
* update hook pseudocode + exception hierarchy: :issue:`218#issuecomment-1666869215`
|
|
686
700
|
|
|
701
|
+
Considerations on using pluggy: :issue:`329`,
|
|
702
|
+
`how adapting reader use cases to the pluggy model may work <https://github.com/pytest-dev/pluggy/issues/151#issuecomment-2421648901>`_.
|
|
703
|
+
Conclusions:
|
|
704
|
+
|
|
705
|
+
even if using pluggy would require some work-arounds for calling, I think the collection / discovery functionality is still more than worth it
|
|
706
|
+
|
|
707
|
+
long-term and/or if reader becomes really popular, pluggy is definitely the way to go
|
|
708
|
+
|
|
687
709
|
|
|
688
710
|
Reserved names
|
|
689
711
|
~~~~~~~~~~~~~~
|
|
@@ -698,16 +720,16 @@ Wrapping underlying storage exceptions
|
|
|
698
720
|
|
|
699
721
|
Which exception to wrap, and which not: :issue:`21#issuecomment-365442439`.
|
|
700
722
|
|
|
701
|
-
In version 3.10 (November 2023), all internal APIs were changed
|
|
702
|
-
to use timezone-aware datetimes, with the timezone set to UTC,
|
|
703
|
-
in preparation for support for any timezone.
|
|
704
|
-
|
|
705
723
|
|
|
706
724
|
Timezone handling
|
|
707
725
|
~~~~~~~~~~~~~~~~~
|
|
708
726
|
|
|
709
727
|
Aware vs. naive, and what's needed to go fully aware: :issue:`233#issuecomment-881618002`.
|
|
710
728
|
|
|
729
|
+
In version 3.10 (November 2023), all internal APIs were changed
|
|
730
|
+
to use timezone-aware datetimes, with the timezone set to UTC,
|
|
731
|
+
in preparation for support for any timezone.
|
|
732
|
+
|
|
711
733
|
|
|
712
734
|
OPML support
|
|
713
735
|
~~~~~~~~~~~~
|
|
@@ -3,7 +3,7 @@ User guide
|
|
|
3
3
|
==========
|
|
4
4
|
|
|
5
5
|
.. module:: reader
|
|
6
|
-
:
|
|
6
|
+
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
This page gives a tour of *reader*'s features,
|
|
@@ -272,34 +272,42 @@ Scheduled updates
|
|
|
272
272
|
Because different feeds need to be updated at different rates,
|
|
273
273
|
*reader* also provides a mechanism for scheduling updates.
|
|
274
274
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
On every update, *reader* determines when a feed should be updated next
|
|
276
|
+
based on a configurable update interval;
|
|
277
|
+
:meth:`update_feeds(scheduled=True) <Reader.update_feeds>`
|
|
278
278
|
updates only the feeds that should be updated at or before the current time.
|
|
279
279
|
|
|
280
|
-
The
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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.
|
|
280
|
+
The interval can be **configured by the user**
|
|
281
|
+
through the ``.reader.update`` global tag,
|
|
282
|
+
and overridden for individual feeds through the matching feed tag;
|
|
283
|
+
if no configuration is provided, the default interval is one hour.
|
|
284
|
+
See :data:`~reader.types.UpdateConfig` for the config format.
|
|
287
285
|
|
|
288
|
-
|
|
286
|
+
In addition, the user can specify a jitter,
|
|
287
|
+
a random amount of time added to the interval
|
|
288
|
+
such that updates don't happen exactly at the the same time;
|
|
289
|
+
for example, with a one-hour interval, a jitter of 0.25 means
|
|
290
|
+
updates will occur any time in the first 15 minutes of each hour.
|
|
291
|
+
|
|
292
|
+
If the server sends the Retry-After HTTP header with
|
|
293
|
+
429 Too Many Requests or 503 Service Unavailable responses,
|
|
294
|
+
:meth:`update_feeds(scheduled=True) <Reader.update_feeds>` will honor it.
|
|
289
295
|
|
|
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
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
.. note::
|
|
298
|
+
|
|
299
|
+
As of |version|, there is no way to enforce a minimum update interval;
|
|
300
|
+
if you want feeds to be updated no more often than e.g. every hour,
|
|
301
|
+
you have to run :meth:`~Reader.update_feeds` no more often than every hour.
|
|
302
|
+
Please :ref:`open an issue <issues>` or :ref:`submit a pull request <prs>`
|
|
303
|
+
if you need a minimum update interval.
|
|
299
304
|
|
|
300
305
|
|
|
301
306
|
.. versionadded:: 3.13
|
|
302
307
|
|
|
308
|
+
.. versionchanged:: 3.15
|
|
309
|
+
Honor the Retry-After HTTP header.
|
|
310
|
+
|
|
303
311
|
|
|
304
312
|
Update status
|
|
305
313
|
~~~~~~~~~~~~~
|
|
@@ -27,6 +27,20 @@ These packages will be installed automatically when installing *reader*:
|
|
|
27
27
|
(at least SQLite 3.18 with the `JSON1`_ and `FTS5`_ extensions).
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
.. _no-vendored-feedparser:
|
|
31
|
+
|
|
32
|
+
.. note::
|
|
33
|
+
|
|
34
|
+
Because `feedparser`_ makes PyPI releases at a lower cadence,
|
|
35
|
+
*reader* uses a vendored version of feedparser's `develop`_ branch
|
|
36
|
+
by default since :ref:`version 2.9`.
|
|
37
|
+
To opt out of this behavior, and make *reader* use
|
|
38
|
+
the installed ``feedparser`` package,
|
|
39
|
+
set the ``READER_NO_VENDORED_FEEDPARSER`` environment variable to ``1``.
|
|
40
|
+
|
|
41
|
+
.. _develop: https://github.com/kurtmckee/feedparser
|
|
42
|
+
|
|
43
|
+
|
|
30
44
|
.. _optional dependencies:
|
|
31
45
|
|
|
32
46
|
Optional dependencies
|
|
@@ -45,7 +45,7 @@ Protocols
|
|
|
45
45
|
~~~~~~~~~
|
|
46
46
|
|
|
47
47
|
.. module:: reader._parser
|
|
48
|
-
:
|
|
48
|
+
:no-index:
|
|
49
49
|
|
|
50
50
|
.. autoclass:: FeedArgument
|
|
51
51
|
:members:
|
|
@@ -54,6 +54,14 @@ Protocols
|
|
|
54
54
|
:members:
|
|
55
55
|
:special-members: __call__
|
|
56
56
|
|
|
57
|
+
.. autoexception:: RetrieveError
|
|
58
|
+
:show-inheritance:
|
|
59
|
+
:members:
|
|
60
|
+
|
|
61
|
+
.. autoexception:: NotModified
|
|
62
|
+
:show-inheritance:
|
|
63
|
+
:members:
|
|
64
|
+
|
|
57
65
|
.. autoclass:: FeedForUpdateRetrieverType
|
|
58
66
|
:members:
|
|
59
67
|
:show-inheritance:
|
|
@@ -62,7 +70,7 @@ Protocols
|
|
|
62
70
|
:members:
|
|
63
71
|
:special-members: __call__
|
|
64
72
|
|
|
65
|
-
.. autoclass::
|
|
73
|
+
.. autoclass:: AcceptParserType
|
|
66
74
|
:members:
|
|
67
75
|
:show-inheritance:
|
|
68
76
|
|
|
@@ -72,7 +80,7 @@ Protocols
|
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
.. module:: reader._parser.requests
|
|
75
|
-
:
|
|
83
|
+
:no-index:
|
|
76
84
|
|
|
77
85
|
.. autoclass:: RequestHook
|
|
78
86
|
:members:
|
|
@@ -87,30 +95,21 @@ Data objects
|
|
|
87
95
|
~~~~~~~~~~~~
|
|
88
96
|
|
|
89
97
|
.. module:: reader._parser
|
|
90
|
-
:
|
|
98
|
+
:no-index:
|
|
91
99
|
|
|
92
100
|
.. autoclass:: RetrieveResult
|
|
93
101
|
:members:
|
|
94
102
|
|
|
95
|
-
..
|
|
96
|
-
|
|
97
|
-
.. autoclass:: ParsedFeed
|
|
103
|
+
.. autoclass:: RetrievedFeed
|
|
98
104
|
:members:
|
|
99
105
|
|
|
100
|
-
.. autoclass::
|
|
101
|
-
:members:
|
|
102
|
-
:undoc-members:
|
|
103
|
-
|
|
104
|
-
.. autoclass:: EntryData
|
|
106
|
+
.. autoclass:: ParseResult
|
|
105
107
|
:members:
|
|
106
|
-
:undoc-members:
|
|
107
108
|
|
|
108
|
-
..
|
|
109
|
-
|
|
110
|
-
.. autoclass:: FeedForUpdate
|
|
109
|
+
.. autoclass:: ParsedFeed
|
|
111
110
|
:members:
|
|
112
111
|
|
|
113
|
-
.. autoclass::
|
|
112
|
+
.. autoclass:: HTTPInfo
|
|
114
113
|
:members:
|
|
115
114
|
|
|
116
115
|
|
|
@@ -155,7 +154,7 @@ but at least one other implementation needs to exists before that.
|
|
|
155
154
|
.. autoattribute:: reader.Reader._search
|
|
156
155
|
|
|
157
156
|
.. module:: reader._types
|
|
158
|
-
:
|
|
157
|
+
:no-index:
|
|
159
158
|
|
|
160
159
|
.. autoclass:: StorageType()
|
|
161
160
|
:members:
|
|
@@ -171,6 +170,9 @@ but at least one other implementation needs to exists before that.
|
|
|
171
170
|
|
|
172
171
|
.. _changes:
|
|
173
172
|
|
|
173
|
+
Change tracking
|
|
174
|
+
~~~~~~~~~~~~~~~
|
|
175
|
+
|
|
174
176
|
.. autoclass:: ChangeTrackingStorageType()
|
|
175
177
|
:members:
|
|
176
178
|
:show-inheritance:
|
|
@@ -193,12 +195,26 @@ but at least one other implementation needs to exists before that.
|
|
|
193
195
|
Data objects
|
|
194
196
|
~~~~~~~~~~~~
|
|
195
197
|
|
|
198
|
+
.. autoclass:: FeedData
|
|
199
|
+
:members:
|
|
200
|
+
:undoc-members:
|
|
201
|
+
|
|
202
|
+
.. autoclass:: EntryData
|
|
203
|
+
:members:
|
|
204
|
+
:undoc-members:
|
|
205
|
+
|
|
196
206
|
.. autoclass:: FeedFilter
|
|
197
207
|
:members:
|
|
198
208
|
|
|
199
209
|
.. autoclass:: EntryFilter
|
|
200
210
|
:members:
|
|
201
211
|
|
|
212
|
+
.. autoclass:: FeedForUpdate
|
|
213
|
+
:members:
|
|
214
|
+
|
|
215
|
+
.. autoclass:: EntryForUpdate
|
|
216
|
+
:members:
|
|
217
|
+
|
|
202
218
|
.. autoclass:: FeedUpdateIntent
|
|
203
219
|
:members:
|
|
204
220
|
|
|
@@ -3,7 +3,7 @@ Plugins
|
|
|
3
3
|
=======
|
|
4
4
|
|
|
5
5
|
.. module:: reader
|
|
6
|
-
:
|
|
6
|
+
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
.. _built-in plugins:
|
|
@@ -116,3 +116,20 @@ For `built-in plugins`_, it is enough to use the plugin name (``reader.XYZ``).
|
|
|
116
116
|
To load web application plugins, set the ``READER_APP_PLUGIN`` environment variable.
|
|
117
117
|
To load CLI plugins (that customize the CLI),
|
|
118
118
|
set the ``READER_CLI_PLUGIN`` environment variable.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
Recipes
|
|
123
|
+
-------
|
|
124
|
+
|
|
125
|
+
I currently don't need this functionality,
|
|
126
|
+
but if you'd be interested in maintaining any of these
|
|
127
|
+
as an experimental or even built-in plugin,
|
|
128
|
+
please :doc:`submit a pull request <contributing>`.
|
|
129
|
+
|
|
130
|
+
.. include:: ../examples/feed_slugs.py
|
|
131
|
+
:start-after: """
|
|
132
|
+
:end-before: """
|
|
133
|
+
.. literalinclude:: ../examples/feed_slugs.py
|
|
134
|
+
:start-at: def init_reader
|
|
135
|
+
:end-before: if __name__
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Feed slugs
|
|
3
|
+
~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
This is a recipe of what a "get feed by slug" plugin may look like
|
|
6
|
+
(e.g. for user-defined short URLs).
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
>>> from reader import make_reader
|
|
11
|
+
>>> import feed_slugs
|
|
12
|
+
>>> reader = make_reader('db.sqlite', plugins=[feed_slugs.init_reader])
|
|
13
|
+
>>> reader.set_feed_slug('https://death.andgravity.com/_feed/index.xml', 'andgravity')
|
|
14
|
+
>>> reader.get_feed_by_slug('andgravity')
|
|
15
|
+
Feed(url='https://death.andgravity.com/_feed/index.xml', ...)
|
|
16
|
+
>>> reader.get_feed_slug(_.url)
|
|
17
|
+
'andgravity'
|
|
18
|
+
|
|
19
|
+
..
|
|
20
|
+
Originally implemented for https://github.com/lemon24/reader/issues/358.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# fmt: off
|
|
25
|
+
# flake8: noqa
|
|
26
|
+
|
|
27
|
+
def init_reader(reader):
|
|
28
|
+
# __get__() allows help(reader.get_feed_by_slug) to work
|
|
29
|
+
reader.get_feed_by_slug = get_feed_by_slug.__get__(reader)
|
|
30
|
+
reader.get_feed_slug = get_feed_slug.__get__(reader)
|
|
31
|
+
reader.set_feed_slug = set_feed_slug.__get__(reader)
|
|
32
|
+
|
|
33
|
+
def get_feed_by_slug(reader, slug):
|
|
34
|
+
tag = _make_tag(reader, slug)
|
|
35
|
+
return next(reader.get_feeds(tags=[tag], limit=1), None)
|
|
36
|
+
|
|
37
|
+
def get_feed_slug(reader, feed):
|
|
38
|
+
if tag := next(_get_tags(reader, feed), None):
|
|
39
|
+
return tag.removeprefix(_make_tag(reader, ''))
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
def set_feed_slug(reader, feed, slug: str | None):
|
|
43
|
+
feed = reader.get_feed(feed)
|
|
44
|
+
tag = _make_tag(reader, slug)
|
|
45
|
+
|
|
46
|
+
if not slug:
|
|
47
|
+
reader.delete_tag(feed, tag, missing_ok=True)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
reader.set_tag(feed, tag)
|
|
51
|
+
|
|
52
|
+
# ensure only one feed has the slug; technically a race condition,
|
|
53
|
+
# when it happens no feed will have the tag
|
|
54
|
+
for other_feed in reader.get_feeds(tags=[tag]):
|
|
55
|
+
if feed.url != other_feed.url:
|
|
56
|
+
reader.delete_tag(other_feed, tag, missing_ok=True)
|
|
57
|
+
|
|
58
|
+
# ensure feed has only one slug; technically a race condition,
|
|
59
|
+
# when it happens the feed will have no slug
|
|
60
|
+
for other_tag in _get_tags(reader, feed):
|
|
61
|
+
if tag != other_tag:
|
|
62
|
+
reader.delete_tag(feed, other_tag, missing_ok=True)
|
|
63
|
+
|
|
64
|
+
def _make_tag(reader, slug):
|
|
65
|
+
return reader.make_plugin_reserved_name('slug', slug)
|
|
66
|
+
|
|
67
|
+
def _get_tags(reader, resource):
|
|
68
|
+
prefix = _make_tag(reader, '')
|
|
69
|
+
# filter tags by prefix would make this faster,
|
|
70
|
+
# https://github.com/lemon24/reader/issues/309
|
|
71
|
+
return (t for t in reader.get_tag_keys(resource) if t.startswith(prefix))
|
|
72
|
+
|
|
73
|
+
if __name__ == '__main__':
|
|
74
|
+
from reader import make_reader
|
|
75
|
+
|
|
76
|
+
reader = make_reader('db.sqlite', plugins=[init_reader])
|
|
77
|
+
url = 'https://death.andgravity.com/_feed/index.xml'
|
|
78
|
+
|
|
79
|
+
reader.set_feed_slug(url, 'one')
|
|
80
|
+
print(
|
|
81
|
+
reader.get_feed_slug(url),
|
|
82
|
+
getattr(reader.get_feed_by_slug('one'), 'url', None),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
reader.set_feed_slug(url, 'two')
|
|
86
|
+
print(
|
|
87
|
+
reader.get_feed_slug(url),
|
|
88
|
+
getattr(reader.get_feed_by_slug('two'), 'url', None),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
reader.set_feed_slug('https://xkcd.com/atom.xml', 'two')
|
|
92
|
+
print(
|
|
93
|
+
reader.get_feed_slug(url),
|
|
94
|
+
getattr(reader.get_feed_by_slug('two'), 'url', None),
|
|
95
|
+
)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
#
|
|
3
|
-
# back up a SQLite database
|
|
3
|
+
# back up a SQLite database
|
|
4
4
|
#
|
|
5
5
|
# usage:
|
|
6
|
-
# backup.sh src dst
|
|
7
|
-
# backup.sh src
|
|
6
|
+
# ./backup.sh src dst
|
|
7
|
+
# ./backup.sh src
|
|
8
8
|
#
|
|
9
9
|
# example:
|
|
10
|
-
# "backup.sh /src/db.sqlite" -> ./db.sqlite.2023-01-28
|
|
10
|
+
# "./backup.sh /src/db.sqlite" -> ./db.sqlite.2023-01-28.gz
|
|
11
11
|
#
|
|
12
12
|
|
|
13
13
|
set -o nounset
|
|
@@ -29,5 +29,8 @@ trap 'rm -rf '"$tmpdir" EXIT
|
|
|
29
29
|
|
|
30
30
|
tmp=$tmpdir/$( basename "$src" )
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
du -sh "$src"
|
|
33
|
+
sqlite3 "$src" "VACUUM INTO '$tmp'"
|
|
34
|
+
du -sh "$tmp"
|
|
35
|
+
gzip -c "$tmp" > "$dst.gz"
|
|
36
|
+
du -sh "$dst.gz"
|
|
@@ -37,7 +37,7 @@ Print the titles of the unread entries::
|
|
|
37
37
|
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
__version__ = '3.
|
|
40
|
+
__version__ = '3.16'
|
|
41
41
|
|
|
42
42
|
# isort: off
|
|
43
43
|
|
|
@@ -52,6 +52,7 @@ from .types import (
|
|
|
52
52
|
Entry as Entry,
|
|
53
53
|
Content as Content,
|
|
54
54
|
Enclosure as Enclosure,
|
|
55
|
+
EntrySource as EntrySource,
|
|
55
56
|
EntrySearchResult as EntrySearchResult,
|
|
56
57
|
HighlightedString as HighlightedString,
|
|
57
58
|
FeedCounts as FeedCounts,
|