Skip to content

Commit 9cf1848

Browse files
committed
docs: add comprehensive documentation for simplug plugin system
- Introduced new guides for defining hooks, implementing hooks, entry points, plugin registry, priority system, and result collection strategies. - Added index page for simplug documentation. - Provided examples and usage patterns for each feature. - Enhanced clarity on plugin registration, execution order, and result handling.
1 parent e3acd04 commit 9cf1848

12 files changed

Lines changed: 2620 additions & 175 deletions

README.md

Lines changed: 42 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,192 +1,74 @@
11
# simplug
22

3-
A simple plugin system for python with async hooks supported
3+
A simple plugin system for Python with async hooks support.
4+
5+
## Features
6+
7+
- **Decorator-based API**: Define hooks with `@simplug.spec` and implement with `@simplug.impl`
8+
- **Async support**: First-class async hooks with sync/async bridging
9+
- **Flexible result collection**: 18 built-in strategies for collecting plugin results
10+
- **Priority system**: Control plugin execution order
11+
- **Setuptools entrypoints**: Load plugins from installed packages
12+
- **Singleton per project**: Same instance returned for same project name
413

514
## Installation
615

716
```shell
817
pip install -U simplug
918
```
1019

11-
## Examples
12-
13-
### A toy example
20+
## Quick Start
1421

1522
```python
1623
from simplug import Simplug
1724

18-
simplug = Simplug('project')
25+
# Create a plugin manager
26+
simplug = Simplug('myproject')
1927

28+
# Define a hook specification
2029
class MySpec:
21-
"""A hook specification namespace."""
22-
2330
@simplug.spec
24-
def myhook(self, arg1, arg2):
25-
"""My special little hook that you can customize."""
26-
27-
class Plugin_1:
28-
"""A hook implementation namespace."""
31+
def process_data(self, data):
32+
"""Process data in plugins."""
2933

34+
# Implement the hook in plugins
35+
class PluginA:
3036
@simplug.impl
31-
def myhook(self, arg1, arg2):
32-
print("inside Plugin_1.myhook()")
33-
return arg1 + arg2
34-
35-
class Plugin_2:
36-
"""A 2nd hook implementation namespace."""
37+
def process_data(self, data):
38+
return data.upper()
3739

40+
class PluginB:
3841
@simplug.impl
39-
def myhook(self, arg1, arg2):
40-
print("inside Plugin_2.myhook()")
41-
return arg1 - arg2
42-
43-
simplug.register(Plugin_1, Plugin_2)
44-
results = simplug.hooks.myhook(arg1=1, arg2=2)
45-
print(results)
46-
```
47-
48-
```shell
49-
inside Plugin_1.myhook()
50-
inside Plugin_2.myhook()
51-
[3, -1]
52-
```
53-
54-
Note that the hooks are executed in the order the plugins are registered. This is different from `pluggy`.
55-
56-
### A complete example
57-
58-
See `examples/complete/`.
59-
60-
Running `python -m examples.complete` gets us:
61-
62-
```shell
63-
Your food. Enjoy some egg, egg, egg, salt, pepper, egg, egg
64-
Some condiments? We have pickled walnuts, steak sauce, mushy peas, mint sauce
65-
```
66-
67-
After install the plugin:
68-
69-
```shell
70-
> pip install --editable examples.complete.plugin
71-
> python -m examples.complete # run again
72-
```
73-
74-
```shell
75-
Your food. Enjoy some egg, egg, egg, salt, pepper, egg, egg, lovely spam, wonderous spam
76-
Some condiments? We have pickled walnuts, mushy peas, mint sauce, spam sauce
77-
Now this is what I call a condiments tray!
78-
```
79-
80-
## Usage
81-
82-
### Definition of hooks
83-
84-
Hooks are specified and implemented by decorating the functions with `simplug.spec` and `simplug.impl` respectively.
42+
def process_data(self, data):
43+
return data.lower()
8544

86-
`simplug` is initialized by:
45+
# Register plugins
46+
simplug.register(PluginA, PluginB)
8747

88-
```python
89-
simplug = Simplug('project')
90-
```
91-
92-
The `'project'` is a unique name to mark the project, which makes sure `Simplug('project')` get the same instance each time.
93-
94-
Note that if `simplug` is initialized without `project`, then a name is generated automatically as such `project-0`, `project-1`, etc.
95-
96-
Hook specification is marked by `simplug.spec`:
97-
98-
```python
99-
simplug = Simplug('project')
100-
101-
@simplug.spec
102-
def setup(args):
103-
...
48+
# Call the hook
49+
results = simplug.hooks.process_data("Hello")
50+
print(results) # ['HELLO', 'hello']
10451
```
10552

