Skip to content

Dev: SpyderCompletionProvider API

Daniel Althviz Moré edited this page Nov 14, 2023 · 2 revisions

Spyder API for completion providers: SpyderCompletionProvider

The Completion and Linting plugin works by registering providers that supply the plugin with the completion results that you see. Spyder implements three main providers (Fallback, Text Snippets and the Language Server Protocol provider). A provider then can be thought as a component that adds functionality to the Completion and Linting plugin.

All completion providers are classes that inherit the SpyderCompletionProvider class in order to interact with Spyder and in general plugins like the Editor and Projects plugin via the Completion and Linting plugin. Following this, it is possible to create an external provider by creating a package which contains a class that inherits from the SpyderCompletionProvider class and makes it available as part of the spyder.completions entrypoint group.

classDiagram

ExternalProviderClass --|> SpyderCompletionProvider : Inherits
SnippetsProvider --|> SpyderCompletionProvider : Inherits
LanguageServerProvider --|> SpyderCompletionProvider : Inherits
FallbackProvider --|> SpyderCompletionProvider : Inherits
CompletionPlugin --|> SpyderPluginV2 : Inherits
CompletionPlugin "1" --> "*" SpyderCompletionProvider : providers

Loading

Kite (A specific example of an external provider)

The Kite provider, originally implemented as part of the source code of the Spyder application, can be used as an example on how to create an external provider. You can find the related code at spyder-ide/kite-provider: Completion plugin provider implementation for Kite, it should look something like the following:

  • 📁kite_provider
    • 📁images
      • 📁Dark
      • 📁Light
    • 📁parsing
      • 📁tests
        • 📄 __init__.py
        • 📄 test_parsing.py
      • 📄 __init__.py
    • 📁providers
      • 📄 __init__.py
      • 📄 document.py
    • 📁utils
      • 📁tests
        • 📄 __init__.py
      • 📄 __init__.py
      • 📄 status.py
    • 📁widgets
      • 📄 __init__.py
      • 📄 status.py
    • 📄 __init__.py
    • 📄 client.py
    • 📄 decorators.py
    • 📄 provider.py
  • 📁requirements
    • 📄 conda.txt
    • 📄 tests.txt
  • 📄 CHANGELOG.md
  • 📄 LICENSE
  • 📄 NOTICE.txt
  • 📄 README.md
  • 📄 pyproject.toml

NOTE: The Kite service is now unsupported so the related provider code is available for demonstration purposes only.

As you can see, the Kite provider implementation defines a provider.py. This Python module contains the main class that reimplements the SpyderCompletionProvider class, the KiteProvider class.

Inside this class, you can find some optional and required attributes which are used to define the provider configuration:

  • COMPLETION_PROVIDER_NAME: Name of the completion provider (Required).
  • DEFAULT_ORDER: Define the priority of this provider, with 1 being the highest one (Required).
  • SLOW: Define if the provider response time is not constant and may take a long time for some requests (Optional).
  • CONF_DEFAULTS: Define configuration options for the provider (Optional)
  • CONF_VERSION: A way to handle the configuration values versioning and compatibility when changing, adding or removing configuration values.

In the Kite provider case, you can see these attributes defined in the following way:

COMPLETION_PROVIDER_NAME = 'kite'
DEFAULT_ORDER = 1
SLOW = True
CONF_DEFAULTS = [
    ('spyder_runs', 1),
    ('show_onboarding', True),
    ('show_installation_error_message', True)

]
CONF_VERSION = "1.0.0"

It's worth mentioning that the CONF_DEFAULTS is a list of tuples where the first element per tuple is the name of the config and the second element is the corresponding default value.

Besides these attributes, a SpyderCompletionProvider needs to reimplement some methods. Using the Kite provider as an example, it reimplements:

  • get_name: Return a human readable name of the completion provider.
    def get_name(self):
        return 'Kite'

The name the completion provider will have towards the GUI when needed, in this case Kite is returned.

  • send_request: Send completion/introspection request from Spyder. The completion request req_type needs to have a response.
    def send_request(self, language, req_type, req, req_id):
        if language in self.available_languages:
            self.client.sig_perform_request.emit(req_id, req_type, req)
        else:
            self.sig_response_ready.emit(self.COMPLETION_PROVIDER_NAME,
                                         req_id, {})

Here the logic to handle sending a request to the Kite. the provider checks if the language from the request is valid following the programming languages that were defined as valid, otherwise and empty response to the request is issued.

  • start_completion_services_for_language: Start completions/introspection services for a given language.
    def start_completion_services_for_language(self, language):
        return language in self.available_languages

In this case kite returns if the language given by parameter is within the list of available Kite languages (the provider can acknowledge or not if it has completion services for a given programming language)

  • start: The completion provider startup logic must be invoked on this method. Once the completion provider is ready, the signal sig_provider_ready must be emitted with the completion provider name.
    def start(self):
        try:
            installed, path = check_if_kite_installed()
            if not installed:
                return
            logger.debug('Kite was found on the system: {0}'.format(path))
            running = check_if_kite_running()
            if running:
                return
            logger.debug('Starting Kite service...')
            self.kite_process = run_program(path)
        except OSError:
            installed, path = check_if_kite_installed()
            logger.debug(
                'Error starting Kite service at {path}...'.format(path=path))
            if self.get_conf('show_installation_error_message'):
                err_str = _(
                    "It seems that your Kite installation is faulty. "
                    "If you want to use Kite, please remove the "
                    "directory that appears bellow, "
                    "and try a reinstallation:<br><br>"
                    "<code>{kite_dir}</code>").format(
                        kite_dir=osp.dirname(path))
                def wrap_message(parent):
                    return QMessageBox.critical(
                        parent, _('Kite error'), err_str
                    )

                self.sig_show_widget.emit(wrap_message)
        finally:
            # Always start client to support possibly undetected Kite builds
            self.client.start()

In this case, the provider first tries to validate if Kite is installed (otherwise the provider will not start). After this, it is validated if Kite is currently running, if not, the Kite service is started. If any of the previous checks fail, the exception is controlled and information about the failure is left in the logger as well as shown inside a dialog to give feedback about the error to the user. Finally, the provider always tries to start client to support the possibility of undetected Kite installations or services running

Also, something worthy to mention in this logic, is that the usage of the signal sig_provider_ready is done via the client attribute signal sig_client_started which is triggered only if the client start is successful

  • shutdown: Stop completion provider.
    def shutdown(self):
        self.client.stop()
        if self.kite_process is not None:
            self.kite_process.kill()

Here, the provider ensures that the client is stopped and also, due to the way Kite works, it is necessary to kill its process

  • on_mainwindow_visible: Actions to be performed after the main window has been shown.
    def on_mainwindow_visible(self):
        self.client.sig_response_ready.connect(self._kite_onboarding)
	  self.client.sig_status_response_ready.connect
       (self._kite_onboarding)
        self.client.sig_onboarding_response_ready.connect(
            self._show_onboarding_file)

Finally, the Kite provider uses this method, to connect to three different signals, which launch the Kite onboarding process and the onboarding file (a file that works as a tutorial in how Kite works and gives a summary of the completion functionality the provider offers). The logic here skips this onboarding process if it’s not possible yet or if the onboarding file has already been displayed before.

The above is just a brief overview of how an external provider can be implemented. but for more information about the attributes and methods that the SpyderCompletionProvider class offers, you can check the full API definition at https://github.com/spyder-ide/spyder/blob/master/spyder/plugins/completion/api.py

Clone this wiki locally