gosling-lang / gos

A declarative interactive genomics visualization library for Python.
https://gosling-lang.github.io/gos
MIT License
218 stars 14 forks source link

feat: add PluginRegistry with entry_points hook #114

Closed manzt closed 2 years ago

manzt commented 2 years ago

This PR adds a pluggable registry to extend rendering behavior via end user code. This will allow users to write their own renderers (or themes) in Python and override the default behavior.

Both gos.renderers and gos.themes are instances of PluginRegistry.

Usage

Users can register and enable their own renderers manually. As a reminder, a renderer is any function that accepts a Gosling specification as a Python dict, and returns a Python dict in Jupyter’s MIME Bundle format

def my_custom_renderer(spec: dict) -> dict:
    ...
import gosling as gos

vis = gos.Track(...).encode(...).view()

# add a custom renderer explicity
gos.renderers.register("my-renderer", my_custom_renderer)

# enable the renderer for the workflow
gos.renderers.enable("my-renderer")

# automatically use renderer
vis

and manually switch back to the default,

# reenable
gos.renderers.enable("default") # switch back to normal
vis # uses the register renderer

or alternatively they can enable plugins in a controlled manner

with gos.themes.enable("dark"):
    vis.save("index.html") # dark theme just enabled for this block

gos.themes.active # ""

Automatic registration for renderers & themes in third-party packages

While the above is nice for many situations, we also want to enable third-party libraries the ability to implement plugins and have those automatically enabled for users.

For example, let's say I implement a custom HTML renderer for gos that I want to share with a colleague. Ideally one could just pip install a package and use this with Gos, i.e.,

pip install gosling[all]
pip install custom-gosling-renderer
import gosling as gos
# automatically discovered
gos.renderers.names() # ['default', 'custom-gosling-renderer']
gos.renderers.enable('custom-gosling-renderer')

This is exactly what this PR supports. Package authors need to add an entrypoint to their setup.cfg or setup.py with the custom group name, and they will be discoverred automatically by the PluginRegistry.

example

# gosling_json_renderer/__init__.py
import json

def main(spec, **kwargs):
    return json.dumps(spec, **kwargs)
# setup.cfg
[metadata]
name = gosling-json-renderer

[options.entry_points]
gosling.renderer =
  my-json-renderer = gosling_json_renderer:main
pip install -e .
import gosling as gos
gos.renderers.names() # ['default', 'my-json-renderer']