plugin_development.rst 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. .. _plugin_development:
  2. Developing new Plugins
  3. ======================
  4. If you want to write a plugin, it's a very good idea to checkout existing
  5. plugins. A good starting point for example is the `Portal Plugin`_.
  6. Also make sure to check out the cookiecutter-flaskbb-plugin project, which is a
  7. cookiecutter template which helps you to create new plugins.
  8. For example, the structure of a plugin could look like this:
  9. .. sourcecode:: text
  10. your_package_name
  11. |-- setup.py
  12. |-- my_plugin
  13. |-- __init__.py
  14. |-- views.py
  15. |-- models.py
  16. |-- forms.py
  17. |-- static
  18. | |-- style.css
  19. |-- templates
  20. |-- myplugin.html
  21. |-- migrations
  22. |-- 59f7c49b6289_init.py
  23. Metadata
  24. --------
  25. FlaskBB Plugins are usually following the naming scheme of
  26. ``flaskbb-plugin-YOUR_PLUGIN_NAME`` which should make them better
  27. distinguishable from other PyPI distributions.
  28. A proper plugin should have at least put the following metadata into
  29. the ``setup.py`` file.
  30. .. sourcecode:: python
  31. setup(
  32. name="flaskbb-plugin-YOUR_PLUGIN_NAME", # name on PyPI
  33. packages=["your_package_name"], # name of the folder your plugin is located in
  34. version='1.0',
  35. url=<url to your project>,
  36. license=<your license>,
  37. author=<you>,
  38. author_email=<your email>,
  39. description=<your short description>,
  40. long_description=__doc__,
  41. include_package_data=True,
  42. zip_safe=False,
  43. platforms='any',
  44. entry_points={
  45. 'flaskbb_plugin': [
  46. 'unique_name_of_plugin = your_package_name.pluginmodule', # most important part
  47. ]
  48. }
  49. )
  50. The most important part here is the ``entry_point``. Here you tell FlaskBB the
  51. unique name of your plugin and where your plugin module is located inside
  52. your project. Entry points are a feature that is provided by setuptools.
  53. FlaskBB looks up the ``flaskbb_plugin`` entrypoint to discover its plugins.
  54. Have a look at the `setup script`_ documentation and the `sample setup.py`_
  55. file to get a better idea what the ``setup.py`` file is all about it.
  56. For a full example, checkout the `Portal Plugin`_.
  57. .. _`setup script`: https://docs.python.org/3.6/distutils/setupscript.html#additional-meta-data
  58. .. _`sample setup.py`: https://github.com/pypa/sampleproject/blob/master/setup.py
  59. .. _`Portal Plugin`: https://github.com/sh4nks/flaskbb-plugins/tree/master/portal
  60. Settings
  61. --------
  62. Plugins can create settings which integrate with the 'Settings' tab of
  63. the Admin Panel.
  64. The settings are stored in a dictionary with a given structure. The name of
  65. the dictionary must be ``SETTINGS`` and be placed in the plugin module.
  66. The structure of the ``SETTINGS`` dictionary is best explained via an
  67. example::
  68. SETTINGS = {
  69. # This key has to be unique across FlaskBB.
  70. # Using a prefix is recommended.
  71. 'forum_ids': {
  72. # Default Value. The type of the default value depends on the
  73. # SettingValueType.
  74. 'value': [1],
  75. # The Setting Value Type.
  76. 'value_type': SettingValueType.selectmultiple,
  77. # The human readable name of your configuration variable
  78. 'name': "Forum IDs",
  79. # A short description of what the settings variable does
  80. 'description': ("The forum ids from which forums the posts "
  81. "should be displayed on the portal."),
  82. # extra stuff like the 'choices' in a select field or the
  83. # validators are defined in here
  84. 'extra': {"choices": available_forums, "coerce": int}
  85. }
  86. }
  87. .. currentmodule:: flaskbb.utils.forms
  88. .. table:: Available Setting Value Types
  89. :widths: auto
  90. ======================================== =================
  91. Setting Value Type Parsed & Saved As
  92. ======================================== =================
  93. :attr:`SettingValueType.string` :class:`str`
  94. :attr:`SettingValueType.integer` :class:`int`
  95. :attr:`SettingValueType.float` :class:`float`
  96. :attr:`SettingValueType.boolean` :class:`bool`
  97. :attr:`SettingValueType.select` :class:`list`
  98. :attr:`SettingValueType.selectmultiple` :class:`list`
  99. ======================================== =================
  100. .. table:: Available Additional Options via the ``extra`` Keyword
  101. =========== ====================== ========================================
  102. Options Applicable Types Description
  103. =========== ====================== ========================================
  104. ``min`` string, integer, float **Optional.** The minimum required
  105. length of the setting value. If used on
  106. a numeric type, it will check the
  107. minimum value.
  108. ``max`` string, integer, float **Optional.** The maximum required
  109. length of the setting value. If used on
  110. a numeric type, it will check the
  111. maximum value.
  112. ``choices`` select, selectmultiple **Required.** A callable which returns
  113. a sequence of (value, label) pairs.
  114. ``coerce`` select, selectmultiple **Optional.** Coerces the select values
  115. to the given type.
  116. =========== ====================== ========================================
  117. Validating the size of the integer/float and the length of the string fields
  118. is also possible via the ``min`` and ``max`` keywords::
  119. 'recent_topics': {
  120. ...
  121. 'extra': {"min": 1},
  122. },
  123. The ``select`` and ``selectmultiple`` fields have to provide a callback which
  124. lists all the available choices. This is done via the ``choices`` keyword.
  125. In addition to that they can also specify the ``coerce`` keyword which will
  126. coerce the input value into the specified type.::
  127. 'forum_ids': {
  128. ...
  129. 'extra': {"choices": available_forums, "coerce": int}
  130. }
  131. For more information see the :doc:`settings` chapter.
  132. Using Hooks
  133. -----------
  134. Hooks are invoked based on an event occurring within FlaskBB. This makes it
  135. possible to change the behavior of certain actions without modifying the
  136. actual source code of FlaskBB.
  137. For your plugin to actually do something useful, you probably want to 'hook'
  138. your code into FlaskBB. This can be done throughout a lot of places in the
  139. code. FlaskBB loads and calls the hook calls hook functions from registered
  140. plugins for any given hook specification.
  141. Each hook specification has a corresponding hook implementation. By default,
  142. all hooks that are prefix with ``flaskbb_`` will be marked as a standard
  143. hook implementation. It is possible to modify the behavior of hooks.
  144. For example, default hooks are called in LIFO registered order.
  145. A hookimpl can influence its call-time invocation position using special
  146. attributes. If marked with a "tryfirst" or "trylast" option it will be executed
  147. first or last respectively in the hook call loop::
  148. hookimpl = HookimplMarker('flaskbb')
  149. @hookimpl(trylast=True)
  150. def flaskbb_additional_setup(app):
  151. return "save the best for last"
  152. In order to extend FlaskBB with your Plugin you will need to connect your
  153. callbacks to the hooks.
  154. Let's look at an actually piece of `used code`_.
  155. .. sourcecode:: python
  156. def flaskbb_load_blueprints(app):
  157. app.register_blueprint(portal, url_prefix="/portal")
  158. By defining a function called ``flaskbb_load_blueprints``, which has a
  159. corresponding hook specification under the same name. FlaskBB will pass
  160. in an ``app`` object as specified in the hook spec, which we will use to
  161. register a new blueprint. It is also possible to completely omit the ``app``
  162. argument from the function where it is **not possible** to add new arguments to
  163. the hook implemention.
  164. For a complete list of all available hooks in FlaskBB see the :doc:`hooks`
  165. section.
  166. pytest and pluggy are good resources to get better understanding on how to
  167. write `hook functions`_ using `pluggy`_.
  168. .. _`used code`: https://github.com/sh4nks/flaskbb-plugins/blob/master/portal/portal/__init__.py#L31
  169. .. _`hook functions`: https://docs.pytest.org/en/latest/writing_plugins.html#writing-hook-functions
  170. .. _`pluggy`: https://pluggy.readthedocs.io/en/latest/#defining-and-collecting-hooks
  171. Plugin Registry
  172. ---------------
  173. The plugin registry holds all available plugins. It shows the plugin's status
  174. whether it is enabled or disabled, installable or installed. The registry also
  175. holds a reference to the plugin's instance, provides an interface to access
  176. the plugins metadata and stores its settings.
  177. You can query it like any SQLAlchemy Model::
  178. plugin = PluginRegistry.query.filter_by(name="portal").first()
  179. .. autoclass:: flaskbb.plugins.models.PluginRegistry
  180. :members:
  181. Plugin Manager
  182. --------------
  183. FlaskBB overrides the PluginManager from pluggy to provide some additional functionality like accessing the information stored in a setup.py file. The plugin manager will only list the currently enabled plugins and can be used to directly access the plugins instance by its name.
  184. Accessing a plugins instance is as easy as::
  185. plugin_instance = current_app.pluggy.get_plugin(name)
  186. .. autoclass:: flaskbb.plugins.manager.FlaskBBPluginManager
  187. :members:
  188. :inherited-members: