cincodex


Namecincodex JSON
Version 0.1.0 PyPI version JSON
download
home_pagehttps://gitlab.com/ameily/cincodex
SummarySimple, flexible, and unopinionated plugin system for Python projects.
upload_time2023-03-12 11:44:53
maintainer
docs_urlNone
authorAdam Meily
requires_python>=3.10,<4.0
licenseMIT
keywords plugin pluggable extension extensible
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # cincodex

[![Build Status](https://gitlab.com/ameily/cincodex/badges/main/pipeline.svg)](https://gitlab.com/ameily/cincodex/-/pipelines/main/latest)
[![Test Coverage](https://gitlab.com/ameily/cincodex/badges/main/coverage.svg)](https://gitlab.com/ameily/cincodex/-/pipelines/main/latest)
[![Read The Docs](https://readthedocs.org/projects/cincodex/badge/?version=latest&style=flat)](https://cincodex.readthedocs.io/en/latest/)
[![Latest Release](https://gitlab.com/ameily/cincodex/-/badges/release.svg)](https://gitlab.com/ameily/cincodex/-/releases)
[![Python Version](https://img.shields.io/badge/python-3.10%2B-blueviolet)](https://www.python.org/downloads/release/python-31010/)
[![MIT License](https://img.shields.io/badge/license-MIT-9cf)](/ameily/cincodex/-/blob/main/LICENSE)

Cincodex is a simple, flexible, and unopinionated plugin system for Python projects. Cincodex is designed for both applications and libraries that wish to be extensible at runtime. Plugins can be any Python object including a class, a function, or an instance of an object. The following is a very basic example showing that the application creates a Codex, which is the plugin system, then discovers all available plugins within the `./plugins` directory, and the `./plugins/foo/bar.py` source file registers a function with the codex.

```python
# app.py
from cincodex import Codex, register_codex

codex = Codex('my_app')
register_codex(codex)

# discover all plugins
codex.discover_plugins('./plugins')

# after this, the `foo.bar` plugin is available and we can call it.
plugin = codex.get('foo.bar')
plugin()
# 'Hello world!'


# ./plugins/foo/bar.py
from app import codex

@codex.register
@codex.metadata(id='foo.bar')
def foo_bar_plugin():
    print('Hello world!')
```

Cincodex has been tested on Linux and Windows systems but should support other operating systems as well. Cincodex supports Python 3.10 and newer.

## Installation

```bash
pip install cincodex
```

## Features

* Cincodex is designed to work with multiple Python packages within a single project. For example, an application can have its own plugin system a multiple dependencies can have their own plugin system and they will not interfere with each other.
* Cincodex automatically discovers and registers all available plugins within one or more root directories.
* The method of finding and loading plugins and registering their metadata are all extensible within cincodex. Easily customize the plugin metadata and change how plugins are loaded.
* The `import` statement and machinery continues to work properly for discovered plugins, supporting both relative and absolute imports within plugins.
* A plugin is anything: a class, a function, or an object instance. Cincodex works seamlessly with all plugin types.

### A more complete example

The following is a cincodex plugin system where:

* A plugin base class that all plugin must implement. Each plugin says "hello" in a different language.
* A custom plugin metadata class
* Loading plugin from multiple directories

**`hello.py`**

This module defines the plugin metadata, plugin base class, and codex.

```python
#
# hello.py
#
from cincodex import Codex, PluginMetadata, register_codex


# First we create a custom plugin metadata class, inheriting from cincodex `PluginMetadata`
class HelloPluginMetadata(PluginMetadata):
    def __init__(self, id: str, *, lang: str):
        super().__init__(id)
        self.lang = lang


# Our plugin system will be class based. So, next we create a base class that all plugins must
# inherit from and implement the `say_hello` method.
class HelloPlugin:
    # __plugin_metadata__ is always set by cincodex with the call to `Codex.register`. We include
    # the field here so that the IDE / type checking knows that __plugin_metadata__ is a class
    # attribute.
    __plugin_metadata__: HelloPluginMetadata

    def say_hello(self) -> None:
        raise NotImplementedError()


# Create and register the codex
codex: Codex[type[HelloPlugin], HelloPluginMetadata] = Codex('hello', HelloPluginMetadata)
register_codex(codex)
```

**`builtin_plugins/english.py`**

Defines a plugin that says "hello" in English.

```python
#
# ./builtin_plugins/english.py
#
from hello import HelloPlugin, codex


@codex.register
@codex.metadata('lang.english', lang='en-us')
class EnglishHello(HelloPlugin):
    def say_hello(self):
        name = input('What is your name? > ')
        print('Hello,', name)
```

**`./contrib_plugins/german.py`**

Defines a plugin that says "hello" in German.

```python
#
# ./contrib_plugins/german.py
#
from hello import HelloPlugin

from cincodex import get_codex

# We registered the codex so we don't technically need to import it
codex = get_codex('hello')


@codex.register
@codex.metadata('lang.german', lang='de')
class GermanLang(HelloPlugin):
    def say_hello(self):
        name = input('Wie heissen sie? > ')
        print('Guten tag,', name)
```

**`app.py`**

Application script that discovers both builtin and contributed plugins and runs each sequentially.

```python
#
# app.py
#
if __name__ == '__main__':
    from hello import HelloPluginMetadata, codex

    # Discover builtin and contributed plugins
    codex.discover_plugins('./builtin_plugins')
    codex.discover_plugins('./contrib_plugins')

    # iterate over all available plugins
    for plugin_cls in codex:
        # `plugin_cls` is a type[HelloPlugin]
        # get the plugin metadata
        metadata = HelloPluginMetadata.get(plugin_cls)
        # our codex registers plugin *classes*, so we need to first instantiate an instance of this
        # plugin
        plugin = plugin_cls()
        print(f'Running plugin: {metadata.id} (lang: {metadata.lang})')
        plugin.say_hello()
        print()

```

In this example, running `python app.py` will output the following:

```
$ python app.py

Running plugin: lang.english (lang: en-us)
What is your name? > Adam
Hello, Adam

Running plugin: lang.german (lang: de)
Wie heissen sie? > Wolfgang
Guten tag, Wolfgang
```

This example is available in the `example_app` directory.

### API Documentation

Cincodex API documentation is generated by Sphinx and hosted on [Read the Docs](https://cincodex.readthedocs.io/en/latest/). The API documentation includes more detailed and complex examples and information on how to extend cincodex.

## License

Cincodex is licensed under the MIT permissive license.

<!--
cspell:ignore heissen Guten
-->

            

Raw data

            {
    "_id": null,
    "home_page": "https://gitlab.com/ameily/cincodex",
    "name": "cincodex",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10,<4.0",
    "maintainer_email": "",
    "keywords": "plugin,pluggable,extension,extensible",
    "author": "Adam Meily",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/db/0e/5a8df05585f5e036720ce0e727e795fe8a47c83748bf991ac590c9c909e5/cincodex-0.1.0.tar.gz",
    "platform": null,
    "description": "# cincodex\n\n[![Build Status](https://gitlab.com/ameily/cincodex/badges/main/pipeline.svg)](https://gitlab.com/ameily/cincodex/-/pipelines/main/latest)\n[![Test Coverage](https://gitlab.com/ameily/cincodex/badges/main/coverage.svg)](https://gitlab.com/ameily/cincodex/-/pipelines/main/latest)\n[![Read The Docs](https://readthedocs.org/projects/cincodex/badge/?version=latest&style=flat)](https://cincodex.readthedocs.io/en/latest/)\n[![Latest Release](https://gitlab.com/ameily/cincodex/-/badges/release.svg)](https://gitlab.com/ameily/cincodex/-/releases)\n[![Python Version](https://img.shields.io/badge/python-3.10%2B-blueviolet)](https://www.python.org/downloads/release/python-31010/)\n[![MIT License](https://img.shields.io/badge/license-MIT-9cf)](/ameily/cincodex/-/blob/main/LICENSE)\n\nCincodex is a simple, flexible, and unopinionated plugin system for Python projects. Cincodex is designed for both applications and libraries that wish to be extensible at runtime. Plugins can be any Python object including a class, a function, or an instance of an object. The following is a very basic example showing that the application creates a Codex, which is the plugin system, then discovers all available plugins within the `./plugins` directory, and the `./plugins/foo/bar.py` source file registers a function with the codex.\n\n```python\n# app.py\nfrom cincodex import Codex, register_codex\n\ncodex = Codex('my_app')\nregister_codex(codex)\n\n# discover all plugins\ncodex.discover_plugins('./plugins')\n\n# after this, the `foo.bar` plugin is available and we can call it.\nplugin = codex.get('foo.bar')\nplugin()\n# 'Hello world!'\n\n\n# ./plugins/foo/bar.py\nfrom app import codex\n\n@codex.register\n@codex.metadata(id='foo.bar')\ndef foo_bar_plugin():\n    print('Hello world!')\n```\n\nCincodex has been tested on Linux and Windows systems but should support other operating systems as well. Cincodex supports Python 3.10 and newer.\n\n## Installation\n\n```bash\npip install cincodex\n```\n\n## Features\n\n* Cincodex is designed to work with multiple Python packages within a single project. For example, an application can have its own plugin system a multiple dependencies can have their own plugin system and they will not interfere with each other.\n* Cincodex automatically discovers and registers all available plugins within one or more root directories.\n* The method of finding and loading plugins and registering their metadata are all extensible within cincodex. Easily customize the plugin metadata and change how plugins are loaded.\n* The `import` statement and machinery continues to work properly for discovered plugins, supporting both relative and absolute imports within plugins.\n* A plugin is anything: a class, a function, or an object instance. Cincodex works seamlessly with all plugin types.\n\n### A more complete example\n\nThe following is a cincodex plugin system where:\n\n* A plugin base class that all plugin must implement. Each plugin says \"hello\" in a different language.\n* A custom plugin metadata class\n* Loading plugin from multiple directories\n\n**`hello.py`**\n\nThis module defines the plugin metadata, plugin base class, and codex.\n\n```python\n#\n# hello.py\n#\nfrom cincodex import Codex, PluginMetadata, register_codex\n\n\n# First we create a custom plugin metadata class, inheriting from cincodex `PluginMetadata`\nclass HelloPluginMetadata(PluginMetadata):\n    def __init__(self, id: str, *, lang: str):\n        super().__init__(id)\n        self.lang = lang\n\n\n# Our plugin system will be class based. So, next we create a base class that all plugins must\n# inherit from and implement the `say_hello` method.\nclass HelloPlugin:\n    # __plugin_metadata__ is always set by cincodex with the call to `Codex.register`. We include\n    # the field here so that the IDE / type checking knows that __plugin_metadata__ is a class\n    # attribute.\n    __plugin_metadata__: HelloPluginMetadata\n\n    def say_hello(self) -> None:\n        raise NotImplementedError()\n\n\n# Create and register the codex\ncodex: Codex[type[HelloPlugin], HelloPluginMetadata] = Codex('hello', HelloPluginMetadata)\nregister_codex(codex)\n```\n\n**`builtin_plugins/english.py`**\n\nDefines a plugin that says \"hello\" in English.\n\n```python\n#\n# ./builtin_plugins/english.py\n#\nfrom hello import HelloPlugin, codex\n\n\n@codex.register\n@codex.metadata('lang.english', lang='en-us')\nclass EnglishHello(HelloPlugin):\n    def say_hello(self):\n        name = input('What is your name? > ')\n        print('Hello,', name)\n```\n\n**`./contrib_plugins/german.py`**\n\nDefines a plugin that says \"hello\" in German.\n\n```python\n#\n# ./contrib_plugins/german.py\n#\nfrom hello import HelloPlugin\n\nfrom cincodex import get_codex\n\n# We registered the codex so we don't technically need to import it\ncodex = get_codex('hello')\n\n\n@codex.register\n@codex.metadata('lang.german', lang='de')\nclass GermanLang(HelloPlugin):\n    def say_hello(self):\n        name = input('Wie heissen sie? > ')\n        print('Guten tag,', name)\n```\n\n**`app.py`**\n\nApplication script that discovers both builtin and contributed plugins and runs each sequentially.\n\n```python\n#\n# app.py\n#\nif __name__ == '__main__':\n    from hello import HelloPluginMetadata, codex\n\n    # Discover builtin and contributed plugins\n    codex.discover_plugins('./builtin_plugins')\n    codex.discover_plugins('./contrib_plugins')\n\n    # iterate over all available plugins\n    for plugin_cls in codex:\n        # `plugin_cls` is a type[HelloPlugin]\n        # get the plugin metadata\n        metadata = HelloPluginMetadata.get(plugin_cls)\n        # our codex registers plugin *classes*, so we need to first instantiate an instance of this\n        # plugin\n        plugin = plugin_cls()\n        print(f'Running plugin: {metadata.id} (lang: {metadata.lang})')\n        plugin.say_hello()\n        print()\n\n```\n\nIn this example, running `python app.py` will output the following:\n\n```\n$ python app.py\n\nRunning plugin: lang.english (lang: en-us)\nWhat is your name? > Adam\nHello, Adam\n\nRunning plugin: lang.german (lang: de)\nWie heissen sie? > Wolfgang\nGuten tag, Wolfgang\n```\n\nThis example is available in the `example_app` directory.\n\n### API Documentation\n\nCincodex API documentation is generated by Sphinx and hosted on [Read the Docs](https://cincodex.readthedocs.io/en/latest/). The API documentation includes more detailed and complex examples and information on how to extend cincodex.\n\n## License\n\nCincodex is licensed under the MIT permissive license.\n\n<!--\ncspell:ignore heissen Guten\n-->\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Simple, flexible, and unopinionated plugin system for Python projects.",
    "version": "0.1.0",
    "split_keywords": [
        "plugin",
        "pluggable",
        "extension",
        "extensible"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "020d9c900dcd9ff2c552b70fc6f77ca03ef522fc001204182d9f76322157dcfd",
                "md5": "c9395659186800ae023cee9eb43b874a",
                "sha256": "308bc1673b19e970dda31727e17eae282cf792a3dbfdad1bd79fb6e36e60dbae"
            },
            "downloads": -1,
            "filename": "cincodex-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c9395659186800ae023cee9eb43b874a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10,<4.0",
            "size": 10507,
            "upload_time": "2023-03-12T11:44:51",
            "upload_time_iso_8601": "2023-03-12T11:44:51.506204Z",
            "url": "https://files.pythonhosted.org/packages/02/0d/9c900dcd9ff2c552b70fc6f77ca03ef522fc001204182d9f76322157dcfd/cincodex-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "db0e5a8df05585f5e036720ce0e727e795fe8a47c83748bf991ac590c9c909e5",
                "md5": "a72ce06e95ea8de8ceb4c170c3a18b6d",
                "sha256": "c5ef87d6c2b672add3bbd9f61490ef7afc1deb4a80eafe1184ba574b73063ee0"
            },
            "downloads": -1,
            "filename": "cincodex-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "a72ce06e95ea8de8ceb4c170c3a18b6d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10,<4.0",
            "size": 17182,
            "upload_time": "2023-03-12T11:44:53",
            "upload_time_iso_8601": "2023-03-12T11:44:53.051141Z",
            "url": "https://files.pythonhosted.org/packages/db/0e/5a8df05585f5e036720ce0e727e795fe8a47c83748bf991ac590c9c909e5/cincodex-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-03-12 11:44:53",
    "github": false,
    "gitlab": true,
    "bitbucket": false,
    "gitlab_user": "ameily",
    "gitlab_project": "cincodex",
    "lcname": "cincodex"
}
        
Elapsed time: 0.04209s