diff --git a/docs/dev/plugins/commands.rst b/docs/dev/plugins/commands.rst new file mode 100644 index 000000000..6a9727859 --- /dev/null +++ b/docs/dev/plugins/commands.rst @@ -0,0 +1,50 @@ +.. _add_subcommands: + +Add Commands to the CLI +~~~~~~~~~~~~~~~~~~~~~~~ + +Plugins can add new subcommands to the ``beet`` command-line interface. Define +the plugin class' ``commands()`` method to return a list of ``Subcommand`` +objects. (The ``Subcommand`` class is defined in the ``beets.ui`` module.) +Here's an example plugin that adds a simple command: + +:: + + from beets.plugins import BeetsPlugin + from beets.ui import Subcommand + + my_super_command = Subcommand('super', help='do something super') + def say_hi(lib, opts, args): + print("Hello everybody! I'm a plugin!") + my_super_command.func = say_hi + + class SuperPlug(BeetsPlugin): + def commands(self): + return [my_super_command] + +To make a subcommand, invoke the constructor like so: ``Subcommand(name, parser, +help, aliases)``. The ``name`` parameter is the only required one and should +just be the name of your command. ``parser`` can be an `OptionParser instance`_, +but it defaults to an empty parser (you can extend it later). ``help`` is a +description of your command, and ``aliases`` is a list of shorthand versions of +your command name. + +.. _optionparser instance: https://docs.python.org/library/optparse.html + +You'll need to add a function to your command by saying ``mycommand.func = +myfunction``. This function should take the following parameters: ``lib`` (a +beets ``Library`` object) and ``opts`` and ``args`` (command-line options and +arguments as returned by OptionParser.parse_args_). + +.. _optionparser.parse_args: https://docs.python.org/library/optparse.html#parsing-arguments + +The function should use any of the utility functions defined in ``beets.ui``. +Try running ``pydoc beets.ui`` to see what's available. + +You can add command-line options to your new command using the ``parser`` member +of the ``Subcommand`` class, which is a ``CommonOptionsParser`` instance. Just +use it like you would a normal ``OptionParser`` in an independent script. Note +that it offers several methods to add common options: ``--album``, ``--path`` +and ``--format``. This feature is versatile and extensively documented, try +``pydoc beets.ui.CommonOptionsParser`` for more information. + diff --git a/docs/dev/plugins/events.rst b/docs/dev/plugins/events.rst new file mode 100644 index 000000000..704d4c794 --- /dev/null +++ b/docs/dev/plugins/events.rst @@ -0,0 +1,142 @@ +.. _plugin_events: + +Listen for Events +~~~~~~~~~~~~~~~~~ + +Event handlers allow plugins to run code whenever something happens in beets' +operation. For instance, a plugin could write a log message every time an album +is successfully autotagged or update MPD's index whenever the database is +changed. + +You can "listen" for events using ``BeetsPlugin.register_listener``. Here's an +example: + +:: + + from beets.plugins import BeetsPlugin + + def loaded(): + print 'Plugin loaded!' + + class SomePlugin(BeetsPlugin): + def __init__(self): + super().__init__() + self.register_listener('pluginload', loaded) + +Note that if you want to access an attribute of your plugin (e.g. ``config`` or +``log``) you'll have to define a method and not a function. Here is the usual +registration process in this case: + +:: + + from beets.plugins import BeetsPlugin + + class SomePlugin(BeetsPlugin): + def __init__(self): + super().__init__() + self.register_listener('pluginload', self.loaded) + + def loaded(self): + self._log.info('Plugin loaded!') + +The events currently available are: + +- ``pluginload``: called after all the plugins have been loaded after the + ``beet`` command starts +- ``import``: called after a ``beet import`` command finishes (the ``lib`` + keyword argument is a Library object; ``paths`` is a list of paths (strings) + that were imported) +- ``album_imported``: called with an ``Album`` object every time the ``import`` + command finishes adding an album to the library. Parameters: ``lib``, + ``album`` +- ``album_removed``: called with an ``Album`` object every time an album is + removed from the library (even when its file is not deleted from disk). +- ``item_copied``: called with an ``Item`` object whenever its file is copied. + Parameters: ``item``, ``source`` path, ``destination`` path +- ``item_imported``: called with an ``Item`` object every time the importer adds + a singleton to the library (not called for full-album imports). Parameters: + ``lib``, ``item`` +- ``before_item_moved``: called with an ``Item`` object immediately before its + file is moved. Parameters: ``item``, ``source`` path, ``destination`` path +- ``item_moved``: called with an ``Item`` object whenever its file is moved. + Parameters: ``item``, ``source`` path, ``destination`` path +- ``item_linked``: called with an ``Item`` object whenever a symlink is created + for a file. Parameters: ``item``, ``source`` path, ``destination`` path +- ``item_hardlinked``: called with an ``Item`` object whenever a hardlink is + created for a file. Parameters: ``item``, ``source`` path, ``destination`` + path +- ``item_reflinked``: called with an ``Item`` object whenever a reflink is + created for a file. Parameters: ``item``, ``source`` path, ``destination`` + path +- ``item_removed``: called with an ``Item`` object every time an item (singleton + or album's part) is removed from the library (even when its file is not + deleted from disk). +- ``write``: called with an ``Item`` object, a ``path``, and a ``tags`` + dictionary just before a file's metadata is written to disk (i.e., just before + the file on disk is opened). Event handlers may change the ``tags`` dictionary + to customize the tags that are written to the media file. Event handlers may + also raise a ``library.FileOperationError`` exception to abort the write + operation. Beets will catch that exception, print an error message and + continue. +- ``after_write``: called with an ``Item`` object after a file's metadata is + written to disk (i.e., just after the file on disk is closed). +- ``import_task_created``: called immediately after an import task is + initialized. Plugins can use this to, for example, change imported files of a + task before anything else happens. It's also possible to replace the task with + another task by returning a list of tasks. This list can contain zero or more + ``ImportTask``. Returning an empty list will stop the task. Parameters: + ``task`` (an ``ImportTask``) and ``session`` (an ``ImportSession``). +- ``import_task_start``: called when before an import task begins processing. + Parameters: ``task`` and ``session``. +- ``import_task_apply``: called after metadata changes have been applied in an + import task. This is called on the same thread as the UI, so use this + sparingly and only for tasks that can be done quickly. For most plugins, an + import pipeline stage is a better choice (see :ref:`plugin-stage`). + Parameters: ``task`` and ``session``. +- ``import_task_before_choice``: called after candidate search for an import + task before any decision is made about how/if to import or tag. Can be used to + present information about the task or initiate interaction with the user + before importing occurs. Return an importer action to take a specific action. + Only one handler may return a non-None result. Parameters: ``task`` and + ``session`` +- ``import_task_choice``: called after a decision has been made about an import + task. This event can be used to initiate further interaction with the user. + Use ``task.choice_flag`` to determine or change the action to be taken. + Parameters: ``task`` and ``session``. +- ``import_task_files``: called after an import task finishes manipulating the + filesystem (copying and moving files, writing metadata tags). Parameters: + ``task`` and ``session``. +- ``library_opened``: called after beets starts up and initializes the main + Library object. Parameter: ``lib``. +- ``database_change``: a modification has been made to the library database. The + change might not be committed yet. Parameters: ``lib`` and ``model``. +- ``cli_exit``: called just before the ``beet`` command-line program exits. + Parameter: ``lib``. +- ``import_begin``: called just before a ``beet import`` session starts up. + Parameter: ``session``. +- ``trackinfo_received``: called after metadata for a track item has been + fetched from a data source, such as MusicBrainz. You can modify the tags that + the rest of the pipeline sees on a ``beet import`` operation or during later + adjustments, such as ``mbsync``. Slow handlers of the event can impact the + operation, since the event is fired for any fetched possible match ``before`` + the user (or the autotagger machinery) gets to see the match. Parameter: + ``info``. +- ``albuminfo_received``: like ``trackinfo_received``, the event indicates new + metadata for album items. The parameter is an ``AlbumInfo`` object instead of + a ``TrackInfo``. Parameter: ``info``. +- ``before_choose_candidate``: called before the user is prompted for a decision + during a ``beet import`` interactive session. Plugins can use this event for + :ref:`appending choices to the prompt ` by returning a + list of ``PromptChoices``. Parameters: ``task`` and ``session``. +- ``mb_track_extract``: called after the metadata is obtained from MusicBrainz. + The parameter is a ``dict`` containing the tags retrieved from MusicBrainz for + a track. Plugins must return a new (potentially empty) ``dict`` with + additional ``field: value`` pairs, which the autotagger will apply to the + item, as flexible attributes if ``field`` is not a hardcoded field. Fields + already present on the track are overwritten. Parameter: ``data`` +- ``mb_album_extract``: Like ``mb_track_extract``, but for album tags. + Overwrites tags set at the track level, if they have the same ``field``. + Parameter: ``data`` + +The included ``mpdupdate`` plugin provides an example use case for event +listeners. \ No newline at end of file diff --git a/docs/dev/plugins/index.rst b/docs/dev/plugins/index.rst new file mode 100644 index 000000000..6c3578e4a --- /dev/null +++ b/docs/dev/plugins/index.rst @@ -0,0 +1,72 @@ +Plugin Development Guide +======================== + +Beets plugins are Python modules or packages that extend the core functionality +of beets. The plugin system is designed to be flexible, allowing developers to +add virtually any type of features. + +.. _writing-plugins: + +Writing Plugins +--------------- + +A beets plugin is just a Python module or package inside the ``beetsplug`` +namespace package. (Check out `this article`_ and `this Stack Overflow +question`_ if you haven't heard about namespace packages.) So, to make one, +create a directory called ``beetsplug`` and add either your plugin module: + +:: + + beetsplug/ + myawesomeplugin.py + +or your plugin subpackage: + +:: + + beetsplug/ + myawesomeplugin/ + __init__.py + myawesomeplugin.py + +.. attention:: + + You do not anymore need to add a ``__init__.py`` file to the ``beetsplug`` + directory. Python treats your plugin as a namespace package automatically, + thus we do not depend on ``pkgutil``-based setup in the ``__init__.py`` file + anymore. + +.. _this article: https://realpython.com/python-namespace-package/#setting-up-some-namespace-packages + +.. _this stack overflow question: https://stackoverflow.com/a/27586272/9582674 + +The meat of your plugin goes in ``myawesomeplugin.py``. There, you'll have to +import ``BeetsPlugin`` from ``beets.plugins`` and subclass it, for example + +.. code-block:: python + + from beets.plugins import BeetsPlugin + + + class MyAwesomePlugin(BeetsPlugin): + pass + +Once you have your ``BeetsPlugin`` subclass, there's a variety of things your +plugin can do. (Read on!) + +To use your new plugin, package your plugin (see how to do this with poetry_ or +setuptools_, for example) and install it into your ``beets`` virtual +environment. Then, add your plugin to beets configuration + +.. _poetry: https://python-poetry.org/docs/pyproject/#packages + +.. _setuptools: https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#finding-simple-packages + +.. code-block:: yaml + + # config.yaml + plugins: + - myawesomeplugin + +and you're good to go! + diff --git a/docs/dev/plugins.rst b/docs/dev/plugins/other.rst similarity index 56% rename from docs/dev/plugins.rst rename to docs/dev/plugins/other.rst index 5ee07347f..9e4589ce7 100644 --- a/docs/dev/plugins.rst +++ b/docs/dev/plugins/other.rst @@ -1,267 +1,4 @@ -Plugin Development Guide -======================== -Beets plugins are Python modules or packages that extend the core functionality -of beets. The plugin system is designed to be flexible, allowing developers to -add virtually any type of features. - -.. _writing-plugins: - -Writing Plugins ---------------- - -A beets plugin is just a Python module or package inside the ``beetsplug`` -namespace package. (Check out `this article`_ and `this Stack Overflow -question`_ if you haven't heard about namespace packages.) So, to make one, -create a directory called ``beetsplug`` and add either your plugin module: - -:: - - beetsplug/ - myawesomeplugin.py - -or your plugin subpackage: - -:: - - beetsplug/ - myawesomeplugin/ - __init__.py - myawesomeplugin.py - -.. attention:: - - You do not anymore need to add a ``__init__.py`` file to the ``beetsplug`` - directory. Python treats your plugin as a namespace package automatically, - thus we do not depend on ``pkgutil``-based setup in the ``__init__.py`` file - anymore. - -.. _this article: https://realpython.com/python-namespace-package/#setting-up-some-namespace-packages - -.. _this stack overflow question: https://stackoverflow.com/a/27586272/9582674 - -The meat of your plugin goes in ``myawesomeplugin.py``. There, you'll have to -import ``BeetsPlugin`` from ``beets.plugins`` and subclass it, for example - -.. code-block:: python - - from beets.plugins import BeetsPlugin - - - class MyAwesomePlugin(BeetsPlugin): - pass - -Once you have your ``BeetsPlugin`` subclass, there's a variety of things your -plugin can do. (Read on!) - -To use your new plugin, package your plugin (see how to do this with poetry_ or -setuptools_, for example) and install it into your ``beets`` virtual -environment. Then, add your plugin to beets configuration - -.. _poetry: https://python-poetry.org/docs/pyproject/#packages - -.. _setuptools: https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#finding-simple-packages - -.. code-block:: yaml - - # config.yaml - plugins: - - myawesomeplugin - -and you're good to go! - -.. _add_subcommands: - -Add Commands to the CLI -~~~~~~~~~~~~~~~~~~~~~~~ - -Plugins can add new subcommands to the ``beet`` command-line interface. Define -the plugin class' ``commands()`` method to return a list of ``Subcommand`` -objects. (The ``Subcommand`` class is defined in the ``beets.ui`` module.) -Here's an example plugin that adds a simple command: - -:: - - from beets.plugins import BeetsPlugin - from beets.ui import Subcommand - - my_super_command = Subcommand('super', help='do something super') - def say_hi(lib, opts, args): - print("Hello everybody! I'm a plugin!") - my_super_command.func = say_hi - - class SuperPlug(BeetsPlugin): - def commands(self): - return [my_super_command] - -To make a subcommand, invoke the constructor like so: ``Subcommand(name, parser, -help, aliases)``. The ``name`` parameter is the only required one and should -just be the name of your command. ``parser`` can be an `OptionParser instance`_, -but it defaults to an empty parser (you can extend it later). ``help`` is a -description of your command, and ``aliases`` is a list of shorthand versions of -your command name. - -.. _optionparser instance: https://docs.python.org/library/optparse.html - -You'll need to add a function to your command by saying ``mycommand.func = -myfunction``. This function should take the following parameters: ``lib`` (a -beets ``Library`` object) and ``opts`` and ``args`` (command-line options and -arguments as returned by OptionParser.parse_args_). - -.. _optionparser.parse_args: https://docs.python.org/library/optparse.html#parsing-arguments - -The function should use any of the utility functions defined in ``beets.ui``. -Try running ``pydoc beets.ui`` to see what's available. - -You can add command-line options to your new command using the ``parser`` member -of the ``Subcommand`` class, which is a ``CommonOptionsParser`` instance. Just -use it like you would a normal ``OptionParser`` in an independent script. Note -that it offers several methods to add common options: ``--album``, ``--path`` -and ``--format``. This feature is versatile and extensively documented, try -``pydoc beets.ui.CommonOptionsParser`` for more information. - -.. _plugin_events: - -Listen for Events -~~~~~~~~~~~~~~~~~ - -Event handlers allow plugins to run code whenever something happens in beets' -operation. For instance, a plugin could write a log message every time an album -is successfully autotagged or update MPD's index whenever the database is -changed. - -You can "listen" for events using ``BeetsPlugin.register_listener``. Here's an -example: - -:: - - from beets.plugins import BeetsPlugin - - def loaded(): - print 'Plugin loaded!' - - class SomePlugin(BeetsPlugin): - def __init__(self): - super().__init__() - self.register_listener('pluginload', loaded) - -Note that if you want to access an attribute of your plugin (e.g. ``config`` or -``log``) you'll have to define a method and not a function. Here is the usual -registration process in this case: - -:: - - from beets.plugins import BeetsPlugin - - class SomePlugin(BeetsPlugin): - def __init__(self): - super().__init__() - self.register_listener('pluginload', self.loaded) - - def loaded(self): - self._log.info('Plugin loaded!') - -The events currently available are: - -- ``pluginload``: called after all the plugins have been loaded after the - ``beet`` command starts -- ``import``: called after a ``beet import`` command finishes (the ``lib`` - keyword argument is a Library object; ``paths`` is a list of paths (strings) - that were imported) -- ``album_imported``: called with an ``Album`` object every time the ``import`` - command finishes adding an album to the library. Parameters: ``lib``, - ``album`` -- ``album_removed``: called with an ``Album`` object every time an album is - removed from the library (even when its file is not deleted from disk). -- ``item_copied``: called with an ``Item`` object whenever its file is copied. - Parameters: ``item``, ``source`` path, ``destination`` path -- ``item_imported``: called with an ``Item`` object every time the importer adds - a singleton to the library (not called for full-album imports). Parameters: - ``lib``, ``item`` -- ``before_item_moved``: called with an ``Item`` object immediately before its - file is moved. Parameters: ``item``, ``source`` path, ``destination`` path -- ``item_moved``: called with an ``Item`` object whenever its file is moved. - Parameters: ``item``, ``source`` path, ``destination`` path -- ``item_linked``: called with an ``Item`` object whenever a symlink is created - for a file. Parameters: ``item``, ``source`` path, ``destination`` path -- ``item_hardlinked``: called with an ``Item`` object whenever a hardlink is - created for a file. Parameters: ``item``, ``source`` path, ``destination`` - path -- ``item_reflinked``: called with an ``Item`` object whenever a reflink is - created for a file. Parameters: ``item``, ``source`` path, ``destination`` - path -- ``item_removed``: called with an ``Item`` object every time an item (singleton - or album's part) is removed from the library (even when its file is not - deleted from disk). -- ``write``: called with an ``Item`` object, a ``path``, and a ``tags`` - dictionary just before a file's metadata is written to disk (i.e., just before - the file on disk is opened). Event handlers may change the ``tags`` dictionary - to customize the tags that are written to the media file. Event handlers may - also raise a ``library.FileOperationError`` exception to abort the write - operation. Beets will catch that exception, print an error message and - continue. -- ``after_write``: called with an ``Item`` object after a file's metadata is - written to disk (i.e., just after the file on disk is closed). -- ``import_task_created``: called immediately after an import task is - initialized. Plugins can use this to, for example, change imported files of a - task before anything else happens. It's also possible to replace the task with - another task by returning a list of tasks. This list can contain zero or more - ``ImportTask``. Returning an empty list will stop the task. Parameters: - ``task`` (an ``ImportTask``) and ``session`` (an ``ImportSession``). -- ``import_task_start``: called when before an import task begins processing. - Parameters: ``task`` and ``session``. -- ``import_task_apply``: called after metadata changes have been applied in an - import task. This is called on the same thread as the UI, so use this - sparingly and only for tasks that can be done quickly. For most plugins, an - import pipeline stage is a better choice (see :ref:`plugin-stage`). - Parameters: ``task`` and ``session``. -- ``import_task_before_choice``: called after candidate search for an import - task before any decision is made about how/if to import or tag. Can be used to - present information about the task or initiate interaction with the user - before importing occurs. Return an importer action to take a specific action. - Only one handler may return a non-None result. Parameters: ``task`` and - ``session`` -- ``import_task_choice``: called after a decision has been made about an import - task. This event can be used to initiate further interaction with the user. - Use ``task.choice_flag`` to determine or change the action to be taken. - Parameters: ``task`` and ``session``. -- ``import_task_files``: called after an import task finishes manipulating the - filesystem (copying and moving files, writing metadata tags). Parameters: - ``task`` and ``session``. -- ``library_opened``: called after beets starts up and initializes the main - Library object. Parameter: ``lib``. -- ``database_change``: a modification has been made to the library database. The - change might not be committed yet. Parameters: ``lib`` and ``model``. -- ``cli_exit``: called just before the ``beet`` command-line program exits. - Parameter: ``lib``. -- ``import_begin``: called just before a ``beet import`` session starts up. - Parameter: ``session``. -- ``trackinfo_received``: called after metadata for a track item has been - fetched from a data source, such as MusicBrainz. You can modify the tags that - the rest of the pipeline sees on a ``beet import`` operation or during later - adjustments, such as ``mbsync``. Slow handlers of the event can impact the - operation, since the event is fired for any fetched possible match ``before`` - the user (or the autotagger machinery) gets to see the match. Parameter: - ``info``. -- ``albuminfo_received``: like ``trackinfo_received``, the event indicates new - metadata for album items. The parameter is an ``AlbumInfo`` object instead of - a ``TrackInfo``. Parameter: ``info``. -- ``before_choose_candidate``: called before the user is prompted for a decision - during a ``beet import`` interactive session. Plugins can use this event for - :ref:`appending choices to the prompt ` by returning a - list of ``PromptChoices``. Parameters: ``task`` and ``session``. -- ``mb_track_extract``: called after the metadata is obtained from MusicBrainz. - The parameter is a ``dict`` containing the tags retrieved from MusicBrainz for - a track. Plugins must return a new (potentially empty) ``dict`` with - additional ``field: value`` pairs, which the autotagger will apply to the - item, as flexible attributes if ``field`` is not a hardcoded field. Fields - already present on the track are overwritten. Parameter: ``data`` -- ``mb_album_extract``: Like ``mb_track_extract``, but for album tags. - Overwrites tags set at the track level, if they have the same ``field``. - Parameter: ``data`` - -The included ``mpdupdate`` plugin provides an example use case for event -listeners. Extend the Autotagger ~~~~~~~~~~~~~~~~~~~~~