Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Modern Python packaging

Python packaging has been fundamentally changed during the previous years.

The changes have been driven by the Python Packaging Authority (PyPA), which is a working group that maintains a core set of software projects used in Python packaging.

There are very good resources on the web, in particular,

so we won’t dive into the details here. However, one important point is that for new project you should not need a setup.py file.

Moreover, there are very good tools to handle all the tasks needed for a Python project. Depending on your need, we would advice these 3 complementary tools:

A simple example using PDM

cd ../common/examples/example-py-project
ls
LICENSE.txt  Makefile  README.md  doc  pdm.lock  pdm.toml  pyproject.toml  src

The file example_py_package/pyproject.toml contains

pyproject.toml
[project]
name = "example-py-project"
version = "0.1.0"
description = "Example of a simple Python package"
authors = [
    {name = "Pierre Augier", email = "pierre.augier@univ-grenoble-alpes.fr"},
]
dependencies = ["click", "textual", "rich-click"]
requires-python = ">=3.11"
readme = "README.md"
license = "MIT"
license-files = ["LICENSE.txt"]

[project.scripts]
example-py-simple = "example_py_package:simple_function"
example-py-cli = "example_py_package.cli:main"
example-py-calculator = "example_py_package.calculator:main"


[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

[dependency-groups]
test = [
    "pytest>=8.3.5",
]
dev = [
    "ruff>=0.11.11",
]
doc = [
    "sphinx>=8.2.3",
    "myst-parser>=4.0.1",
]
debug = [
    "ipython>=9.2.0",
    "ipdb>=0.13.13",
]


[tool.pdm]
distribution = true

[tool.pdm.scripts]
format-py = "ruff format src doc"
format = {composite = ["format-py"]}

[tool.pdm.options]
lock = ["-G", ":all"]


[tool.pytest.ini_options]
addopts = "--pdbcls=IPython.terminal.debugger:TerminalPdb"


[tool.ruff]
line-length = 88
target-version = "py311"

Note that this package define three “entry points” (project.scripts), two CLI[1] and a TUI[2].

The source files are in the directory src:

ls src
example_py_package
ls src/example_py_package
__init__.py  calculator.py  calculator.tcss  cli.py  tests  util.py

One can create a virtual environment for the project and run a command provided by the project:

Notebook Cell
export PDM_IGNORE_ACTIVE_VENV=1
unset PDM_PROJECT
pdm sync --clean
Output
WARNING: Project requires a python version of >=3.11, The virtualenv is being 
created for you as it cannot be matched to the right version.
INFO: python.use_venv is on, creating a virtualenv for this project...
INFO: Using uv is experimental and might break due to uv updates.
Using CPython 3.13.14 interpreter at: /builds/py-edu-fr/py-edu-fr/.venv/bin/python
Creating virtual environment at: .venv
Virtualenv is created successfully at 
/builds/py-edu-fr/py-edu-fr/src/common/examples/example-py-project/.venv
 Resolving packages from lockfile...
 Resolving packages from lockfile...
 Resolving packages from lockfile...
 Resolving packages from lockfile...
 Resolving packages from lockfile...
 Resolving packages from lockfile...
 Resolving packages from lockfile...
 Resolving packages from lockfile...Resolved 57 packages in 324ms

 Resolving packages from lockfile...
 Resolving packages from lockfile...

warning: `VIRTUAL_ENV=/builds/py-edu-fr/py-edu-fr/.venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead
Prepared 56 packages in 1.67s
Installed 56 packages in 542ms
 + alabaster==1.0.0
 + asttokens==3.0.0
 + babel==2.17.0
 + certifi==2025.11.12
 + charset-normalizer==3.4.4
 + click==8.3.0
 + decorator==5.2.1
 + docutils==0.21.2
 + example-py-project==0.1.0 (from file:///builds/py-edu-fr/py-edu-fr/src/common/examples/example-py-project)
 + executing==2.2.1
 + idna==3.11
 + imagesize==1.4.1
 + iniconfig==2.3.0
 + ipdb==0.13.13
 + ipython==9.7.0
 + ipython-pygments-lexers==1.1.1
 + jedi==0.19.2
 + jinja2==3.1.6
 + linkify-it-py==2.0.3
 + markdown-it-py==3.0.0
 + markupsafe==3.0.3
 + matplotlib-inline==0.2.1
 + mdit-py-plugins==0.5.0
 + mdurl==0.1.2
 + myst-parser==4.0.1
 + packaging==25.0
 + parso==0.8.5
 + pexpect==4.9.0
 + platformdirs==4.5.0
 + pluggy==1.6.0
 + prompt-toolkit==3.0.52
 + ptyprocess==0.7.0
 + pure-eval==0.2.3
 + pygments==2.19.2
 + pytest==9.0.1
 + pyyaml==6.0.3
 + requests==2.32.5
 + rich==14.2.0
 + rich-click==1.9.4
 + roman-numerals-py==3.1.0
 + ruff==0.14.5
 + snowballstemmer==3.0.1
 + sphinx==8.2.3
 + sphinxcontrib-applehelp==2.0.0
 + sphinxcontrib-devhelp==2.0.0
 + sphinxcontrib-htmlhelp==2.1.0
 + sphinxcontrib-jsmath==1.0.1
 + sphinxcontrib-qthelp==2.0.0
 + sphinxcontrib-serializinghtml==2.0.0
 + stack-data==0.6.3
 + textual==6.6.0
 + traitlets==5.14.3
 + typing-extensions==4.15.0
 + uc-micro-py==1.0.3
 + urllib3==2.5.0
 + wcwidth==0.2.14
pdm run example-py-simple
simple_function() called

One can also use the python of the virtual environment:

pdm run python -c "from example_py_package import simple_function as sf; sf()"
simple_function() called

Let us see what gives example-py-cli another command provided by the project:

# another command defined in this project
pdm run example-py-cli -h
Usage: example-py-cli [OPTIONS]

  Print a short sentence

Options:
  --version        Show the version and exit.
  -n, --name TEXT  The person to greet.
  -h, --help       Show this message and exit.
pdm run example-py-cli --version
example-py-cli, version 0.1.0
pdm run example-py-cli -n Mourad
Welcome Mourad

One can format the code of the project with:

pdm run format
7 files left unchanged

In real cases (i.e. for a real project and with execution outside of a notebook), one can activate the environment and directly run commands.

. .venv/bin/activate
pytest src --color=no --code-highlight=no
example-py-simple
example-py-calculator
Footnotes
  1. CLI is for Command Line Interface.

  2. TUI is for Terminal User Interface.