lemon24 / reader

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

Minimal API to enable/disable built-in plugins #229

Closed lemon24 closed 3 years ago

lemon24 commented 3 years ago

With #226, it's become obvious that some plugins should be enabled by default.

There's also reader._config.make_reader_from_config(); ideally, make_reader() should tend toward it.

Important: This is not to make public hooks etc. (#80); the only promise we make is: this is how you enable/disable built-in plugins, and these are the built-in plugins.

Additionally, it would probably nice to allow callable-style plugins (a la Mistune v2); here, the promise we make is: given this list of callables, we'll call them with the instantiated reader before returning it.

Candidates for the status of built-in:

lemon24 commented 3 years ago

Case study: Mistune v2

markdown = mistune.create_markdown(plugins=['strikethrough'])

renderer = mistune.HTMLRenderer()
markdown = mistune.Markdown(renderer, plugins=[plugin_strikethrough])

markdown = mistune.create_markdown(plugins=[DirectiveToc(...)])

The string version uses plugins from a hand-written dict of "built-in" plugins.

On the second strikethrough version, the renderer is required by the plugin (because it needs to do stuff to it); I assume create_markdown() adds it automatically.

Case study: Flask extensions

db = SQLAlchemy(app, ...)

db = SQLAlchemy(...)
db.init_app(app)

Unlike Mistune:

Case study: make_reader_from_config()

reader = make_reader_from_config(..., plugins=[
    'reader._plugins.regex_mark_as_read:regex_mark_as_read',
    reader._plugins.ua_fallback.init,
])

Like Mistune, it takes either a string or a callable; unlike Mistune, the string is an import path, not a name.


It should be possible to change a make_reader() call to make_reader_from_config() without changing anything else.

If we use plugin names in make_reader(), we risk shadowing other packages; put another way, we need to be able to distinguish between plugin names and import paths from the start. Possible ways to deal with this:

It seems we have a trade-off between:

Update: A one-user focus group concluded import paths everywhere is better:

reader = make_reader('db.sqlite', plugins=[
    'reader.plugins.ua_fallback',
    'thirdparty',  # implicit callable
    'thirdparty:init',  # explicit callable also OK
])

This is more intuitive (users just have to learn one thing), and we don't have to explain why and how things are different.

Update #2: reader.ua_fallback is not much harder to support, and still has (most) the benefits above.

lemon24 commented 3 years ago

To do: