File: uv_integration.rst

package info (click to toggle)
python-semantic-release 10.3.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,112 kB
  • sloc: python: 36,523; sh: 340; makefile: 156
file content (332 lines) | stat: -rw-r--r-- 12,059 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
.. _config-guides-uv_integration:

Ultraviolet (``uv``) Integration
================================

.. _uv: https://docs.astral.sh/uv/

`uv`_ is an extremely fast Python package and project manager that
provides a modern alternative to `pip <https://pip.pypa.io/en/stable/>`_
and `venv <https://docs.python.org/3/library/venv.html>`_. It provides a lot
of features that solve the common problems of Python package management but
it also introduces a few quirks that need to be taken into account when using
Python Semantic Release.

.. important::

  **Prerequisite:** Make sure you have run through the
  :ref:`Getting Started Guide <getting-started-guide>` before proceeding with
  this guide.


Updating the ``uv.lock``
------------------------

One of the best features of ``uv`` is that it automatically generates a lock file
(``uv.lock``) that contains the exact versions of all the dependencies used in
your project. The lock file is generated when you run the ``uv install`` command,
and it is used to ensure that CI workflows are repeatable and development environments
are consistent.

When creating a new release using Python Semantic Release, PSR will update the version
in the project's definition file (e.g., ``pyproject.toml``) to indicate the new version.
Unfortunately, this action will cause ``uv`` to fail on the next execution because the
lock file will be out of sync with the project's definition file. There are two ways to
resolve this issue depending on your preference:

#.  **Add a step to your build command**: Modify your
    :ref:`semantic_release.build_command <config-build_command>` to include the command
    to update the lock file and stage it for commit.  This is commonly used with the
    :ref:`GitHub Action <gh_actions-psr>` and other CI/CD tools when you are building
    the artifact at the time of release.

    .. code-block:: toml

      [tool.semantic_release]
      build_command = """
          uv lock --upgrade-package "$PACKAGE_NAME"
          git add uv.lock
          uv build
      """

    The intent of the lock upgrade-package call is **ONLY** to update
    the version of your project within the lock file after PSR has updated the version
    in your project's definition file (e.g., ``pyproject.toml``). When you are running
    PSR, you have already tested the project as is and you don't want to actually
    update the dependencies if a new one just became available.

    For ease of use, PSR provides the ``$PACKAGE_NAME`` environment variable that
    contains the name of your package from the project's definition file
    (``pyproject.toml:project.name``).

    If you are using the :ref:`PSR GitHub Action <gh_actions-psr>`, you will need to add an
    installation command for ``uv`` to the :ref:`build_command <config-build_command>`
    because the action runs in a Docker environment does not include ``uv`` by default.
    The best way to ensure that the correct version of ``uv`` is installed is to define
    the version of ``uv`` in an optional dependency list (e.g. ``build``). This will
    also help with other automated tools like Dependabot or Renovate to keep the version
    of ``uv`` up to date.

    .. code-block:: toml

      [project.optional-dependencies]
      build = ["uv ~= 0.7.12"]

      [tool.semantic_release]
      build_command = """
          python -m pip install -e '.[build]'
          uv lock --upgrade-package "$PACKAGE_NAME"
          git add uv.lock
          uv build
      """

#.  **Stamp the code first & then separately run release**: If you prefer to not modify the
    build command, then you will need to run the ``uv lock --upgrade-package <your-package-name>``
    command prior to actually creating the release. Essentially, you will run PSR twice:
    (1) once to update the version in the project's definition file, and (2) a second time
    to generate the release.

    The intent of the ``uv lock --upgrade-package <your-package-name>`` command is **ONLY**
    to update the version of your project within the lock file after PSR has updated the
    version in your project's definition file (e.g., ``pyproject.toml``). When you are
    running PSR, you have already tested the project as is and you don't want to actually
    update the dependencies if a new one just became available.

    .. code-block:: bash

      # 1. PSR stamps version into files (nothing else)
      # don't build the changelog (especially in update mode)
      semantic-release -v version --skip-build --no-commit --no-tag --no-changelog

      # 2. run UV lock as pyproject.toml is updated with the next version
      uv lock --upgrade-package <your-package-name>

      # 3. stage the lock file to ensure it is included in the PSR commit
      git add uv.lock

      # 4. run PSR fully to create release
      semantic-release -v version

**Advanced Example**

Of course, you can mix and match these 2 approaches as needed. If PSR's pipeline was using
``uv``, we would have a mixture of the 2 approaches because we run the build in a separate
job from the release. In our case, PSR would also need to carry the lock file as a workflow
artifact along the pipeline for the release job to commit it. This advanced workflow would
look like this:

.. code-block:: text

  # File: .tool-versions
  uv 0.7.12

.. code-block:: text

  # File: .python-version
  3.11.11

.. code-block:: toml

  # File: pyproject.toml
  [project.optional-dependencies]
  build = ["python-semantic-release ~= 10.0"]
  test = ["pytest ~= 8.0"]

  [tool.semantic_release]
  build_command = """
    uv lock --upgrade-package "$PACKAGE_NAME"
    uv build
  """

