reader 3.13__tar.gz → 3.15__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.13 → reader-3.15}/CHANGES.rst +74 -2
- {reader-3.13/src/reader.egg-info → reader-3.15}/PKG-INFO +5 -4
- {reader-3.13 → reader-3.15}/docs/api.rst +3 -1
- {reader-3.13 → reader-3.15}/docs/dev.rst +21 -9
- {reader-3.13 → reader-3.15}/docs/guide.rst +28 -20
- {reader-3.13 → reader-3.15}/docs/install.rst +14 -0
- {reader-3.13 → reader-3.15}/docs/internal.rst +42 -21
- {reader-3.13 → reader-3.15}/docs/plugins.rst +18 -1
- {reader-3.13 → reader-3.15}/docs/tutorial.rst +1 -1
- reader-3.15/examples/custom_headers.py +50 -0
- reader-3.15/examples/feed_slugs.py +95 -0
- {reader-3.13 → reader-3.15}/examples/parser_only.py +1 -1
- {reader-3.13 → reader-3.15}/pyproject.toml +6 -4
- {reader-3.13 → reader-3.15}/scripts/backup.sh +9 -6
- {reader-3.13 → reader-3.15}/src/reader/__init__.py +1 -1
- {reader-3.13 → reader-3.15}/src/reader/_app/__init__.py +19 -3
- {reader-3.13 → reader-3.15}/src/reader/_cli.py +13 -20
- {reader-3.13 → reader-3.15}/src/reader/_parser/__init__.py +187 -83
- {reader-3.13 → reader-3.15}/src/reader/_parser/_http_utils.py +1 -0
- {reader-3.13 → reader-3.15}/src/reader/_parser/_lazy.py +183 -154
- {reader-3.13 → reader-3.15}/src/reader/_parser/_url_utils.py +15 -4
- {reader-3.13 → reader-3.15}/src/reader/_parser/feedparser.py +10 -4
- {reader-3.13 → reader-3.15}/src/reader/_parser/file.py +2 -6
- reader-3.15/src/reader/_parser/http.py +107 -0
- {reader-3.13 → reader-3.15}/src/reader/_parser/jsonfeed.py +1 -1
- {reader-3.13 → reader-3.15}/src/reader/_parser/requests/__init__.py +2 -0
- {reader-3.13 → reader-3.15}/src/reader/_parser/requests/_lazy.py +26 -6
- reader-3.15/src/reader/_plugins/enclosure_tags.py +166 -0
- {reader-3.13 → reader-3.15}/src/reader/_storage/_base.py +7 -2
- {reader-3.13 → reader-3.15}/src/reader/_storage/_entries.py +3 -4
- {reader-3.13 → reader-3.15}/src/reader/_storage/_feeds.py +10 -9
- {reader-3.13 → reader-3.15}/src/reader/_storage/_schema.py +50 -3
- {reader-3.13 → reader-3.15}/src/reader/_storage/_search.py +16 -4
- {reader-3.13 → reader-3.15}/src/reader/_storage/_sqlite_utils.py +2 -1
- {reader-3.13 → reader-3.15}/src/reader/_storage/_tags.py +158 -52
- {reader-3.13 → reader-3.15}/src/reader/_types.py +52 -84
- {reader-3.13 → reader-3.15}/src/reader/_update.py +58 -59
- {reader-3.13 → reader-3.15}/src/reader/_utils.py +12 -4
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/__init__.py +29 -10
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/api.py +106 -135
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/__init__.py +3 -2
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/asctime.py +26 -17
- reader-3.15/src/reader/_vendor/feedparser/datetimes/greek.py +90 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/hungarian.py +19 -25
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/iso8601.py +62 -52
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/korean.py +35 -24
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/perforce.py +21 -4
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/rfc822.py +52 -23
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +36 -22
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/encodings.py +215 -147
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/exceptions.py +6 -6
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/html.py +93 -84
- reader-3.15/src/reader/_vendor/feedparser/http.py +78 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/mixin.py +330 -275
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/_base.py +210 -166
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/admin.py +10 -10
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/cc.py +20 -19
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/dc.py +21 -17
- reader-3.15/src/reader/_vendor/feedparser/namespaces/georss.py +683 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/itunes.py +25 -21
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/mediarss.py +37 -36
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/psc.py +13 -13
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/parsers/json.py +54 -52
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/parsers/loose.py +27 -29
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/parsers/strict.py +34 -28
- reader-3.15/src/reader/_vendor/feedparser/sanitizer.py +974 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/sgml.py +17 -17
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/urls.py +91 -72
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/util.py +40 -40
- {reader-3.13 → reader-3.15}/src/reader/core.py +20 -32
- {reader-3.13 → reader-3.15}/src/reader/exceptions.py +6 -10
- {reader-3.13 → reader-3.15}/src/reader/plugins/__init__.py +1 -1
- {reader-3.13 → reader-3.15}/src/reader/plugins/entry_dedupe.py +42 -9
- {reader-3.13 → reader-3.15}/src/reader/plugins/mark_as_read.py +1 -1
- {reader-3.13 → reader-3.15}/src/reader/plugins/readtime.py +1 -1
- {reader-3.13 → reader-3.15}/src/reader/types.py +14 -4
- {reader-3.13 → reader-3.15/src/reader.egg-info}/PKG-INFO +5 -4
- {reader-3.13 → reader-3.15}/src/reader.egg-info/SOURCES.txt +2 -0
- {reader-3.13 → reader-3.15}/src/reader.egg-info/requires.txt +2 -2
- {reader-3.13 → reader-3.15}/tests/conftest.py +4 -5
- {reader-3.13 → reader-3.15}/tests/fakeparser.py +15 -16
- {reader-3.13 → reader-3.15}/tests/test_bench.py +3 -6
- {reader-3.13 → reader-3.15}/tests/test_cli.py +14 -0
- {reader-3.13 → reader-3.15}/tests/test_lazy_imports.py +11 -1
- {reader-3.13 → reader-3.15}/tests/test_parser.py +220 -89
- {reader-3.13 → reader-3.15}/tests/test_plugins_entry_dedupe.py +39 -5
- {reader-3.13 → reader-3.15}/tests/test_plugins_sqlite_releases.py +4 -2
- {reader-3.13 → reader-3.15}/tests/test_reader.py +43 -7
- {reader-3.13 → reader-3.15}/tests/test_reader_counts.py +41 -3
- {reader-3.13 → reader-3.15}/tests/test_reader_integration.py +21 -1
- {reader-3.13 → reader-3.15}/tests/test_reader_private.py +26 -20
- {reader-3.13 → reader-3.15}/tests/test_reader_search.py +3 -3
- {reader-3.13 → reader-3.15}/tests/utils.py +9 -4
- {reader-3.13 → reader-3.15}/tox.ini +3 -3
- reader-3.13/src/reader/_parser/http.py +0 -108
- reader-3.13/src/reader/_plugins/enclosure_tags.py +0 -119
- reader-3.13/src/reader/_vendor/feedparser/datetimes/greek.py +0 -86
- reader-3.13/src/reader/_vendor/feedparser/http.py +0 -227
- reader-3.13/src/reader/_vendor/feedparser/namespaces/georss.py +0 -276
- reader-3.13/src/reader/_vendor/feedparser/sanitizer.py +0 -950
- {reader-3.13 → reader-3.15}/LICENSE +0 -0
- {reader-3.13 → reader-3.15}/MANIFEST.in +0 -0
- {reader-3.13 → reader-3.15}/README.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/Makefile +0 -0
- {reader-3.13 → reader-3.15}/docs/app.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/changelog.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/cli.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/conf.py +0 -0
- {reader-3.13 → reader-3.15}/docs/config.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/contributing.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/dev-app.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/images/redesign-01.png +0 -0
- {reader-3.13 → reader-3.15}/docs/index.rst +0 -0
- {reader-3.13 → reader-3.15}/docs/make.bat +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/dillo.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/entries-feed.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/entries.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/entry-one.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/entry-two.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/feeds.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/lynx.png +0 -0
- {reader-3.13 → reader-3.15}/docs/screenshots/search.png +0 -0
- {reader-3.13 → reader-3.15}/docs/why.rst +0 -0
- {reader-3.13 → reader-3.15}/examples/config.yaml +0 -0
- {reader-3.13 → reader-3.15}/examples/podcast.py +0 -0
- {reader-3.13 → reader-3.15}/examples/terminal.py +0 -0
- {reader-3.13 → reader-3.15}/run.sh +0 -0
- {reader-3.13 → reader-3.15}/scripts/.coverage +0 -0
- {reader-3.13 → reader-3.15}/scripts/bench.py +0 -0
- {reader-3.13 → reader-3.15}/scripts/debug_storage_stats.py +0 -0
- {reader-3.13 → reader-3.15}/scripts/generate_import_all.py +0 -0
- {reader-3.13 → reader-3.15}/scripts/jscontrols.html +0 -0
- {reader-3.13 → reader-3.15}/scripts/jscontrols.py +0 -0
- {reader-3.13 → reader-3.15}/scripts/lines.sh +0 -0
- {reader-3.13 → reader-3.15}/scripts/release.py +0 -0
- {reader-3.13 → reader-3.15}/setup.cfg +0 -0
- {reader-3.13 → reader-3.15}/src/reader/__main__.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/api_thing.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/cli.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/static/controls.js +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/static/style.css +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/add_entry.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/entries.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/entry.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/macros.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/metadata.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/templates/tags.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_app/wsgi.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_config.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_hash_utils.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/cli_status.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/preview_feed_list.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/share.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/sqlite_releases.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/templates/preview_feed_list.html +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_plugins/timer.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_storage/__init__.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_storage/_changes.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_storage/_html_utils.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.13 → reader-3.15}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/plugins/ua_fallback.py +0 -0
- {reader-3.13 → reader-3.15}/src/reader/py.typed +0 -0
- {reader-3.13 → reader-3.15}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.13 → reader-3.15}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.13 → reader-3.15}/tests/data/10.json +0 -0
- {reader-3.13 → reader-3.15}/tests/data/10.json.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/custom +0 -0
- {reader-3.13 → reader-3.15}/tests/data/empty.atom +0 -0
- {reader-3.13 → reader-3.15}/tests/data/empty.atom.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/empty.json +0 -0
- {reader-3.13 → reader-3.15}/tests/data/empty.json.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/empty.rss +0 -0
- {reader-3.13 → reader-3.15}/tests/data/empty.rss.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/full.atom +0 -0
- {reader-3.13 → reader-3.15}/tests/data/full.atom.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/full.json +0 -0
- {reader-3.13 → reader-3.15}/tests/data/full.json.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/full.rss +0 -0
- {reader-3.13 → reader-3.15}/tests/data/full.rss.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/invalid.json +0 -0
- {reader-3.13 → reader-3.15}/tests/data/invalid.json.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/relative.atom +0 -0
- {reader-3.13 → reader-3.15}/tests/data/relative.atom.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/relative.rss +0 -0
- {reader-3.13 → reader-3.15}/tests/data/relative.rss.py +0 -0
- {reader-3.13 → reader-3.15}/tests/data/sqlite_releases.html +0 -0
- {reader-3.13 → reader-3.15}/tests/data/unknown.json +0 -0
- {reader-3.13 → reader-3.15}/tests/data/unknown.json.py +0 -0
- {reader-3.13 → reader-3.15}/tests/reader_methods.py +0 -0
- {reader-3.13 → reader-3.15}/tests/reader_test_plugins/__init__.py +0 -0
- {reader-3.13 → reader-3.15}/tests/reader_test_plugins/good.py +0 -0
- {reader-3.13 → reader-3.15}/tests/reader_test_plugins/init_error.py +0 -0
- {reader-3.13 → reader-3.15}/tests/reader_test_plugins/missing_dependency.py +0 -0
- {reader-3.13 → reader-3.15}/tests/reader_test_plugins/missing_entry_point.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test__types.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_app.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_app_wsgi.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_changes.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_config.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_exceptions.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_hash_utils.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_html_utils.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_plugins_cli_status.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_plugins_readtime.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_context.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_deprecations.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_filter.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_hooks.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_plugins.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_sort.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_reader_update.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_search.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_sql_utils.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_sqlite_utils.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_storage.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_tags.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_test_utils.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_types.py +0 -0
- {reader-3.13 → reader-3.15}/tests/test_utils.py +0 -0
|
@@ -3,7 +3,77 @@ Changelog
|
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
5
|
.. module:: reader
|
|
6
|
-
:
|
|
6
|
+
:no-index:
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Version 3.15
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2024-11-11
|
|
13
|
+
|
|
14
|
+
* During :ref:`scheduled updates <scheduled>`,
|
|
15
|
+
honor the Retry-After HTTP header sent with
|
|
16
|
+
429 Too Many Requests or 503 Service Unavailable responses.
|
|
17
|
+
(:issue:`307`)
|
|
18
|
+
* Optimize OR-only :meth:`~Reader.get_feeds()` and :meth:`~Reader.get_entries()`
|
|
19
|
+
tag filters (e.g. ``[['one', ...]]``). (:issue:`359`)
|
|
20
|
+
* Expose :data:`reader.core.DEFAULT_RESERVED_NAME_SCHEME`. (:issue:`287`)
|
|
21
|
+
|
|
22
|
+
* Update the (unstable) parser API to expose HTTP information to the updater.
|
|
23
|
+
(:issue:`307`)
|
|
24
|
+
|
|
25
|
+
* The :class:`.RetrieverType` protocol used by retrievers changed
|
|
26
|
+
(new return type, allow storing arbitrary caching data via
|
|
27
|
+
:attr:`~.RetrievedFeed.caching_info`).
|
|
28
|
+
* The ``HTTPAcceptParserType`` was renamed to :class:`.AcceptParserType`.
|
|
29
|
+
|
|
30
|
+
* Allow :ref:`using the installed feedparser <no-vendored-feedparser>`
|
|
31
|
+
instead of the one vendored by *reader*.
|
|
32
|
+
This is useful for working around issues in the vendored feedparser,
|
|
33
|
+
like the libxml2 <=2.13.3 incompatibility reported in :issue:`350`;
|
|
34
|
+
thanks to `Maks Verver`_ for reporting,
|
|
35
|
+
root causing, and following up with both dependencies.
|
|
36
|
+
|
|
37
|
+
* Fix a number of brittle / broken tests.
|
|
38
|
+
Thanks to `Maks Verver`_ for the issues and fixes.
|
|
39
|
+
(:issue:`348`, :issue:`349`, :issue:`355`)
|
|
40
|
+
|
|
41
|
+
.. _Maks Verver: https://github.com/maksverver
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Version 3.14
|
|
45
|
+
------------
|
|
46
|
+
|
|
47
|
+
Released 2024-07-23
|
|
48
|
+
|
|
49
|
+
* Add :attr:`~EntryCounts.unimportant` attribute to
|
|
50
|
+
:class:`EntryCounts` and :class:`EntrySearchCounts`.
|
|
51
|
+
Thanks to `chenthur`_ for the pull request.
|
|
52
|
+
(:issue:`283`)
|
|
53
|
+
* Fix bug in the :mod:`~reader.plugins.entry_dedupe` plugin causing entries
|
|
54
|
+
to flip-flop if there were multiple *new* duplicates of the same issue
|
|
55
|
+
(on the first update, one entry remains, on the second update, the other);
|
|
56
|
+
related to the bug fixed in `version 3.2 <Version 3.2_>`_.
|
|
57
|
+
(:issue:`340`)
|
|
58
|
+
|
|
59
|
+
* :mod:`~reader._plugins.enclosure_tags` plugin improvements. (:issue:`344`)
|
|
60
|
+
|
|
61
|
+
* Set genre to `Podcast` if the feed has any tag containing "podcast".
|
|
62
|
+
* Rewrite tags on the fly,
|
|
63
|
+
instead of downloading the entire file before sending it to the user;
|
|
64
|
+
allows browsers to display accurate download progress.
|
|
65
|
+
* Prefer feed user title to feed title if available.
|
|
66
|
+
* Use feed title as artist, instead of author.
|
|
67
|
+
|
|
68
|
+
* Support Python 3.13. (:issue:`341`)
|
|
69
|
+
* Update vendored `feedparser`_ to the ``develop`` branch as of 2024-06-26.
|
|
70
|
+
Needed because upstream removed dependency on stdlib module ``cgi``
|
|
71
|
+
(removed in Python 3.13), but the `version 2.9 <Version 2.9_>`_
|
|
72
|
+
memory usage reduction was not released upstream yet.
|
|
73
|
+
(:issue:`341`)
|
|
74
|
+
|
|
75
|
+
.. _chenthur: https://github.com/chenthur
|
|
76
|
+
.. _feedparser: https://feedparser.readthedocs.io/en/latest/
|
|
7
77
|
|
|
8
78
|
|
|
9
79
|
Version 3.13
|
|
@@ -341,7 +411,7 @@ Released 2022-09-14
|
|
|
341
411
|
became optional.
|
|
342
412
|
(:issue:`96`)
|
|
343
413
|
* Fix bug in :mod:`~reader.plugins.entry_dedupe` causing updates to fail
|
|
344
|
-
if there were multiple *new* duplicates of the same
|
|
414
|
+
if there were multiple *new* duplicates of the same entry.
|
|
345
415
|
(:issue:`292`)
|
|
346
416
|
* Fix bug in :mod:`~reader.plugins.readtime`
|
|
347
417
|
and :mod:`~reader.plugins.mark_as_read` causing updates to fail
|
|
@@ -615,6 +685,8 @@ Released 2022-03-12
|
|
|
615
685
|
Fix broken error flashing.
|
|
616
686
|
|
|
617
687
|
|
|
688
|
+
.. _version 2.9:
|
|
689
|
+
|
|
618
690
|
Version 2.9
|
|
619
691
|
-----------
|
|
620
692
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.15
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
27
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
28
|
Classifier: Topic :: Internet
|
|
@@ -65,11 +66,11 @@ Requires-Dist: pytest-rerunfailures; extra == "tests"
|
|
|
65
66
|
Requires-Dist: coverage; extra == "tests"
|
|
66
67
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
67
68
|
Requires-Dist: requests-mock; extra == "tests"
|
|
68
|
-
Requires-Dist: mechanicalsoup; (implementation_name != "pypy"
|
|
69
|
+
Requires-Dist: mechanicalsoup; (implementation_name != "pypy" and python_version <= "3.12") and extra == "tests"
|
|
69
70
|
Requires-Dist: requests-wsgi-adapter; extra == "tests"
|
|
70
|
-
Requires-Dist: lxml; (implementation_name != "pypy"
|
|
71
|
+
Requires-Dist: lxml; (implementation_name != "pypy" and python_version <= "3.12") and extra == "tests"
|
|
71
72
|
Requires-Dist: html5lib; extra == "tests"
|
|
72
|
-
Requires-Dist: numpy; (implementation_name != "pypy" and os_name == "posix") and extra == "tests"
|
|
73
|
+
Requires-Dist: numpy; (implementation_name != "pypy" and os_name == "posix" and python_version <= "3.12") and extra == "tests"
|
|
73
74
|
Requires-Dist: mypy; implementation_name != "pypy" and extra == "tests"
|
|
74
75
|
Requires-Dist: types-requests; extra == "tests"
|
|
75
76
|
Requires-Dist: types-beautifulsoup4; extra == "tests"
|
|
@@ -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:
|
|
@@ -184,4 +184,6 @@ Type aliases
|
|
|
184
184
|
Constants
|
|
185
185
|
---------
|
|
186
186
|
|
|
187
|
+
.. autodata:: reader.core.DEFAULT_RESERVED_NAME_SCHEME
|
|
188
|
+
|
|
187
189
|
.. autodata:: reader.plugins.DEFAULT_PLUGINS
|
|
@@ -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.
|
|
@@ -378,8 +378,14 @@ Requests session plugins:
|
|
|
378
378
|
Retriever / parser split:
|
|
379
379
|
|
|
380
380
|
* :issue:`205#issuecomment-766321855`
|
|
381
|
-
* split exception hierarchy (not implemented as of 3.
|
|
381
|
+
* split exception hierarchy (not implemented as of 3.15): :issue:`218#issuecomment-1687094315`
|
|
382
|
+
* API overview as of 3.14, meant to show the dataflow: :issue:`307#issuecomment-2266647310`
|
|
383
|
+
* ~ideal API (mostly implemented in 3.15): :issue:`307#issuecomment-2281797898`
|
|
382
384
|
|
|
385
|
+
* exposes HTTP information (so it can be used by the updater)
|
|
386
|
+
* introduces ~internal RetrieveError and NotModified :exc:`ParseError` subclasses;
|
|
387
|
+
notably, this doesn't really follow the split exception hierarchy mentioned above,
|
|
388
|
+
and is only meant to surface HTTP information in error cases
|
|
383
389
|
|
|
384
390
|
Alternative feed parsers:
|
|
385
391
|
|
|
@@ -398,17 +404,13 @@ Lessons learned from the :ref:`Twitter` plugin:
|
|
|
398
404
|
:meth:`~reader._parser.ParserType` being generic.
|
|
399
405
|
|
|
400
406
|
* It is useful for a Retriever to store arbitrary caching data;
|
|
401
|
-
the plugin (mis)used
|
|
402
|
-
:attr:`~reader._parser.RetrieveResult.http_etag`
|
|
407
|
+
the plugin (mis)used ``RetrieveResult.http_etag``
|
|
403
408
|
to store the (integer) id of the newest tweet in the thread.
|
|
404
409
|
|
|
405
|
-
|
|
406
|
-
"arbitrary caching data" attribute;
|
|
407
|
-
also see `this comment <https://github.com/lemon24/reader/blob/0ada24bf4e65c69c3028641bada8eaeabbe02754/src/reader/_parser.py#L569>`_.
|
|
410
|
+
Update: This was formalized as :attr:`.RetrievedFeed.caching_info` in 3.15.
|
|
408
411
|
|
|
409
412
|
* 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
|
|
413
|
+
the plugin (mis)used ``FeedForUpdate.http_etag`` to pass from
|
|
412
414
|
:meth:`~reader._parser.FeedForUpdateRetrieverType.process_feed_for_update`
|
|
413
415
|
to :meth:`~reader._parser.RetrieverType.__call__`:
|
|
414
416
|
|
|
@@ -684,6 +686,14 @@ Update hook error handling:
|
|
|
684
686
|
* expected behavior: :issue:`218#issuecomment-1595691410`, :issue:`218#issuecomment-1666823222`
|
|
685
687
|
* update hook pseudocode + exception hierarchy: :issue:`218#issuecomment-1666869215`
|
|
686
688
|
|
|
689
|
+
Considerations on using pluggy: :issue:`329`,
|
|
690
|
+
`how adapting reader use cases to the pluggy model may work <https://github.com/pytest-dev/pluggy/issues/151#issuecomment-2421648901>`_.
|
|
691
|
+
Conclusions:
|
|
692
|
+
|
|
693
|
+
even if using pluggy would require some work-arounds for calling, I think the collection / discovery functionality is still more than worth it
|
|
694
|
+
|
|
695
|
+
long-term and/or if reader becomes really popular, pluggy is definitely the way to go
|
|
696
|
+
|
|
687
697
|
|
|
688
698
|
Reserved names
|
|
689
699
|
~~~~~~~~~~~~~~
|
|
@@ -721,6 +731,8 @@ Duplicate entries
|
|
|
721
731
|
Duplicate entries are mainly handled by the :mod:`reader.entry_dedupe` plugin.
|
|
722
732
|
|
|
723
733
|
* Using MinHash to speed up similarity checks (maybe): https://gist.github.com/lemon24/b9af5ade919713406bda9603847d32e5
|
|
734
|
+
* Discussion of unifying "on-line" dedupe (after an entry is added/updated),
|
|
735
|
+
and "on-demand" dedupe (backfill): :issue:`340`.
|
|
724
736
|
|
|
725
737
|
However, it is also possible for a feed to have two entries with the same id
|
|
726
738
|
– yes, even though in most (if not all) formats,
|
|
@@ -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::
|
|
106
|
+
.. autoclass:: ParseResult
|
|
101
107
|
:members:
|
|
102
|
-
:undoc-members:
|
|
103
|
-
|
|
104
|
-
.. autoclass:: EntryData
|
|
105
|
-
: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
|
|
|
@@ -219,9 +235,14 @@ Type aliases
|
|
|
219
235
|
Recipes
|
|
220
236
|
-------
|
|
221
237
|
|
|
222
|
-
.. include:: ../examples/
|
|
238
|
+
.. include:: ../examples/custom_headers.py
|
|
223
239
|
:start-after: """
|
|
224
|
-
:end-before: """
|
|
240
|
+
:end-before: """
|
|
241
|
+
.. literalinclude:: ../examples/custom_headers.py
|
|
242
|
+
:start-at: import
|
|
225
243
|
|
|
244
|
+
.. include:: ../examples/parser_only.py
|
|
245
|
+
:start-after: """
|
|
246
|
+
:end-before: """
|
|
226
247
|
.. literalinclude:: ../examples/parser_only.py
|
|
227
|
-
:start-
|
|
248
|
+
:start-at: import
|
|
@@ -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,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adding custom headers when retrieving feeds
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Example of adding custom request headers with :attr:`.SessionFactory.request_hooks`:
|
|
6
|
+
|
|
7
|
+
.. code-block:: console
|
|
8
|
+
|
|
9
|
+
$ python examples/custom_headers.py
|
|
10
|
+
updating...
|
|
11
|
+
server: Hello, world!
|
|
12
|
+
updated!
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# fmt: off
|
|
17
|
+
# flake8: noqa
|
|
18
|
+
|
|
19
|
+
import http.server
|
|
20
|
+
import threading
|
|
21
|
+
from reader import make_reader
|
|
22
|
+
|
|
23
|
+
# start a background server that logs the received header
|
|
24
|
+
|
|
25
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
26
|
+
def log_message(self, *_): pass
|
|
27
|
+
def do_GET(self):
|
|
28
|
+
print("server:", self.headers.get('my-header'))
|
|
29
|
+
self.send_error(304)
|
|
30
|
+
|
|
31
|
+
server = http.server.HTTPServer(('localhost', 8080), Handler)
|
|
32
|
+
threading.Thread(target=server.handle_request).start()
|
|
33
|
+
|
|
34
|
+
# create a reader object
|
|
35
|
+
|
|
36
|
+
reader = make_reader(':memory:')
|
|
37
|
+
reader.add_feed('http://localhost:8080')
|
|
38
|
+
|
|
39
|
+
# set up a hook that adds the header to each request
|
|
40
|
+
|
|
41
|
+
def hook(session, request, **kwargs):
|
|
42
|
+
request.headers.setdefault('my-header', 'Hello, world!')
|
|
43
|
+
|
|
44
|
+
reader._parser.session_factory.request_hooks.append(hook)
|
|
45
|
+
|
|
46
|
+
# updating the feed sends the modified request to the server
|
|
47
|
+
|
|
48
|
+
print("updating...")
|
|
49
|
+
reader.update_feeds()
|
|
50
|
+
print("updated!")
|
|
@@ -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
|
+
)
|
|
@@ -11,7 +11,7 @@ retrieved asynchronously with `HTTPX <https://www.python-httpx.org/>`_:
|
|
|
11
11
|
death and gravity
|
|
12
12
|
Has your password been pwned? Or, how I almost failed to search a 37 GB text file in under 1 millisecond (in Python)
|
|
13
13
|
|
|
14
|
-
"""
|
|
14
|
+
"""
|
|
15
15
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import io
|
|
@@ -26,6 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: 3.10",
|
|
27
27
|
"Programming Language :: Python :: 3.11",
|
|
28
28
|
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
29
30
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
30
31
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
31
32
|
"Topic :: Internet",
|
|
@@ -107,14 +108,15 @@ tests = [
|
|
|
107
108
|
"pytest-cov",
|
|
108
109
|
"requests-mock",
|
|
109
110
|
# mechanicalsoup hard-depends on lxml (see below)
|
|
110
|
-
'mechanicalsoup; (implementation_name != "pypy"
|
|
111
|
+
'mechanicalsoup; (implementation_name != "pypy" and python_version <= "3.12")',
|
|
111
112
|
"requests-wsgi-adapter",
|
|
112
113
|
# we want to test search with all known bs4 parsers.
|
|
113
|
-
# lxml does not have
|
|
114
|
-
|
|
114
|
+
# lxml usually does not have recent PyPy wheels.
|
|
115
|
+
# lxml usually does not have pre-relase CPython wheels.
|
|
116
|
+
'lxml; (implementation_name != "pypy" and python_version <= "3.12")',
|
|
115
117
|
"html5lib",
|
|
116
118
|
# for bench.py
|
|
117
|
-
'numpy; (implementation_name != "pypy" and os_name == "posix")',
|
|
119
|
+
'numpy; (implementation_name != "pypy" and os_name == "posix" and python_version <= "3.12")',
|
|
118
120
|
# mypy does not work on pypy (yet).
|
|
119
121
|
'mypy; implementation_name != "pypy"',
|
|
120
122
|
"types-requests",
|