PDM: Python Package and Dependency Manager

repository·main·Indexed 11 days ago

https://github.com/pdm-project/pdm

A modern Python package and dependency manager supporting the latest PEP standards, including PEP 517, 582, 621, 631, and 639. PDM provides a fast dependency resolver, a flexible plugin system, and versatile user scripts. It supports integration with uv for high-performance installation and allows for local package installation via PEP 582.

Tokens
57.4K
Snippets
221
Records
293
Agent score
90%

What's inside PDM

  1. Key features of PDM

    main

    PDM is a modern Python package manager that supports the latest PEP standards. Key features include:

    • Fast Dependency Resolver: Optimized for large binary package releases.
    • PEP 517 Compatibility: Supports build backends for creating source and wheel distributions.
    • PEP 621 Support: Uses the standardized metadata format in pyproject.toml.
    • Plugin System: Flexible and powerful for extending functionality.
    • Centralized Cache: Uses a centralized installation cache (similar to pnpm) to save disk space.
    • Python Installation: Supports installing Python via astral-sh/python-build-standalone.
    • User Scripts: Provides powerful support for defining and running user scripts.
  2. Control package source priority and selection

    main

    Respect source order

    By default, PDM selects the highest version matching requirements regardless of the source. To prioritize a specific source (e.g., a private index), set respect-source-order = true in the [tool.pdm.resolution] table.

    Bind packages to specific sources

    You can use include_packages and exclude_packages (glob patterns) within a [[tool.pdm.source]] definition to control which packages are searched in which index.

    • include_packages: If a package matches, it is only searched in this source.
    • exclude_packages: If a package matches, it is excluded from this source.
    [tool.pdm.resolution]
    respect-source-order = true
    
    [[tool.pdm.source]]
    name = "private"
    url = "https://private.pypi.org/simple"
    
    [[tool.pdm.source]]
    name = "pypi"
    url = "https://pypi.org/simple"
    
    # Example: Binding specific packages to a source
    [[tool.pdm.source]]
    name = "private"
    url = "https://private.pypi.org/simple"
    include_packages = ["foo", "foo-*"]
    exclude_packages = ["bar-*"]
  3. How PDM handles virtual environments vs PEP 582

    main

    PDM supports two primary modes for managing Python environments:

    1. Virtual Environments (Default): PDM uses a virtualenv (created via virtualenv, venv, or conda) to isolate dependencies. This is the default behavior and is highly compatible with IDEs and the broader Python ecosystem.
    2. PEP 582 Mode: An alternative where dependencies are installed into a __pypackages__ directory.

    Switching Modes:

    • To disable virtualenv mode and force PEP 582, use: pdm config python.use_venv False.
    • Note that if python.use_venv is disabled, PEP 582 will be used even if the selected interpreter is itself a virtualenv.
    • PDM automatically uses a virtualenv if the project interpreter (stored in .pdm-python) is from a virtualenv.
  4. Manage development-only dependency groups

    main

    To define dependencies that are useful for development (like testing or linting) but should not appear in the package distribution metadata, use dependency groups. These are stored in the [dependency-groups] table in pyproject.toml.

    • Use pdm add -dG <group_name> <package> to add a package to a specific development group.
    • If you only use -d or --dev, the package is added to the default dev group.
    • Constraint: A group name MUST NOT appear in both [dependency-groups] and [project.optional-dependencies].
    # Add pytest to a group named 'test'
    pdm add -dG test pytest
  5. Define `requires-python` for dependency resolution

    main

    The requires-python field in pyproject.toml defines the range of Python versions your project supports. This is critical for dependency resolution because PDM creates a cross-platform lockfile that must work for all versions in that range.

    Warning: If a dependency's requires-python does not fully cover your project's range, you will encounter a ResolutionImpossible error.

    Example Scenario:

    • Project: requires-python = ">=3.9"
    • Dependency foo: requires-python = ">=3.7,<3.11"
    • Result: Error. The dependency does not support Python 3.11+, but your project claims to support it.
    • Fix: Narrow your project range to requires-python = ">=3.9,<3.11".

    Common Specifiers (PEP 440):

    • >=3.7 (3.7 and above)
    • >=3.7,<3.11 (3.7 through 3.10)
    • >=3.6,!=3.8.*,!=3.9.* (3.6+ except 3.8 and 3.9)
  6. Difference between `[tool.pdm.scripts]` and `[project.scripts]`

    main

    It is important to distinguish between these two sections in pyproject.toml:

    Feature[tool.pdm.scripts][project.scripts]
    PurposeTask runner/shortcuts (like Makefile) for development/testing.Defines console script entry points for the installed package.
    RequirementRequires a pyproject.toml file.Requires distribution = true (the project must be installed).
    Executionpdm run <name>Executable after installation.
    ScopeWorks regardless of installation status.Only works if the package is installed in the environment.
  7. How project name replacement works in templates

    main

    When initializing a project from a template, PDM performs a recursive full-text search and replace to update the project name throughout the generated files.

    1. Project Name: The name provided to pdm new or pdm init replaces the template's project name in all .md and .rst files.
    2. Import Name: The 'import name' (the project name with non-alphanumeric characters replaced by underscores and lowercased) is replaced in all .py files, directory names, and file names.

    Example: If the template uses foo-project and you run pdm new foo-bar:

    • foo-project becomes foo-bar in .md/.rst files.
    • foo_project becomes foo_bar in .py files.
    • foo_project.py becomes foo_bar.py in filenames.
    • The directory name becomes foo_bar.
  8. Configure site-packages isolation for scripts

    main

    By default, site-packages from the selected interpreter are not loaded into sys.path to ensure isolation. To enable loading site-packages, you must meet one of these conditions:

    1. The executable is from PATH but not inside the __pypackages__ folder.
    2. Use the -s or --site-packages flag with pdm run.
    3. Set site_packages = true in the specific script table or under the global _ key in [tool.pdm.scripts].
    [tool.pdm.scripts]
    _
    site_packages = true
  9. Manage the global Python project

    main

    PDM allows you to track dependencies for your global Python interpreter using the -g/--global option. When used, PDM treats <CONFIG_ROOT>/global-project as the project directory.

    Key Behaviors

    • Automatic Fallback: By default, PDM does not automatically use the global project if a local project is missing. You must pass -g explicitly. You can change this behavior by setting the config global_project.fallback = true.
    • Custom Project Path: Use -p/--project <path> with --global to point to a specific project file. For example, pdm --global --project . will install the current project's dependencies into the global Python.
    • Configuration:
      • global_project.fallback_verbose: Set to false to disable the message Project is not found, fallback to the global project.

    !!! warning Be extremely careful when using remove or sync --clean/--pure with the --global flag, as these commands may remove packages from your system Python.

    # Install dependencies to the global Python
    pdm install -g
    
    # Install current project dependencies to the global Python
    pdm --global --project . install
  10. Configure project as Library or Application

    main

    In PDM, the distinction between a library and an application affects how dependencies are installed and how metadata is handled.

    Library

    A library is intended to be distributed (e.g., to PyPI).

    • Metadata: Requires name and version fields in pyproject.toml, plus a [build-system] table.
    • Installation: When running pdm install or pdm sync, the project itself is installed into the environment unless --no-self is specified.
    • Configuration: You can explicitly tell PDM to treat the project as a library by setting distribution = true under the [tool.pdm] table in pyproject.toml.

    Application

    An application is meant for direct use/deployment and typically lacks the name metadata required for distribution.

  11. Use context variables in dependency strings

    main

    PDM supports expanding variables in dependency strings depending on the build backend being used. This is useful for injecting credentials or handling relative paths without leaking them into the lock file.

    Environment Variables

    • pdm-backend: Use ${VARIABLE_NAME} syntax.
    • hatchling: Use {env:VARIABLE_NAME} syntax.

    Relative Paths

    When adding a package via a relative path (e.g., pdm add ./my-package), PDM automatically saves it as a relative path compatible with your backend:

    • pdm-backend: Uses file:///${PROJECT_ROOT}/path/to/package.
    • hatchling: Uses {root:uri}/path/to/package. Note that for hatchling, you must enable direct references in pyproject.toml using [tool.hatch.metadata] allow-direct-references = true.
    # Example for pdm-backend using environment variables
    [project]
    dependencies = ["flask @ https://${USERNAME}:${PASSWORD}/artifacts.io/Flask-1.1.2.tar.gz"]