Plugins

This section describes how plugins can be developed to extend the functionality of Kadi4Mat without having to touch the main application code. As currently all first party plugins are part of the main application package, all existing examples can be found in the kadi.plugins module. This also includes code not implemented as a separate plugin but still using the plugin hooks.

Plugin infrastructure

The plugin infrastructure in Kadi4Mat builds upon pluggy, a simple and flexible plugin system. It allows implementing different plugin interface functions, called hook specifications, which a plugin can choose to implement. Each hook will be called in certain places in the application flow, which will invoke all corresponding implementations of that hook. A list of all currently existing hooks can be found at the end of this section.

In order for the main application to find a plugin, it has to register itself using the kadi_plugins setuptools entry point. Each plugin needs to specify a unique name and the module that contains all hook implementations:

entry_points={
    "kadi_plugins": [
        "example=kadi_plugin_example.plugin",
    ],
},

In this case, the plugin is called example, while the hook implementations are loaded via the kadi_plugin_example.plugin module of some package called kadi_plugin_example.

Plugin hooks

Plugin hooks can be implemented by using the hookimpl decorator. The name of the decorated function must correspond to the respective plugin hook. Plugin configurations can be loaded via the get_config function, see also Installing plugins. An example implementation could look like the following:

from kadi.plugins import get_config
from kadi.plugins import hookimpl

@hookimpl
def kadi_example_plugin_hook():
    # Load the configuration of the "example" plugin, if necessary.
    config = get_config("example")
    # Do something else.

Warning

As long as Kadi4Mat is still in beta, all plugin hooks are considered beta as well. Once Kadi4Mat version 1.0+ is released, backwards incompatible changes will be documented and deprecated hooks will be marked accordingly. Until then, hook specifications are still subject to change.

The following hook specifications for use in plugins currently exist:

kadi.plugins.spec.kadi_register_blueprints(app)[source]

Hook for registering custom Flask blueprints.

An example Flask blueprint, including a custom static path, could look like the following:

from flask import Blueprint

bp = Blueprint(
    'my_plugin',
    __name__,
    template_folder='templates',
    static_folder='static',
    static_url_path='/my_plugin/static',
)
Parameters

app – The application object, which is used to register the blueprint via its register_blueprint method.

kadi.plugins.spec.kadi_get_translations_paths()[source]

Hook for collecting translation paths.

The translations path returned by a plugin must be absolute. Note that currently translations of the main application and plugins are simply merged together, where translations of the main application will always take precedence.

kadi.plugins.spec.kadi_register_oauth2_providers(registry)[source]

Hook for registering OAuth2 providers.

Currently, only the authorization code grant is supported. Each provider needs to register itself to the given registry provided by the Authlib library using a unique name.

Needs to be used together with kadi_get_oauth2_providers().

Parameters

registry – The OAuth2 provider registry, which is used to register the provider via its register method.

kadi.plugins.spec.kadi_get_oauth2_providers()[source]

Hook for collecting OAuth2 providers.

Each OAuth2 provider has to be returned as a dictionary containing all necessary information about the provider. A provider must at least provide the unique name that was also used to register it.

Example:

{
    "name": "my_provider",
    "title": "My provider",
    "website": "https://example.com",
    "description": "The (HTML) description of the OAuth2 provider.",
}

Needs to be used together with kadi_register_oauth2_providers().

kadi.plugins.spec.kadi_get_publication_providers()[source]

Hook for collecting publication providers.

Each publication provider has to be returned as a dictionary containing all necessary information about the provider. A provider must at least provide the unique name that was also used to register the OAuth2 provider that this provider should use.

Example:

{
    "name": "my_provider",
    "description": "The (HTML) description of the publication provider.",
}

Needs to be used together with kadi_register_oauth2_providers() and kadi_get_oauth2_providers().

kadi.plugins.spec.kadi_publish_record(record, provider, client, token, task)[source]

Hook for publishing a record using a given provider.

Each plugin has to check the given provider and decide whether it should start the publishing process, otherwise it has to return None. After performing the publishing operation, the plugin has to return a tuple containing the result status of the operation and a template further describing the result in a user-readable manner, e.g. containing a link to view the published result if the operation was successful.

