Skip to content

Plugin API Reference

This page shows the available public API available for plugins to access.

You can access them by importing them like this:

from web_portal.plugin_api import login_admin_required

# Or

from web_portal import plugin_api

Variables

current_user: AuthUserEnhanced = quart_auth.current_user module-attribute

Route Decorators

ensure_not_setup(func)

used to ensure the app has not gone through setup wizard, aborting to 404 if it has.

Source code in web_portal/core/auth.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def ensure_not_setup(func: Callable) -> Callable:
    """
    used to ensure the app has not gone through setup wizard,
    aborting to 404 if it has.
    """

    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> Any:
        if await get_system_setting(SystemSettingKeys.HAS_SETUP, default=False):
            abort(404)
        return await func(*args, **kwargs)

    return wrapper

login_admin_required(func)

used the same as login_required but checks whether user is admin

Source code in web_portal/core/auth.py
73
74
75
76
77
78
79
80
81
82
83
84
85
def login_admin_required(func: Callable) -> Callable:
    """
    used the same as login_required
    but checks whether user is admin
    """

    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> Any:
        if not (await current_user.is_authenticated_admin):
            raise quart_auth.Unauthorized
        return await func(*args, **kwargs)

    return wrapper

login_required_if_secured(func)

login is required if public access is disabled, otherwise skip

Source code in web_portal/core/auth.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def login_required_if_secured(func: Callable) -> Callable:
    """
    login is required if public access is disabled, otherwise skip
    """

    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> Any:
        if (await get_system_setting(SystemSettingKeys.PORTAL_SECURED, default=False)) and not (
            await current_user.is_authenticated
        ):
            raise quart_auth.Unauthorized
        return await func(*args, **kwargs)

    return wrapper

login_standard_required = quart_auth.login_required module-attribute

redirect_using_back_to(func)

Used to decorate a Quart response, allowing redirects to a provided back_to request arg

Source code in web_portal/core/helpers.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def redirect_using_back_to(func: Callable) -> Callable:
    """
    Used to decorate a Quart response,
    allowing redirects to a provided back_to request arg
    """

    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> Response:
        response = await func(*args, **kwargs)
        if response is not None:
            # allow the function to overide wrapper
            return response
        # redirect from request args
        if (back_to_url := request.args.get("back_to")) is not None:
            return redirect(back_to_url)
        # failover if nothing else applies
        return redirect(url_for("portal.portal"))

    return wrapper

Settings Access

get_plugin_system_setting(plugin_name, key, /, *, default=None, skip_cache=False) async

Gets a plugin's system setting stored in db or from cache

:param plugin_name: The plugin's internal name
:param key: The setting's key
:param default: The default value to use if no setting was found, defaults to None
:param skip_cache: Whether the skip cache and load from db directly, defaults to False
:return: The loaded value or None
Source code in web_portal/core/plugin.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
async def get_plugin_system_setting(
    plugin_name: str, key: str, /, *, default: Any | None = None, skip_cache: bool = False
) -> Any | None:
    """
    Gets a plugin's system setting stored in db or from cache

        :param plugin_name: The plugin's internal name
        :param key: The setting's key
        :param default: The default value to use if no setting was found, defaults to None
        :param skip_cache: Whether the skip cache and load from db directly, defaults to False
        :return: The loaded value or None
    """
    full_key = make_system_setting_plugin_key(plugin_name, key)
    return get_system_setting(full_key, default=default, skip_cache=skip_cache)

set_plugin_system_setting(plugin_name, key, value) async

Set a plugin's system setting stored in db and updates cache

:param plugin_name: The plugin's internal name
:param key: The setting's key
:param value: Value to update setting to
Source code in web_portal/core/plugin.py
244
245
246
247
248
249
250
251
252
253
async def set_plugin_system_setting(plugin_name: str, key: str, value: Any, /):
    """
    Set a plugin's system setting stored in db and updates cache

        :param plugin_name: The plugin's internal name
        :param key: The setting's key
        :param value: Value to update setting to
    """
    full_key = make_system_setting_plugin_key(plugin_name, key)
    await set_system_setting(full_key, value)

remove_plugin_system_setting(plugin_name, key) async

Removes a set plugin's system setting stored in db and cache

:param plugin_name: The plugin's internal name
:param key: The setting's key
Source code in web_portal/core/plugin.py
256
257
258
259
260
261
262
263
264
async def remove_plugin_system_setting(plugin_name: str, key: str, /):
    """
    Removes a set plugin's system setting stored in db and cache

        :param plugin_name: The plugin's internal name
        :param key: The setting's key
    """
    full_key = make_system_setting_plugin_key(plugin_name, key)
    await remove_system_setting(full_key)

Widget Access

WidgetDetails dataclass

