# 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"
}