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:
PDM: package and project manager supporting the latest PEP standards.
UV: fast package and project manager.
Pixi: generalist package manager based on the conda ecosystem.
A simple example using PDM¶
cd ../common/examples/example-py-project
lsLICENSE.txt Makefile README.md doc pdm.lock pdm.toml pyproject.toml src
The file example_py_package/pyproject.toml contains
[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 srcexample_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_PROJECTpdm sync --cleanOutput
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-simplesimple_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 -hUsage: 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 --versionexample-py-cli, version 0.1.0
pdm run example-py-cli -n MouradWelcome Mourad
One can format the code of the project with:
pdm run format7 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