Project Management with PDM#
Part 1 of notes from talk given to the WSU Python Working Group on February 8, 2023. See Part 2 here.
In this chapter, I show the basics of project management with PDM: how to create a project, add dependencies, and add development dependencies.
This tutorial is primarily aimed at macOS and Linux users, although the commands for Windows should mostly translate.
Note
A companion repository with the example project created in these notes is available here.
Requirements#
The ability to get python executables of different versions, such as with pyenv or conda
PDM available globally
These notes were last updated:
date
Sat May 20 13:24:18 PDT 2023
The version of PDM used is:
pdm --version
PDM, version 2.6.1
Set the python version and initialize the project#
First, create the project directory and cd
into it:
mkdir eeskew-pwg-test-000
cd eeskew-pwg-test-000
Important
Because this is a throwaway test project, it is important that you give your project a name that won’t conflict with any other package on PyPI or TestPyPI. Adding your name and some numbers is a good way to ensure this.
Here, we’ll use python version 3.11, but you may change this to be whatever you like. I’ll cover two methods of setting the python version: using pyenv
, and using conda
.
Install python 3.11 if it is not already (see installed versions with pyenv versions
):
pyenv install 3.11
Set the local python version for this project and initialize using that version:
pyenv local 3.11
pdm init --python python
pyenv local
creates a file .python-version
, which pyenv
reads and redirects the command python
to the installed python3.11
. Thus, we only need to tell pdm to use the usual python
executable.
PDM can use conda
to create your virtual environment. To do this simply, we create the virtual environment before initializing the project, so that we can pass the right python executable to pdm init
.
pdm venv create -w conda 3.11
pdm init --python .venv/bin/python
pdm init
options#
For
Would you like to create a virtualenv with <path-to-python>? [y/n] (y):
Ensure you select y
. Otherwise, PDM will operate in “PEP 582 mode” - see note on this below.
For
Is the project a library that is installable?
If yes, we will need to ask a few more questions to include the project name and build backend [y/n] (n):
select y
.
If you want to use pdm-bump
in the test-publish script described in Part 2, you’ll need to use the now-deprecated pdm-pep517
backend, so for:
Which build backend to use?
0. pdm-backend
1. setuptools
2. flit-core
3. hatchling
4. pdm-pep517
Please select (0):
select 4
.
Otherwise, all the default options should be good, except where you want to fill in your own information (project description, email, etc).
The PDM project#
Let’s take a look at what we’ve created:
ls -a
. .git .pdm-python .venv pyproject.toml
.. .gitignore .python-version README.md
The relevant files created are the README.md
; .pdm.toml
, which holds local configuration for this PDM project; and pyproject.toml
, which holds project tool configuration and package metadata.
See also
PDM has also created a virtual environment for us in the .venv
directory. This is where our package and its dependencies will be installed. If you are unfamiliar with virtual environments, I recommend this article.
Note
PDM supports the rejected Python Enhancement Proposal (PEP) 582. An alternative to virtual environments, PEP 582 would automatically get dependencies from a __pypackages__
directory in the project root, without having to activate a virtualenv. See the PDM docs on PEP 582 here. We will not be using PEP 582 mode in this tutorial.
The pyproject.toml
file#
The central location of our project configuration is pyproject.toml
:
# pyproject.toml
[project]
name = "eeskew-pwg-test-000"
version = "0.1.0"
description = "A test project for presentation to the WSU Python Working Group."
authors = [
{name = "Edward Eskew", email = "edward.eskew@wsu.edu"},
]
dependencies = []
requires-python = ">=3.11"
readme = "README.md"
license = {text = "MIT"}
[build-system]
requires = ["pdm-pep517>=1.0"]
build-backend = "pdm.pep517.api"
This file is written in .toml
format, which stands for Tom’s Obvious Minimal Language.
The project
table contains the metadata needed to install our project. Its values thus far were set by the options we chose while running pdm init
.
The build-system
section tells the build frontend (e.g. pip
) what build backend to use - the build backend is what will actually create the distribution artifacts (wheels and sdists), which we’ll see later. See PEP 517 for more information.
See also
See the PDM docs on writing pyproject.toml
for more on what can be specified in this file.
Adding code#
First we create our package directory in src layout, with an empty (for now) __init__.py
file to indicate that it is a python package:
mkdir src
mkdir src/eeskew_pwg_test_000
touch src/eeskew_pwg_test_000/__init__.py
Note
By convention, we use snake_case for the package name, while we use kebab-case for the repository name.
Add a module#
Let’s add some code in src/eeskew_pwg_test_000/sarcasm.py
:
# src/eeskew_pwg_test_000/sarcasm.py
def sarcasm(s):
"""Convert string `s` to sArCaSm TeXt."""
out = ""
for i, c in enumerate(s):
if i % 2 == 0:
out += c.lower()
else:
out += c.upper()
return out
The actual content of this code is not too important for the purposes of these notes, but for completeness, all it does is capitalize and lowercase alternating letters in a string.
Install the project#
To make our code available in the virtual environment, we have to install it:
pdm install
Lock file does not exist
Updating the lock file...
?25l⠋ Fetching hashes for resolved packages...
🔒 Lock successful
Changes are written to pdm.lock.
?25l⠋ Fetching hashes for resolved packages...
All packages are synced to date, nothing to do.
Installing the project as an editable package...
✔ Install eeskew-pwg-test-000 0.1.0 successful
🎉 All complete!
?25h
Now we can import our package:
pdm run python -c 'from eeskew_pwg_test_000.sarcasm import sarcasm; print(sarcasm("Hello world!"))'
hElLo wOrLd!
Note we have to type pdm run
before our command for it to be run within our project environment.
Tip
If you don’t want to type pdm run
every time before a command to be run in the project virtual environment, you can activate the environment, which will modify your sys.prefix
to point to the .venv
directory. See the python venv
docs here for more on how virtual environments work.
pdm
will print the command to activate the project virtual environment with the command pdm venv activate
. You can copy and paste that output, or, if you want to activate the environment in one line, use:
eval $(pdm venv activate)
To simplify things further, add this as an alias to your ~/.bashrc
or ~/.bash_profile
(and don’t forget to restart your shell or source ~/.bashrc
after):
# ~/.bashrc
alias pdm-activate='eval $(pdm venv activate)'
This will let you activate the environment with the command pdm-activate
.
You can deactivate an active virtual environment with the command deactivate
. See also the PDM docs on virtualenv activation.
The pdm.lock
file#
Running pdm install
also created a new file, pdm.lock
:
ls
README.md pdm.lock pyproject.toml src
# pdm.lock
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
lock_version = "4.2"
cross_platform = true
groups = ["default"]
content_hash = "sha256:86165c41f17b4b263a688544a3ebc55eccc1713dd177c40649b2e936dab66751"
[metadata.files]
This is a lockfile, which will contain the exact versions of each project dependency we install. It is useful for creating a perfect reproduction of the project virtual environment, which keeps our development reproducible over time and across different machines.
Right now, we have not installed anything other than the project itself, so it is essentially empty.
Add a dependency#
Let’s add a dependency to our project, cowsay:
pdm add cowsay
Adding packages to default dependencies: cowsay
⠹ Fetching hashes for resolved packages...
🔒 Lock successful
Changes are written to pyproject.toml.
?25l⠋ Fetching hashes for resolved packages...
Synchronizing working set with lock file: 1 to add, 0 to update, 0 to remove
✔ Install cowsay 5.0 successful
Installing the project as an editable package...
✔ Update eeskew-pwg-test-000 0.1.0 -> 0.1.0 successful
🎉 All complete!
?25h
What did pdm add
do?#
cowsay
now appears as a dependency in pyproject.toml
:
# pyproject.toml
...
authors = [
{name = "Edward Eskew", email = "edward.eskew@wsu.edu"},
]
dependencies = [
"cowsay>=5.0",
]
requires-python = ">=3.11"
readme = "README.md"
license = {text = "MIT"}
...
We’ve also updated pdm.lock
to include cowsay:
# pdm.lock
# This file is @generated by PDM.
# It is not intended for manual editing.
[[package]]
name = "cowsay"
version = "5.0"
summary = "The famous cowsay for GNU/Linux is now available for python"
[metadata]
lock_version = "4.2"
cross_platform = true
groups = ["default"]
content_hash = "sha256:a04c8eb9409090bc9acb94e5cec5ef19afdecacb2c169628142af5ca472135f0"
[metadata.files]
"cowsay 5.0" = [
{url = "https://files.pythonhosted.org/packages/6b/b8/9f497fd045d74fe21d91cbe8debae0b451229989e35b539d218547d79fc6/cowsay-5.0.tar.gz", hash = "sha256:c00e02444f5bc7332826686bd44d963caabbaba9a804a63153822edce62bbbf3"},
]
We can now import cowsay
:
pdm run python -c 'import cowsay; cowsay.cow("moo!")'
____
| moo! |
====
\
\
^__^
(oo)\_______
(__)\ )\/\
||----w |
|| ||
See also
See the PDM docs on managing dependencies for more information.
Adding more code#
Let’s add a new function to src/eeskew_pwg_test_000/sarcasm.py
:
# src/eeskew_pwg_test_000/sarcasm.py
import cowsay
...
def sarcastic_cowsay(s):
"""Cowsay `s`, sArCaStIcAlLy."""
sarcastic_s = sarcasm(s)
cowsay.cow(sarcastic_s)
We can now run this new function:
pdm run python -c 'from eeskew_pwg_test_000.sarcasm import sarcastic_cowsay; sarcastic_cowsay("mooo!")'
_____
| mOoO! |
=====
\
\
^__^
(oo)\_______
(__)\ )\/\
||----w |
|| ||
Note
We didn’t have to re-run pdm install
to use our new function - this is because PDM installs our eeskew_pwg_test_000
package in “editable mode”, which acts sort of like a symlink between the source code and the installed files in the .venv
directory.
Add a development dependency#
The dependencies listed in the project.dependencies
section of pyproject.toml
will all be installed when someone runs pip install eeskew-pwg-test-000
. What if we have dependencies we only want in our development environment?
Let’s add black
, a tool to automatically format our code:
pdm add -d black
Adding group dev to lockfile
Adding packages to dev dev-dependencies: black
⠋ Fetching hashes for resolved packages...
🔒 Lock successful
Changes are written to pyproject.toml.
?25l⠋ Fetching hashes for resolved packages...
Synchronizing working set with lock file: 6 to add, 0 to update, 0 to remove
⠋ Installing black 23.3.0...
⠋ Installing black 23.3.0...
⠋ Installing click 8.1.3...
⠋ Installing black 23.3.0...
⠋ Installing click 8.1.3...
⠋ Installing mypy-extensions 1.0.0...
⠋ Installing black 23.3.0...
⠋ Installing click 8.1.3...
⠋ Installing mypy-extensions 1.0.0...
⠋ Installing packaging 23.1...
⠋ Installing black 23.3.0...
⠋ Installing click 8.1.3...
⠋ Installing mypy-extensions 1.0.0...
⠋ Installing packaging 23.1...
⠋ Installing pathspec 0.11.1...
✔ Install mypy-extensions 1.0.0 successful
⠋ Installing black 23.3.0...
⠋ Installing click 8.1.3...
⠋ Installing mypy-extensions 1.0.0...
⠋ Installing packaging 23.1...
⠋ Installing pathspec 0.11.1...
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
✔ Install platformdirs 3.5.1 successful
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
✔ Install pathspec 0.11.1 successful
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
✔ Install packaging 23.1 successful
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
✔ Install click 8.1.3 successful
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
✔ Install black 23.3.0 successful
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
🎉 All complete!
⠙ Installing black 23.3.0...
⠙ Installing click 8.1.3...
⠙ Installing packaging 23.1...
⠙ Installing pathspec 0.11.1...
?25h
What did pdm add -d
do?#
We’ve added a new [tool.pdm.dev-dependencies]
table to pyproject.toml
, which contains black
:
# pyproject.toml
...
requires = ["pdm-pep517>=1.0"]
build-backend = "pdm.pep517.api"
[tool.pdm.dev-dependencies]
dev = [
"black>=23.3.0",
]
When we run pdm install
, by default, PDM will install dependencies from here in addition to the dependencies listed in [project.dependencies]
. A different tool like pip
, however, will not.
See also
See the PDM docs on adding development dependencies for more information on development dependencies.
The lockfile has also been updated:
cat pdm.lock
# This file is @generated by PDM.
# It is not intended for manual editing.
[[package]]
name = "black"
version = "23.3.0"
requires_python = ">=3.7"
summary = "The uncompromising code formatter."
dependencies = [
"click>=8.0.0",
"mypy-extensions>=0.4.3",
"packaging>=22.0",
"pathspec>=0.9.0",
"platformdirs>=2",
]
[[package]]
name = "click"
version = "8.1.3"
requires_python = ">=3.7"
summary = "Composable command line interface toolkit"
dependencies = [
"colorama; platform_system == \"Windows\"",
]
[[package]]
name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
[[package]]
name = "cowsay"
version = "5.0"
summary = "The famous cowsay for GNU/Linux is now available for python"
[[package]]
name = "mypy-extensions"
version = "1.0.0"
requires_python = ">=3.5"
summary = "Type system extensions for programs checked with the mypy type checker."
[[package]]
name = "packaging"
version = "23.1"
requires_python = ">=3.7"
summary = "Core utilities for Python packages"
[[package]]
name = "pathspec"
version = "0.11.1"
requires_python = ">=3.7"
summary = "Utility library for gitignore style pattern matching of file paths."
[[package]]
name = "platformdirs"
version = "3.5.1"
requires_python = ">=3.7"
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
[metadata]
lock_version = "4.2"
cross_platform = true
groups = ["default", "dev"]
content_hash = "sha256:715ad39cb3ddf659e0afedfb18dcade3de83c16696c31ed976bbf46bb61aaa3a"
[metadata.files]
"black 23.3.0" = [
{url = "https://files.pythonhosted.org/packages/06/1e/273d610249f0335afb1ddb03664a03223f4826e3d1a95170a0142cb19fb4/black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
{url = "https://files.pythonhosted.org/packages/12/4b/99c71d1cf1353edd5aff2700b8960f92e9b805c9dab72639b67dbb449d3a/black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
{url = "https://files.pythonhosted.org/packages/13/0a/ed8b66c299e896780e4528eed4018f5b084da3b9ba4ee48328550567d866/black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"},
{url = "https://files.pythonhosted.org/packages/13/25/cfa06788d0a936f2445af88f13604b5bcd5c9d050db618c718e6ebe66f74/black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"},
{url = "https://files.pythonhosted.org/packages/21/14/d5a2bec5fb15f9118baab7123d344646fac0b1c6939d51c2b05259cd2d9c/black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"},
{url = "https://files.pythonhosted.org/packages/24/eb/2d2d2c27cb64cfd073896f62a952a802cd83cf943a692a2f278525b57ca9/black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"},
{url = "https://files.pythonhosted.org/packages/27/70/07aab2623cfd3789786f17e051487a41d5657258c7b1ef8f780512ffea9c/black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"},
{url = "https://files.pythonhosted.org/packages/29/b1/b584fc863c155653963039664a592b3327b002405043b7e761b9b0212337/black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"},
{url = "https://files.pythonhosted.org/packages/3c/d7/85f3d79f9e543402de2244c4d117793f262149e404ea0168841613c33e07/black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"},
{url = "https://files.pythonhosted.org/packages/3f/0d/81dd4194ce7057c199d4f28e4c2a885082d9d929e7a55c514b23784f7787/black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"},
{url = "https://files.pythonhosted.org/packages/49/36/15d2122f90ff1cd70f06892ebda777b650218cf84b56b5916a993dc1359a/black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"},
{url = "https://files.pythonhosted.org/packages/49/d7/f3b7da6c772800f5375aeb050a3dcf682f0bbeb41d313c9c2820d0156e4e/black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"},
{url = "https://files.pythonhosted.org/packages/69/49/7e1f0cf585b0d607aad3f971f95982cc4208fc77f92363d632d23021ee57/black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"},
{url = "https://files.pythonhosted.org/packages/6d/b4/0f13ab7f5e364795ff82b76b0f9a4c9c50afda6f1e2feeb8b03fdd7ec57d/black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"},
{url = "https://files.pythonhosted.org/packages/ad/e7/4642b7f462381799393fbad894ba4b32db00870a797f0616c197b07129a9/black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
{url = "https://files.pythonhosted.org/packages/c0/53/42e312c17cfda5c8fc4b6b396a508218807a3fcbb963b318e49d3ddd11d5/black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"},
{url = "https://files.pythonhosted.org/packages/ca/44/eb41edd3f558a6139f09eee052dead4a7a464e563b822ddf236f5a8ee286/black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"},
{url = "https://files.pythonhosted.org/packages/ce/f4/2b0c6ac9e1f8584296747f66dd511898b4ebd51d6510dba118279bff53b6/black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"},
{url = "https://files.pythonhosted.org/packages/d1/6e/5810b6992ed70403124c67e8b3f62858a32b35405177553f1a78ed6b6e31/black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"},
{url = "https://files.pythonhosted.org/packages/d6/36/66370f5017b100225ec4950a60caeef60201a10080da57ddb24124453fba/black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
{url = "https://files.pythonhosted.org/packages/d7/6f/d3832960a3b646b333b7f0d80d336a3c123012e9d9d5dba4a622b2b6181d/black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"},
{url = "https://files.pythonhosted.org/packages/db/f4/7908f71cc71da08df1317a3619f002cbf91927fb5d3ffc7723905a2113f7/black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"},
{url = "https://files.pythonhosted.org/packages/de/b4/76f152c5eb0be5471c22cd18380d31d188930377a1a57969073b89d6615d/black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"},
{url = "https://files.pythonhosted.org/packages/eb/a5/17b40bfd9b607b69fa726b0b3a473d14b093dcd5191ea1a1dd664eccfee3/black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"},
{url = "https://files.pythonhosted.org/packages/fd/5b/fc2d7922c1a6bb49458d424b5be71d251f2d0dc97be9534e35d171bdc653/black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"},
]
"click 8.1.3" = [
{url = "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
{url = "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
]
"colorama 0.4.6" = [
{url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
"cowsay 5.0" = [
{url = "https://files.pythonhosted.org/packages/6b/b8/9f497fd045d74fe21d91cbe8debae0b451229989e35b539d218547d79fc6/cowsay-5.0.tar.gz", hash = "sha256:c00e02444f5bc7332826686bd44d963caabbaba9a804a63153822edce62bbbf3"},
]
"mypy-extensions 1.0.0" = [
{url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
"packaging 23.1" = [
{url = "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
{url = "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
]
"pathspec 0.11.1" = [
{url = "https://files.pythonhosted.org/packages/95/60/d93628975242cc515ab2b8f5b2fc831d8be2eff32f5a1be4776d49305d13/pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
{url = "https://files.pythonhosted.org/packages/be/c8/551a803a6ebb174ec1c124e68b449b98a0961f0b737def601e3c1fbb4cfd/pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
]
"platformdirs 3.5.1" = [
{url = "https://files.pythonhosted.org/packages/89/7e/c6ff9ddcf93b9b36c90d88111c4db354afab7f9a58c7ac3257fa717f1268/platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"},
{url = "https://files.pythonhosted.org/packages/9c/0e/ae9ef1049d4b5697e79250c4b2e72796e4152228e67733389868229c92bb/platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"},
]
Many new packages now exist in the lockfile, not just cowsay
and black
. This is because (unlike cowsay
), black
itself has dependencies that we needed to install to get it to work. This lock file records exactly the versions of those sub-dependencies that we’ve now installed into our project virtual environment.
When we run pdm install
, if the lock file exists (and pyproject.toml
hasn’t been changed since the lockfile was last updated), PDM will install precisely the packages listed in the lockfile, so we’ll always be working in the same virtual environment. This is useful for developing and testing the code, so you should always include the lockfile in your project version control.
However, we don’t want to impose these restrictions on users of our library, or our project would rapidly become impossible to install due to other packages requiring different versions of the packages in the lockfile. The only thing that we care about is that users have the right versions of the dependencies we directly use, which are listed in the project.dependencies
array in pyproject.toml
. This is why pip install
does not care about the existence of the lockfile.
See also
See the PDM docs on version control for more on best practices for version-controlling a PDM project.
Using black#
We can now run black
within our environment.
Let’s re-write src/eeskew_pwg_test_000/sarcasm.py
with deliberately poor formatting:
# src/eeskew_pwg_test_000/sarcasm.py
import cowsay
def sarcasm(
s
):
"""Convert string `s` to sArCaSm TeXt."""
out = ''
for i,c in \
enumerate( s ):
if i% 2 ==0: out +=c.lower()
else:
out+= c.upper()
return out
def sarcastic_cowsay(s):
"""Cowsay `s`, sArCaStIcAlLy."""
sarcastic_s = sarcasm(s); cowsay.cow(sarcastic_s)
This is bad formatting! Rather than fix it manually, we can run black
on our code, which will auto-impose a reasonable style.
pdm run black src/
reformatted /Users/Ed/python/eds-notes/repos/_tmp_pwg_presentation_02-08-2023_part1/eeskew-pwg-test-000/src/eeskew_pwg_test_000/sarcasm.py
All done! ✨ 🍰 ✨
1 file reformatted, 1 file left unchanged.
What did black
do?#
black
has automatically re-formatted src/eeskew_pwg_test_000/sarcasm.py
, fixing the poor formatting we introduced earlier:
# src/eeskew_pwg_test_000/sarcasm.py
import cowsay
def sarcasm(s):
"""Convert string `s` to sArCaSm TeXt."""
out = ""
for i, c in enumerate(s):
if i % 2 == 0:
out += c.lower()
else:
out += c.upper()
return out
def sarcastic_cowsay(s):
"""Cowsay `s`, sArCaStIcAlLy."""
sarcastic_s = sarcasm(s)
cowsay.cow(sarcastic_s)
See also
See the black documentation for more information.
Conclusion#
That’s all for Part 1! The examples here should be enough to get you started using PDM to manage your own python projects. For more advanced usage, check out the PDM documentation on:
In Part 2, we’ll cover how to publish a PDM project on PyPI, so that it can be installed with a simple pip install
.