Skip to content

Commit

Permalink
Plugin blueprint registration (#171)
Browse files Browse the repository at this point in the history
Plugin versions should now be defined on the module level, and plugins may contain multiple blueprints.



* Let the root view ('/') be configurable by account role(s).

* enable more control over menu through FLEXMEASURES_LISTED_VIEWS, refactor

* add changelog entry

* mention the FM version this requires in the docs

* Fix bug for Flask AnonymousUser not having an account associated with it. (#167)

* Allow a plugin to define multiple Blueprints and relax naming assumption (#164)

This gives more flexibility in naming the plugin, and less worrying about consistently naming plugins and blueprints in different places (thereby facilitating renaming plugins).

The possibility of defining multiple Blueprints in a plugin is more of a perk, but I can imagine it comes in handy if you want to register separate Blueprints for your UI and API, for example, with a different url_prefix.

* a few smaller corrections from code review

* support adding and deleting of roles

* use a better name for the function dealing with these config entries, and use it throughout

* Swap lines so that the default root_view can be set in time (#165)

* also make FLEXMEASURES_PLATFORM_NAME flexible w.r.t. to account roles

* Customize view titles and icons (#166)

* Allow to set view icons and titles as a config setting

* Add config defaults

* Allow Font Awesome icons to be used in the documentation

* Add documentation for the two new config settings

Co-authored-by: Nicolas Höning <iam@nicolashoening.de>

* add MENU to menu-related config setting names

* separate the application of updates to the menu config, so we apply everything, not just to menu entries not in the original list

* Revert "Allow a plugin to define multiple Blueprints and relax naming assumption (#164)"

This reverts commit 5bc278f.

* Typographical edits

* Corrections in documentation of root view setting

* Derive plugin version from module rather than from blueprint

* Allow a plugin to define multiple Blueprints and relax naming assumption (#164)

This gives more flexibility in naming the plugin, and less worrying about consistently naming plugins and blueprints in different places (thereby facilitating renaming plugins).

The possibility of defining multiple Blueprints in a plugin is more of a perk, but I can imagine it comes in handy if you want to register separate Blueprints for your UI and API, for example, with a different url_prefix.

From PR#164.

* Changelog entry

* More nicely formatted list of plugins

* Add changelog warning

Co-authored-by: Nicolas Höning <nicolas@seita.nl>
Co-authored-by: Nicolas Höning <iam@nicolashoening.de>
  • Loading branch information
3 people committed Sep 3, 2021
1 parent 1cb33ea commit 0bd483a
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 14 deletions.
6 changes: 4 additions & 2 deletions documentation/changelog.rst
Expand Up @@ -10,10 +10,12 @@ v0.6.0 | August XX, 2021

.. warning:: The config setting ``FLEXMEASURES_LISTED_VIEWS`` has been renamed to ``FLEXMEASURES_MENU_LISTED_VIEWS``.

.. warning:: Plugins now need to set their version on their module rather than on their blueprint. See the `documentation for writing plugins <https://flexmeasures.readthedocs.io/en/v0.6.0/dev/plugins.html>`_.

New features
-----------
* Analytics view offers grouping of all assets by location [see `PR #148 <http://www.github.com/SeitaBV/flexmeasures/pull/148>`_]
* Multi-tenancy: Supporting multiple customers per FlexMeasures server, by introducing the `Account` concept. Accounts have users and assets associated. [see `PR #159 <http://www.github.com/SeitaBV/flexmeasures/pull/159>`_ and `PR #163 <http://www.github.com/SeitaBV/flexmeasures/pull/163>`_]
* Multi-tenancy: Supporting multiple customers per FlexMeasures server, by introducing the `Account` concept. Accounts have users and assets associated. [see `PR #159 <http://www.github.com/SeitaBV/flexmeasures/pull/159>`_ and `PR #163 <http://www.github.com/SeitaBV/flexmeasures/pull/163>`_]
* In the UI, the root view ("/"), the platform name and the visible menu items can now be more tightly controlled (per account roles of the current user) [see also `PR #163 <http://www.github.com/SeitaBV/flexmeasures/pull/163>`_]
* Add (experimental) endpoint to post sensor data for any sensor. Also supports our ongoing integration with data internally represented using the `timely beliefs <https://github.com/SeitaBV/timely-beliefs>`_ lib [see `PR #147 <http://www.github.com/SeitaBV/flexmeasures/pull/147>`_]

Expand All @@ -22,10 +24,10 @@ Bugfixes

Infrastructure / Support
----------------------

* Add possibility to send errors to Sentry [see `PR #143 <http://www.github.com/SeitaBV/flexmeasures/pull/143>`_]
* Add CLI task to monitor if tasks ran successfully and recently enough [see `PR #146 <http://www.github.com/SeitaBV/flexmeasures/pull/146>`_]
* Document how to use a custom favicon in plugins [see `PR #152 <http://www.github.com/SeitaBV/flexmeasures/pull/152>`_]
* Allow plugins to register multiple Flask blueprints [see `PR #171 <http://www.github.com/SeitaBV/flexmeasures/pull/171>`_]
* Continue experimental integration with `timely beliefs <https://github.com/SeitaBV/timely-beliefs>`_ lib: link multiple sensors to a single asset [see `PR #157 <https://github.com/SeitaBV/flexmeasures/pull/157>`_]
* The experimental parts of the data model can now be visualised, as well, via `make show-data-model --uml --dev` [also in `PR #157 <https://github.com/SeitaBV/flexmeasures/pull/157>`_]

Expand Down
12 changes: 6 additions & 6 deletions documentation/dev/plugins.rst
Expand Up @@ -18,7 +18,7 @@ Use the config setting :ref:`plugin-config` to point to your plugin(s).
Here are the assumptions FlexMeasures makes to be able to import your Blueprint:

- The plugin folder contains an ``__init__.py`` file.
- In this init, you define a Blueprint object called ``<plugin folder>_bp``.
- In that file, you define a Blueprint object (or several).

We'll refer to the plugin with the name of your plugin folder.

Expand All @@ -37,17 +37,17 @@ With the ``__init__.py`` below, plus the custom Jinja2 template, ``our_client``

.. code-block:: python
__version__ = "2.0"
from flask import Blueprint, render_template, abort
from flask_security import login_required
from flexmeasures.ui.utils.view_utils import render_flexmeasures_template
our_client_bp = Blueprint('our_client', 'our_client',
our_client_bp = Blueprint('our_client', __name__,
template_folder='templates')
our_client_bp.__version__ = "2.0"
# Showcase: Adding a view
@our_client_bp.route('/')
Expand Down Expand Up @@ -79,7 +79,7 @@ With the ``__init__.py`` below, plus the custom Jinja2 template, ``our_client``
.. note:: You can overwrite FlexMeasures routing in your plugin. In our example above, we are using the root route ``/``. FlexMeasures registers plugin routes before its own, so in this case visiting the root URL of your app will display this plugged-in view (the same you'd see at `/my-page`).

.. note:: The ``__version__`` attribute on our blueprint object is being displayed in the standard FlexMeasures UI footer, where we show loaded plugins. Of course, it can also be useful for your own maintenance.
.. note:: The ``__version__`` attribute on our module is being displayed in the standard FlexMeasures UI footer, where we show loaded plugins. Of course, it can also be useful for your own maintenance.


The template would live at ``<some_folder>/our_client/templates/my_page.html``, which works just as other FlexMeasures templates (they are Jinja2 templates):
Expand All @@ -90,7 +90,7 @@ The template would live at ``<some_folder>/our_client/templates/my_page.html``,

{% set active_page = "my-page" %}

{% block title %} Our client Dashboard {% endblock %}
{% block title %} Our client dashboard {% endblock %}

{% block divs %}

Expand Down
2 changes: 1 addition & 1 deletion flexmeasures/ui/utils/view_utils.py
Expand Up @@ -71,7 +71,7 @@ def render_flexmeasures_template(html_filename: str, **variables):
) = get_git_description()
app_start_time = current_app.config.get("START_TIME")
variables["app_running_since"] = time_utils.naturalized_datetime_str(app_start_time)
variables["loaded_plugins"] = ",".join(
variables["loaded_plugins"] = ", ".join(
f"{p_name} (v{p_version})"
for p_name, p_version in current_app.config.get("LOADED_PLUGINS", {}).items()
)
Expand Down
23 changes: 18 additions & 5 deletions flexmeasures/utils/app_utils.py
Expand Up @@ -5,7 +5,7 @@
from importlib.abc import Loader

import click
from flask import Flask, current_app, redirect
from flask import Blueprint, Flask, current_app, redirect
from flask.cli import FlaskGroup, with_appcontext
from flask_security import current_user
import sentry_sdk
Expand Down Expand Up @@ -183,7 +183,7 @@ def register_plugins(app: Flask):
Assumptions:
- Your plugin folders contains an __init__.py file.
- In this init, you define a Blueprint object called <plugin folder>_bp
- In that file, you define a Blueprint object (or several).
We'll refer to the plugins with the name of your plugin folders (last part of the path).
"""
Expand Down Expand Up @@ -214,9 +214,22 @@ def register_plugins(app: Flask):
sys.modules[plugin_name] = module
assert isinstance(spec.loader, Loader)
spec.loader.exec_module(module)
plugin_blueprint = getattr(module, f"{plugin_name}_bp")
app.register_blueprint(plugin_blueprint)
plugin_version = getattr(plugin_blueprint, "__version__", "0.1")

# Look for blueprints in the plugin's main __init__ module and register them
plugin_blueprints = [
getattr(module, a)
for a in dir(module)
if isinstance(getattr(module, a), Blueprint)
]
if not plugin_blueprints:
app.logger.warning(
f"No blueprints found for plugin {plugin_name} at {plugin_path}."
)
continue
for plugin_blueprint in plugin_blueprints:
app.register_blueprint(plugin_blueprint)

plugin_version = getattr(module, "__version__", "0.1")
app.config["LOADED_PLUGINS"][plugin_name] = plugin_version
app.logger.info(f"Loaded plugins: {app.config['LOADED_PLUGINS']}")
sentry_sdk.set_context("plugins", app.config.get("LOADED_PLUGINS", {}))

0 comments on commit 0bd483a

Please sign in to comment.