106-
`simplug.spec` can take some arguments:
107-
108-
- `required`: Whether this hook is required to be implemented in plugins
109-
- `result`: An enumerator to specify the way to collec the results.
110-
- `SimplugResult.ALL`: Collect all results from all plugins
111-
- `SimplugResult.ALL_AVAILS`: Get all the results from the hook, as a list, not including `None`s
112-
- `SimplugResult.ALL_FIRST`: Executing all implementations and get the first result
113-
- `SimplugResult.ALL_LAST`: Executing all implementations and get the last result
114-
- `SimplugResult.TRY_ALL_FIRST`: Executing all implementations and get the first result, if no result is returned, return `None`
115-
- `SimplugResult.TRY_ALL_LAST`: Executing all implementations and get the last result, if no result is returned, return `None`
116-
- `SimplugResult.ALL_FIRST_AVAIL`: Executing all implementations and get the first non-`None` result
117-
- `SimplugResult.ALL_LAST_AVAIL`: Executing all implementations and get the last non-`None` result
118-
- `SimplugResult.TRY_ALL_FIRST_AVAIL`: Executing all implementations and get the first non-`None` result, if no result is returned, return `None`
119-
- `SimplugResult.TRY_ALL_LAST_AVAIL`: Executing all implementations and get the last non-`None` result, if no result is returned, return `None`
120-
- `SimplugResult.FIRST`: Get the first result, don't execute other implementations
121-
- `SimplugResult.LAST`: Get the last result, don't execute other implementations
122-
- `SimplugResult.TRY_FIRST`: Get the first result, don't execute other implementations, if no result is returned, return `None`
123-
- `SimplugResult.TRY_LAST`: Get the last result, don't execute other implementations, if no result is returned, return `None`
124-
- `SimplugResult.FIRST_AVAIL`: Get the first non-`None` result, don't execute other implementations
125-
- `SimplugResult.LAST_AVAIL`: Get the last non-`None` result, don't execute other implementations
126-
- `SimplugResult.TRY_FIRST_AVAIL`: Get the first non-`None` result, don't execute other implementations, if no result is returned, return `None`
127-
- `SimplugResult.TRY_LAST_AVAIL`: Get the last non-`None` result, don't execute other implementations, if no result is returned, return `None`
128-
- `SimplugResult.SINGLE`: Get the result from a single implementation
129-
- `SimplugResult.TRY_SINGLE`: Get the result from a single implementation, if no result is returned, return `None`
130-
- A callable to collect the result, take `calls` as the argument, a 3-element tuple with first element as the implementation, second element as the positional arguments, and third element as the keyword arguments.
131-
132-
Hook implementation is marked by `simplug.impl`, which takes no additional arguments.
133-
134-
The name of the function has to match the name of the function by `simplug.spec`. And the signatures of the specification function and the implementation function have to be the same in terms of names. This means you can specify default values in the specification function, but you don't have to write the default values in the implementation function.
135-
136-
Note that default values in implementation functions will be ignored.
137-
138-
Also note if a hook specification is under a namespace, it can take `self` as argument. However, this argument will be ignored while the hook is being called (`self` will be `None`, and you still have to specify it in the function definition).
139-
140-
### Loading plugins from setuptools entrypoint
141-
142-
You have to call `simplug.load_entrypoints(group)` after the hook specifications are defined to load the plugins registered by setuptools entrypoint. If `group` is not given, the project name will be used.
53+
## Documentation
14354

144-
### The plugin registry
55+
Full documentation is available at [https://pwwang.github.io/simplug/](https://pwwang.github.io/simplug/)
14556

146-
The plugins are registered by `simplug.register(*plugins)`. Each plugin of `plugins` can be either a python object or a str denoting a module that can be imported by `importlib.import_module`.
57+
## Getting Started
14758

148-
The python object must have an attribute `name`, `__name__` or `__class.__name__` for `simplug` to determine the name of the plugin. If the plugin name is determined from `__name__` or `__class__.__name__`, it will be lowercased.
59+
1. **Define hooks** - Specify what plugins can customize
60+
2. **Implement hooks** - Create plugins with implementations
61+
3. **Register plugins** - Load plugins into your application
62+
4. **Call hooks** - Trigger plugin execution
14963

150-
If a plugin is loaded from setuptools entrypoint, then the entrypoint name will be used (no matter what name is defined inside the plugin)
64+
For detailed guides and API reference, see the [full documentation](https://pwwang.github.io/simplug/).
15165

152-
You can enable or disable a plugin temporarily after registration by:
153-
154-
```python
155-
simplug.disable('plugin_name')
156-
simplug.enable('plugin_name')
157-
```
158-
159-
You can use following methods to inspect the plugin registry:
160-
161-
- `simplug.get_plugin`: Get the plugin by name
162-
- `simplug.get_all_plugins`: Get a dictionary of name-plugin mappings of all plugins
163-
- `simplug.get_all_plugin_names`: Get the names of all plugins, in the order it will be executed.
164-
- `simplug.get_enabled_plugins`: Get a dictionary of name-plugin mappings of all enabled plugins
165-
- `simplug.get_enabled_plugin_names`: Get the names of all enabled plugins, in the order it will be executed.
166-
167-
### Calling hooks
168-
169-
Hooks are call by `simplug.hooks.<hook_name>(<arguments>)` and results are collected based on the `result` argument passed in `simplug.spec` when defining hooks.
170-
171-
### Async hooks
172-
173-
It makes no big difference to define an async hook:
174-
175-
```python
176-
@simplug.spec
177-
async def async_hook(arg):
178-
...
179-
180-
# to supress warnings for sync implementation
181-
@simplug.spec(warn_sync_impl_on_async=False)
182-
async def async_hook(arg):
183-
...
184-
```
185-
186-
One can implement this hook in either an async or a sync way. However, when implementing it in a sync way, a warning will be raised. To suppress the warning, one can pass a `False` value of argument `warn_sync_impl_on_async` to `simplug.spec`.
66+
## Examples
18767

188-
To call the async hooks (`simplug.hooks.async_hook(arg)`), you will just need to call it like any other async functions (using `asyncio.run`, for example)
68+
See the [examples](examples/) directory for complete examples:
69+
- [`examples/toy.py`](examples/toy.py) - A minimal 30-line demo
70+
- [`examples/complete/`](examples/complete/) - Full example with setuptools entrypoints
18971

190-
## API
72+
## License
19173

192-
https://pwwang.github.io/simplug/
74+
MIT

0 commit comments

Comments
 (0)