-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
643 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# Plugins | ||
|
||
Poetry supports using and building plugins if you wish to | ||
alter or expand Poetry's functionality with your own. | ||
|
||
For example if your environment poses special requirements | ||
on the behaviour of Poetry which do not apply to the majority of its users | ||
or if you wish to accomplish something with Poetry in a way that is not desired by most users. | ||
|
||
In these cases you could consider creating a plugin to handle your specific logic. | ||
|
||
|
||
## Creating a plugin | ||
|
||
A plugin is a regular Python package which ships its code as part of the package | ||
and may also depend on further packages. | ||
|
||
### Plugin package | ||
|
||
The plugin package must depend on Poetry | ||
and declare a proper [plugin](/docs/pyproject/#plugins) in the `pyproject.toml` file. | ||
|
||
```toml | ||
[tool.poetry] | ||
name = "my-poetry-plugin" | ||
version = "1.0.0" | ||
|
||
# ... | ||
[tool.poetry.dependency] | ||
python = "~2.7 || ^3.7" | ||
poetry = "^1.0" | ||
|
||
[tool.poetry.plugins."poetry.plugin"] | ||
demo = "poetry_demo_plugin.plugin:MyPlugin" | ||
``` | ||
|
||
### Generic plugins | ||
|
||
Every plugin has to supply a class which implements the `poetry.plugins.Plugin` interface. | ||
|
||
The `activate()` method of the plugin is called after the plugin is loaded | ||
and receives an instance of `Poetry` as well as an instance of `cleo.io.IO`. | ||
|
||
Using these two objects all configuration can be read | ||
and all public internal objects and state can be manipulated as desired. | ||
|
||
Example: | ||
|
||
```python | ||
from cleo.io.io import IO | ||
|
||
from poetry.plugins.plugin import Plugin | ||
from poetry.poetry import Poetry | ||
|
||
|
||
class MyPlugin(Plugin): | ||
|
||
def activate(self, poetry: Poetry, io: IO): | ||
version = self.get_custom_version() | ||
io.write_line(f"Setting package version to <b>{version}</b>") | ||
poetry.package.set_version(version) | ||
|
||
def get_custom_version(self) -> str: | ||
... | ||
``` | ||
|
||
### Application plugins | ||
|
||
If you want to add commands or options to the `poetry` script you need | ||
to create an application plugin which implements the `poetry.plugins.ApplicationPlugin` interface. | ||
|
||
The `activate()` method of the application plugin is called after the plugin is loaded | ||
and receives an instance of `console.Application`. | ||
|
||
```python | ||
from cleo.commands.command import Command | ||
from poetry.plugins.application_plugin import ApplicationPlugin | ||
|
||
|
||
class CustomCommand(Command): | ||
|
||
name = "my-command" | ||
|
||
def handle(self) -> int: | ||
self.line("My command") | ||
|
||
return 0 | ||
|
||
|
||
def factory(): | ||
return CustomCommand() | ||
|
||
|
||
class MyApplicationPlugin(ApplicationPlugin): | ||
def activate(self, application): | ||
application.command_loader.register_factory("my-command", factory) | ||
``` | ||
|
||
!!!note | ||
|
||
It's possible to do the following to register the command: | ||
|
||
```python | ||
application.add(MyCommand()) | ||
``` | ||
|
||
However, it is **strongly** recommended to register a new factory | ||
in the command loader to defer the loading of the command when it's actually | ||
called. | ||
|
||
This will help keep the performances of Poetry good. | ||
|
||
The plugin also must be declared in the `pyproject.toml` file of the plugin package | ||
as an `application.plugin` plugin: | ||
|
||
```toml | ||
[tool.poetry.plugins."poetry.application.plugin"] | ||
foo-command = "poetry_demo_plugin.plugin:MyApplicationPlugin" | ||
``` | ||
|
||
!!!warning | ||
|
||
A plugin **must not** remove or modify in any way the core commands of Poetry. | ||
|
||
|
||
### Event handler | ||
|
||
Plugins can also listen to specific events and act on them if necessary. | ||
|
||
There are two types of events: application events and generic events. | ||
|
||
These events are fired by [Cleo](https://github.com/sdispater/cleo) | ||
and are accessible from the `cleo.events.console_events` module. | ||
|
||
- `COMMAND`: this event allows attaching listeners before any command is executed. | ||
- `SIGNAL`: this event allows some actions to be performed after the command execution is interrupted. | ||
- `TERMINATE`: this event allows listeners to be attached after the command. | ||
- `ERROR`: this event occurs when an uncaught exception is raised. | ||
|
||
Let's see how to implement an application event handler. For this example | ||
we will see how to load environment variables from a `.env` file before executing | ||
a command. | ||
|
||
|
||
```python | ||
from cleo.events.console_events import COMMAND | ||
from cleo.events.console_command_event import ConsoleCommandEvent | ||
from cleo.events.event_dispatcher import EventDispatcher | ||
from dotenv import load_dotenv | ||
from poetry.console.application import Application | ||
from poetry.console.commands.env_command import EnvCommand | ||
from poetry.plugins.application_plugin import ApplicationPlugin | ||
|
||
|
||
class MyApplicationPlugin(ApplicationPlugin): | ||
def activate(self, application: Application): | ||
application.event_dispatcher.add_listener(COMMAND, self.load_dotenv) | ||
|
||
def load_dotenv( | ||
self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher | ||
) -> None: | ||
command = event.io | ||
if not isinstance(command, EnvCommand): | ||
return | ||
|
||
io = event.io | ||
|
||
if io.is_debug(): | ||
io.write_line("<debug>Loading environment variables.</debug>") | ||
|
||
load_dotenv() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from typing import Callable | ||
|
||
from cleo.exceptions import LogicException | ||
from cleo.loaders.factory_command_loader import FactoryCommandLoader | ||
|
||
|
||
class CommandLoader(FactoryCommandLoader): | ||
def register_factory(self, command_name: str, factory: Callable) -> None: | ||
if command_name in self._factories: | ||
raise LogicException(f'The command "{command_name}" already exists.') | ||
|
||
self._factories[command_name] = factory |
Empty file.
Empty file.
Oops, something went wrong.