Changelog
Version 3.20
Released 2025-11-26
Add the
read_onlymake_reader()argument. Thanks to Roman Milko for the PR. (#319)Include the
altandtitleHTML attributes in the full-text search index. Also, don’t add extra whitespace between tags (especially important for inline tags). (#371)Note
Old entries will not be re-indexed automatically; to do so, disable then enable search.
entry_dedupeimprovements. (#371)Use more heuristics to find potential duplicates (in addition to title matching): match link, match published timestamp, strip common title prefixes for new entries.
When comparing entries, include the
altandtitleHTML attributes, strip accents, and treat dates and versions as single tokens.Increase required minimum content length from 32 to 48 words.
Add the
.reader.dedupe.once.linkand.reader.dedupe.once.title.prefixescape hatches.Clean up documentation, refactor code, and add extensive tests.
Log run times of before/after update hooks.
Version 3.19
Released 2025-10-01
Support Python 3.14.
Remove deprecated empty
searchandreadtimeextras.Remove undocumented development
tests,docs, anddevextras.enclosure_tagsplugin: strip HTML tags from values used as MP3 tags (for example,<b>tags used to highlight search result matches).Project infrastructure improvements.
Clean up Contributing documentation and run.sh script.
Use GitHub Actions to publish releases to PyPI.
Split type checking and testing jobs in the
buildGHA workflow.Make tox runs slightly faster.
Use dependency groups instead of extras for development dependencies.
Version 3.18
Released 2025-05-04
Drop Python 3.10 support. (#354)
Add the
overwriteargument toadd_entry().Add
FeedSort,EntrySort, andEntrySearchSortenums. (#354)Fix bug causing
Reader.update_feeds()to not honorupdates_enabled=Truewhenscheduled=True. (#365)In the
cli_statusplugin, overwrite the entry so it doesn’t appear briefly as unread.
Version 3.17
Released 2025-03-08
Attention
This is the last release to support Python 3.10; see #354 for details.
Support PyPy 3.11. (#354)
Start working on a web app re-design based on htmx and Bootstrap, beginning with a new version of the main entries page, and dark mode support; some screenshots. (#318)
Entry.feed_resolved_title: return both the source and feed titles only if they are different.The
cli_statusplugin now creates only one entry per command, with the newest run first.
Version 3.16
Released 2024-12-08
Parse and store
Entry.sourcefor Atom and RSS feeds. (#267)Add
Entry.feed_resolved_titleandFeed.resolved_titleproperties.The
feedsearch column now indexesEntry.feed_resolved_title, instead of feeduser_titleortitle.Allow filtering entries by the entry source.
Add
reader.utils.archive_entries()for copying entries to an “archived” feed. (#290)Add
copy_entry().Allow archiving entries from the web app.
Fix bug causing
Readeroperations from a thread other than the one that created the instance to happen with foreign key constraint enforcement disabled (e.g. deleting a feed from another thread would not delete its entries).This bug exists since using
Readerinstances from other threads became allowed in 2.15.Serving the web application with
python -m reader serveis known to be affected. Serving it with uWSGI without threads (the default) should not be affected.Attention
Your database may be in an inconsistent state because of this bug.
It is recommended you run PRAGMA foreign_key_check on your database.
If you are upgrading from a version prior to 3.16 (i.e. were not using a pre-release version of reader), the migration will do so for you. If there are inconsistencies, you will get this error:
StorageError: integrity error: after migrating to version 43: integrity error: FOREIGN KEY constraint failed
Fix
enable_search()/update_search()not working when the search database is missing but change tracking is enabled (e.g. when restoring the main database from backup). (#362)
Version 3.15
Released 2024-11-11
During scheduled updates, honor the Retry-After HTTP header sent with 429 Too Many Requests or 503 Service Unavailable responses. (#307)
Optimize OR-only
get_feeds()andget_entries()tag filters (e.g.[['one', ...]]). (#359)Expose
reader.core.DEFAULT_RESERVED_NAME_SCHEME. (#287)Update the (unstable) parser API to expose HTTP information to the updater. (#307)
The
RetrieverTypeprotocol used by retrievers changed (new return type, allow storing arbitrary caching data viacaching_info).The
HTTPAcceptParserTypewas renamed toAcceptParserType.
Allow using the installed feedparser instead of the one vendored by reader. This is useful for working around issues in the vendored feedparser, like the libxml2 <=2.13.3 incompatibility reported in #350; thanks to Maks Verver for reporting, root causing, and following up with both dependencies.
Fix a number of brittle / broken tests. Thanks to Maks Verver for the issues and fixes. (#348, #349, #355)
Version 3.14
Released 2024-07-23
Add
unimportantattribute toEntryCountsandEntrySearchCounts. Thanks to chenthur for the pull request. (#283)Fix bug in the
entry_dedupeplugin causing entries to flip-flop if there were multiple new duplicates of the same issue (on the first update, one entry remains, on the second update, the other); related to the bug fixed in version 3.2. (#340)enclosure_tagsplugin improvements. (#344)Set genre to Podcast if the feed has any tag containing “podcast”.
Rewrite tags on the fly, instead of downloading the entire file before sending it to the user; allows browsers to display accurate download progress.
Prefer feed user title to feed title if available.
Use feed title as artist, instead of author.
Support Python 3.13. (#341)
Update vendored feedparser to the
developbranch as of 2024-06-26. Needed because upstream removed dependency on stdlib modulecgi(removed in Python 3.13), but the version 2.9 memory usage reduction was not released upstream yet. (#341)
Version 3.13
Released 2024-06-19
Allow different feed update intervals; see Scheduled updates for details. (#332)
Add
scheduledargument toupdate_feeds(),get_feeds(), etc.Add
Feed.update_afterandlast_retrievedattributes.The
newfilter ofupdate_feeds()etc. considers a feed new if it was never retrieved (last_retrieved), not if it was never updated successfully (last_updated).Update an entry whenever its
updatedchanges (don’t require it to be newer).
Fix bug introduced in version 3.12 causing an assertion error when there are multiple entries with the same id in the same feed, or when parallel
update_feeds()calls add the same entry. The fix restores the pre-3.12 first-entry-wins / last-write-wins behavior. Thanks to Joakim Hellsén for reporting and helping debug this issue. (#335)Fix assertion error when an entry is deleted while being updated.
Allow re-running the
mark_as_readplugin for existing entries. Thanks to Michael Han for the pull request. (#317)Other changes for scheduled updates. (#332)
Add
--scheduledflag to theupdatecommand.The
cli_statusplugin now records the output of multiple runs instead of just the last one, with output from the same hour grouped in a single entry, up to 24 entries/hours.Group mutually-exclusive attributes of
FeedUpdateIntentinto a newvalueunion attribute.
New and improved Updating feeds user guide section.
Rename
update --new-onlyCLI flag to--new;--new-onlyremains available as an alias. (#334)
Version 3.12
Released 2024-03-05
Split the full-text search index into a separate, attached database. (#323)
Require at least SQLite 3.18. Previously, reader core required 3.15, and only
update_search()required 3.18. (#323)Enable write-ahead logging only once, when the database is created, instead of every time it is opened. (#323)
Vacuum the main database after migrations. (#323)
Add an internal change tracking API to formalize how search keeps in sync with storage. (#323)
Refactor storage internals. (#323)
Version 3.11
Released 2023-12-30
Version 3.10
Released 2023-11-12
Stop using deprecated
sqlite3datetime converters/adapters. (#321)Document the storage Internal API. (#325)
Change all internal APIs to use timezone-aware datetimes, with the timezone set to UTC. (#321)
In the API documentation, fall back to type hints if hand-written parameter types are not available. Add relevant Documentation guidelines to the dev documentation. (#287)
Add the
shareexperimental plugin to add social sharing links in the web app.
Version 3.9
Released 2023-08-28
Wrap unexpected retriever/parser errors in
ParseError, instead of letting them bubble up, so exceptions raised by custom retrievers/parsers for one feed don’t prevent updates for the others duringupdate_feeds_iter()/update_feeds(). (#218)Store the details of any
UpdateErrorinFeed.last_exception(except hook errors), not just the__cause__ofParseErrors. (#218)Add the
timerexperimental plugin to collect per-call method timings. Show per-request statistics in the web app. (#306)
Version 3.8
Released 2023-08-20
Drop Python 3.9 support. (#302)
Use
concurrent.futuresinstead ofmultiprocessing.dummywhen updating feeds in parallel;multiprocessing.dummydoes not work on some environments (e.g. AWS Lambda).Wrap unexpected hook errors in
UpdateHookErrorinstead of letting them bubble up, so plugin-raised exceptions for one feed don’t prevent updates for the others duringupdate_feeds_iter()/update_feeds(). (#218)Warning
This is a minor compatibility break; it is considered acceptable, since it fixes a bug / unexpected behavior.
Add new exceptions
UpdateHookError,SingleUpdateHookError, andUpdateHookErrorGroup.Try to run all
after_entry_update_hooks,after_feed_update_hooks, andafter_feeds_update_hooks, don’t stop after one fails.
Add
UpdateErroras parent of all update-related exceptions. (#218)Narrow down the error type of
UpdateResult.valuefromReaderErrortoUpdateError.Make
ParseErrorinherit fromUpdateError.Document
update_feeds_iter(),update_feeds(), andupdate_feed()can raiseUpdateErrors (other thanUpdateHookErrorandParseError).
Make
ReaderWarninginherit fromReaderError.Include a diagram of the Exception hierarchy in the API reference.
Add werkzeug dependency, instead of vendoring selected
werkzeug.httputilities.Rework lazy imports introduced in version 3.3. (#316)
Make
reader._parsera package, and move parsing-related modules into it. (#316)
Version 3.7
Released 2023-07-15
Attention
This is the last release to support Python 3.9; see #302 for details.
Version 3.6
Released 2023-06-16
Add documentation on Contributing and a detailed Roadmap. Thanks to Katharine Jarmul for finally getting me to do this. (#60)
Document the low-level
delete_entries()storage method. (#301, #96)Update vendored
reader._http_utilsto werkzeug 2.3.5.Deprecate the twitter experimental plugin, since the Twitter API does not have a (useful) free tier anymore. (#310)
Attention
The twitter plugin will be removed in version 3.7.
Version 3.5
Released 2023-03-19
Make
Entry.importantan optional boolean defaulting toNone, so one can express “explicitly unimportant” (don’t care) by setting it toFalse. This replaces the semantics for don’t care introduced in version 2.2. (#254)Warning
This is a minor compatibility break, and should mostly affect code that checks identity (
if entry.important is True: ...); code that usesimportantin a boolean context (if entry.important: ...) should not be affected.Entry.importantvalues will be migrated as follows:if read and not important and important_modified: important = False elif not important: important = None else: important = important
The
importantargument ofget_entries(),search_entries(), etc. can also take string literals for more precise filtering, seeTristateFilterInput.The
mark_as_readplugin does not setread_modifiedandimportant_modifiedanymore.The web app uses the new don’t care semantics.
set_entry_read()andset_entry_important()do not coerce the flag value toboolanymore, and require it to beTrueorFalse(orNone).
Version 3.4
Released 2023-01-22
Version 3.3
Released 2022-12-19
This release marks reader’s 5th anniversary and its 2000th commit.
Attention
This is the last release to support Python 3.8; see #298 for details.
Support Python 3.11. (#289)
Postpone update-related imports until needed. Shortens time from process start to usable Reader instance by 3x (imports are 72% faster). (#297)
Refactor parser internals. (#297)
Note
Plugins using the (unstable) session hooks should replace:
reader._parser.session_hooks.request.append(...) reader._parser.session_hooks.response.append(...)
with:
reader._parser.session_factory.request_hooks.append(...) reader._parser.session_factory.response_hooks.append(...)
twitter plugin: don’t fail when deserializing tweets with missing
edit_history_tweet_ids(fails in tweepy 4.11, warns in tweepy >4.12).
Version 3.2
Released 2022-09-14
UpdatedFeedchanges: added fieldunmodifiedand propertytotal; fieldsnewandmodifiedbecame optional. (#96)Fix bug in
entry_dedupecausing updates to fail if there were multiple new duplicates of the same entry. (#292)Fix bug in
readtimeandmark_as_readcausing updates to fail if an entry was deleted by another plugin. (#292)Fix bug in
mark_as_readcausing updates to fail if an entry had no title.In the CLI, don’t suppress the traceback of
ReaderError, since it would also suppress it for bugs.In the CLI, stop using deprecated
click.get_terminal_size().
Version 3.1
Released 2022-08-29
Drop
readtimeplugin dependency on readtime (which has a transitive dependency on lxml, which does not always have PyPy Windows wheels on PyPI). Thereadtimeextra is deprecated, but remains available to avoid breaking dependent packages. (#286)Sort entries by added date most of the time, with the exception of those imported on the first update. Previously, entries would be sorted by added only if they were published less than 7 days ago, causing entries that appear in the feed months after their published to never appear at the top (so the user would never see them). (#279)
Version 3.0
Released 2022-07-30
Attention
This release contains backwards incompatible changes.
Remove old database migrations.
Remove
mark_as_readconfig tag name migration.If you are upgrading from reader 2.10 or newer, no action is required.
Attention
If you are upgrading to reader 3.0 from a version older than 2.10, you must open your database with reader 2.10 or newer once, to run the removed migrations:
pip install 'reader>=2.10,<3' && \ python - db.sqlite << EOF import sys from reader import make_reader from reader.plugins.mark_as_read import _migrate_pre_2_7_metadata as migrate_mark_as_read reader = make_reader(sys.argv[1]) for feed in reader.get_feeds(): migrate_mark_as_read(reader, feed) print("OK") EOF
Remove code that issued deprecation warnings in versions 2.* (#268):
Reader.get_feed_metadata()Reader.get_feed_metadata_item()Reader.set_feed_metadata_item()Reader.delete_feed_metadata_item()Reader.get_feed_tags()Reader.add_feed_tag()Reader.remove_feed_tag()MetadataErrorMetadataNotFoundErrorFeedMetadataNotFoundErrorEntryMetadataNotFoundErrorthe
object_idproperty of data objects and related exceptions
Make some of the parameters of the following positional-only (#268):
Reader.add_feed():feedReader.delete_feed():feedReader.change_feed_url():old,newReader.get_feed():feed,defaultReader.set_feed_user_title():feed,titleReader.update_feed():feedReader.get_entry():entry,defaultReader.set_entry_read():entry,readReader.mark_entry_as_read():entryReader.set_entry_important():entry,importantReader.add_entry():entryReader.delete_entry():entryReader.search_entries():queryReader.search_entry_counts():queryReader.get_tags():resourceReader.get_tag_keys():resourceReader.get_tag():resource,key,defaultReader.set_tag():resource,key,valueReader.delete_tag():resource,keyReader.make_plugin_reserved_name():plugin_name,keyFeedError(and subclasses):urlEntryError(and subclasses):feed_url,entry_idTagError(and subclasses):resource_id,key
In
make_reader(), wrap exceptions raised during plugin initialization in new exceptionPluginInitErrorinstead of letting them bubble up. (#268)Swap the order of the first two arguments of
TagError(and subclasses);TagError(key, resource_id, ...)becomesTagError(resource_id, key, ...). (#268)
Version 2.17
Released 2022-07-23
Deprecate the
object_idproperty of data objects in favor of new propertyresource_id.resource_idis the same asobject_id, except for feeds and feed-related exceptions it is of typetuple[str]instead ofstr.object_idwill be removed in version 3.0. (#266, #268)Do not attempt too hard to run
PRAGMA optimizeif the database is busy. Prevents rare “database is locked” errors when multiple threads using the same reader terminate at the same time. (#206)
Version 2.16
Released 2022-07-17
Version 2.15
Released 2022-07-08
Version 2.14
Released 2022-06-30
Version 2.13
Released 2022-06-28
Add the twitter experimental plugin, which allows using a Twitter account as a feed. (#271)
Skip with a warning entries that have no <guid> or <link> in an RSS feed; only raise
ParseErrorif all entries have a missing id. (Note that both Atom and JSON Feed entries are required to have an id by their respective specifications.) Thanks to Mirek Długosz for the issue and pull request. (#281)Add
ReaderWarning.
Version 2.12
Released 2022-03-31
Add the
readtimebuilt-in plugin, which stores the entry read time as a tag during feed update. (#275)Allow running arbitrary actions once before/after updating feeds via
before_feeds_update_hooks/after_feeds_update_hooks.Add
Entry.get_content()andContent.is_html.In the web app, use the read time provided by the
readtimeplugin, instead of calculating it on each page load. Speeds up the rendering of the entries page by 20-30%, hopefully winning back the time lost when the read time feature was first added in 2.6. (#275)In the web app, also show the read time for search results.
Version 2.11
Released 2022-03-17
Fix issue causing
make_reader()to fail with messagedatabase requirement error: required SQLite compile options missing: ['ENABLE_JSON1']when using SQLite 3.38 or newer. (#273)
Version 2.10
Released 2022-03-12
Remove
get_tags()support for the(None,)(any feed) andNone(any resource) wildcard resource values.Warning
This is a minor compatibility break, but is unlikely to affect existing users; the usefulness of the wildcards was limited, because it was impossible to tell to which resource a (key, value) pair belongs.
Allow passing a (feed URL,) 1-tuple anywhere a feed URL can be passed to a
Readermethod.Remove the
global_metadataexperimental plugin (superseded by global tags).In the web application, support editing entry and global metadata. Fix broken delete metadata button. Fix broken error flashing.
Version 2.9
Released 2022-02-07
Decrease
update_feeds()memory usage by ~35% (using the maxrss before the call as baseline; overall process maxrss decreases by ~20%). The improvement is not in reader code, but in feedparser; reader will temporarily vendor feedparser until the fix makes it upstream and is released on PyPI. (#265)In the web application, allow sorting feeds by the number of entries: important, unread, per day during the last 1, 3, 12 months. (#249, #245).
Version 2.8
Released 2022-01-22
Add generic tag methods
get_tags(),get_tag_keys(),get_tag(),set_tag(), anddelete_tag(), providing a unified interface for accessing tags as key-value pairs. (#266)Add the
TagError,TagNotFoundError, andResourceNotFoundErrorexceptions.Deprecate feed-specific tag and metadata methods (#266):
get_feed_metadata(), useget_tags()insteadget_feed_metadata_item(), useget_tag()insteadset_feed_metadata_item(), useset_tag()insteaddelete_feed_metadata_item(), usedelete_tag()insteadget_feed_tags(), useget_tag_keys()insteadadd_feed_tag(), useset_tag()insteadremove_feed_tag(), usedelete_tag()instead
Deprecate
MetadataError,MetadataNotFoundError, andFeedMetadataNotFoundError.All deprecated methods/exceptions will be removed in version 3.0.
Add the
missing_okargument todelete_feed()anddelete_entry().Add the
exist_okargument toadd_feed().In the web application, show maxrss when debug is enabled. (#269)
In the web application, decrease memory usage of the entries page when there are a lot of entries (e.g. for 2.5k entries, maxrss decreased from 115 MiB to 75 MiB), at the expense of making “entries for feed” slightly slower. (#269)
Version 2.7
Released 2022-01-04
Tags and metadata now share the same namespace. See the Resource tags user guide section for details. (#266)
The
mark_as_readplugin now uses the.reader.mark-as-readmetadata for configuration. Feeds using the old metadata,.reader.mark_as_read, will be migrated automatically on update until reader 3.0.Allow running arbitrary actions before updating feeds via
before_feed_update_hooks.Expose
reader.plugins.DEFAULT_PLUGINS.Add the
global_metadataexperimental plugin.
Version 2.6
Released 2021-11-15
Retrieve feeds in parallel, but parse them serially; previously, feeds would be parsed in parallel. Decreases Linux memory usage by ~20% when using
workers; the macOS decrease is less notable. (#261)Allow
update_feeds()andupdate_feeds_iter()to filter feeds byfeed,tags,broken, andupdates_enabled. (#193, #219, #220)Allow
get_feeds()andget_feed_counts()to filter feeds bynew. (#217)Reuse the requests session when retrieving feeds; previously, each feed would get its own session.
Add support for CLI plugins.
Add the
cli_statusexperimental plugin.In the web application, show entry read time.
Version 2.5
Released 2021-10-28
In
add_feed()andchange_feed_url(), validate if the current Reader configuration can handle the new feed URL; if not, raiseInvalidFeedURLError(aValueErrorsubclass). (#155)Warning
This is a minor compatibility break; previously,
ValueErrorwould never be raised forstrarguments. To get the previous behavior (no validation), useallow_invalid_url=True.Allow users to add entries to an existing feed through the new
add_entry()method. Allow deleting user-added entries throughdelete_entry(). (#239)Entry.updatedis nowNoneif missing in the feed (updatedbecame optional in version 2.0). Useupdated_not_nonefor the pre-2.5 behavior. Do not swapEntry.publishedwithEntry.updatedfor RSS feeds whereupdatedis missing. (#183)Support PyPy 3.8.
Fix bug causing
read_modifiedandimportant_modifiedto be reset toNonewhen an entry is updated.Fix bug where deleting an entry and then adding it again (with the same id) would fail if search was enabled and
update_search()was not run before adding the new entry.
Version 2.4
Released 2021-10-19
Enable search by default. (#252)
Add the
search_enabledmake_reader()argument. By default, search is enabled on the firstupdate_search()call; the previous behavior was to do nothing.Always install the full-text search dependencies (previously optional). The
searchextra remains available to avoid breaking dependent packages.
Change the
mark_as_readplugin to also explicitly mark matching entries as unimportant, similar to how the don’t care web application button works. (#260)In the web application, show the feed subtitle. (#223)
Version 2.3
Released 2021-10-11
Support Python 3.10. (#248)
entry_dedupenow deletes old duplicates instead of marking them as read/unimportant. (#140)Note
Please comment in #140 / open an issue if you were relying on the old behavior.
Fix
entry_dedupebug introduced in 2.2, causing the newest read entry to be marked as unread if none of its duplicates are read (idem for important). This was an issue only when re-running the plugin for existing entries, not for new entries (since new entries are unread/unimportant).
Version 2.2
Released 2021-10-08
entry_dedupeplugin improvements: reduce false negatives by using approximate content matching, and make it possible to re-run the plugin for existing entries. (#202)Allow running arbitrary actions for updated feeds via
after_feed_update_hooks. (#202)Add
set_entry_read()andset_entry_important()to allow marking an entry as (un)read/(un)important through a boolean flag. (#256)Record when an entry is marked as read/important, and make it available through
read_modifiedandimportant_modified. Allow providing a custom value using themodifiedargument ofset_entry_read()andset_entry_important(). (#254)Make
entry_dedupecopyread_modifiedandimportant_modifiedfrom the duplicates to the new entry. (#254)In the web application, allow marking an entry as don’t care (read + unimportant explicitly set by the user) with a single button. (#254)
In the web application, show the entry read modified / important modified timestamps as button tooltips. (#254)
Version 2.1
Released 2021-08-18
Return entry averages for the past 1, 3, 12 months from the entry count methods. (#249)
Use an index for
get_entry_counts(feed=...)calls. Makes the /feeds?counts=yes page load 2-4x faster. (#251)Add
UpdateResultupdated_feed,error, andnot_modifiedconvenience properties. (#204)In the web application, show the feed entry count averages as a bar sparkline. (#249)
Make the minimum SQLite version and required SQLite compile options
reader._storagemodule globals, for easier monkeypatching. (#163)This is allows supplying a user-defined
json_array_lengthfunction on platforms where SQLite doesn’t come with the JSON1 extension (e.g. on Windows with stock Python earlier than 3.9; details).Note these globals are private, and thus not covered by the backwards compatibility policy.
Version 2.0
Released 2021-07-17
Attention
This release contains backwards incompatible changes.
Remove old database migrations.
If you are upgrading from reader 1.15 or newer, no action is required.
Attention
If you are upgrading to reader 2.0 from a version older than 1.15, you must open your database with reader 1.15 or newer once, to run the removed migrations:
pip install 'reader>=1.15,<2' && \ python - db.sqlite << EOF import sys from reader import make_reader make_reader(sys.argv[1]) print("OK") EOF
Remove code that issued deprecation warnings in versions 1.* (#183):
Reader.remove_feed()Reader.mark_as_read()Reader.mark_as_unread()Reader.mark_as_important()Reader.mark_as_unimportant()Reader.iter_feed_metadata()the
get_feed_metadata(feed, key, default=no value, /)form ofReader.get_feed_metadata()Reader.set_feed_metadata()Reader.delete_feed_metadata()the
new_onlyparameter ofupdate_feeds()andupdate_feeds_iter()EntryError.urlUpdatedFeed.updated
The
datetimeattributes ofFeedandEntryobjects are now timezone-aware, with the timezone set toutc. Previously, they were naive datetimes representing UTC times. (#233)The parameters of
update_feeds()andupdate_feeds_iter()are now keyword-only. (#183)The
feed_rootargument ofmake_reader()now defaults toNone(don’t open local feeds) instead of''(full filesystem access).make_reader()may now raise anyReaderError, not justStorageError.Entry.updatedmay now beNone; useupdated_not_nonefor the pre-2.0 behavior.
Version 1.20
Released 2021-07-12
Add
after_entry_update_hooks, which allow running arbitrary actions for updated entries. Thanks to Mirek Długosz for the issue and pull request. (#241)Raise
StorageErrorwhen opening / operating on an invalid database, instead of a plainsqlite3.DatabaseError. (#243)
Version 1.19
Released 2021-06-16
Drop Python 3.6 support. (#237)
Support PyPy 3.7. (#234)
Skip enclosures with no
href/url; previously, they would result in a parse error. (#240)Stop using Travis CI (only use GitHub Actions). (#199)
Add the
newargument toupdate_feeds()andupdate_feeds_iter();new_onlyis deprecated and will be removed in 2.0. (#217)Rename
UpdatedFeed.updatedtomodified; for backwards compatibility, the old attribute will be available as a property until version 2.0, when it will be removed.. (#241)Warning
The signature of
UpdatedFeedchanged fromUpdatedFeed(url, new, updated)toUpdatedFeed(url, new, modified).This is a minor compatibility break, but only affects third-party code that instantiates UpdatedFeed directly with
updatedas a keyword argument.
Version 1.18
Released 2021-06-03
Rename
Readerfeed metadata methods:iter_feed_metadata()toget_feed_metadata()get_feed_metadata()toget_feed_metadata_item()set_feed_metadata()toset_feed_metadata_item()delete_feed_metadata()todelete_feed_metadata_item()
For backwards compatibility, the old method signatures will continue to work until version 2.0, when they will be removed. (#183)
Warning
The
get_feed_metadata(feed, key[, default]) -> valueform is backwards-compatible only when the arguments are positional.This is a minor compatibility break; the following work in 1.17, but do not in 1.18:
# raises TypeError reader.get_feed_metadata(feed, key, default=None) # returns `(key, value), ...` instead of `value` reader.get_feed_metadata(feed, key=key)
The pre-1.18
get_feed_metadata()(1.18get_feed_metadata_item()) is intended to have positional-only arguments, but this cannot be expressed easily until Python 3.8.Rename
MetadataNotFoundErrortoFeedMetadataNotFoundError.MetadataNotFoundErrorremains available, and is a superclass ofFeedMetadataNotFoundErrorfor backwards compatibility. (#228)Warning
The signatures of the following exceptions changed:
MetadataErrorTakes a new required
keyargument, instead of no required arguments.MetadataNotFoundErrorTakes only one required argument,
key; theurlargument has been removed.Use
FeedMetadataNotFoundErrorinstead.
This is a minor compatibility break, but only affects third-party code that instantiates these exceptions directly.
Rename
EntryError.urltofeed_url; for backwards compatibility, the old attribute will be available as a property until version 2.0, when it will be removed. (#183).Warning
The signature of
EntryError(and its subclasses) changed fromEntryError(url, id)toEntryError(feed_url, id).This is a minor compatibility break, but only affects third-party code that instantiates these exceptions directly with
urlas a keyword argument.Rename
remove_feed()todelete_feed(). For backwards compatibility, the old method will continue to work until version 2.0, when it will be removed. (#183)Rename
Readermark_as_...methods:mark_as_read()tomark_entry_as_read()mark_as_unread()tomark_entry_as_unread()mark_as_important()tomark_entry_as_important()mark_as_unimportant()tomark_entry_as_unimportant()
For backwards compatibility, the old methods will continue to work until version 2.0, when they will be removed. (#183)
Fix feeds with no title sometimes missing from the
get_feeds()results when there are more than 256 feeds (Storage.chunk_size). (#203)When serving the web application with
python -m reader serve, don’t set theRefererheader for cross-origin requests. (#209)
Version 1.17
Released 2021-05-06
Reserve tags and metadata keys starting with
.reader.and.plugin.for reader- and plugin-specific uses. See the Reserved names user guide section for details. (#186)Ignore
updatedwhen updating feeds; only update the feed if other feed data changed or if any entries were added/updated. (#231)Prevents spurious updates for feeds whose
updatedchanges excessively (either because the entries’ content changes excessively, or because an RSS feed does not have adc:dateelement, and feedparser falls back tolastBuildDateforupdated).The
regex_mark_as_readexperimental plugin is now built-in. To use it with the CLI / web application, use the plugin name instead of the entry point (reader.mark_as_read).The config metadata key and format changed; the config will be migrated automatically on the next feed update, during reader version 1.17 only. If you used
regex_mark_as_readand are upgrading to a version >1.17, install 1.17 (pip install reader==1.17) and run a full feed update (python -m reader update) before installing the newer version.The
enclosure-tags,preview-feed-list, andsqlite-releasesunstable extras are not available anymore. Use theunstable-pluginsextra to install dependencies of the unstable plugins instead.In the web application, allow updating a feed manually. (#195)
Version 1.16
Released 2021-03-29
Allow
make_reader()to load plugins through thepluginsargument. (#229)Enable the
ua_fallbackplugin by default.make_reader()may now raiseInvalidPluginError(aValueErrorsubclass, which it already raises implicitly) for invalid plugin names.The
enclosure_dedupe,feed_entry_dedupe, andua_fallbackplugins are now built-in. (#229)To use them with the CLI / web application, use the plugin name instead of the entry point:
reader._plugins.enclosure_dedupe:enclosure_dedupe -> reader.enclosure_dedupe reader._plugins.feed_entry_dedupe:feed_entry_dedupe -> reader.entry_dedupe reader._plugins.ua_fallback:init -> reader.ua_fallback
Remove the
pluginsextra; plugin loading machinery does not have additional dependencies anymore.Mention in the User guide that all reader functions/methods can raise
ValueErrororTypeErrorif passed invalid arguments. There is no behavior change, this is just documenting existing, previously undocumented behavior.
Version 1.15
Released 2021-03-21
Update entries whenever their content changes, regardless of their
updateddate. (#179)Limit content-only updates (not due to an
updatedchange) to 24 consecutive updates, to prevent spurious updates for entries whose content changes excessively (for example, because it includes the current time). (#225)Previously, entries would be updated only if the entry
updatedwas newer than the stored one.Fix bug causing entries that don’t have
updatedset in the feed to not be updated if the feed is marked as stale. Feed staleness is an internal feature used during storage migrations; this bug could only manifest when migrating from 0.22 to 1.x. (found during #179)Minor web application improvements.
Minor CLI improvements.
Version 1.14
Released 2021-02-22
Add the
update_feeds_iter()method, which yields the update status of each feed as it gets updated. (#204)Change the return type of
update_feed()fromNonetoOptional[UpdatedFeed]. (#204)Add the
session_timeoutargument tomake_reader()to set a timeout for retrieving HTTP(S) feeds. The default (connect timeout, read timeout) is (3.05, 60) seconds; the previous behavior was to never time out.Use
PRAGMA user_versioninstead of a version table. (#210)Use
PRAGMA application_idto identify reader databases; the id is0x66656564–readin ASCII / UTF-8. (#211)Change the
reader updatecommand to show a progress bar and update summary (with colors), instead of plain log output. (#204)Fix broken Mypy config following 0.800 release. (#213)
Version 1.13
Released 2021-01-29
JSON Feed support. (#206)
Split feed retrieval from parsing; should make it easier to add new/custom parsers. (#206)
Prevent any logging output from the
readerlogger by default. (#207)In the
preview_feed_listplugin, add<link rel=alternative ...>tags as a feed detection heuristic.In the
preview_feed_listplugin, add<a>tags as a fallback feed detection heuristic.In the web application, fix bug causing the entries page to crash when counts are enabled.
Version 1.12
Released 2020-12-13
Add the
limitandstarting_afterarguments toget_feeds(),get_entries(), andsearch_entries(), allowing them to be used in a paginated fashion. (#196)Add the
object_idproperty that allows getting the unique identifier of a data object in a uniform way. (#196)In the web application, add links to toggle feed/entry counts. (#185)
Version 1.11
Released 2020-11-28
Allow disabling feed updates for specific feeds. (#187)
Add methods to get aggregated feed and entry counts. (#185)
In the web application: allow disabling feed updates for a feed; allow filtering feeds by whether they have updates enabled; do not show feed update errors for feeds that have updates disabled. (#187)
In the web application, show feed and entry counts when
?counts=yesis used. (#185)In the web application, use YAML instead of JSON for the tags and metadata fields.
Version 1.10
Released 2020-11-20
Use indexes for
get_entries()(recent order); should make calls 10-30% faster. (#134)Allow sorting
search_entries()results randomly. Allow sorting search results randomly in the web application. (#200)Reraise unexpected errors caused by parser bugs instead of replacing them with an
AssertionError.Add the
sqlite_releasescustom parser plugin.Refactor the HTTP feed sub-parser to allow reuse by custom parsers.
Add a user guide, and improve other parts of the documentation. (#194)
Version 1.9
Released 2020-10-28
Support Python 3.9. (#199)
Support Windows (requires Python >= 3.9). (#163)
Use GitHub Actions to do macOS and Windows CI builds. (#199)
Rename the
cloudflare_ua_fixplugin toua_fallback. Retry any feed that gets a 403, not just those served by Cloudflare. (#181)Fix type annotation to avoid mypy 0.790 errors. (#198)
Version 1.8
Released 2020-10-02
Drop feedparser 5.x support (deprecated in 1.7); use feedparser 6.x instead. (#190)
Make the string representation of
ReaderErrorand its subclasses more consistent; add error messages and improve the existing ones. (#173)Add method
change_feed_url()to change the URL of a feed. (#149)Allow changing the URL of a feed in the web application. (#149)
Add more tag navigation links to the web application. (#184)
In the
feed_entry_dedupeplugin, copy the important flag from the old entry to the new one. (#140)
Version 1.7
Released 2020-09-19
Add new methods to support feed tags:
add_feed_tag(),remove_feed_tag(), andget_feed_tags(). Allow filtering feeds and entries by their feed tags. (#184)Add the
brokenargument toget_feeds(), which allows getting only feeds that failed / did not fail during the last update. (#189)feedparser 5.x support is deprecated in favor of feedparser 6.x. Using feedparser 5.x will raise a deprecation warning in version 1.7, and support will be removed the following version. (#190)
Tag-related web application features: show tags in the feed list; allow adding/removing tags; allow filtering feeds and entries by their feed tag; add a page that lists all tags. (#184)
In the web application, allow showing only feeds that failed / did not fail. (#189)
In the
preview_feed_listplugin, add<meta>tags as a feed detection heuristic.Add a few property-based tests. (#188)
Version 1.6
Released 2020-09-04
Add the
feed_rootargument tomake_reader(), which allows limiting local feed parsing to a specific directory or disabling it altogether. Using it is recommended, since by default reader will access any local feed path (in 2.0, local file parsing will be disabled by default). (#155)Support loading CLI and web application settings from a configuration file. (#177)
Fail fast for feeds that return HTTP 4xx or 5xx status codes, instead of (likely) failing later with an ambiguous XML parsing error. The cause of the raised
ParseErroris now an instance ofrequests.HTTPError. (#182)Add
cloudflare_ua_fixplugin (work around Cloudflare sometimes blocking requests). (#181)feedparser 6.0 (beta) compatibility fixes.
Internal parser API changes to support alternative parsers, pre-request hooks, and making arbitrary HTTP requests using the same logic
Readeruses. (#155)In the /preview page and the
preview_feed_listplugin, use the same plugins the mainReaderdoes. (enabled by #155)
Version 1.5
Released 2020-07-30
Use rowid when deleting from the search index, instead of the entry id. Previously, each
update_search()call would result in a full scan, even if there was nothing to update/delete. This should reduce the amount of reads significantly (deleting 4 entries from a database with 10k entries resulted in an 1000x decrease in bytes read). (#178)Require at least SQLite 3.18 (released 2017-03-30) for the current
update_search()implementation; all other reader features continue to work with SQLite >= 3.15. (#178)Run
PRAGMA optimizeonclose(). This should increase the performance of all methods. As an example, in #178 it was found thatupdate_search()resulted in a full scan of the entries table, even if there was nothing to update; this change should prevent this from happening. (#143)Note
PRAGMA optimizeis a no-op in SQLite versions earlier than 3.18. In order to avoid the case described above, you should run ANALYZE regularly (e.g. every few days).
Version 1.4
Released 2020-07-13
Work to reduce the likelihood of “database is locked” errors during updates (#175):
Prepare entries to be added to the search index (
update_search()) outside transactions.Fix bug causing duplicate rows in the search index when an entry changes while updating the search index.
Update the search index only when the indexed values change (details below).
Use SQLite WAL (details below).
Update the search index only when the indexed values change. Previously, any change on a feed would result in all its entries being re-indexed, even if the feed title or the entry content didn’t change. This should reduce the
update_search()run time significantly.Use SQLite’s write-ahead logging to increase concurrency. At the moment there is no way to disable WAL. This change may be reverted in the future. (#169)
Require at least click 7.0 for the
cliextra.Do not fail for feeds with incorrectly-declared media types, if feedparser can parse the feed; this is similar to the current behavior for incorrectly-declared encodings. (#171)
Raise
ParseErrorduring update for feeds feedparser can’t detect the type of, instead of silently returning an empty feed. (#171)Add
sortargument tosearch_entries(). Allow sorting search results by recency in addition to relevance (the default). (#176)In the web application, display a nice error message for invalid search queries instead of returning an HTTP 500 Internal Server Error.
Other minor web application improvements.
Minor CLI logging improvements.
Version 1.3
Released 2020-06-23
If a feed failed to update, provide details about the error in
Feed.last_exception. (#68)Show details about feed update errors in the web application. (#68)
Expose the
addedandlast_updatedFeed attributes.Expose the
last_updatedEntry attribute.Raise
ParseError/ log during update if an entry has no id, instead of unconditionally raisingAttributeError. (#170)Fall back to <link> as entry id if an entry in an RSS feed has no <guid>; previously, feeds like this would fail on update. (#170)
Minor web application improvements (show feed added/updated date).
In the web application, handle previewing an invalid feed nicely instead of returning an HTTP 500 Internal Server Error. (#172)
Internal API changes to support multiple storage implementations in the future. (#168)
Version 1.2
Released 2020-05-18
Minor web application improvements.
Remove unneeded additional query in methods that use pagination (for n = len(result) / page size, always do n queries instead n+1).
get_entries()andsearch_entries()are now 33–7% and 46–36% faster, respectively, for results of size 32–256. (#166)All queries are now chunked/paginated to avoid locking the SQLite storage for too long, decreasing the chance of concurrent queries timing out; the problem was most visible during
update_search(). This should cap memory usage for methods returning an iterable that were not paginated before; previously the whole result set would be read before returning it. (#167)
Version 1.1
Released 2020-05-08
Add
sortargument toget_entries(). Allow sorting entries randomly in addition to the default most-recent-first order. (#105)Allow changing the entry sort order in the web application. (#105)
Use a query builder instead of appending strings manually for the more complicated queries in search and storage. (#123)
Make searching entries faster by filtering them before searching; e.g. if 1/5 of the entries are read, searching only read entries is now ~5x faster. (enabled by #123)
Version 1.0.1
Released 2020-04-30
Fix bug introduced in 0.20 causing
update_feeds()to silently stop updating the remaining feeds after a feed failed. (#164)
Version 1.0
Released 2020-04-28
Make all private submodules explicitly private. (#156)
Note
All direct imports from
readercontinue to work.The
reader.core.*modules moved toreader.*(most of them prefixed by_).The web application WSGI entry point moved from
reader.app.wsgi:apptoreader._app.wsgi:app.The entry points for plugins that ship with reader moved from
reader.plugins.*toreader._plugins.*.
Require at least beautifulsoup4 4.5 for the
searchextra (before, the version was unspecified). (#161)Rename the web application dependencies extra from
web-apptoapp.Fix relative link resolution and content sanitization; sgmllib3k is now a required dependency for this reason. (#125, #157)
Version 0.22
Released 2020-04-14
Add the
Entry.feed_urlattribute. (#159)Rename the
EntrySearchResultfeedattribute tofeed_url. Usingfeedwill raise a deprecation warning in version 0.22, and will be removed in the following version. (#159)Use
executemany()instead ofexecute()in the SQLite storage. Makes updating feeds (excluding network calls) 5-10% faster. (#144)In the web app, redirect to the feed’s page after adding a feed. (#119)
In the web app, show highlighted search result snippets. (#122)
Version 0.21
Released 2020-04-04
Minor consistency improvements to the web app search button. (#122)
Add support for web application plugins. (#80)
The enclosure tag proxy is now a plugin, and is disabled by default. See its documentation for details. (#52)
In the web app, the “add feed” button shows a preview before adding the feed. (#145)
In the web app, if the feed to be previewed is not actually a feed, show a list of feeds linked from that URL. This is a plugin, and is disabled by default. (#150)
reader now uses a User-Agent header like
python-reader/0.21when retrieving feeds instead of the default requests one. (#154)
Version 0.20
Released 2020-03-31
Fix bug in
enable_search()that caused it to fail if search was already enabled and the reader had any entries.Add an
entryargument toget_entries(), for symmetry withsearch_entries().Add a
feedargument toget_feeds().Add a
keyargument toget_feed_metadata().Require at least requests 2.18 (before, the version was unspecified).
Allow updating feeds concurrently; add a
workersargument toupdate_feeds(). (#152)
Version 0.19
Released 2020-03-25
Support PyPy 3.6.
Allow searching for entries. (#122)
Stricter type checking for the core modules.
Various changes to the storage internal API.
Version 0.18
Released 2020-01-26
Support Python 3.8.
Increase the
get_entries()recent threshold from 3 to 7 days. (#141)Enforce type checking for the core modules. (#132)
Use dataclasses for the data objects instead of attrs. (#137)
Version 0.17
Released 2019-10-12
Remove the
whichargument ofget_entries(). (#136)Readerobjects should now be created usingmake_reader(). Instantiating Reader directly will raise a deprecation warning.The resources associated with a reader can now be released explicitly by calling its
close()method. (#139)Make the database schema more strict regarding nulls. (#138)
Tests are now run in a random order. (#142)
Version 0.16
Released 2019-09-02
Allow marking entries as important. (#127)
get_entries()andget_feeds()now take only keyword arguments.get_entries()argumentwhichis now deprecated in favor ofread. (#136)
Version 0.15
Released 2019-08-24
Improve entry page rendering for text/plain content. (#117)
Improve entry page rendering for images and code blocks. (#126)
Show enclosures on the entry page. (#128)
Show the entry author. (#129)
Fix bug causing the enclosure tag proxy to use too much memory. (#133)
Start using mypy on the core modules. (#132)
Version 0.14
Released 2019-08-12
Version 0.13
Released 2019-07-12
Add entry page. (#117)
get_feed()now raisesFeedNotFoundErrorif the feed does not exist; useget_feed(..., default=None)for the old behavior.Add
get_entry(). (#120)
Version 0.12
Released 2019-06-22
Version 0.11
Released 2019-05-26
Version 0.10
Released 2019-05-18
Unify plugin loading and error handling code. (#112)
Minor improvements to CLI error reporting.
Version 0.9
Released 2019-05-12
Improve the
get_entries()sorting algorithm. Fixes a bug introduced by #106 (entries of new feeds would always show up at the top). (#113)
Version 0.8
Released 2019-04-21
Version 0.7
Released 2019-04-14
Increase timeout of the button actions from 2 to 10 seconds.
get_entries()now sorts entries by the import date first, and then bypublished/updated. (#106)Add
enclosure_dedupeplugin (deduplicate enclosures of an entry). (#78)The
servecommand now supports loading plugins. (#78)reader.app.wsginow supports loading plugins. (#78)
Version 0.6
Released 2019-04-13
Version 0.5
Released 2019-02-09
Make updating new feeds up to 2 orders of magnitude faster; fixes a problem introduced by #94. (#104)
Move the core modules to a separate subpackage and enforce test coverage (
make coveragenow fails if the coverage for core modules is less than 100%). (#101)Support Python 3.8 development branch.
Add
devanddocsextras (to install development requirements).Build HTML documentation when running tox.
Add
test-allanddocsmake targets (to run tox / build HTML docs).
Version 0.4
Released 2019-01-02
Support Python 3.7.
Entry
contentandenclosuresnow default to an empty tuple instead ofNone. (#99)get_feeds()now sorts feeds byuser_titleortitleinstead of justtitle. (#102)get_feeds()now sorts feeds in a case insensitive way. (#103)Add
sortargument toget_feeds(); allows sorting feeds by title or by when they were added. (#98)Allow changing the feed sort order in the web application. (#98)
Version 0.3
Released on 2018-12-22
get_entries()now prefers sorting bypublished(if present) to sorting byupdated. (#97)Add
regex_mark_as_readplugin (mark new entries as read based on a regex). (#79)Add
feed_entry_dedupeplugin (deduplicate new entries for a feed). (#79)Plugin loading machinery dependencies are now installed via the
pluginsextra.Add a plugins section to the documentation.
Version 0.2
Released on 2018-11-25
Version 0.1.1
Released on 2018-10-21
Fix broken
reader servecommand (broken in 0.1).Raise
StorageErrorfor unsupported SQLite configurations atReaderinstantiation instead of failing at run-time with a genericStorageError("sqlite3 error"). (#92)Fix wrong submit button being used when pressing enter in non-button fields. (#69)
Raise
StorageErrorfor failed migrations instead of an undocumented exception. (#92)Use
requests-mockin parser tests instead of a web server (test suite run time down by ~35%). (#90)
Version 0.1
Released on 2018-09-15
Initial release; public API stable.
Support broken Tumblr feeds via the the tumblr_gdpr plugin. (#67)