Writing third party plugins

See also

Plugins

project-config discover third party plugins from plugin entrypoints looking for classes in the entrypoints group project_config.plugins, so the first thing is to add the entrypoint to the group:

[project."entry-points"."project_config.plugins"]
my_plugin = "package.subpackage.my_plugin_module:PluginClass"

The name of the entrypoint (my_plugin in the previous example) is the name of the plugin, the one that must be defined in plugins (string[]) if the style need it.

Plugin class

A valid plugin class must implement actions, which must be public static methods. For example:

from typing import Any
from project_config import ActionsContext, Rule, Results, tree

class PluginClass:
    @staticmethod
    def verb(  # public method names which do not start with 'if' are verbs
        value: Any,
        rule: Rule,
        context: ActionsContext,
    ) -> Results:
        ...

    @staticmethod
    def ifConditional(  # conditional starts with 'if'
        value: Any,
        rule: Rule,
        context: ActionsContext,
    ) -> Results:
        ...
action(value: Any, rule: project_config.types_.Rule, context: project_config.types_.ActionsContext) project_config.types_.Results

Action definition.

Parameters:
  • value (Any) – Value that takes the action. It could be of any type, it depends to the action.

  • rule (project_config.types_.Rule) – Complete rule dictionary in which the action is being executed.

  • context (project_config.types_.ActionsContext) – Context of the actions. It has a property fix which is used to determine if the user has enabled the fix mode in the current execution and other property files which stores the content of the files array of the rule.

Yield:

Checking results.

Return type:

project_config.types_.Results

Results

Each action must yield results, which are tuples of two items, defined next as result type - result value:

  • Error - Checking error, a dictionary (optionally but recommendably typed as project_config.types_.ErrorDict) which must contains the required keys message (error message shown in the report) and definition (definition in which the error has been thrown) and an optional key file (file for which the error has been thrown). If raised from conditionals their behaviour is the same that raising an InterruptingError.

  • InterruptingError - The same as a checking error, but this type of error will stop the execution of the subsequent rules during the checking. Useful if the user has passed some unexpected value that could lead to an invalid context in some later rule.

Additionally, conditionals can yield result values, which define if the verbs of the rule should be executed or not.

  • ResultValue - A boolean. When a conditional yields it, the execution of the conditional is terminated and, if the yielded value is False, the execution of the verbs of the rule are skipped. If no result values are yielded by a conditional, the verbs of the rule are always executed as if the conditional would returned True.

You must import these variables from project_config because their values can change between versions:

from project_config import Error, InterruptingError, ResultValue

See also

The best way to learn the most common patterns to write plugins is checking the source code of the simplest built-in plugins:

Testing plugins

project-config comes with a built-in pytest fixture to easily test plugin actions. See project_config.tests.pytest_plugin.plugin.