apluggy


Nameapluggy JSON
Version 1.0.2 PyPI version JSON
download
home_pageNone
SummaryA wrapper of "pluggy" to support asyncio and context managers
upload_time2024-09-23 20:46:06
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # apluggy

[![PyPI - Version](https://img.shields.io/pypi/v/apluggy.svg)](https://pypi.org/project/apluggy)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apluggy.svg)](https://pypi.org/project/apluggy)

[![Test Status](https://github.com/simonsobs/apluggy/actions/workflows/unit-test.yml/badge.svg)](https://github.com/simonsobs/apluggy/actions/workflows/unit-test.yml)
[![Test Status](https://github.com/simonsobs/apluggy/actions/workflows/type-check.yml/badge.svg)](https://github.com/simonsobs/apluggy/actions/workflows/type-check.yml)
[![codecov](https://codecov.io/gh/simonsobs/apluggy/branch/main/graph/badge.svg)](https://codecov.io/gh/simonsobs/apluggy)

A wrapper of [pluggy](https://pluggy.readthedocs.io/) to support asyncio and context managers.

This package provides a subclass of
[`pluggy.PluginManager`](https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluginManager)
which

- allows async functions, context managers, and async context managers to be hooks
- and accepts plugin factories in addition to plugin instances for registration.

---

**Table of Contents**

- [Installation](#installation)
- [How to use](#how-to-use)
  - [Start Python](#start-python)
  - [Import packages](#import-packages)
  - [Create hook specification and implementation decorators](#create-hook-specification-and-implementation-decorators)
  - [Define hook specifications](#define-hook-specifications)
  - [Define plugins](#define-plugins)
  - [Create a plugin manager and register plugins](#create-a-plugin-manager-and-register-plugins)
  - [Call hooks](#call-hooks)
    - [Async function](#async-function)
    - [Context manager](#context-manager)
    - [Async context manager](#async-context-manager)
- [Links](#links)
- [License](#license)

---

## Installation

You can install apluggy with pip:

```console
pip install apluggy
```

---

## How to use

Here, we show a simple example of how to use apluggy.

We only describe the usage of additional features provided by apluggy. For the
usage of pluggy itself, please refer to the [pluggy
documentation](https://pluggy.readthedocs.io/).

### Start Python

You can try this example in a Python interpreter.

```console
$ python
Python 3.10.13 (...)
...
...
>>>
```

### Import packages

Import necessary packages of this example.

```python
>>> import asyncio
>>> import apluggy as pluggy
>>> from apluggy import asynccontextmanager, contextmanager

```

In this example, `apluggy` is imported with the alias `pluggy`.

The decorators `asynccontextmanager` and `contextmanager` are imported from
`apluggy`. They are wrappers of the decorators of the same names in the
[contextlib package](https://docs.python.org/3/library/contextlib.html). The
wrappers preserve the signatures of decorated functions, which are necessary for
pluggy to pass arguments to hook implementations correctly. (The decorator
`contextmanger` in `apluggy` is the same object as the decorator
`contextmanager` in the [decorator
package](https://pypi.org/project/decorator/). The decorator package does not
provide `asynccontextmanager` decorator as of version 5.1. The decorator
`asynccontextmanger` in `apluggy` is implemented in a similar way as the
decorator `contextmanager` in the decorator package.)

### Create hook specification and implementation decorators

```python
>>> hookspec = pluggy.HookspecMarker('project')
>>> hookimpl = pluggy.HookimplMarker('project')

```

### Define hook specifications

In this example, we define three hooks: async function, context manager, and
async context manager.

```python
>>> class Spec:
...     """A hook specification namespace."""
...
...     @hookspec
...     async def afunc(self, arg1, arg2):
...         pass
...
...     @hookspec
...     @contextmanager
...     def context(self, arg1, arg2):
...         pass
...
...     @hookspec
...     @asynccontextmanager
...     async def acontext(self, arg1, arg2):
...         pass

```

### Define plugins

We define two plugins as classes. Each plugin implements the three hooks
defined above.

```python
>>> class Plugin_1:
...     """A hook implementation namespace."""
...
...     @hookimpl
...     async def afunc(self, arg1, arg2):
...         print('inside Plugin_1.afunc()')
...         return arg1 + arg2
...
...     @hookimpl
...     @contextmanager
...     def context(self, arg1, arg2):
...         print('inside Plugin_1.context(): before')
...         yield arg1 + arg2
...         print('inside Plugin_1.context(): after')
...
...     @hookimpl
...     @asynccontextmanager
...     async def acontext(self, arg1, arg2):
...         print('inside Plugin_1.acontext(): before')
...         yield arg1 + arg2
...         print('inside Plugin_1.acontext(): after')

>>> class Plugin_2:
...     """A 2nd hook implementation namespace."""
...
...     @hookimpl
...     async def afunc(self, arg1, arg2):
...         print('inside Plugin_2.afunc()')
...         return arg1 - arg2
...
...     @hookimpl
...     @contextmanager
...     def context(self, arg1, arg2):
...         print('inside Plugin_2.context(): before')
...         yield arg1 - arg2
...         print('inside Plugin_2.context(): after')
...
...     @hookimpl
...     @asynccontextmanager
...     async def acontext(self, arg1, arg2):
...         print('inside Plugin_2.acontext(): before')
...         yield arg1 - arg2
...         print('inside Plugin_2.acontext(): after')

```

### Create a plugin manager and register plugins

Plugins can be registered as instances or factories. In the following
example, we register two plugins: `Plugin_1` as an instance, and `Plugin_2`
as a factory.

```python
>>> pm = pluggy.PluginManager('project')
>>> pm.add_hookspecs(Spec)
>>> _ = pm.register(Plugin_1())  # instantiation is optional.
>>> _ = pm.register(Plugin_2)  # callable is considered a plugin factory.

```

[Pluggy accepts a class or
module](https://pluggy.readthedocs.io/en/stable/#define-and-collect-hooks) as a
plugin. However, it actually accepts a class instance, not a class itself.
Consequently, when plugins are loaded with
[`load_setuptools_entrypoints()`](https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluginManager.load_setuptools_entrypoints),
the entry points must be class instances or modules. Classes themselves cannot
be used as entry points (if understood correctly).

So that classes themselves can be entry points, apluggy accepts a class itself for
a plugin registration. When apluggy receives a callable object, apluggy considers
the object as a plugin factory.

### Call hooks

The following example shows how to call hooks.

#### Async function

```python
>>> async def call_afunc():
...     results = await pm.ahook.afunc(arg1=1, arg2=2)  # ahook instead of hook
...     print(results)

>>> asyncio.run(call_afunc())
inside Plugin_2.afunc()
inside Plugin_1.afunc()
[-1, 3]

```

#### Context manager

```python
>>> with pm.with_.context(arg1=1, arg2=2) as y:  # with_ instead of hook
...     print(y)
inside Plugin_2.context(): before
inside Plugin_1.context(): before
[-1, 3]
inside Plugin_1.context(): after
inside Plugin_2.context(): after

```

In the reverse order:

```python
>>> with pm.with_reverse.context(arg1=1, arg2=2) as y:  # with_reverse instead of hook
...     print(y)
inside Plugin_1.context(): before
inside Plugin_2.context(): before
[3, -1]
inside Plugin_2.context(): after
inside Plugin_1.context(): after

```

#### Async context manager

```python
>>> async def call_acontext():
...     async with pm.awith.acontext(arg1=1, arg2=2) as y:  # awith instead of hook
...         print(y)

>>> asyncio.run(call_acontext())
inside Plugin_2.acontext(): before
inside Plugin_1.acontext(): before
[-1, 3]
inside Plugin_1.acontext(): after
inside Plugin_2.acontext(): after

```

In the reverse order:

```python
>>> async def call_acontext():
...     async with pm.awith_reverse.acontext(arg1=1, arg2=2) as y:  # awith_reverse instead of hook
...         print(y)

>>> asyncio.run(call_acontext())
inside Plugin_1.acontext(): before
inside Plugin_2.acontext(): before
[3, -1]
inside Plugin_2.acontext(): after
inside Plugin_1.acontext(): after

```

---

## Links

- [pluggy](https://pluggy.readthedocs.io/)
- [decorator](https://pypi.org/project/decorator/)

---

## License

- _apluggy_ is licensed under the [MIT](https://spdx.org/licenses/MIT.html) license.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "apluggy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "Tai Sakuma <tai.sakuma@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/a2/16/0ffea012c795cc9b876d4fe4644dda5954aeb5b1a9532aeae78f91624c04/apluggy-1.0.2.tar.gz",
    "platform": null,
    "description": "# apluggy\n\n[![PyPI - Version](https://img.shields.io/pypi/v/apluggy.svg)](https://pypi.org/project/apluggy)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apluggy.svg)](https://pypi.org/project/apluggy)\n\n[![Test Status](https://github.com/simonsobs/apluggy/actions/workflows/unit-test.yml/badge.svg)](https://github.com/simonsobs/apluggy/actions/workflows/unit-test.yml)\n[![Test Status](https://github.com/simonsobs/apluggy/actions/workflows/type-check.yml/badge.svg)](https://github.com/simonsobs/apluggy/actions/workflows/type-check.yml)\n[![codecov](https://codecov.io/gh/simonsobs/apluggy/branch/main/graph/badge.svg)](https://codecov.io/gh/simonsobs/apluggy)\n\nA wrapper of [pluggy](https://pluggy.readthedocs.io/) to support asyncio and context managers.\n\nThis package provides a subclass of\n[`pluggy.PluginManager`](https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluginManager)\nwhich\n\n- allows async functions, context managers, and async context managers to be hooks\n- and accepts plugin factories in addition to plugin instances for registration.\n\n---\n\n**Table of Contents**\n\n- [Installation](#installation)\n- [How to use](#how-to-use)\n  - [Start Python](#start-python)\n  - [Import packages](#import-packages)\n  - [Create hook specification and implementation decorators](#create-hook-specification-and-implementation-decorators)\n  - [Define hook specifications](#define-hook-specifications)\n  - [Define plugins](#define-plugins)\n  - [Create a plugin manager and register plugins](#create-a-plugin-manager-and-register-plugins)\n  - [Call hooks](#call-hooks)\n    - [Async function](#async-function)\n    - [Context manager](#context-manager)\n    - [Async context manager](#async-context-manager)\n- [Links](#links)\n- [License](#license)\n\n---\n\n## Installation\n\nYou can install apluggy with pip:\n\n```console\npip install apluggy\n```\n\n---\n\n## How to use\n\nHere, we show a simple example of how to use apluggy.\n\nWe only describe the usage of additional features provided by apluggy. For the\nusage of pluggy itself, please refer to the [pluggy\ndocumentation](https://pluggy.readthedocs.io/).\n\n### Start Python\n\nYou can try this example in a Python interpreter.\n\n```console\n$ python\nPython 3.10.13 (...)\n...\n...\n>>>\n```\n\n### Import packages\n\nImport necessary packages of this example.\n\n```python\n>>> import asyncio\n>>> import apluggy as pluggy\n>>> from apluggy import asynccontextmanager, contextmanager\n\n```\n\nIn this example, `apluggy` is imported with the alias `pluggy`.\n\nThe decorators `asynccontextmanager` and `contextmanager` are imported from\n`apluggy`. They are wrappers of the decorators of the same names in the\n[contextlib package](https://docs.python.org/3/library/contextlib.html). The\nwrappers preserve the signatures of decorated functions, which are necessary for\npluggy to pass arguments to hook implementations correctly. (The decorator\n`contextmanger` in `apluggy` is the same object as the decorator\n`contextmanager` in the [decorator\npackage](https://pypi.org/project/decorator/). The decorator package does not\nprovide `asynccontextmanager` decorator as of version 5.1. The decorator\n`asynccontextmanger` in `apluggy` is implemented in a similar way as the\ndecorator `contextmanager` in the decorator package.)\n\n### Create hook specification and implementation decorators\n\n```python\n>>> hookspec = pluggy.HookspecMarker('project')\n>>> hookimpl = pluggy.HookimplMarker('project')\n\n```\n\n### Define hook specifications\n\nIn this example, we define three hooks: async function, context manager, and\nasync context manager.\n\n```python\n>>> class Spec:\n...     \"\"\"A hook specification namespace.\"\"\"\n...\n...     @hookspec\n...     async def afunc(self, arg1, arg2):\n...         pass\n...\n...     @hookspec\n...     @contextmanager\n...     def context(self, arg1, arg2):\n...         pass\n...\n...     @hookspec\n...     @asynccontextmanager\n...     async def acontext(self, arg1, arg2):\n...         pass\n\n```\n\n### Define plugins\n\nWe define two plugins as classes. Each plugin implements the three hooks\ndefined above.\n\n```python\n>>> class Plugin_1:\n...     \"\"\"A hook implementation namespace.\"\"\"\n...\n...     @hookimpl\n...     async def afunc(self, arg1, arg2):\n...         print('inside Plugin_1.afunc()')\n...         return arg1 + arg2\n...\n...     @hookimpl\n...     @contextmanager\n...     def context(self, arg1, arg2):\n...         print('inside Plugin_1.context(): before')\n...         yield arg1 + arg2\n...         print('inside Plugin_1.context(): after')\n...\n...     @hookimpl\n...     @asynccontextmanager\n...     async def acontext(self, arg1, arg2):\n...         print('inside Plugin_1.acontext(): before')\n...         yield arg1 + arg2\n...         print('inside Plugin_1.acontext(): after')\n\n>>> class Plugin_2:\n...     \"\"\"A 2nd hook implementation namespace.\"\"\"\n...\n...     @hookimpl\n...     async def afunc(self, arg1, arg2):\n...         print('inside Plugin_2.afunc()')\n...         return arg1 - arg2\n...\n...     @hookimpl\n...     @contextmanager\n...     def context(self, arg1, arg2):\n...         print('inside Plugin_2.context(): before')\n...         yield arg1 - arg2\n...         print('inside Plugin_2.context(): after')\n...\n...     @hookimpl\n...     @asynccontextmanager\n...     async def acontext(self, arg1, arg2):\n...         print('inside Plugin_2.acontext(): before')\n...         yield arg1 - arg2\n...         print('inside Plugin_2.acontext(): after')\n\n```\n\n### Create a plugin manager and register plugins\n\nPlugins can be registered as instances or factories. In the following\nexample, we register two plugins: `Plugin_1` as an instance, and `Plugin_2`\nas a factory.\n\n```python\n>>> pm = pluggy.PluginManager('project')\n>>> pm.add_hookspecs(Spec)\n>>> _ = pm.register(Plugin_1())  # instantiation is optional.\n>>> _ = pm.register(Plugin_2)  # callable is considered a plugin factory.\n\n```\n\n[Pluggy accepts a class or\nmodule](https://pluggy.readthedocs.io/en/stable/#define-and-collect-hooks) as a\nplugin. However, it actually accepts a class instance, not a class itself.\nConsequently, when plugins are loaded with\n[`load_setuptools_entrypoints()`](https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluginManager.load_setuptools_entrypoints),\nthe entry points must be class instances or modules. Classes themselves cannot\nbe used as entry points (if understood correctly).\n\nSo that classes themselves can be entry points, apluggy accepts a class itself for\na plugin registration. When apluggy receives a callable object, apluggy considers\nthe object as a plugin factory.\n\n### Call hooks\n\nThe following example shows how to call hooks.\n\n#### Async function\n\n```python\n>>> async def call_afunc():\n...     results = await pm.ahook.afunc(arg1=1, arg2=2)  # ahook instead of hook\n...     print(results)\n\n>>> asyncio.run(call_afunc())\ninside Plugin_2.afunc()\ninside Plugin_1.afunc()\n[-1, 3]\n\n```\n\n#### Context manager\n\n```python\n>>> with pm.with_.context(arg1=1, arg2=2) as y:  # with_ instead of hook\n...     print(y)\ninside Plugin_2.context(): before\ninside Plugin_1.context(): before\n[-1, 3]\ninside Plugin_1.context(): after\ninside Plugin_2.context(): after\n\n```\n\nIn the reverse order:\n\n```python\n>>> with pm.with_reverse.context(arg1=1, arg2=2) as y:  # with_reverse instead of hook\n...     print(y)\ninside Plugin_1.context(): before\ninside Plugin_2.context(): before\n[3, -1]\ninside Plugin_2.context(): after\ninside Plugin_1.context(): after\n\n```\n\n#### Async context manager\n\n```python\n>>> async def call_acontext():\n...     async with pm.awith.acontext(arg1=1, arg2=2) as y:  # awith instead of hook\n...         print(y)\n\n>>> asyncio.run(call_acontext())\ninside Plugin_2.acontext(): before\ninside Plugin_1.acontext(): before\n[-1, 3]\ninside Plugin_1.acontext(): after\ninside Plugin_2.acontext(): after\n\n```\n\nIn the reverse order:\n\n```python\n>>> async def call_acontext():\n...     async with pm.awith_reverse.acontext(arg1=1, arg2=2) as y:  # awith_reverse instead of hook\n...         print(y)\n\n>>> asyncio.run(call_acontext())\ninside Plugin_1.acontext(): before\ninside Plugin_2.acontext(): before\n[3, -1]\ninside Plugin_2.acontext(): after\ninside Plugin_1.acontext(): after\n\n```\n\n---\n\n## Links\n\n- [pluggy](https://pluggy.readthedocs.io/)\n- [decorator](https://pypi.org/project/decorator/)\n\n---\n\n## License\n\n- _apluggy_ is licensed under the [MIT](https://spdx.org/licenses/MIT.html) license.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A wrapper of \"pluggy\" to support asyncio and context managers",
    "version": "1.0.2",
    "project_urls": {
        "Documentation": "https://github.com/simonsobs/apluggy#readme",
        "Issues": "https://github.com/simonsobs/apluggy/issues",
        "Source": "https://github.com/simonsobs/apluggy"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "13492d85d872ba9764ad1825a2c95c9af7c412715c287c89fe26a216d3809edc",
                "md5": "48c206b4e1220fff7644691277f53061",
                "sha256": "5fac688745476f85fb226366ce25ea80e48ee7b0663beb5cd6dd33fe2344c629"
            },
            "downloads": -1,
            "filename": "apluggy-1.0.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "48c206b4e1220fff7644691277f53061",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 13054,
            "upload_time": "2024-09-23T20:46:05",
            "upload_time_iso_8601": "2024-09-23T20:46:05.668865Z",
            "url": "https://files.pythonhosted.org/packages/13/49/2d85d872ba9764ad1825a2c95c9af7c412715c287c89fe26a216d3809edc/apluggy-1.0.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a2160ffea012c795cc9b876d4fe4644dda5954aeb5b1a9532aeae78f91624c04",
                "md5": "b906f9f09d5f75fcb49154b9fd739b5a",
                "sha256": "87376a0d1e212976d7ab0e68e88069483c93a006275529472f1649fd1f115c6c"
            },
            "downloads": -1,
            "filename": "apluggy-1.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "b906f9f09d5f75fcb49154b9fd739b5a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 23107,
            "upload_time": "2024-09-23T20:46:06",
            "upload_time_iso_8601": "2024-09-23T20:46:06.543856Z",
            "url": "https://files.pythonhosted.org/packages/a2/16/0ffea012c795cc9b876d4fe4644dda5954aeb5b1a9532aeae78f91624c04/apluggy-1.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-09-23 20:46:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "simonsobs",
    "github_project": "apluggy#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "apluggy"
}
        
Elapsed time: 0.29523s