.. code-block:: yaml

  # File: .github/workflows/release.yml
  on:
    push:
      branches:
        - main

  jobs:

    build:
      runs-on: ubuntu-latest
      permissions:
        contents: read
      env:
        dist_artifacts_name: dist
        dist_artifacts_dir: dist
        lock_file_artifact: uv.lock
      steps:
        - name: Setup | Checkout Repository at workflow sha
          uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
          with:
            ref: ${{ github.sha }}
            fetch-depth: 0

        - name: Setup | Force correct release branch on workflow sha
          run: git checkout -B ${{ github.ref_name }}

        - name: Setup | Install uv
          uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302  # v4.0.2

        - name: Setup | Install Python & Project dependencies
          run: uv sync --extra build

        - name: Build | Build next version artifacts
          id: version
          env:
            GH_TOKEN: "none"
          run: uv run semantic-release -v version --no-commit --no-tag

        - name: Upload | Distribution Artifacts
          if: ${{ steps.version.outputs.released == 'true' }}
          uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02  # v4.6.2
          with:
            name: ${{ env.dist_artifacts_name }}
            path: ${{ format('{0}/**', env.dist_artifacts_dir) }}
            if-no-files-found: error
            retention-days: 2

        - name: Upload | Lock File Artifact
          if: ${{ steps.version.outputs.released == 'true' }}
          uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02  # v4.6.2
          with:
            name: ${{ env.lock_file_artifact }}
            path: ${{ env.lock_file_artifact }}
            if-no-files-found: error
            retention-days: 2

      outputs:
        new-release-detected: ${{ steps.version.outputs.released }}
        new-release-version: ${{ steps.version.outputs.version }}
        new-release-tag: ${{ steps.version.outputs.tag }}
        new-release-is-prerelease: ${{ steps.version.outputs.is_prerelease }}
        distribution-artifacts: ${{ env.dist_artifacts_name }}
        lock-file-artifact: ${{ env.lock_file_artifact }}


    test-e2e:
      needs: build
      runs-on: ubuntu-latest
      steps:
        - name: Setup | Checkout Repository
          uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
          with:
            ref: ${{ github.sha }}
            fetch-depth: 1

        - name: Setup | Download Distribution Artifacts
          uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0
          if: ${{ needs.build.outputs.new-release-detected == 'true' }}
          id: artifact-download
          with:
            name: ${{ needs.build.outputs.distribution-artifacts }}
            path: ./dist

        - name: Setup | Install uv
          uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302  # v4.0.2

        - name: Setup | Install Python & Project dependencies
          run: uv sync --extra test

        - name: Setup | Install distribution artifact
          if: ${{ steps.artifact-download.outcome == 'success' }}
          run: |
            uv pip uninstall my-package
            uv pip install dist/python_semantic_release-*.whl

        - name: Test | Run pytest
          run: uv run pytest -vv tests/e2e


    release:
      runs-on: ubuntu-latest
      needs:
        - build
        - test-e2e

      if: ${{ needs.build.outputs.new-release-detected == 'true' }}

      concurrency:
        group: ${{ github.workflow }}-release-${{ github.ref_name }}
        cancel-in-progress: false

      permissions:
        contents: write

      steps:
        - name: Setup | Checkout Repository on Release Branch
          uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
          with:
            ref: ${{ github.ref_name }}
            fetch-depth: 0

        - name: Setup | Force release branch to be at workflow sha
          run: git reset --hard ${{ github.sha }}

        - name: Setup | Install uv
          uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302  # v4.0.2

        - name: Setup | Install Python & Project dependencies
          run: uv sync --extra build

        - name: Setup | Download Build Artifacts
          uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0
          id: artifact-download
          with:
            name: ${{ needs.build.outputs.distribution-artifacts }}
            path: dist

        - name: Setup | Download Lock File Artifact
          uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0
          with:
            name: ${{ needs.build.outputs.lock-file-artifact }}

        - name: Setup | Stage Lock File for Version Commit
          run: git add uv.lock

        - name: Release | Create Release
          id: release
          shell: bash
          env:
            GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          run: |
            bash .github/workflows/verify_upstream.sh
            uv run semantic-release -v --strict version --skip-build
            uv run semantic-release publish

      outputs:
        released: ${{ steps.release.outputs.released }}
        new-release-version: ${{ steps.release.outputs.version }}
        new-release-tag: ${{ steps.release.outputs.tag }}


    deploy:
      name: Deploy
      runs-on: ubuntu-latest
      if: ${{ needs.release.outputs.released == 'true' && github.repository == 'python-semantic-release/my-package' }}
      needs:
        - build
        - release

      environment:
        name: pypi
        url: https://pypi.org/project/my-package/

      permissions:
        id-token: write

      steps:
        - name: Setup | Download Build Artifacts
          uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0
          id: artifact-download
          with:
            name: ${{ needs.build.outputs.distribution-artifacts }}
            path: dist

        - name: Publish package distributions to PyPI
          uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc  # v1.12.4
          with:
            packages-dir: dist
            print-hash: true
            verbose: true