Skip to content

Commit 9d8d46a

Browse files
committed
📝 Switch to uv for building envs, packaging etc.
* Install different Python versions in parallel including PyPy and free-threaded Python 3.13. * Add tox-uv * Publishing packages * Update uv.lock file with a pre-commit hook
1 parent e5c7145 commit 9d8d46a

File tree

19 files changed

+547
-184
lines changed

19 files changed

+547
-184
lines changed

docs/apps.rst

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
Apps
2+
====
3+
4+
App projects are suitable for web servers, scripts and :abbr:`CLI (command line
5+
interfaces)`. We can also create them with ``uv init --package``:
6+
7+
.. code-block:: console
8+
9+
$ uv init --package myapp
10+
tree mypack -a
11+
myapp
12+
$ uv init --app myapp
13+
$ tree myapp -a
14+
myapp
15+
├── .git
16+
│   └── ...
17+
├── .gitignore
18+
├── .python-version
19+
├── README.md
20+
├── pyproject.toml
21+
└── src
22+
└── myapp
23+
└── __init__.py
24+
25+
:file:`myapp/pyproject.toml`
26+
The :file:`pyproject.toml` file contains a ``scripts`` entry point
27+
``myapp:main``:
28+
29+
.. literalinclude:: myapp/pyproject.toml
30+
:caption: myapp/pyproject.toml
31+
:lines: 12-13
32+
33+
:file:`myapp/src/myapp/__init__.py`
34+
The module defines a CLI function :func:`main`:
35+
36+
.. literalinclude:: myapp/src/myapp/__init__.py
37+
:caption: myapp/src/myapp/__init__.py
38+
39+
It can be called up with ``uv run``:
40+
41+
.. code-block:: console
42+
43+
$ uv run mypack
44+
Hello from myapp!
45+
46+
Alternatively, you can also build a :ref:`virtual environment <venv>` and
47+
then call :func:`main` from Python:
48+
49+
.. code-block:: console
50+
51+
$ uv add --dev .
52+
Resolved 1 package in 1ms
53+
Audited in 0.01ms
54+
$ uv run python
55+
>>> import myapp
56+
>>> myapp.main()
57+
Hello from myapp!
58+
59+
.. note::
60+
I strongly believe that a Python application should be properly packaged to
61+
enjoy the many benefits, such as
62+
63+
* source management with :doc:`importlib <python3:library/importlib>`
64+
* executable scripts with ``project.scripts`` instead of attached
65+
:file:`scripts` folders
66+
* the benefits of :file:`src` layout with a common, documented and well
67+
understood structure.
68+
69+
.. _uv_lock:
70+
71+
:file:`uv.lock` file
72+
--------------------
73+
74+
With ``uv add --dev .`` the :file:`uv.lock` file was also created alongside the
75+
:file:`pyproject.toml` file. :file:`uv.lock` is a cross-platform lock file that
76+
records the packages that are to be installed across all possible Python
77+
features such as operating system, architecture and Python version.
78+
79+
Unlike :file:`pyproject.toml`, which specifies the general requirements of your
80+
project, :file:`uv.lock` contains the exact resolved versions that are installed
81+
in the project environment. This file should be checked into the :doc:`Git
82+
<Python4DataScience:productive/git/index>` version control system to enable
83+
consistent and reproducible installations on different computers.
84+
85+
.. literalinclude:: myapp/uv.lock
86+
:caption: myapp/uv.lock
87+
88+
:file:`uv.lock` is a human-readable
89+
:doc:`Python4DataScience:data-processing/serialisation-formats/toml/index` file,
90+
but is managed by ``uv`` and should not be edited manually.
91+
92+
.. note::
93+
If ``uv`` is to be integrated into other tools or workflows, you can export
94+
the content to the `requirements file format
95+
<https://pip.pypa.io/en/stable/reference/requirements-file-format/>`_ using
96+
:samp:`uv export --format requirements-txt > {CONSTRAINTS.TXT}`. Conversely,
97+
the :samp:`{CONSTRAINTS.TXT}` file created can then be used with ``uv pip
98+
install`` or other tools.
99+
100+
.. _update-uv-lock:
101+
102+
Aktualisieren von :file:`uv.lock`
103+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
104+
105+
:file:`uv.lock` is updated regularly when ``uv sync`` and ``uv run`` are called.
106+
107+
``--frozen``
108+
leaves the :file:`uv.lock file` unchanged.
109+
``--no-sync``
110+
avoids updating the environment during ``uv run`` calls.
111+
``--locked``
112+
ensures that the lock file matches the project metadata. If the lockfile is
113+
not up-to-date, an error message is issued instead of updating the lockfile.
114+
115+
By default, ``uv`` favours the locked versions of the packages when executing
116+
``uv sync`` and ``uv lock``. Package versions are only changed if the dependency
117+
conditions of the project exclude the previous, locked version.
118+
119+
``uv lock --upgrade``
120+
updates all packages.
121+
:samp:`uv lock --upgrade-package {PACKAGE}=={VERSION}`
122+
upgrades a single package to a specific version.
123+
124+
You can also use the
125+
:doc:`Python4DataScience:productive/git/advanced/hooks/pre-commit` to regularly
126+
update your uv.lock file:
127+
128+
.. code-block:: yaml
129+
:caption: .pre-commit-config.yaml
130+
131+
- repo: https://github.com/astral-sh/uv-pre-commit
132+
rev: 0.4.24
133+
hooks:
134+
- id: uv-lock
135+
136+
Restrict platform and Python versions
137+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138+
139+
If your project only supports a limited number of platforms or Python versions,
140+
you can do this in the :file:`pyprojects.toml` file in compliance with
141+
:pep:`508`, for example to restrict the :file:`uv.lock` file to macOS and Linux
142+
only:
143+
144+
.. code-block:: toml
145+
146+
[tool.uv]
147+
environments = [
148+
"sys_platform == 'darwin'",
149+
"sys_platform == 'linux'",
150+
]

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ All tutorials serve as seminar documents for our harmonised training courses:
109109
functions/index
110110
modules/index
111111
libs/index
112+
apps
112113
oop/index
113114
save-data/index
114115
dataclasses

