write.md 6.9 KB
Newer Older
F
Frost Ming 已提交
1 2 3
# Write a plugin

PDM is aiming at being a community driven package manager.
F
Frost Ming 已提交
4
It is shipped with a full-featured plug-in system, with which you can:
F
Frost Ming 已提交
5 6 7 8 9 10 11 12

- Develop a new command for PDM
- Add additional options to existing PDM commands
- Change PDM's behavior by reading additional config items
- Control the process of dependency resolution or installation

## What should a plugin do

T
Thomas Pohl 已提交
13
The core PDM project focuses on dependency management and package publishing. Other
F
Frost Ming 已提交
14
functionalities you wish to integrate with PDM are preferred to lie in their own plugins
T
Thomas Pohl 已提交
15
and released as standalone PyPI projects. In case the plugin is considered a good supplement
F
Frost Ming 已提交
16 17 18 19
of the core project it may have a chance to be absorbed into PDM.

## Write your own plugin

F
Frost Ming 已提交
20
In the following sections, I will show an example of adding a new command `hello` which reads the `hello.name` config.
F
Frost Ming 已提交
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

### Write the command

The PDM's CLI module is designed in a way that user can easily "inherit and modify". To write a new command:

```python
from pdm.cli.commands.base import BaseCommand

class HelloCommand(BaseCommand):
    """Say hello to the specified person.
    If none is given, will read from "hello.name" config.
    """

    def add_arguments(self, parser):
        parser.add_argument("-n", "--name", help="the person's name to whom you greet")

    def handle(self, project, options):
        if not options.name:
            name = project.config["hello.name"]
        else:
            name = options.name
        print(f"Hello, {name}")
```

T
Timothée Mazzucotelli 已提交
45
First, let's create a new `HelloCommand` class inheriting from `pdm.cli.commands.base.BaseCommand`. It has two major functions:
F
Frost Ming 已提交
46 47 48 49 50 51

- `add_arguments()` to manipulate the argument parser passed as the only argument,
  where you can add additional command line arguments to it
- `handle()` to do something when the subcommand is matched, you can do nothing by writing a single `pass` statement.
  It accepts two arguments: an `pdm.project.Project` object as the first one and the parsed `argparse.Namespace` object as the second.

F
Frost Ming 已提交
52
The document string will serve as the command help text, which will be shown in `pdm --help`.
F
Frost Ming 已提交
53 54

Besides, PDM's subcommand has two default options: `-v/--verbose` to change the verbosity level and `-g/--global` to enable global project.
F
Frost Ming 已提交
55 56
If you don't want these default options, override the `arguments` class attribute to a list of `pdm.cli.options.Option` objects, or
assign it to an empty list to have no default options:
F
Frost Ming 已提交
57 58 59 60 61 62 63 64

```python hl_lines="3"
class HelloCommand(BaseCommand):

    arguments = []
```

!!! note
F
Frost Ming 已提交
65
    The default options are loaded first, then `add_arguments()` is called.
F
Frost Ming 已提交
66 67 68

### Register the command to the core object

T
Thomas Pohl 已提交
69
Write a function somewhere in your plugin project. There is no limit on what the name of the function is,
F
Frost Ming 已提交
70
but the function should take only one argument -- the PDM core object:
F
Frost Ming 已提交
71 72 73 74 75 76

```python hl_lines="2"
def hello_plugin(core):
    core.register_command(HelloCommand, "hello")
```

F
Frost Ming 已提交
77
Call `core.register_command()` to register the command. The second argument as the name of the subcommand is optional.
F
Frost Ming 已提交
78 79 80 81
PDM will look for the `HelloCommand`'s `name` attribute if the name is not passed.

### Add a new config item

F
Frost Ming 已提交
82
Let's recall the first code snippet, `hello.name` config key is consulted for the name if not passed via the command line.
F
Frost Ming 已提交
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

