clyjin


Nameclyjin JSON
Version 0.2.12 PyPI version JSON
download
home_pagehttps://github.com/ryzhovalex/clyjin
SummaryCore for building plugins with CLI interfaces
upload_time2023-10-09 22:00:57
maintainer
docs_urlNone
authorryzhovalex
requires_python>=3.11,<4.0
licenseMIT
keywords toolbox cli plugin custom
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 🛠️ Clyjin

Provides a core for building plugins with CLI interfaces.

## 👩‍🏭 Installation

If you want to install some plugin, you probably need only one command:
```sh
pip install clyjin_someplugin
```

Additional steps might be required, consult with plugin's documentation for
details.

If you want to install Clyjin Core to develop own plugin, you might want to
start with:
```sh
pip install clyjin
```

Or, using [poetry](https://python-poetry.org/):
```sh
poetry add clyjin
```

## 📓 Getting Started

To get grasp of Clyjin's functionality we'll develop a new plugin called
`Hello`.

First, you need to create a new Python project which name starts with
`clyjin_`, e.g. `clyjin_hello`.

Structure of the project might look like:
```
.
├── .venv
├── clyjin_hello/
│   ├── __init__.py
│   └── _main.py
├── poetry.lock
└── pyproject.toml
```

Main class of your plugin-project is `Plugin`. Clyjin on initialization makes
imports of your defined plugin with statement like
`from clyjin_hello import MainPlugin`.

This means you need to define a variable called `MainPlugin` in your
`__init__.py` pointing to your plugin class:
```python
# clyjin_hello/__init__.py
from clyjin_hello._main import HelloPlugin

MainPlugin = HelloPlugin
```

That said, our plugin can be defined wherever we want, but we should reference
it to be importable from our root Python package via name `MainPlugin`.

The plugin itself in our example is defined within `_main.py`:
```python
# clyjin_hello/main.py
from clyjin.base import Plugin, PluginInitializeData

class HelloPlugin(Plugin):
    Name = "hello"
    ModuleClasses = [
        RootModule,
        GreetingsModule
    ]
    # retrieve version of our package dynamically
    Version = importlib.metadata.version("clyjin_hello")

    @classmethod
    async def initialize(
        clas,
        data: PluginInitializeData
    ) -> None:
        # our custom initialization logic
        ...
```

Let's do a quick overview of the things happened here:
- `Name = "hello"` - defines a callable name for our plugin. It will be used
    as a main entrypoint in CLI and in other references.
- `ModuleClasses = [...]` - list of module classes defined for your plugin.
- `Version = importlib.metadata.version("clyjin_hello")` - version of our
    plugin. In this case retrieved dynamically from package's data.
- `async def initialize(...)` - extra initialization logic for our plugin.

Our plugin's modules can be defined as follows:
```python
from clyjin.base import Module, ModuleData

class RootModule(Module[MyRootArgs, MyRootConfig]):
    Name = "$root"
    Description = "show count of hello words said"
    Args = MyRootArgs(
        output_dir=ModuleArg[str](
            names=["output_dir"],
            type=Path,
            help="where to write the result output"
        )
        is_world_only=ModuleArg[bool](
            names=["-w", "--world"],
            action="store_true",
            type=bool,
            argparse_type=type,
            help="whether should count only \"hello world\" messages"
        )
    )

    def __init__(
        self,
        module_data: ModuleData[MyRootArgs, MyRootConfig]
    ) -> None:
        super().__init__(module_data)

        # ... do whatever you want on your module's initialization
        # module data fields is available under self protected attributes,
        # e.g. self._args.output_dir or self._config as well as additional
        # helper variables, like self._rootdir

    async def execute(self) -> None:
        # ... called on module's execution
        # args values can be accessed like `self.args.output_dir.value`
        ...
```

- `class RootModule(Module[MyRootArgs, MyRootConfig])` - declares our module's
    class, inheriting it from generic module with setting as first argument
    our Args class and as second argument our Config class.
- `Name = "$root"` - special name for a module indicates that this module is a
    root module. Root modules are called directly under Plugin's namespace,
    e.g. `clyjin hello ...`, while other modules are called like
    `clyjin hello.module_name ...`.
- `Description = "..."` - description of the module, appeared at least in CLI
    help output
- `Args = MyRootArgs(...)` - initialization of our module's args class.
    Args class contains information about CLI arguments supported by our
    module. It's structure is mostly similar like arguments passed to
    argparse.add_argument.
- `def __init__(...)` - initialization method of our module accepting
    ModuleData object as first argument. ModuleData contains information about
    args, config and some extra helper fields, such as `rootdir`. Insider our
    Module super class all these fields are set to protected instance's
    variables, like `self._rootdir` making them available for further usage.
- `async def execute(self) -> None` - method called if Clyjin core decided to
    chose this module to execute next request. Typically, if you need to
    access CLI arguments passed within this method, you will need to use
    `this.args.your_cli_arg.value` statement.

You can define as many modules as you need in your `Plugin.ModuleClasses`
field, but you can have only one root module, i.e. module with name `$root`,
and other module names should be all unique.

To call your plugin anywhere, you need to install `clyjin` and `clyjin_hello`
(i.e. your plugin) and issue a CLI command:
```sh
clyjin <your_plugin_name>.<your_module_name> <...arguments>
```

For example for our imaginary GreetingsModule, which is part of HelloPlugin,
we might issue a command:
```sh
clyjin clyjin_hello.greetings --to world --from $USER
```

## 💪 Advanced

### 📁 System directories

[in process of writing, take some coffee ☕]

## 🔶 Official Plugins

- [📑 Clyjin Templates](https://github.com/ryzhovalex/clyjin_templates)
- [⚙️ Clyjin Make](https://github.com/ryzhovalex/clyjin_make)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/ryzhovalex/clyjin",
    "name": "clyjin",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.11,<4.0",
    "maintainer_email": "",
    "keywords": "toolbox,cli,plugin,custom",
    "author": "ryzhovalex",
    "author_email": "thed4rkof@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/64/ac/6e39694a1df0efbda605d17b3d73df9e0f71c8e43a0c6e2362b359e7303f/clyjin-0.2.12.tar.gz",
    "platform": null,
    "description": "# \ud83d\udee0\ufe0f Clyjin\n\nProvides a core for building plugins with CLI interfaces.\n\n## \ud83d\udc69\u200d\ud83c\udfed Installation\n\nIf you want to install some plugin, you probably need only one command:\n```sh\npip install clyjin_someplugin\n```\n\nAdditional steps might be required, consult with plugin's documentation for\ndetails.\n\nIf you want to install Clyjin Core to develop own plugin, you might want to\nstart with:\n```sh\npip install clyjin\n```\n\nOr, using [poetry](https://python-poetry.org/):\n```sh\npoetry add clyjin\n```\n\n## \ud83d\udcd3 Getting Started\n\nTo get grasp of Clyjin's functionality we'll develop a new plugin called\n`Hello`.\n\nFirst, you need to create a new Python project which name starts with\n`clyjin_`, e.g. `clyjin_hello`.\n\nStructure of the project might look like:\n```\n.\n\u251c\u2500\u2500 .venv\n\u251c\u2500\u2500 clyjin_hello/\n\u2502   \u251c\u2500\u2500 __init__.py\n\u2502   \u2514\u2500\u2500 _main.py\n\u251c\u2500\u2500 poetry.lock\n\u2514\u2500\u2500 pyproject.toml\n```\n\nMain class of your plugin-project is `Plugin`. Clyjin on initialization makes\nimports of your defined plugin with statement like\n`from clyjin_hello import MainPlugin`.\n\nThis means you need to define a variable called `MainPlugin` in your\n`__init__.py` pointing to your plugin class:\n```python\n# clyjin_hello/__init__.py\nfrom clyjin_hello._main import HelloPlugin\n\nMainPlugin = HelloPlugin\n```\n\nThat said, our plugin can be defined wherever we want, but we should reference\nit to be importable from our root Python package via name `MainPlugin`.\n\nThe plugin itself in our example is defined within `_main.py`:\n```python\n# clyjin_hello/main.py\nfrom clyjin.base import Plugin, PluginInitializeData\n\nclass HelloPlugin(Plugin):\n    Name = \"hello\"\n    ModuleClasses = [\n        RootModule,\n        GreetingsModule\n    ]\n    # retrieve version of our package dynamically\n    Version = importlib.metadata.version(\"clyjin_hello\")\n\n    @classmethod\n    async def initialize(\n        clas,\n        data: PluginInitializeData\n    ) -> None:\n        # our custom initialization logic\n        ...\n```\n\nLet's do a quick overview of the things happened here:\n- `Name = \"hello\"` - defines a callable name for our plugin. It will be used\n    as a main entrypoint in CLI and in other references.\n- `ModuleClasses = [...]` - list of module classes defined for your plugin.\n- `Version = importlib.metadata.version(\"clyjin_hello\")` - version of our\n    plugin. In this case retrieved dynamically from package's data.\n- `async def initialize(...)` - extra initialization logic for our plugin.\n\nOur plugin's modules can be defined as follows:\n```python\nfrom clyjin.base import Module, ModuleData\n\nclass RootModule(Module[MyRootArgs, MyRootConfig]):\n    Name = \"$root\"\n    Description = \"show count of hello words said\"\n    Args = MyRootArgs(\n        output_dir=ModuleArg[str](\n            names=[\"output_dir\"],\n            type=Path,\n            help=\"where to write the result output\"\n        )\n        is_world_only=ModuleArg[bool](\n            names=[\"-w\", \"--world\"],\n            action=\"store_true\",\n            type=bool,\n            argparse_type=type,\n            help=\"whether should count only \\\"hello world\\\" messages\"\n        )\n    )\n\n    def __init__(\n        self,\n        module_data: ModuleData[MyRootArgs, MyRootConfig]\n    ) -> None:\n        super().__init__(module_data)\n\n        # ... do whatever you want on your module's initialization\n        # module data fields is available under self protected attributes,\n        # e.g. self._args.output_dir or self._config as well as additional\n        # helper variables, like self._rootdir\n\n    async def execute(self) -> None:\n        # ... called on module's execution\n        # args values can be accessed like `self.args.output_dir.value`\n        ...\n```\n\n- `class RootModule(Module[MyRootArgs, MyRootConfig])` - declares our module's\n    class, inheriting it from generic module with setting as first argument\n    our Args class and as second argument our Config class.\n- `Name = \"$root\"` - special name for a module indicates that this module is a\n    root module. Root modules are called directly under Plugin's namespace,\n    e.g. `clyjin hello ...`, while other modules are called like\n    `clyjin hello.module_name ...`.\n- `Description = \"...\"` - description of the module, appeared at least in CLI\n    help output\n- `Args = MyRootArgs(...)` - initialization of our module's args class.\n    Args class contains information about CLI arguments supported by our\n    module. It's structure is mostly similar like arguments passed to\n    argparse.add_argument.\n- `def __init__(...)` - initialization method of our module accepting\n    ModuleData object as first argument. ModuleData contains information about\n    args, config and some extra helper fields, such as `rootdir`. Insider our\n    Module super class all these fields are set to protected instance's\n    variables, like `self._rootdir` making them available for further usage.\n- `async def execute(self) -> None` - method called if Clyjin core decided to\n    chose this module to execute next request. Typically, if you need to\n    access CLI arguments passed within this method, you will need to use\n    `this.args.your_cli_arg.value` statement.\n\nYou can define as many modules as you need in your `Plugin.ModuleClasses`\nfield, but you can have only one root module, i.e. module with name `$root`,\nand other module names should be all unique.\n\nTo call your plugin anywhere, you need to install `clyjin` and `clyjin_hello`\n(i.e. your plugin) and issue a CLI command:\n```sh\nclyjin <your_plugin_name>.<your_module_name> <...arguments>\n```\n\nFor example for our imaginary GreetingsModule, which is part of HelloPlugin,\nwe might issue a command:\n```sh\nclyjin clyjin_hello.greetings --to world --from $USER\n```\n\n## \ud83d\udcaa Advanced\n\n### \ud83d\udcc1 System directories\n\n[in process of writing, take some coffee \u2615]\n\n## \ud83d\udd36 Official Plugins\n\n- [\ud83d\udcd1 Clyjin Templates](https://github.com/ryzhovalex/clyjin_templates)\n- [\u2699\ufe0f Clyjin Make](https://github.com/ryzhovalex/clyjin_make)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Core for building plugins with CLI interfaces",
    "version": "0.2.12",
    "project_urls": {
        "Homepage": "https://github.com/ryzhovalex/clyjin",
        "Repository": "https://github.com/ryzhovalex/clyjin"
    },
    "split_keywords": [
        "toolbox",
        "cli",
        "plugin",
        "custom"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f721ef5c03a7152bf08ccc17d8fe3b1accc9eb1a1bf9e454b65c6c59b2bb9967",
                "md5": "591fa6f2b790cc1295010380aedae433",
                "sha256": "6496026a47864f5b24d226f0b346ed6025f1f981d4e5cfb6f6c6393f14824470"
            },
            "downloads": -1,
            "filename": "clyjin-0.2.12-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "591fa6f2b790cc1295010380aedae433",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11,<4.0",
            "size": 20063,
            "upload_time": "2023-10-09T22:00:55",
            "upload_time_iso_8601": "2023-10-09T22:00:55.715020Z",
            "url": "https://files.pythonhosted.org/packages/f7/21/ef5c03a7152bf08ccc17d8fe3b1accc9eb1a1bf9e454b65c6c59b2bb9967/clyjin-0.2.12-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "64ac6e39694a1df0efbda605d17b3d73df9e0f71c8e43a0c6e2362b359e7303f",
                "md5": "79f5268294d294da783f21d730d5a0f1",
                "sha256": "9f79775644594d11f428e9e1ad6cdd718deb989d839d43be16c7276e4bbc80a8"
            },
            "downloads": -1,
            "filename": "clyjin-0.2.12.tar.gz",
            "has_sig": false,
            "md5_digest": "79f5268294d294da783f21d730d5a0f1",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11,<4.0",
            "size": 15546,
            "upload_time": "2023-10-09T22:00:57",
            "upload_time_iso_8601": "2023-10-09T22:00:57.575501Z",
            "url": "https://files.pythonhosted.org/packages/64/ac/6e39694a1df0efbda605d17b3d73df9e0f71c8e43a0c6e2362b359e7303f/clyjin-0.2.12.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-10-09 22:00:57",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ryzhovalex",
    "github_project": "clyjin",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "clyjin"
}
        
Elapsed time: 0.13078s