# Technical Documentation: pdm-project/pdm
> ℹ️ **Provenance:** Hybrid Fusion: `pdm-project/pdm` (README + 7 In-Tree Chapters) · [CodeWiki Reference](https://codewiki.google/github.com/pdm-project/pdm) · Recency: Active (< 180 days)
## 1. Project Overview & Quickstart (pdm-project/pdm)
## What is PDM?
PDM is meant to be a next-generation package manager.
It was originally built for personal use. If you feel you are going well
with `Pipenv` or `Poetry` and don't want to introduce another package manager,
just stick to it. But if you are missing something that is not present in those tools,
you can probably find some goodness in `pdm`.
## Highlights of features
- Simple and fast dependency resolver, mainly for large binary distributions.
- A [PEP 517] build backend.
- [PEP 621] project metadata.
- Flexible and powerful plug-in system.
- Versatile user scripts.
- Install Pythons using [astral-sh's python-build-standalone](https://github.com/astral-sh/python-build-standalone).
- Opt-in, centralized installation cache like [pnpm](https://pnpm.io/motivation#saving-disk-space-and-boosting-installation-speed).
[pep 517]: https://www.python.org/dev/peps/pep-0517
[pep 621]: https://www.python.org/dev/peps/pep-0621
[pnpm]: https://pnpm.io/motivation#saving-disk-space-and-boosting-installation-speed
## Comparisons to other alternatives
### [Pipenv](https://pipenv.pypa.io)
Pipenv is a dependency manager that combines `pip` and `venv`, as the name implies.
It can install packages from a non-standard `Pipfile.lock` or `Pipfile`.
However, Pipenv does not handle any packages related to packaging your code,
so it’s useful only for developing non-installable applications (Django sites, for example).
If you’re a library developer, you need `setuptools` anyway.
### [Poetry](https://python-poetry.org)
Poetry manages environments and dependencies in a similar way to Pipenv,
but it can also build .whl files with your code, and it can upload wheels and source distributions to PyPI.
It has a pretty user interface and users can customize it via a plugin. Poetry uses the `pyproject.toml` standard.
### [Hatch](https://hatch.pypa.io)
Hatch can also manage environments, allowing multiple environments per project. By default it has a central location for all environments but it can be configured to put a project's environment(s) in the project root directory. It can manage packages but without lockfile support. It can also be used to package a project (with PEP 621 compliant pyproject.toml files) and upload it to PyPI.
### This project
PDM can manage virtual environments (venvs) in both project and centralized locations, similar to Pipenv. It reads project metadata from a standardized `pyproject.toml` file and supports lockfiles. Users can add additional functionality through plugins, which can be shared by uploading them as distributions.
Unlike Poetry and Hatch, PDM is not limited to a specific build backend; users have the freedom to choose any build backend they prefer.
## Installation
PDM requires python version 3.10 or higher. Alternatively, you can download the standalone binary from the [release assets](https://github.com/pdm-project/pdm/releases).
### Install Binary via Script (recommended)
Install the standalone binary directly with the installer scripts:
**For Linux/Mac**
```bash
curl -sSL https://pdm-project.org/install.sh | bash
```
**For Windows**
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://pdm-project.org/install.ps1 | iex"
```
For alternative installation methods (Python script, package managers, etc.), see the [installation section in documentation](https://pdm-project.org/en/latest/#installation).
## Quickstart
**Create a new PDM project**
```bash
pdm new my-project
```
Answer the prompts, and a PDM project with a `pyproject.toml` file will be ready to use., and a PDM project with a `pyproject.toml` file will be ready to use.
**Install dependencies**
```bash
pdm add requests flask
```
You can add multiple dependencies in the same command. After a while, check the `pdm.lock` file to see what is locked for each package.
## Badges
Tell people you are using PDM in your project by including the markdown code in README.md:
```markdown
[](https://pdm-project.org)
```
[](https://pdm-project.org)
## PDM Eco-system
[Awesome PDM](https://github.com/pdm-project/awesome-pdm) is a curated list of awesome PDM plugins and resources.
## Experimental
Enable [PEP 582](https://peps.python.org/pep-0582/) for a project:
pdm config python.use_venv False
This makes PDM install packages into a local project folder instead of a venv (similar to how npm installs into node_modules).
Enable [uv](https://github.com/astral-sh/uv) integration:
pdm config use_uv true
uv is a very fast Python package installer written in Rust.
Note: `uv` does not work with `PEP 582`.
## Sponsors
## Credits
This project is strongly inspired by [pyflow] and [poetry].
[pyflow]: https://github.com/David-OConnor/pyflow
[poetry]: https://github.com/python-poetry/poetry
## License
This project is open sourced under MIT license, see the [LICENSE](LICENSE) file for more details.
## 2. In-Tree Documentation Chapters (pdm-project/pdm)
## File: README.md
## What is PDM?
PDM is meant to be a next-generation package manager.
It was originally built for personal use. If you feel you are going well
with `Pipenv` or `Poetry` and don't want to introduce another package manager,
just stick to it. But if you are missing something that is not present in those tools,
you can probably find some goodness in `pdm`.
## Highlights of features
- Simple and fast dependency resolver, mainly for large binary distributions.
- A [PEP 517] build backend.
- [PEP 621] project metadata.
- Flexible and powerful plug-in system.
- Versatile user scripts.
- Install Pythons using [astral-sh's python-build-standalone](https://github.com/astral-sh/python-build-standalone).
- Opt-in, centralized installation cache like [pnpm](https://pnpm.io/motivation#saving-disk-space-and-boosting-installation-speed).
[pep 517]: https://www.python.org/dev/peps/pep-0517
[pep 621]: https://www.python.org/dev/peps/pep-0621
[pnpm]: https://pnpm.io/motivation#saving-disk-space-and-boosting-installation-speed
## Comparisons to other alternatives
### [Pipenv](https://pipenv.pypa.io)
Pipenv is a dependency manager that combines `pip` and `venv`, as the name implies.
It can install packages from a non-standard `Pipfile.lock` or `Pipfile`.
However, Pipenv does not handle any packages related to packaging your code,
so it’s useful only for developing non-installable applications (Django sites, for example).
If you’re a library developer, you need `setuptools` anyway.
### [Poetry](https://python-poetry.org)
Poetry manages environments and dependencies in a similar way to Pipenv,
but it can also build .whl files with your code, and it can upload wheels and source distributions to PyPI.
It has a pretty user interface and users can customize it via a plugin. Poetry uses the `pyproject.toml` standard.
### [Hatch](https://hatch.pypa.io)
Hatch can also manage environments, allowing multiple environments per project. By default it has a central location for all environments but it can be configured to put a project's environment(s) in the project root directory. It can manage packages but without lockfile support. It can also be used to package a project (with PEP 621 compliant pyproject.toml files) and upload it to PyPI.
### This project
PDM can manage virtual environments (venvs) in both project and centralized locations, similar to Pipenv. It reads project metadata from a standardized `pyproject.toml` file and supports lockfiles. Users can add additional functionality through plugins, which can be shared by uploading them as distributions.
Unlike Poetry and Hatch, PDM is not limited to a specific build backend; users have the freedom to choose any build backend they prefer.
## Installation
PDM requires python version 3.10 or higher. Alternatively, you can download the standalone binary from the [release assets](https://github.com/pdm-project/pdm/releases).
### Install Binary via Script (recommended)
Install the standalone binary directly with the installer scripts:
**For Linux/Mac**
```bash
curl -sSL https://pdm-project.org/install.sh | bash
```
**For Windows**
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://pdm-project.org/install.ps1 | iex"
```
For alternative installation methods (Python script, package managers, etc.), see the [installation section in documentation](https://pdm-project.org/en/latest/#installation).
## Quickstart
**Create a new PDM project**
```bash
pdm new my-project
```
Answer the prompts, and a PDM project with a `pyproject.toml` file will be ready to use., and a PDM project with a `pyproject.toml` file will be ready to use.
**Install dependencies**
```bash
pdm add requests flask
```
You can add multiple dependencies in the same command. After a while, check the `pdm.lock` file to see what is locked for each package.
## Badges
Tell people you are using PDM in your project by including the markdown code in README.md:
```markdown
[](https://pdm-project.org)
```
[](https://pdm-project.org)
## PDM Eco-system
[Awesome PDM](https://github.com/pdm-project/awesome-pdm) is a curated list of awesome PDM plugins and resources.
## Experimental
Enable [PEP 582](https://peps.python.org/pep-0582/) for a project:
pdm config python.use_venv False
This makes PDM install packages into a local project folder instead of a venv (similar to how npm installs into node_modules).
Enable [uv](https://github.com/astral-sh/uv) integration:
pdm config use_uv true
uv is a very fast Python package installer written in Rust.
Note: `uv` does not work with `PEP 582`.
## Sponsors
## Credits
This project is strongly inspired by [pyflow] and [poetry].
[pyflow]: https://github.com/David-OConnor/pyflow
[poetry]: https://github.com/python-poetry/poetry
## License
This project is open sourced under MIT license, see the [LICENSE](LICENSE) file for more details.
---
## File: docs/dev/benchmark.md
# Benchmark
This page has been removed, please visit [Python Package Manager Shootout by Lincoln Loop](https://lincolnloop.github.io/python-package-manager-shootout/) for a detailed benchmark report.
---
## File: docs/dev/fixtures.md
# Pytest fixtures
::: pdm.pytest
options:
show_source: false
show_root_heading: false
show_root_toc_entry: false
heading_level: 2
---
## File: docs/dev/write.md
# PDM Plugins
PDM is aiming at being a community driven package manager.
It is shipped with a full-featured plug-in system, with which you can:
- 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
The core PDM project focuses on dependency management and package publishing.
Other functionalities you wish to integrate with PDM are preferred to lie in their own plugins and released as standalone PyPI projects.
In case the plugin is considered a good supplement of the core project it may have a chance to be absorbed into PDM.
## Write your own plugin
In the following sections, I will show an example of adding a new command `hello` which reads the `hello.name` config.
### 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}")
```
First, let's create a new `HelloCommand` class inheriting from `pdm.cli.commands.base.BaseCommand`. It has two major functions:
- `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.
The document string will serve as the command help text, which will be shown in `pdm --help`.
Besides, PDM's subcommand has two default options: `-v/--verbose` to change the verbosity level and `-g/--global` to enable global project.
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:
```python hl_lines="3"
class HelloCommand(BaseCommand):
arguments = []
```
!!! note
The default options are loaded first, then `add_arguments()` is called.
### Register the command to the core object
Write a function somewhere in your plugin project. There is no limit on what the name of the function is,
but the function should take only one argument -- the PDM core object:
```python hl_lines="2"
def hello_plugin(core):
core.register_command(HelloCommand, "hello")
```
Call `core.register_command()` to register the command. The second argument as the name of the subcommand is optional.
PDM will look for the `HelloCommand`'s `name` attribute if the name is not passed.
### Add a new config item
Let's recall the first code snippet, `hello.name` config key is consulted for the name if not passed via the command line.
```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
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/api.md) for more details.
### 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.
You can achieve this by installing the plugin in editable mode. To do this, specify the dependencies in `tool.pdm.plugins` array:
```toml
[tool.pdm]
plugins = [
"-e file:///${PROJECT_ROOT}"
]
```
Then install it with:
```bash
pdm install --plugins
```
After that, all the dependencies are available in a project plugin library, including the plugin itself, in editable mode. That means any change
to the codebase will take effect immediately without re-installation. The `pdm` executable also uses a Python interpreter under the hood,
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.
### Testing your plugin
PDM exposes some pytest fixtures as a plugin in the [`pdm.pytest`](fixtures.md) module.
To benefit from them, you must add `pdm[pytest]` as a test dependency.
To enable them in your test, add `pdm.pytest` as a plugin. You can do so by in your root `conftest.py`:
```python title="conftest.py"
# single plugin
pytest_plugins = "pytest.plugin"
# many plugins
pytest_plugins = [
...
"pdm.pytest",
...
]
```
You can see some usage examples into PDM own [tests](https://github.com/pdm-project/pdm/tree/main/tests), especially the [conftest.py file](https://github.com/pdm-project/pdm/blob/main/tests/conftest.py) for configuration.
See the [pytest fixtures documentation](fixtures.md) for more details.
## 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.
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):
**PEP 621**:
```toml
# pyproject.toml
[project.entry-points.pdm]
hello = "my_plugin:hello_plugin"
```
**setuptools**:
```python
# setup.py
setup(
...
entry_points={"pdm": ["hello = my_plugin:hello_plugin"]}
...
)
```
## Activate the plugin
As plugins are loaded via entry points, they can be activated with no more steps than just installing the plugin.
For convenience, PDM provides a `plugin` command group to manage plugins.
Assume your plugin is published as `pdm-hello`:
```bash
pdm self add pdm-hello
```
Now type `pdm --help` in the terminal, you will see the new added `hello` command and use it:
```bash
$ pdm hello Jack
Hello, Jack
```
See more plugin management subcommands by typing `pdm self --help` in the terminal.
## Specify the plugins in project
To specify the required plugins for a project, you can use the `tool.pdm.plugins` config in the `pyproject.toml` file.
These dependencies can be installed into a project plugin library by running `pdm install --plugins`.
The project plugin library will be loaded in subsequent PDM commands.
This is useful when you want to share the same plugin set with the contributors.
```toml
# pyproject.toml
[tool.pdm]
plugins = [
"pdm-packer"
]
```
Run `pdm install --plugins` to install and activate the plugins.
Alternatively, you can have project-local plugins that are not published to PyPI, by using editable local dependencies:
```toml
# pyproject.toml
[tool.pdm]
plugins = [
"-e file:///${PROJECT_ROOT}/plugins/my_plugin"
]
```
---
## File: docs/reference/api.md
# API Reference
::: pdm.core.Core
options:
show_root_heading: yes
show_source: false
heading_level: 2
::: pdm.core.Project
options:
show_root_heading: yes
show_source: false
heading_level: 2
## Signals
!!! tip
Added in 1.12.0.
::: pdm.signals
options:
heading_level: 3
---
## File: docs/reference/build.md
# Build Configuration
`pdm` uses the [PEP 517](https://www.python.org/dev/peps/pep-0517/) to build the package. It acts as a build frontend that calls the build backend to build the package.
A build backend is what drives the build system to build source distributions and wheels from arbitrary source trees.
If you run [`pdm init`](../reference/cli.md#init), PDM will let you choose the build backend to use. Unlike other package managers, PDM does not force you to use a specific build backend. You can choose the one you like. Here is a list of build backends and corresponding configurations initially supported by PDM:
=== "pdm-backend"
`pyproject.toml` configuration:
```toml
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
```
[:book: Read the docs](https://backend.pdm-project.org/)
=== "setuptools"
`pyproject.toml` configuration:
```toml
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
```
[:book: Read the docs](https://setuptools.pypa.io/)
=== "flit"
`pyproject.toml` configuration:
```toml
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
```
[:book: Read the docs](https://flit.pypa.io/)
=== "hatchling"
`pyproject.toml` configuration:
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```
[:book: Read the docs](https://hatch.pypa.io/)
=== "maturin"
`pyproject.toml` configuration:
```toml
[build-system]
requires = ["maturin>=1.4,<2.0"]
build-backend = "maturin"
```
[:book: Read the docs](https://www.maturin.rs/)
Apart from the above mentioned backends, you can also use any other backend that supports PEP 621, however, [poetry-core](https://python-poetry.org/) is not supported because it does not support reading PEP 621 metadata.
!!! info
If you are using a custom build backend that is not in the above list, PDM will handle the relative paths as PDM-style(`${PROJECT_ROOT}` variable).
---
## File: docs/reference/pep621.md
# PEP 621 Metadata
The project metadata are stored in the `pyproject.toml`. The specifications are defined by [PEP 621], [PEP 631] and [PEP 639]. Read the detailed specifications in the PEPs.
[PEP 621]: https://www.python.org/dev/peps/pep-0621/
[PEP 631]: https://www.python.org/dev/peps/pep-0631/
[PEP 639]: https://www.python.org/dev/peps/pep-0639/
_In the following part of this document, metadata should be written under `[project]` table if not given explicitly._
## Multiline description
You can split a long description onto multiple lines, thanks to TOML support for multiline strings.
Just remember to escape new lines, so the final description appears [on one line only in your package metadata](https://packaging.python.org/specifications/core-metadata/#summary).
Indentation will be removed as well when escaping new lines:
```toml
description = """\
Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
Ut enim ad minim veniam, quis nostrud exercitation ullamco \
laboris nisi ut aliquip ex ea commodo consequat.\
"""
```
See [TOML's specification on strings](https://toml.io/en/v1.0.0#string).
## Package version
=== "Static"
```toml
[project]
version = "1.0.0"
```
=== "Dynamic"
```toml
[project]
...
dynamic = ["version"]
[tool.pdm]
version = { source = "file", path = "mypackage/__version__.py" }
```
The version will be read from the `mypackage/__version__.py` file searching for the pattern: `__version__ = "{version}"`.
Read more information about other configurations in [dynamic project version](https://backend.pdm-project.org/metadata/#dynamic-project-version) from the `pdm-backend` documentation.
## Python version
The required version of Python is specified as the string `requires-python`:
```toml
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
...
]
```
Note: As per [PEP 621](https://peps.python.org/pep-0621/#allow-tools-to-add-extend-data),
PDM is not permitted to dynamically update the `classifiers` section like some other non-compliant tools.
Thus, you should also include the appropriate [trove classifiers](https://pypi.org/classifiers/) as shown above if you plan on publishing your package on [PyPI](https://pypi.org/).
## License
The license is specified as the string `license`:
```toml
license = {text = "BSD-2-Clause"}
classifiers = [
"License :: OSI Approved :: BSD License",
...
]
```
Note: As per [PEP 621](https://peps.python.org/pep-0621/#allow-tools-to-add-extend-data),
PDM is not permitted to dynamically update the `classifiers` section like some other non-compliant tools.
Thus, you should also include the appropriate [trove classifiers](https://pypi.org/classifiers/) as shown above if you plan on publishing your package on [PyPI](https://pypi.org/).
## Dependency specification
The `project.dependencies` is an array of dependency specification strings following the [PEP 440](https://www.python.org/dev/peps/pep-0440/) and [PEP 508](https://www.python.org/dev/peps/pep-0508/).
Examples:
```toml
[project]
...
dependencies = [
# Named requirement
"requests",
# Named requirement with version specifier
"flask >= 1.1.0",
# Requirement with environment marker
"pywin32; sys_platform == 'win32'",
# URL requirement
"pip @ git+https://github.com/pypa/pip.git@20.3.1"
]
```
## Optional dependencies
You can have some requirements optional, which is similar to `setuptools`' `extras_require` parameter.
```toml
[project.optional-dependencies]
socks = [ 'PySocks >= 1.5.6, != 1.5.7, < 2' ]
tests = [
'ddt >= 1.2.2, < 2',
'pytest < 6',
'mock >= 1.0.1, < 4; python_version < "3.4"',
]
```
To install a group of optional dependencies:
```bash
pdm install -G socks
```
`-G` option can be given multiple times to include more than one group.
## Context variables expansion
Depending on which build backend you are using, PDM will expand some variables in the dependency strings.
### Environment variables
=== "pdm-backend"
```toml
[project]
dependencies = ["flask @ https://${USERNAME}:${PASSWORD}/artifacts.io/Flask-1.1.2.tar.gz"]
```
=== "hatchling"
```toml
[project]
dependencies = ["flask @ https://{env:USERNAME}:{env:PASSWORD}/artifacts.io/Flask-1.1.2.tar.gz"]
```
Find more usages [here](https://hatch.pypa.io/dev/config/context/#environment-variables)
Don't worry about credential leakage, the environment variables will be expanded when needed and kept untouched in the lock file.
### Relative paths
When you add a package from a relative path, PDM will automatically save it as a relative path for `pdm-backend` and `hatchling`.
For example, if you run `pdm add ./my-package`, it will result in the following line in `pyproject.toml`.
=== "pdm-backend"
```toml
[project]
dependencies = ["my-package @ file:///${PROJECT_ROOT}/my-package"]
```
=== "hatchling"
```toml
[project]
dependencies = ["my-package @ {root:uri}/my-package"]
```
By default, hatchling doesn't support [direct references](https://hatch.pypa.io/dev/config/dependency/#direct-references)
in the dependency string, you need to turn it on in `pyproject.toml`:
```toml
[tool.hatch.metadata]
allow-direct-references = true
```
The relative path will be expanded based on the project root when installing or locking.
## Console scripts
The following content:
```toml
[project.scripts]
mycli = "mycli.__main__:main"
```
will be translated to `setuptools` style:
```python
entry_points = {
'console_scripts': [
'mycli=mycli.__main__:main'
]
}
```
Also, `[project.gui-scripts]` will be translated to `gui_scripts` entry points group in `setuptools` style.
## Entry points
Other types of entry points are given by `[project.entry-points.]` section, with the same format of `[project.scripts]`:
```toml
[project.entry-points.pytest11]
myplugin = "mypackage.plugin:pytest_plugin"
```
If the entry point name contains dots or other special characters, wrap it in quotes:
```toml
[project.entry-points."flake8.extension"]
myplugin = "mypackage.plugin:flake8_plugin"
```
--- METRICS ---
- Files Extracted: 8
- Estimated Token Budget: ~6925 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/pdm-project/pdm