lemon24 / reader

A Python feed reader library.
https://reader.readthedocs.io
BSD 3-Clause "New" or "Revised" License
438 stars 36 forks source link

Is it possible to store custom data in readers database? #267

Closed TheLovinator1 closed 2 years ago

TheLovinator1 commented 2 years ago

Hi,

I made a RSS bot for Discord (https://github.com/TheLovinator1/discord-rss-bot) and right now I am storing the Discord webhook in a separate config file. Is it possible to store this webhook in the same database that the feeds are stored in?

lemon24 commented 2 years ago

Hello!

Currently, only feeds can have metadata attached (user guide section).

However, you can mimic global metadata by creating a fake feed that has updates disabled:

METADATA_FEED = 'system:metadata'
try:
    reader.add_feed(METADATA_FEED, allow_invalid_url=True)
    reader.disable_feed_updates(METADATA_FEED)
except FeedExistsError:
    pass

reader.set_feed_metadata_item(METADATA_FEED, 'config', {'key': 'value'})
config = reader.get_feed_metadata_item(METADATA_FEED, 'config')

You can exclude this feed from get_feeds() by using tags (you don't need to do this for entries, because the metadata feed will never have any):

SYSTEM_TAG = '.system'
reader.add_feed_tag(METADATA_FEED, SYSTEM_TAG)

reader.get_feeds(tags=[f'-{SYSTEM_TAG}', ...])
To make everything neat, you can wrap this into a plugin (click to expand): ```python from reader import make_reader, FeedExistsError METADATA_FEED = 'system:metadata' SYSTEM_TAG = '.system' def global_metadata_plugin(reader): try: reader.add_feed(METADATA_FEED, allow_invalid_url=True) except FeedExistsError: pass else: reader.disable_feed_updates(METADATA_FEED) reader.add_feed_tag(METADATA_FEED, SYSTEM_TAG) def set_global_metadata_item(*args): reader.set_feed_metadata_item(METADATA_FEED, *args) def get_global_metadata_item(*args): return reader.get_feed_metadata_item(METADATA_FEED, *args) def get_global_metadata(*args): return reader.get_feed_metadata(METADATA_FEED, *args) reader.set_global_metadata_item = set_global_metadata_item reader.get_global_metadata_item = get_global_metadata_item reader.get_global_metadata = get_global_metadata def get_feeds(*, tags=None, **kwargs): tags = list(tags) if tags else [] tags += [f'-{SYSTEM_TAG}'] return original_get_feeds(tags=tags, **kwargs) original_get_feeds = get_feeds.__wrapped__ = reader.get_feeds reader.get_feeds = get_feeds # plugins defaults to ['reader.ua_fallback'], so we need to add it back reader = make_reader('db.sqlite', plugins=['reader.ua_fallback', global_metadata_plugin]) ```

I outlined a plan for entry metadata in https://github.com/lemon24/reader/issues/228#issuecomment-844161398, and the same scaffolding can be used for "real" global metadata too, but currently I don't have a use case for it. (I am open to contributions, though – no pressure :)

However, until "real" global metadata actually happens, I think I can add the plugin above to reader as an experimental plugin, since the code is already mostly written; watch out for the upcoming releases.

lemon24 commented 2 years ago

I added the plugin above as an experimental plugin: https://reader.readthedocs.io/en/latest/plugins.html#global-metadata

To use it before 2.7 is released: https://reader.readthedocs.io/en/latest/install.html#living-on-the-edge

TheLovinator1 commented 2 years ago

Perfect! Thank you so much for helping me. This will work perfectly. Your library have saved me so much time.

Thanks!

lemon24 commented 2 years ago

Glad to hear both of those things! :)

lemon24 commented 2 years ago

Hi @TheLovinator1, starting with reader 2.10 (released today), it is possible to store entry and global metadata using resource tags.


I removed the global metadata plugin, since the functionality it offered is now superseded by resource tags.

To migrate the data from the plugin, you need to run something like (pseudocode):

for key, value in reader.get_tags('reader:global-metadata'):
    reader.set_tag((), key, value)  # () indicates the global namespace
# feel free to delete reader:global-metadata after this

Alternatively, you can keep using the plugin by vendoring it in your code (from here).