docs/install.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ installed, this should not be a problem.
2929
One disadvantage is that you have to return to the website regularly to
3030
check for security updates as there is no integrated auto-updater.
3131

32-
If older Python versions are required, for example to test libraries with
33-
:doc:`test/tox`, we use `deadsnakes
34-
<https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa>`_.
32+
.. _various-python-versions:
33+
34+
If different Python versions are required, for example to test libraries with
35+
:doc:`test/tox`, I use `uv <https://docs.astral.sh/uv/guides/install-python/>`_.
36+
This allows not only older CPython versions to be installed but also `PyPy
37+
<https://pypy.org>`_ or free-threaded Python 3.13 with ``uv python install
38+
[email protected]`` or ``uv python install 3.13t``.
3539

3640
.. tab:: macOS
3741

docs/libs/distribution.rst

Lines changed: 74 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,6 @@ uploaded to a package index such as :term:`pypi.org` and installed with
99
`cusy seminar: Advanced Python
1010
<https://cusy.io/en/our-training-courses/advanced-python>`_
1111

12-
Some of the following commands require a new version of pip, so you should make
13-
sure you have the latest version installed:
14-
15-
16-
.. tab:: Linux/macOS
17-
18-
.. code-block:: console
19-
20-
$ python3 -m pip install --upgrade pip
21-
22-
.. tab:: Windows
23-
24-
.. code-block:: ps1
25-
26-
> python -m pip install --upgrade pip
27-
2812
Structure
2913
---------
3014