Used for storing information about a widget, returned by get_widget_details()

Source code in web_portal/core/plugin.py
40
41
42
43
44
45
46
47
48
49
50
@dataclass
class WidgetDetails:
    """
    Used for storing information about a widget,
    returned by get_widget_details()
    """

    human_name: str
    internal_name: str
    plugin_name: str
    config: Any | None

get_widget_owner_id(widget_id) async

Get a widget's owner id

:param widget_id: The widgets id
:return: The owner id
Source code in web_portal/core/plugin.py
267
268
269
270
271
272
273
274
275
async def get_widget_owner_id(widget_id: int, /) -> int:
    """
    Get a widget's owner id

        :param widget_id: The widgets id
        :return: The owner id
    """
    widget = await app_models.DashboardWidget.get(id=widget_id).prefetch_related("dashboard")
    return widget.dashboard.owner_id

get_widget_details(widget_id) async

Get a widgets details

:param widget_id: The widgets id
:return: The details
Source code in web_portal/core/plugin.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
async def get_widget_details(widget_id: int, /) -> WidgetDetails:
    """
    Get a widgets details

        :param widget_id: The widgets id
        :return: The details
    """
    widget = await app_models.DashboardWidget.get(id=widget_id).prefetch_related(
        "widget",
        "widget__plugin",
    )
    return WidgetDetails(
        widget.name,
        deconstruct_widget_name(widget.widget.plugin.internal_name, widget.widget.internal_name),
        widget.widget.plugin.internal_name,
        widget.config,
    )

set_widget_config(widget_id, config) async

Set a widgets config

:param widget_id: The widgets id
:param config: The config to update to
Source code in web_portal/core/plugin.py
297
298
299
300
301
302
303
304
305
306
async def set_widget_config(widget_id: int, config: Any | None, /):
    """
    Set a widgets config

        :param widget_id: The widgets id
        :param config: The config to update to
    """
    widget = await app_models.DashboardWidget.get(id=widget_id)
    widget.config = config
    await widget.save()

Plugin

PluginMeta dataclass

Class used when creating a plugin, stores all information about a plugin and what it supports.

Source code in web_portal/core/plugin.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@dataclass
class PluginMeta:
    """
    Class used when creating a plugin,
    stores all information about a plugin and what it supports.
    """

    version_specifier: str
    human_name: str
    widgets: dict[str, str]
    db_models: Collection[str | ModuleType]
    blueprints: Collection[Blueprint]
    index_route_url: str
    get_rendered_widget: Callable[[str, int, dict | None], Awaitable[str]] | None = None
    get_rendered_widget_edit: Callable[[str, int, dict | None, str], Awaitable[str]] | None = None
    get_settings: Callable[[], dict] | None = None
    get_injected_head: Callable[[], Awaitable[str]] | None = None
    do_demo_setup: Callable[[], Awaitable] | None = None

    def is_supported_version(self, app_version: str) -> bool:
        """
        Check whether the version requirement matches the given app version

            :param app_version: The app version, given as a semantic version number
            :return: Whether it is supported
        """
        try:
            ver_specifier = SpecifierSet(self.version_specifier)
        except InvalidSpecifier:
            raise PluginVersionException(
                "unexpected version specifier, " "please use format from PEP 440 e.g. '== 2'"
            ) from None
        else:
            return app_version in ver_specifier

is_supported_version(app_version)

Check whether the version requirement matches the given app version

:param app_version: The app version, given as a semantic version number
:return: Whether it is supported
Source code in web_portal/core/plugin.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def is_supported_version(self, app_version: str) -> bool:
    """
    Check whether the version requirement matches the given app version

        :param app_version: The app version, given as a semantic version number
        :return: Whether it is supported
    """
    try:
        ver_specifier = SpecifierSet(self.version_specifier)
    except InvalidSpecifier:
        raise PluginVersionException(
            "unexpected version specifier, " "please use format from PEP 440 e.g. '== 2'"
        ) from None
    else:
        return app_version in ver_specifier

get_plugin_data_path(plugin_name)

Get a plugins's data path for storing persistant data outside of the database, path will be created if not exists when this function is run

:param plugin_name: The plugin's internal name
:return: The data path
Source code in web_portal/core/plugin.py
309
310
311
312
313
314
315
316
317
318
319
320
321
def get_plugin_data_path(plugin_name: str) -> Path:
    """
    Get a plugins's data path for storing
    persistant data outside of the database,
    path will be created if not exists
    when this function is run

        :param plugin_name: The plugin's internal name
        :return: The data path
    """
    data_path = get_settings().DATA_PATH / "plugins" / plugin_name
    data_path.mkdir(parents=True, exist_ok=True)
    return data_path

Endpoints

PORTAL_ENDPOINT = 'portal.portal' module-attribute