Needs to be used together with kadi_get_publication_providers(). Note that the hook chain will stop after the first returned result that is not None.

Parameters
  • record – The record to publish.

  • provider – The provider to use for publishing.

  • client – The OAuth2 client to use for publishing.

  • token – The OAuth2 token to use for publishing.

  • task – A Task object that may be provided if this hook is executed in a background task. Can be used to check whether the publishing operation was cancelled and to update the current progress of the operation via the task.

kadi.plugins.spec.kadi_get_custom_mimetype(file, base_mimetype)[source]

Hook for determining a custom MIME type of a file.

Each plugin has to check the given base MIME type and decide whether it should try determining a custom MIME type or not. Otherwise, it has to return None. The returned MIME type should only be based on the content a file actually contains.

Can be used together with kadi_get_preview_data(). Note that the hook chain will stop after the first returned result that is not None.

Parameters
  • file – The File to get the custom MIME type of.

  • base_mimetype – The base MIME type of the file, based on the actual file content, which a plugin can base its decision to return a custom MIME type on.

kadi.plugins.spec.kadi_get_preview_data(file)[source]

Hook for obtaining preview data of a file object to be passed to the frontend.

Each plugin has to check if preview data should be returned for the given file, based on e.g. its storage type, size or MIME types, otherwise it has to return None. The preview data must consist of a tuple containing the preview type and the actual preview data used for rendering the preview later.

Should be used together with kadi_get_preview_templates() and kadi_get_preview_scripts(). Note that the hook chain will stop after the first returned result that is not None.

Parameters

file – The File to get the preview data of.

kadi.plugins.spec.kadi_get_preview_templates()[source]

Hook for collecting templates for rendering preview data.

Each template should consist of an HTML snippet containing all necessary markup to render the preview data. As currently all previews are rendered using Vue.js components, the easiest way to include a custom preview is by using such a component, which can automatically receive the preview data from the backend as shown in the following example:

<!-- Check the preview type first before rendering the component. -->
<div v-if="previewData.type === 'my_plugin_preview_type'">
    <!-- Pass the preview data from the backend into the component. -->
    <my-plugin-preview :data="previewData.data"></my-plugin-preview>
</div>

In order to actually register the custom component via JavaScript, kadi_get_preview_scripts() needs to be used. Should also be used together with kadi_get_preview_data().

kadi.plugins.spec.kadi_get_preview_scripts()[source]

Hook for collecting scripts for rendering preview data.

Each script has to be returned as a string or a list of strings, each string representing the full URL where the script can be loaded from. As only internal scripts can currently be used, scripts should be loaded via a custom static route, which a plugin can define by using kadi_register_blueprints().

The following example shows how a custom (global) Vue.js component can be registered, which can be used in combination with a template such as the one shown in kadi_get_preview_templates():

Vue.component('my-plugin-preview', {
    // The type of the passed data prop depends on how the preview data is
    // returned from the backend.
    props: {
        data: String,
    },
    // Note the custom delimiters, which are used so they can coexist with
    // Jinja's templating syntax when not using single file components.
    template: `
        <div>{$ data $}</div>
    `,
})

Should also be used together with kadi_get_preview_data().

kadi.plugins.spec.kadi_post_resource_change(resource, user, created)[source]

Hook to run operations after a resource was created or changed.

Currently, this includes all revisioned resources, namely records, files, collections and groups.

Note that the hook is only executed after all changes have been persisted in the database and the creation or change triggered the creation of a new revision. For reacting to specific changes, the resource type can be checked using isinstance, e.g. isinstance(resource, Record), while the latest revision of a resource can be retrieved via resource.ordered_revisions.first().

Parameters
  • resource – The resource that was created or changed.

  • user – The user that triggered the creation or change. Defaults to the current user.

  • created – Flag indicating if the resource was newly created.

kadi.plugins.spec.kadi_get_resource_nav_items(resource)[source]

Hook for collecting templates for navigation menu items of resources.

Currently, this includes the menus of records, files, collections, groups and templates.

For limiting the returned items to a specific resource, the resource type can be checked using isinstance, e.g. isinstance(resource, Record). For ignored resources, None can be returned instead.

Parameters

resource – The resource which the navigation menu belongs to.