@@ -453,53 +437,93 @@ For more instructions in :file:`Manifest.in`, see `MANIFEST.in commands
453437
.. note::
454438
If you want files and directories from :file:`MANIFEST.in` to be installed as
455439
well, for example if they are runtime-relevant data, you can specify this
456-
with ``include_package_data=True`` in your ``setup()`` call.
440+
with ``include_package_data=True`` in your :func:`setup` call.
441+
442+
.. _uv-package-structure:
443+
444+
Create package structure
445+
------------------------
446+
447+
With :samp:`uv init --package {MYPACK}` you can easily create an initial file
448+
structure for packages.
449+
450+
.. code-block:: console
451+
452+
$ uv init --package mypack
453+
$ tree mypack -a
454+
mypack
455+
├── .git
456+
│ └── ...
457+
├── .gitignore
458+
├── .python-version
459+
├── README.md
460+
├── pyproject.toml
461+
└── src
462+
└── mypack
463+
└── __init__.py
464+
465+
:file:`mypack/pyproject.toml`
466+
The file :file:`pyproject.toml` contains a ``scripts`` entry point
467+
``mypack:main``:
468+
469+
.. literalinclude:: mypack/pyproject.toml
470+
:caption: mypack/pyproject.toml
471+
:emphasize-lines: 12-13
472+
473+
:file:`mypack/src/mypack/__init__.py`
474+
The module defines a CLI function :func:`main`:
475+
476+
.. literalinclude:: mypack/src/mypack/__init__.py
477+
:caption: mypack/src/mypack/__init__.py
478+
479+
It can be called with ``uv run``:
480+
481+
.. code-block:: console
482+
483+
$ uv run mypack
484+
Hello from mypack!
485+
486+
.. note::
487+
If necessary, ``uv run`` creates a :ref:`virtual Python environment
488+
<venv>` in the :file:`.venv` folder before :func:`main` is executed.
489+
490+
.. _uv-build:
457491

458492
Build
459493
-----
460494

461495
The next step is to create distribution packages for the package. These are
462496
archives that can be uploaded to the :term:`Python Package Index` (:term:`PyPI`)
463-
and installed by :term:`pip`.
464-
465-
Make sure you have the latest version of ``build`` installed:
466-
467-
Now run the command in the same directory where :file:`pyproject.toml` is
468-
located:
497+
and installed by :term:`pip`. Now execute the command in the same directory
498+
where :file:`pyproject.toml` is located:
469499

470500
.. tab:: Linux/macOS
471501

472502
.. code-block:: console
473503
474-
$ python -m pip install build
475-
$ cd /PATH/TO/YOUR/DISTRIBUTION_PACKAGE
476-
$ rm -rf build dist
477-
$ python -m build
504+
$ uv build
505+
Building source distribution...
506+
Building wheel from source distribution...
507+
Successfully built dist/mypack-0.1.0.tar.gz and dist/mypack-0.1.0-py3-none-any.whl
478508
479509
.. tab:: Windows
480510

481511
.. code-block:: ps1
482512
483-
> python -m pip install build
484-
> cd C:\PATH\TO\YOUR\DISTRIBUTION_PACKAGE
485-
> rm -rf build dist
486-
> python -m build
513+
> uv build
514+
Building source distribution...
515+
Building wheel from source distribution...
516+
Successfully built dist/mypack-0.1.0.tar.gz and dist/mypack-0.1.0-py3-none-any.whl
487517
488-
The second line ensures that a clean build is created without artefacts from
489-
previous builds. The third line should output a lot of text and create two files
490-
in the ``dist`` directory when finished:
518+
:file:`dist/mypack-0.1.0-py3-none-any.whl`
519+
is a build distribution. :term:`pip` prefers to install build distributions
520+
and only uses the source distributions if no suitable build distribution is
521+
available. You should always upload a source distribution and provide build
522+
distributions for the platforms with which your project is compatible. In
523+
this case, our example package is compatible with Python on every platform,
524+
so only one build distribution is required:
491525

492-
.. code-block:: console
493-
494-
dist
495-
├── dataprep-0.1.0-py3-none-any.whl
496-
└── dataprep-0.1.0.tar.gz
497-
498-
:file:`dataprep-0.1.0-py3-none-any.whl`
499-
is a binary distribution format with the suffix :file:`..whl`, where the
500-
filename is composed as follows:
501-
502-
``dataprep``
526+
``mypack``
503527
is the normalised package name
504528
``0.1.0``
505529
is the version of the distribution package
@@ -514,16 +538,14 @@ in the ``dist`` directory when finished:
514538
other hand only for chips with the x86 instruction set and a 64-bit
515539
architecture
516540

517-
:file:`dataprep-0.1.0.tar.gz`
541+
:file:`mypack-0.1.0.tar.gz`
518542
is a :term:`source distribution`.
519543

520544
.. seealso::
521-
The reference for the file names can be found in `File name convention
522-
<https://peps.python.org/pep-0427/#file-name-convention>`_.
523-
524-
For more information on ``sdist``, see `Creating a Source Distribution
525-
<https://docs.python.org/2/distutils/sourcedist.html#creating-a-source-distribution>`__
526-
and :pep:`376`.
545+
For more information on ``sdist``, see `Core metadata specifications
546+
<https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_
547+
and `PyPA specifications
548+
<https://packaging.python.org/en/latest/specifications/>`_.
527549

528550
Testing
529551
-------

0 commit comments

Comments
 (0)