Skip to content

Creating an Extension

An extension is a Python package that implements one or more ActionHandlers. Each handler provides the logic for executing a specific action (e.g. running a linter, formatter, or build tool).

1. Create the package

my_linter/
    pyproject.toml
    my_linter/
        __init__.py
        handler.py

pyproject.toml — declare finecode_extension_api as a dependency:

[project]
name = "my_linter"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["finecode_extension_api~=0.4.0"]

[build-system]
requires = ["setuptools>=64"]
build-backend = "setuptools.build_meta"

2. Implement a handler

Import the action you want to handle and subclass ActionHandler:

# my_linter/handler.py
from finecode_extension_api import code_action
from finecode_extension_api.actions.lint_files import (
    LintFilesAction,
    LintFilesRunPayload,
    LintFilesRunContext,
    LintFilesRunResult,
    LintMessage,
)


class MyLinterHandler(
    code_action.ActionHandler[
        LintFilesRunPayload,
        LintFilesRunContext,
        LintFilesRunResult,
    ]
):
    action = LintFilesAction

    async def run(
        self, payload: LintFilesRunPayload, context: LintFilesRunContext
    ) -> LintFilesRunResult:
        diagnostics: list[LintMessage] = []

        for file_path in payload.file_paths:
            # run your tool and collect results
            messages = run_my_tool(file_path)
            diagnostics.extend(messages)

        return LintFilesRunResult(diagnostics=diagnostics)

3. Export from __init__.py

# my_linter/__init__.py
from my_linter.handler import MyLinterHandler

__all__ = ["MyLinterHandler"]

4. Register the handler in a project

Add the handler to the target action in pyproject.toml:

[tool.finecode.action.lint]
source = "finecode_extension_api.actions.lint.LintAction"
handlers = [
    {
        name = "my_linter",
        source = "my_linter.MyLinterHandler",
        env = "dev_no_runtime",
        dependencies = ["my_linter~=0.1.0"]
    }
]

Then run python -m finecode prepare-envs to install your handler into the venv.

Handler configuration

To make your handler configurable, define a config model and declare CONFIG_TYPE:

import dataclasses
from finecode_extension_api import code_action
from finecode_extension_api.actions.lint_files import (
    LintFilesAction, LintFilesRunPayload, LintFilesRunContext, LintFilesRunResult,
)


@dataclasses.dataclass
class MyLinterConfig:
    line_length: int = 88
    extend_ignore: list[str] = dataclasses.field(default_factory=list)


class MyLinterHandler(
    code_action.ActionHandler[
        LintFilesRunPayload,
        LintFilesRunContext,
        LintFilesRunResult,
    ]
):
    action = LintFilesAction
    CONFIG_TYPE = MyLinterConfig

    async def run(
        self, payload: LintFilesRunPayload, context: LintFilesRunContext
    ) -> LintFilesRunResult:
        config: MyLinterConfig = context.handler_config
        # use config.line_length, config.extend_ignore, ...
        ...

Users can then configure it in pyproject.toml:

[[tool.finecode.action_handler]]
source = "my_linter.MyLinterHandler"
config.line_length = 100
config.extend_ignore = ["E501"]

Or via CLI/env vars at runtime (see Configuration).

Handler lifecycle

For handlers that need to start a background process (e.g. a language server), use the lifecycle hooks:

class MyLspHandler(code_action.ActionHandler[...]):
    action = LintFilesAction

    async def run(self, payload, context):
        ...

    def on_start(self) -> None:
        # called once when the handler is first loaded
        self._process = start_my_server()

    def on_shutdown(self) -> None:
        # called when the Extension Runner shuts down
        self._process.terminate()

Sequential handlers: using current_result

If your handler runs in sequential mode and depends on the result of a previous handler, read it from the context:

async def run(self, payload, context):
    previous: MyActionResult = context.current_result
    # extend or modify the previous result
    ...

Warning

context.current_result raises RuntimeError in concurrent handler mode. Only use it when run_handlers_concurrently is false (the default).

Available actions to handle

See the Built-in Actions reference for the full list of action classes, payload types, and result types you can implement handlers for.