Fix: PDM Not Working — Lock File Errors, PEP 582 Confusion, and Script Issues
Quick Answer
How to fix PDM errors — pdm install fails resolving dependencies, lock file outdated warning, __pypackages__ vs venv confusion, pdm run script not found, build backend missing, and dependency groups setup.
The Error
You install PDM and try to install your project — resolution fails:
$ pdm install
ERROR: Unable to find a resolution for dependency requirement
The constraints are:
- flask >= 3.0
- django >= 5.0 # Conflict — both pull incompatible WerkzeugOr the lock file warns about being outdated every time:
$ pdm install
WARNING: Lock file is out of date. Run `pdm lock` to update it.
# But you just ran pdm lock yesterdayOr PEP 582’s __pypackages__ confuses your IDE:
import requests # Works on CLI
# IDE: "requests" cannot be resolvedOr pdm run can’t find a script you defined:
[tool.pdm.scripts]
test = "pytest tests/"$ pdm run test
ERROR: Command 'test' not foundOr the build backend isn’t set up correctly:
$ pdm build
ERROR: Cannot find a build backendPDM is a Python package manager that started by championing PEP 582 (__pypackages__ directory instead of virtual envs), but now supports both venv and PEP 582 modes. The dependency resolver is fast (Rust-backed via unearth), and the dependency groups feature is more flexible than Poetry’s extras. But the PEP 582 model and PDM-specific config patterns produce specific failures. This guide covers each.
Why This Happens
PDM defaults to PEP 582 mode — packages install into __pypackages__/X.Y/lib/ in the project root rather than a venv. This bypasses the typical “activate venv” step but requires IDE support (Python extension knows to look in __pypackages__). Without that support, IDEs show every import as unresolved even when CLI commands work.
The lock file (pdm.lock) records exact versions. Changes to pyproject.toml invalidate it — PDM warns you to re-lock. But the warning persists even after pdm lock if the lock file’s content hash doesn’t match the dependency spec hash, which can happen with multi-strategy resolutions.
Fix 1: Installing PDM
# Standalone (recommended — isolated from project)
pipx install pdm
# Or via uv
uv tool install pdm
# Or pip (less isolated)
pip install --user pdm
# Or via Homebrew
brew install pdmVerify:
pdm --version
pdm python list # Show available Pythons PDM can useInitialize a project:
pdm init
# Interactive prompts: name, version, Python version, license, etc.This creates pyproject.toml with PDM-specific sections.
Common Mistake: Installing PDM via pip install pdm into a project venv. When PDM manages that same venv, it can uninstall itself during dependency resolution. Always install PDM globally via pipx, uv, or brew.
Fix 2: Venv vs PEP 582 (__pypackages__)
PDM has two install modes:
PEP 582 (default) — __pypackages__:
my-project/
├── pyproject.toml
├── pdm.lock
└── __pypackages__/
└── 3.12/
├── lib/ (installed packages here)
└── bin/pdm install # Installs into __pypackages__
pdm run python my_script.py # Runs using PEP 582 lookupVirtualenv mode:
pdm config python.use_venv true # Switch globally
pdm venv create
pdm install # Now installs into .venvmy-project/
├── pyproject.toml
├── pdm.lock
└── .venv/
└── (standard venv layout)When to use which:
| Mode | Pros | Cons |
|---|---|---|
| PEP 582 | No activate needed, project-local | Tooling support varies, niche standard |
| venv | Universal tool support, well-understood | Need to activate or use pdm run |
Pro Tip: Use venv mode (pdm config python.use_venv true) unless you specifically want PEP 582 semantics. Every IDE, debugger, profiler, and CI tool understands venvs. PEP 582 was rejected as a Python standard in 2023 — tooling support won’t expand further. Venv mode gives you PDM’s resolver and project model without the PEP 582 compatibility tax.
Fix 3: Minimal pyproject.toml for PDM
[project]
name = "mypackage"
version = "0.1.0"
description = "My package"
authors = [
{name = "Your Name", email = "[email protected]"},
]
dependencies = [
"requests>=2.31",
"pydantic>=2.0",
]
requires-python = ">=3.10"
license = {text = "MIT"}
readme = "README.md"
[project.optional-dependencies]
test = ["pytest>=7", "pytest-cov"]
docs = ["sphinx", "furo"]
[project.scripts]
mycli = "mypackage.cli:main"
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pdm]
distribution = true # This package builds for distribution (PyPI)
[tool.pdm.scripts]
test = "pytest tests/"
lint = "ruff check ."
type = "mypy src/"
[tool.pdm.dev-dependencies]
dev = [
"ruff>=0.5",
"mypy>=1.10",
"pytest>=7",
]pdm-backend is PDM’s build backend — analogous to hatchling for Hatch or setuptools for setuptools-based projects.
distribution = true marks this as a distributable package (vs. an application). PDM behaves slightly differently for distributable vs application projects (lockfile precision, build behavior).
Common Mistake: Omitting the [build-system] section. Without it, pdm build fails with “cannot find a build backend.” Even if you only use PDM for dependency management and never publish, include [build-system] — it makes pdm install work correctly for editable installs.
Fix 4: Dependency Groups
PDM has three categories of dependencies:
[project]
dependencies = ["requests"] # Runtime, always installed
[project.optional-dependencies]
test = ["pytest"] # Optional — pip install "mypkg[test]"
docs = ["sphinx"]
[tool.pdm.dev-dependencies]
dev = ["ruff", "mypy"] # PDM-specific, never published
lint = ["ruff"]
typing = ["mypy", "types-requests"]Install groups:
pdm install # Default group (production deps only)
pdm install -G test # Production + test group
pdm install -G test -G docs # Multiple groups
pdm install -G:all # All groups
pdm install -d # Include dev-dependencies
pdm install --without test # All groups except testDev-dependencies vs optional-dependencies:
optional-dependencies— Standard, published to PyPI in package metadata. Users canpip install mypkg[test].tool.pdm.dev-dependencies— PDM-specific, NOT in published metadata. Local-only.
Use optional-deps for features end users opt into; use dev-deps for tools only developers need (linters, formatters).
Pro Tip: Group dev dependencies by purpose, not by tool. lint, typing, test, docs — easy to install only what’s needed for a specific workflow:
pdm install -d -G lint # Just for fast lint check in CI
pdm install -d -G test -G typing # For local test runsFix 5: Lock File Issues
$ pdm install
WARNING: Lock file is out of date.The lockfile’s recorded content_hash doesn’t match the current pyproject.toml dependency spec.
Update the lockfile:
pdm lock # Re-resolve and update pdm.lock
pdm lock --update-reuse # Only update changed deps, keep others pinned
pdm lock --upgrade # Upgrade all to latest compatible versions
pdm lock --upgrade requests # Upgrade just requestsCross-platform lock files — PDM defaults to “strategy” mode where the lock works across platforms:
pdm lock --strategy cross_platform # (Default since 2.x)
pdm lock --strategy direct_minimal_versions # Resolve to minimum versionsMulti-strategy locking — combine strategies:
[tool.pdm]
lock-strategy = "cross_platform,inherit_metadata"Common Mistake: Committing pdm.lock and pyproject.toml separately. The lock file’s content hash is based on pyproject.toml — if you bump a dep version in pyproject.toml and forget to update the lock, every PDM command warns. Always commit both files together after any dependency change.
pdm install --frozen-lockfile for CI — fail if lockfile is outdated:
pdm install --frozen-lockfile
# Errors instead of warning, prevents accidental installs against stale lockFix 6: Scripts and Tasks
[tool.pdm.scripts]
# Simple
test = "pytest tests/"
# With args via {args}
test-verbose = "pytest tests/ -v"
test-file = "pytest {args}"
# Multi-line shell script
setup = {shell = '''
echo "Setting up..."
pdm install -d
pdm run pre-commit install
'''}
# Composite — run other scripts in sequence
all = {composite = ["lint", "type", "test"]}
# With environment variables
serve = {cmd = "uvicorn main:app", env = {ENV = "development"}}
# Help text (shows in `pdm run --list`)
test = {cmd = "pytest tests/", help = "Run the test suite"}Run scripts:
pdm run test
pdm run all # Runs lint, type, test in sequence
pdm run test-file tests/unit/ # Pass args
pdm run --list # List all defined scriptsCommon Mistake: Defining test = "pytest" and trying pdm run pytest. The script name is test, not pytest. To run the actual pytest binary from the env, use pdm run pytest only if pytest is exposed as a CLI. For scripts you defined, use the script name.
Pre-commit hooks via PDM:
[tool.pdm.scripts]
pre_install = {composite = ["pdm run lint", "pdm run test"]}These run before pdm install — useful for blocking installs that would break the project.
Fix 7: Resolver Conflicts and Strategy
ERROR: Unable to find a resolutionPDM’s resolver couldn’t find versions of all packages that satisfy every constraint.
Diagnose:
pdm install --verbose # Show resolution steps
pdm lock --verbose # See where conflicts occurLoosen constraints:
# Too strict — likely the source of conflict
dependencies = [
"flask==3.0.1",
"werkzeug==3.0.0",
]
# Better — let resolver pick compatible versions
dependencies = [
"flask>=3.0",
# werkzeug pulled as flask's dep; don't pin separately
]Override a transitive dependency:
[tool.pdm.resolution.overrides]
urllib3 = ">=2.0"
certifi = ">=2024.0"Forces PDM to use those versions even if a dep requests something else. Use sparingly — it’s bypassing the package author’s pin for a reason.
Exclude a dependency:
[tool.pdm.resolution.excludes]
some-transitive-dep = "*"This prevents PDM from installing it. Useful when a transitive dep is broken on your platform.
For dependency resolution patterns in other package managers, see Poetry dependency conflict and uv not working.
Fix 8: Publishing to PyPI
# Build wheel and sdist
pdm build
# Output: dist/mypackage-0.1.0.tar.gz and dist/mypackage-0.1.0-py3-none-any.whl
# Publish to PyPI
pdm publish
# Or publish to TestPyPI first
pdm publish --repository testpypiConfigure credentials:
# Use PYPI_TOKEN env var (recommended)
export PYPI_TOKEN=pypi-xxx
pdm publish
# Or via PDM config (stored in ~/.config/pdm/config.toml)
pdm config repository.pypi.username __token__
pdm config repository.pypi.password pypi-xxxTrusted publishers for GitHub Actions (no token in secrets):
# .github/workflows/publish.yml
permissions:
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pdm-project/setup-pdm@v4
- run: pdm build
- uses: pypa/gh-action-pypi-publish@release/v1PyPI supports OIDC trusted publishers — no PYPI_TOKEN needed if configured in PyPI project settings.
Pro Tip: For a publishing workflow, use pdm bump:
pdm bump patch # 0.1.0 → 0.1.1
pdm bump minor # 0.1.0 → 0.2.0
pdm bump major # 0.1.0 → 1.0.0
pdm bump pre alpha # 0.1.0 → 0.1.0a0
pdm bump 0.5.0 # Set explicitCombined with CI on git tags, this gives a clean release flow without manual version edits.
Still Not Working?
PDM vs Poetry vs uv vs Hatch
- PDM — PEP 582 pioneer (deemphasized now), strong resolver, dependency groups. Best for projects that want non-venv mode or fine-grained dep groups.
- Poetry — Most mature, single-config opinionated. Best for production apps. See Poetry dependency conflict.
- uv — Rust-based, dramatically faster. Best when speed matters. See uv not working.
- Hatch — PyPA-official, env matrix support. See Hatch not working.
All four solve overlapping problems. For new projects in 2025, uv has the strongest momentum. PDM is a solid choice if you specifically want its dependency groups model or have an existing PDM project.
IDE Configuration for PEP 582
For VS Code with PEP 582:
// .vscode/settings.json
{
"python.analysis.extraPaths": [
"__pypackages__/3.12/lib"
],
"python.defaultInterpreterPath": "/usr/local/bin/python3.12"
}For PyCharm with PEP 582:
- Settings → Project → Python Interpreter → Add → System Interpreter
- Add
__pypackages__/3.12/libto interpreter paths
Or switch to venv mode (pdm config python.use_venv true) — VS Code and PyCharm auto-detect .venv/ without configuration.
Importing into Other Tools (tox, nox)
# tox.ini
[testenv]
allowlist_externals = pdm
commands = pdm run pytestOr use pdm export to generate requirements.txt:
pdm export -o requirements.txt --without-hashes
pdm export -dG test -o requirements-dev.txt --without-hashesFor Nox patterns that drive PDM, see Nox not working. For Tox patterns, see Tox not working.
CI Integration
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: pdm-project/setup-pdm@v4
with:
python-version: ${{ matrix.python }}
cache: true
- run: pdm install -d
- run: pdm run test
- run: pdm run lintsetup-pdm action handles installation, caching, and Python selection. The cache: true option caches __pypackages__ or .venv based on your config.
Migrating from requirements.txt or Poetry
# From requirements.txt
pdm import -f requirements requirements.txt
# From Poetry (pyproject.toml [tool.poetry] section)
pdm import -f poetry pyproject.toml
# Or auto-detect format
pdm importThen run pdm install to verify everything resolves.
Pre-commit and Hatch Integration
For pre-commit hooks that work with PDM projects, see pre-commit not working. For comparing PDM’s distribution model to Hatch’s, see Hatch not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Pipenv Not Working — Lock File Generation, Shell Activation, and Dependency Resolution
How to fix Pipenv errors — pipenv lock takes forever, Pipfile.lock not generated, shell activation broken, no virtualenv created, dependency conflict, and migration to uv or Poetry.
Fix: Hatch Not Working — Environment Errors, Build Backend, and pyproject.toml Issues
How to fix Hatch errors — hatch env create fails, scripts not found, build backend hatchling missing, version not detected, plugin install errors, and publishing to PyPI.
Fix: uv Not Working — Command Not Found, Python Version Error, and Lock File Conflicts
How to fix uv errors — uv command not found after install, no Python interpreter found, uv run vs activate confusion, uv.lock merge conflicts, uv pip vs uv add, migrating from pip and Poetry, and workspace resolution failures.
Fix: joblib Not Working — Parallel Backends, Memory Cache, and Pickling Errors
How to fix joblib errors — Parallel n_jobs slower than expected, Memory cache miss, backend loky vs threading vs multiprocessing, pickling lambda not supported, dump load file size, and pytest interference.