```python hl_lines="11"
class HelloCommand(BaseCommand):
    """Say hello to the specified person.
    If none is given, will read from "hello.name" config.
    """

    def add_arguments(self, parser):
        parser.add_argument("-n", "--name", help="the person's name to whom you greet")

    def handle(self, project, options):
        if not options.name:
            name = project.config["hello.name"]
        else:
            name = options.name
        print(f"Hello, {name}")
```

Till now, if you query the config value by `pdm config get hello.name`, an error will pop up saying it is not a valid config key.
You need to register the config item, too:

```python hl_lines="5"
from pdm.project.config import ConfigItem

def hello_plugin(core):
    core.register_command(HelloCommand, "hello")
    core.add_config("hello.name", ConfigItem("The person's name", "John"))
```

where `ConfigItem` class takes 4 parameters, in the following order:

- `description`: a description of the config item
- `default`: default value of the config item
- `global_only`: whether the config is allowed to set in home config only
- `env_var`: the name of environment variable which will be read as the config value

### Other plugin points

121 122 123
Besides of commands and configurations, the `core` object exposes some other methods and attributes to override.
PDM also provides some signals you can listen to.
Please read the [API reference](reference.md) for more details.
F
Frost Ming 已提交
124

125 126 127 128 129 130 131
### Tips about developing a PDM plugin.

When developing a plugin, one hopes to activate and plugin in development and get updated when the code changes. This is usually done
by `pip install -e .` or `python setup.py develop` in the **traditional** Python packaging world which leverages `setup.py` to do so. However,
as there is no such `setup.py` in a PDM project, how can we do that?

Fortunately, it becomes even easier with PDM and PEP 582. First, you should enable PEP 582 globally following the
F
Frost Ming 已提交
132
[corresponding part of this doc](../usage/pep582.md#enable-pep-582-globally). Then you just need to install all dependencies into the `__pypackages__` directory by:
133 134

```bash
135
pdm install
136 137 138
```

After that, all the dependencies are available with a compatible Python interpreter, including the plugin itself, in editable mode. That means any change
F
Frost Ming 已提交
139
to the codebase will take effect immediately without re-installation. The `pdm` executable also uses a Python interpreter under the hood,
T
Thomas Pohl 已提交
140
so if you run `pdm` from inside the plugin project, the plugin in development will be activated automatically, and you can do some testing to see how it works.
141 142
That is how PEP 582 benefits our development workflow.

F
Frost Ming 已提交
143 144 145
## Publish your plugin

Now you have defined your plugin already, let's distribute it to PyPI. PDM's plugins are discovered by entry point types.
F
Frost Ming 已提交
146
Create an `pdm` entry point and point to your plugin callable (yeah, it doesn't need to be a function, any callable object can work):
F
Frost Ming 已提交
147

F
Frost Ming 已提交
148
**PEP 621**:
F
Frost Ming 已提交
149 150 151 152

```toml
# pyproject.toml

F
Frost Ming 已提交
153
[project.entry-points.pdm]
F
Frost Ming 已提交
154 155 156
hello = "my_plugin:hello_plugin"
```

F
Frost Ming 已提交
157 158 159 160 161 162 163
**setuptools**:

```python
# setup.py

setup(
    ...
F
Frost Ming 已提交
164
    entry_points={"pdm": ["hello = my_plugin:hello_plugin"]}
F
Frost Ming 已提交
165 166 167
    ...
)
```
168 169 170 171

## Activate the plugin

As plugins are loaded via entry points, they can be activated with no more steps than just installing the plugin.
172
For convenience, PDM provides a `plugin` command group to manage plugins.
173

174
Assume your plugin is published as `pdm-hello`:
175 176

```bash
177
pdm plugin add pdm-hello
178 179
```

180
Now type `pdm --help` in the terminal, you will see the new added `hello` command and use it:
181 182 183 184

```bash
$ pdm hello Jack
Hello, Jack
F
Frost Ming 已提交
185
```
186 187

See more plugin management subcommands by typing `pdm plugin --help` in the terminal.