diff --git a/.gitlab/merge_request_templates/new_feature.md b/.gitlab/merge_request_templates/new_feature.md
index 74abae94c94dc0768bb5c51fe51ad253fce113fe..491b7f98ded7e0da03d18c95978eafcb7d86619f 100644
--- a/.gitlab/merge_request_templates/new_feature.md
+++ b/.gitlab/merge_request_templates/new_feature.md
@@ -8,8 +8,8 @@ Closes #999
 ## Checklist
 
 * General:
-    * [ ] use [rules](https://docs.gitlab.com/ee/ci/yaml/#rules) instead of [only/except](https://docs.gitlab.com/ee/ci/yaml/#onlyexcept-advanced)
-    * [ ] optimized [cache](https://docs.gitlab.com/ee/ci/caching/) configuration (wherever applicable)
+    * [ ] use [rules](https://docs.gitlab.com/ci/yaml/#rules) instead of [only/except](https://docs.gitlab.com/ci/yaml/#onlyexcept-advanced)
+    * [ ] optimized [cache](https://docs.gitlab.com/ci/caching/) configuration (wherever applicable)
 * Publicly usable:
     * [ ] untagged runners
     * [ ] no proxy configuration but support `http_proxy`/`https_proxy`/`no_proxy`
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1faff804e89c152860b763071bb1a79225cba71..80e65e82ef61dea84bd442927e54b1658df25026 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,69 @@
-# [7.8.0](https://git.code.tecnalia.dev/smartdatalab/public/ci-cd-components/python/compare/7.7.1...7.8.0) (2025-01-29)
+## [7.10.2](https://gitlab.com/to-be-continuous/python/compare/7.10.1...7.10.2) (2025-05-03)
+
+
+### Bug Fixes
+
+* add python cmd when python3 is present ([e6c8d7f](https://gitlab.com/to-be-continuous/python/commit/e6c8d7f98cd785d2a4ccaf777a9c3d0016a3da19))
+
+## [7.10.1](https://gitlab.com/to-be-continuous/python/compare/7.10.0...7.10.1) (2025-05-02)
+
+
+### Bug Fixes
+
+* change to pytest bin instead of module ([19be433](https://gitlab.com/to-be-continuous/python/commit/19be433bf16097a98ae5de4f633ebd9fe807e4ef)), closes [#109](https://gitlab.com/to-be-continuous/python/issues/109)
+* exclude venv on py-lint ([d459124](https://gitlab.com/to-be-continuous/python/commit/d45912485cd7e8a1d802dda72eca6b6bfe1860b8))
+* py-package remove reports dir ([300d31f](https://gitlab.com/to-be-continuous/python/commit/300d31f51e6670cc6f09d6af01531b77e9d270af)), closes [#98](https://gitlab.com/to-be-continuous/python/issues/98)
+
+# [7.10.0](https://gitlab.com/to-be-continuous/python/compare/7.9.2...7.10.0) (2025-04-16)
+
+
+### Features
+
+* **Hatch:** add Hatch support as a new build system ([f684e63](https://gitlab.com/to-be-continuous/python/commit/f684e634496711d984843b25141f57df6e3826be))
+
+## [7.9.2](https://gitlab.com/to-be-continuous/python/compare/7.9.1...7.9.2) (2025-04-02)
+
+
+### Bug Fixes
+
+* **sbom:** disable file catalogers for Syft SBOM (to minimize SBOM file) ([d83edb0](https://gitlab.com/to-be-continuous/python/commit/d83edb06767741edd400ed195981df778414e9cd))
+
+## [7.9.1](https://gitlab.com/to-be-continuous/python/compare/7.9.0...7.9.1) (2025-03-11)
+
+
+### Bug Fixes
+
+* **bump-my-version:** improve bump-my-version config verification (solves [#106](https://gitlab.com/to-be-continuous/python/issues/106)) ([64b624a](https://gitlab.com/to-be-continuous/python/commit/64b624a4d0abde429d50a00a9c595993c369fbd0))
+
+# [7.9.0](https://gitlab.com/to-be-continuous/python/compare/7.8.3...7.9.0) (2025-03-10)
+
+
+### Features
+
+* skip GCP ADC authent when GCP_JWT is not present ([b43207f](https://gitlab.com/to-be-continuous/python/commit/b43207f6eee26a8d17bc75ed19b54208534b3ad9))
+
+## [7.8.3](https://gitlab.com/to-be-continuous/python/compare/7.8.2...7.8.3) (2025-02-23)
+
+
+### Bug Fixes
+
+* change _pip to pass cmd then PIP_OPTS ([c1b277e](https://gitlab.com/to-be-continuous/python/commit/c1b277e31b977b41eedd5e213e7672d11c66da33))
+
+## [7.8.2](https://gitlab.com/to-be-continuous/python/compare/7.8.1...7.8.2) (2025-02-03)
+
+
+### Bug Fixes
+
+* **gcp:** reduce scope of GCP App Default Creds script to template ([829bfce](https://gitlab.com/to-be-continuous/python/commit/829bfceffe3a2e097914c719d4a4488d544be7ab))
+
+## [7.8.1](https://gitlab.com/to-be-continuous/python/compare/7.8.0...7.8.1) (2025-01-31)
+
+
+### Bug Fixes
+
+* **sbom:** only generate SBOMs on prod branches, integ branches and release tags ([8da756f](https://gitlab.com/to-be-continuous/python/commit/8da756f273cb22dbd12c866ba1e6f7f07b52cb4a))
+
+# [7.8.0](https://gitlab.com/to-be-continuous/python/compare/7.7.1...7.8.0) (2025-01-27)
 
 
 ### Features
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 61bf40af56b1cf41088143fc212889d847b11ae7..be729277830ae90e789a9a0bbd8d2b38889bc15d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,7 +61,7 @@ To contribute:
 
 1. Create an issue describing the bug or enhancement you want to propose (select the right issue template).
 2. Make sure the issue has been reviewed and agreed.
-3. Create a Merge Request, from your **own** fork (see [forking workflow](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html) documentation).
+3. Create a Merge Request, from your **own** fork (see [forking workflow](https://docs.gitlab.com/user/project/repository/forking_workflow/) documentation).
    Don't hesitate to mark your MR as `Draft` as long as you think it's not ready to be reviewed.
 
 ### Git Commit Conventions
diff --git a/README.md b/README.md
index 8375c603d802a7fc9155b49087a7513a6602f951..0826f446643a8f85775211f28cfbed055e3f9105 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ This project implements a GitLab CI/CD template to build, test and analyse your
 
 ## Usage
 
-This template can be used both as a [CI/CD component](https://docs.gitlab.com/ee/ci/components/#use-a-component)
-or using the legacy [`include:project`](https://docs.gitlab.com/ee/ci/yaml/index.html#includeproject) syntax.
+This template can be used both as a [CI/CD component](https://docs.gitlab.com/ci/components/#use-a-component)
+or using the legacy [`include:project`](https://docs.gitlab.com/ci/yaml/#includeproject) syntax.
 
 ### Use as a CI/CD component
 
@@ -14,7 +14,7 @@ Add the following to your `.gitlab-ci.yml`:
 ```yaml
 include:
   # 1: include the component
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.10.2
     # 2: set/override component inputs
     inputs:
       image: registry.hub.docker.com/library/python:3.12-slim
@@ -29,7 +29,7 @@ Add the following to your `.gitlab-ci.yml`:
 include:
   # 1: include the template
   - project: 'to-be-continuous/python'
-    ref: '7.8.0'
+    ref: '7.10.2'
     file: '/templates/gitlab-ci-python.yml'
 
 variables:
@@ -44,13 +44,13 @@ The Python template uses some global configuration used throughout all jobs.
 
 | Input / Variable     | Description                                                                           | Default value      |
 | -------------------- | ------------------------------------------------------------------------------------- | ------------------ |
-| `image` / `PYTHON_IMAGE` | The Docker image used to run Python <br/>:warning: **set the version required by your project** | `registry.hub.docker.com/library/python:3-slim` |
+| `image` / `PYTHON_IMAGE` | The Docker image used to run Python <br/>:warning: **set the version required by your project** | `registry.hub.docker.com/library/python:3-slim` <br/>[![Trivy Badge](https://to-be-continuous.gitlab.io/doc/secu/trivy-badge-PYTHON_IMAGE.svg)](https://to-be-continuous.gitlab.io/doc/secu/trivy-PYTHON_IMAGE) |
 | `project-dir` / `PYTHON_PROJECT_DIR` | Python project root directory                                                         | `.`                |
 | `build-system` / `PYTHON_BUILD_SYSTEM`| Python build-system to use to install dependencies, build and package the project (see below) | `auto` (auto-detect) |
 | `PIP_INDEX_URL`      | Python repository url                                                                 | _none_             |
 | `PIP_EXTRA_INDEX_URL` | Extra Python repository url                                                           | _none_             |
 | `pip-opts` / `PIP_OPTS` | pip [extra options](https://pip.pypa.io/en/stable/cli/pip/#general-options)     | _none_             |
-| `extra-deps` / `PYTHON_EXTRA_DEPS` | Python extra sets of dependencies to install<br/>For [Setuptools](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html?highlight=extras#optional-dependencies) or [Poetry](https://python-poetry.org/docs/pyproject/#extras) only | _none_ |
+| `extra-deps` / `PYTHON_EXTRA_DEPS` | Python extra sets of dependencies to install<br/>For [Setuptools](https://setuptools.pypa.io/en/latest/userguide/dependency_management.html?highlight=extras#optional-dependencies) or [Poetry](https://python-poetry.org/docs/pyproject/#extras) or [uv](https://docs.astral.sh/uv/) only | _none_ |
 | `reqs-file` / `PYTHON_REQS_FILE` | Main requirements file _(relative to `$PYTHON_PROJECT_DIR`)_<br/>For [Requirements Files](https://pip.pypa.io/en/stable/user_guide/#requirements-files) build-system only | `requirements.txt` |
 | `extra-reqs-files` / `PYTHON_EXTRA_REQS_FILES` | Extra dev requirements file(s) to install _(relative to `$PYTHON_PROJECT_DIR`)_ | `requirements-dev.txt` |
 | `py-publish-job-tags` / `PY_PUBLISH_JOB_TAGS` | Tags to be used for selecting runners for the job | `[]`            |
@@ -67,15 +67,58 @@ and/or `setup.py` and/or `requirements.txt`), but the build system might also be
 
 | Value            | Build System (scope)                                                                                   |
 | ---------------- |--------------------------------------------------------------------------------------------------------|
-| _none_ (default) or `auto` | The template tries to **auto-detect** the actual build system                                          |
-| `setuptools`     | [Setuptools](https://setuptools.pypa.io/) (dependencies, build & packaging)                            |
-| `poetry`         | [Poetry](https://python-poetry.org/) (dependencies, build, test & packaging)                           |
-| `uv`             | [uv](https://docs.astral.sh/uv/) (dependencies, build, test & packaging)|
-| `pipenv`         | [Pipenv](https://pipenv.pypa.io/) (dependencies only)                                                  |
-| `reqfile`        | [Requirements Files](https://pip.pypa.io/en/stable/user_guide/#requirements-files) (dependencies only) |
+| _none_ (default) or `auto` | The template tries to **auto-detect** 	:sparkles: the actual build system                                          |
+| `setuptools`     | [![setuptools](https://img.shields.io/badge/setuptools-grey)](https://setuptools.pypa.io/) ![setuptools](https://img.shields.io/badge/dependencies_%7C_build_%7C_packaging-blue)                          |
+| `poetry`         | [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) ![Poetry](https://img.shields.io/badge/dependencies_%7C_build_%7C_test_%7C_packaging-blue)                           |
+| `uv`             | [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://docs.astral.sh/uv/) ![uv](https://img.shields.io/badge/dependencies_%7C_build_%7C_test_%7C_packaging-blue)  |
+| `hatch`          | [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://hatch.pypa.io/latest/) ![Hatch](https://img.shields.io/badge/dependencies_%7C_build_%7C_test_%7C_packaging-blue) |
+| `pipenv`         | [![Pipenv](https://img.shields.io/badge/Pipenv-grey)](https://pipenv.pypa.io/) ![Pipenv](https://img.shields.io/badge/dependencies-blue)                                                  |
+| `reqfile`        | [![Requirements Files](https://img.shields.io/badge/Requirements_Files-grey)](https://pip.pypa.io/en/stable/user_guide/#requirements-files) ![Pipenv](https://img.shields.io/badge/dependencies-blue) |
 
 :warning: You can explicitly set the build tool version by setting `$PYTHON_BUILD_SYSTEM` variable including a [version identification](https://peps.python.org/pep-0440/). For example `PYTHON_BUILD_SYSTEM="poetry==1.1.15"`
 
+### Hatch
+
+All template jobs use the `default` Hatch environment. So dev dependencies should defined in the `[tool.hatch.envs.default.dependencies]` section of `pyproject.toml`.
+
+```toml
+[tool.hatch.envs.default]
+dependencies = [
+  "pytest>=8.0.0,<9",
+...
+]
+```
+
+## Use Multiple Python Versions
+
+For some jobs, it can be relevant to use multiple Python versions, such as:
+
+- Test jobs: `py-unittest`, `py-pytest`, `py-nosetest`
+- `py-publish` jobs (especially if you're not generating a pure Python package)
+
+This setup is done by defining the PYTHON_IMAGE variable with a parallel/matrix strategy in your .gitlab-ci.yml.
+
+For example, to run py-test jobs using both python:3.13-slim and python:3.12-alpine:
+
+```yaml
+py-pytest:
+  parallel:
+    matrix:
+      - PYTHON_IMAGE: python:3.13-slim
+      - PYTHON_IMAGE: python:3.12-alpine
+```
+
+If your tests cannot be executed concurrently due to shared resources (e.g. database access), you can use the `resource_group` feature to limit parallel execution:
+
+```yaml
+py-pytest:
+  parallel:
+    matrix:
+      - PYTHON_IMAGE: python:3.13-slim
+      - PYTHON_IMAGE: python:3.12-alpine
+  resource_group: db_access_tests
+```
+
 ## Jobs
 
 ### `py-package` job
@@ -88,6 +131,13 @@ It is bound to the `build` stage, it is **disabled by default** and can be enabl
 | ------------------------ | ---------------------------------- | ----------------- |
 | `py-package-job-tags` / `PY_PACKAGE_JOB_TAGS` | Tags to be used for selecting runners for the job | `[]`            |
 
+#### UV tip
+
+Currently, UV supports the following underlying build systems:
+
+- [hatchling](https://pypi.org/project/hatchling/) (this is the default setting when creating a new project with UV), so you may use the same build configuration as [Hatch](https://hatch.pypa.io/latest/build/). The recommended file layout is [`src-layout`](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/) to simplify [finding the source](https://hatch.pypa.io/latest/plugins/builder/wheel/#default-file-selection) otherwise configure [Hatch build config](https://hatch.pypa.io/latest/config/build/).
+- or [setuptools](https://pypi.org/project/setuptools/) (this is the default if nothing is defined in your `pyproject.toml`). In this case, the recommended file layout is also [`src-layout`](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/), otherwise configure [`package_discovery`](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html).
+
 ### Lint jobs
 
 #### `py-lint` job
@@ -108,7 +158,7 @@ In addition to a textual report in the console, this job produces the following
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/py-lint.codeclimate.json` | [Code Climate](https://docs.codeclimate.com/docs/pylint) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) |
+| `$PYTHON_PROJECT_DIR/reports/py-lint.codeclimate.json` | [Code Climate](https://docs.codeclimate.com/docs/pylint) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscodequality) |
 | `$PYTHON_PROJECT_DIR/reports/py-lint.parseable.txt` | [parseable](https://pylint.pycqa.org/en/latest/user_guide/usage/output.html) | [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/importing-external-issues/external-analyzer-reports/) |
 
 ### Test jobs
@@ -153,8 +203,8 @@ In addition to a textual report in the console, this job produces the following
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/test-execution-parameters/#python) |
-| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/python-test-coverage/) |
+| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/test-execution-parameters/#python) |
+| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/python-test-coverage/) |
 
 #### `py-pytest` job
 
@@ -187,8 +237,8 @@ In addition to a textual report in the console, this job produces the following
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/test-execution-parameters/#python) |
-| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/python-test-coverage/) |
+| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/test-execution-parameters/#python) |
+| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/python-test-coverage/) |
 
 #### `py-nosetests` job
 
@@ -212,8 +262,8 @@ In addition to a textual report in the console, this job produces the following
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/test-execution-parameters/#python) |
-| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/python-test-coverage/) |
+| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/test-execution-parameters/#python) |
+| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/test-coverage/python-test-coverage/) |
 
 #### `py-compile` job
 
@@ -286,16 +336,17 @@ It is bound to the `test` stage, and uses the following variables:
 | Input / Variable      | Description                            | Default value     |
 | --------------------- | -------------------------------------- | ----------------- |
 | `sbom-disabled` / `PYTHON_SBOM_DISABLED` | Set to `true` to disable this job | _none_ |
+| `TBC_SBOM_MODE`                          | Controls when SBOM reports are generated (`onrelease`: only on `$INTEG_REF`, `$PROD_REF` and `$RELEASE_REF` pipelines; `always`: any pipeline).<br/>:warning: `sbom-disabled` / `PYTHON_SBOM_DISABLED` takes precedence | `onrelease` |
 | `sbom-syft-url` / `PYTHON_SBOM_SYFT_URL` | Url to the `tar.gz` package for `linux_amd64` of Syft to use (ex: `https://github.com/anchore/syft/releases/download/v0.62.3/syft_0.62.3_linux_amd64.tar.gz`)<br/>_When unset, the latest version will be used_ | _none_ |
 | `sbom-name` / `PYTHON_SBOM_NAME` | Component name of the emitted SBOM | `$CI_PROJECT_PATH/$PYTHON_PROJECT_DIR` |
-| `sbom-opts` / `PYTHON_SBOM_OPTS` | Options for syft used for SBOM analysis | `--override-default-catalogers python-package-cataloger` |
+| `sbom-opts` / `PYTHON_SBOM_OPTS` | Options for syft used for SBOM analysis | `--override-default-catalogers python-package-cataloger --select-catalogers -file` |
 | `py-sbom-job-tags` / `PY_SBOM_JOB_TAGS` | Tags to be used for selecting runners for the job | `[]`            |
 
 In addition to logs in the console, this job produces the following reports, kept for one week:
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/py-sbom.cyclonedx.json` | [CycloneDX JSON](https://cyclonedx.org/docs/latest/json/) | [Security & Compliance integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscyclonedx) |
+| `$PYTHON_PROJECT_DIR/reports/py-sbom.cyclonedx.json` | [CycloneDX JSON](https://cyclonedx.org/docs/latest/json/) | [Security & Compliance integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscyclonedx) |
 
 ### `py-black` job
 
@@ -331,7 +382,7 @@ In addition to logs in the console, this job produces the following reports, kep
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/py-ruff.gitlab.json` | [GitLab](https://docs.astral.sh/ruff/settings/#output-format) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) |
+| `$PYTHON_PROJECT_DIR/reports/py-ruff.gitlab.json` | [GitLab](https://docs.astral.sh/ruff/settings/#output-format) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscodequality) |
 | `$PYTHON_PROJECT_DIR/reports/py-ruff.native.json` | [JSON](https://docs.astral.sh/ruff/settings/#output-format) | [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/importing-external-issues/external-analyzer-reports/)<br/>_This report is generated only if SonarQube template is detected_ |
 
 ### `py-ruff-format` job
@@ -362,7 +413,7 @@ In addition to a textual report in the console, this job produces the following
 
 | Report         | Format                                                                       | Usage             |
 | -------------- | ---------------------------------------------------------------------------- | ----------------- |
-| `$PYTHON_PROJECT_DIR/reports/py-mypy.codeclimate.json` | [Code Climate](https://github.com/soul-catcher/mypy-gitlab-code-quality) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) |
+| `$PYTHON_PROJECT_DIR/reports/py-mypy.codeclimate.json` | [Code Climate](https://github.com/soul-catcher/mypy-gitlab-code-quality) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscodequality) |
 | `$PYTHON_PROJECT_DIR/reports/py-mypy.console.txt` | [mypy console output](https://mypy.readthedocs.io/) | [SonarQube integration](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/importing-external-issues/external-analyzer-reports/) |
 
 ### SonarQube analysis
@@ -407,12 +458,13 @@ This job is **disabled by default** and allows to perform a complete release of
 1. increase the Python project version,
 2. Git commit changes and create a Git tag with the new version number,
 3. build the [Python packages](https://packaging.python.org/),
-4. publish the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/ee/user/packages/pypi_repository/) by default).
+4. publish the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/user/packages/pypi_repository/) by default).
 
 The Python template supports three packaging systems:
 
 * [Poetry](https://python-poetry.org/): uses Poetry-specific [version](https://python-poetry.org/docs/cli/#version), [build](https://python-poetry.org/docs/cli/#build) and [publish](https://python-poetry.org/docs/cli/#publish) commands.
-* [uv](https://docs.astral.sh/uv/): uses [uv](https://docs.astral.sh/uv/) as version management, [build](https://docs.astral.sh/uv/guides/publish/#building-your-package) as package builder and [publish](https://docs.astral.sh/uv/guides/publish/) to publish.
+* [uv](https://docs.astral.sh/uv/): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://docs.astral.sh/uv/guides/publish/#building-your-package) as package builder and [publish](https://docs.astral.sh/uv/guides/publish/) to publish.
+* [hatch](https://hatch.pypa.io/latest/): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://hatch.pypa.io/latest/build/) as package builder and [publish](https://hatch.pypa.io/latest/publish/) to publish.
 * [Setuptools](https://setuptools.pypa.io/): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://pypa-build.readthedocs.io/) as package builder and [Twine](https://twine.readthedocs.io/) to publish.
 
 The release job is bound to the `publish` stage, appears only on production and integration branches and uses the following variables:
@@ -432,12 +484,13 @@ When `py-release` job is enabled, `py-publish` job is automatically enabled too.
 
 ### `py-publish` job
 
-This job is **disabled by default** and allow to publish the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/ee/user/packages/pypi_repository/) by default.
+This job is **disabled by default** and allow to publish the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/user/packages/pypi_repository/) by default.
 
 The Python template supports three packaging systems:
 
 * [Poetry](https://python-poetry.org/): uses Poetry-specific [version](https://python-poetry.org/docs/cli/#version), [build](https://python-poetry.org/docs/cli/#build) and [publish](https://python-poetry.org/docs/cli/#publish) commands.
-* [uv](https://docs.astral.sh/uv/): uses [uv](https://docs.astral.sh/uv/) as version management, [build](https://docs.astral.sh/uv/guides/publish/#building-your-package) as package builder and [publish](https://docs.astral.sh/uv/guides/publish/) to publish.
+* [uv](https://docs.astral.sh/uv/): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://docs.astral.sh/uv/guides/publish/#building-your-package) as package builder and [publish](https://docs.astral.sh/uv/guides/publish/) to publish.
+* [hatch](https://hatch.pypa.io/latest//): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://hatch.pypa.io/latest/build/) as package builder and [publish](https://hatch.pypa.io/latest/publish/) to publish.
 * [Setuptools](https://setuptools.pypa.io/): uses [bump-my-version](https://github.com/callowayproject/bump-my-version) as version management, [build](https://pypa-build.readthedocs.io/) as package builder and [Twine](https://twine.readthedocs.io/) to publish.
 
 The publish job is bound to the `publish` stage, is executed on a Git tag matching [semantic versioning pattern](https://semver.org/) and uses the following variables:
@@ -445,11 +498,12 @@ The publish job is bound to the `publish` stage, is executed on a Git tag matchi
 | Input / Variable        | Description                                                             | Default value     |
 | ----------------------- | ----------------------------------------------------------------------- | ----------------- |
 | `publish-enabled` / `PYTHON_PUBLISH_ENABLED`| Set to `true` to enable the publish job                                 | _none_ (disabled) |
-| `repository-url` / `PYTHON_REPOSITORY_URL`| Target PyPI repository to publish packages                              | _[GitLab project's PyPI packages repository](https://docs.gitlab.com/ee/user/packages/pypi_repository/)_ |
+| `repository-url` / `PYTHON_REPOSITORY_URL`| Target PyPI repository to publish packages                              | _[GitLab project's PyPI packages repository](https://docs.gitlab.com/user/packages/pypi_repository/)_ |
 | `PYTHON_REPOSITORY_USERNAME`| Target PyPI repository username credential                              | `gitlab-ci-token` |
 | :lock: `PYTHON_REPOSITORY_PASSWORD`| Target PyPI repository password credential                              | `$CI_JOB_TOKEN` |
 | `py-release-job-tags` / `PY_RELEASE_JOB_TAGS` | Tags to be used for selecting runners for the job | `[]`            |
 
+For information on building recommandations look (`py-package`)[/README.md#py-package-job]
 
 #### Setuptools tip
 
@@ -479,6 +533,30 @@ current_version = "0.0.0"
 filename = "project-name/__init__.py"
 ```
 
+#### Manage your private repositories credentials
+
+When using Poetry, UV or other dependency management systems, it is possible to use private repositories (for retrieving project dependencies and/or publishing your project packages).
+
+Each tool has its own way of providing credentials as environment variables (see [Poetry documentation](https://python-poetry.org/docs/configuration#http-basicnameusernamepassword), [UV documentation](https://docs.astral.sh/uv/configuration/environment/#uv_index_name_password)). 
+You should manage those credentials as any other secret (i.e. defined as [project or group CI/CD variables](https://docs.gitlab.com/ci/variables/#for-a-project), if possible [**masked**](https://docs.gitlab.com/ci/variables/#mask-a-cicd-variable) to prevent them from being inadvertently displayed in your job logs).
+
+##### How to propagate AWS CodeArtifact credentials
+
+If you're using the **AWS CodeArtifact variant**, you can propagate AWS CodeArtifact credentials (obtained dynamically by the variant) for Poetry and UV by declaring the following variables (replace the `AWS_REPO_NAME` part with your actual configured private repository name):
+
+```yaml
+variables:
+  # if using Poetry
+  POETRY_HTTP_BASIC_AWS_REPO_NAME_USER: $PYTHON_REPOSITORY_USER
+  POETRY_HTTP_BASIC_AWS_REPO_NAME_PASSWORD: $PYTHON_REPOSITORY_PASSWORD
+  # if using UV
+  UV_INDEX_AWS_REPO_NAME_USERNAME: $PYTHON_REPOSITORY_USER
+  UV_INDEX_AWS_REPO_NAME_PASSWORD: $PYTHON_REPOSITORY_PASSWORD
+```
+
+:information_source: the AWS CodeArtifact variant obtains temporary authentication credentials and makes them available in the `$PYTHON_REPOSITORY_USER` / `$PYTHON_REPOSITORY_PASSWORD` variable.
+The above piece of code will simply reuse those values as Poetry/UV configuration.
+
 #### `semantic-release` integration
 
 If you activate the [`semantic-release-info` job from the `semantic-release` template](https://gitlab.com/to-be-continuous/semantic-release/#semantic-release-info-job), the `py-release` job will rely on the generated next version info.
@@ -496,9 +574,9 @@ You can either use a SSH key or user/password credentials.
 
 ##### Using a SSH key
 
-We recommend you to use a [project deploy key](https://docs.gitlab.com/ee/user/project/deploy_keys/#project-deploy-keys) with write access to your project.
+We recommend you to use a [project deploy key](https://docs.gitlab.com/user/project/deploy_keys/#project-deploy-keys) with write access to your project.
 
-The key should not have a passphrase (see [how to generate a new SSH key pair](https://docs.gitlab.com/ee/user/ssh.html#generate-an-ssh-key-pair)).
+The key should not have a passphrase (see [how to generate a new SSH key pair](https://docs.gitlab.com/user/ssh/#generate-an-ssh-key-pair)).
 
 Specify :lock: `$GIT_PRIVATE_KEY` as secret project variable with the private part of the deploy key.
 
@@ -514,11 +592,11 @@ The template handles both classic variable and file variable.
 
 Simply specify :lock: `$GIT_USERNAME` and :lock: `$GIT_PASSWORD` as secret project variables.
 
-Note that the password should be an access token (preferably a [project](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) or [group](https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html) access token) with `write_repository` scope and `Maintainer` role.
+Note that the password should be an access token (preferably a [project](https://docs.gitlab.com/user/project/settings/project_access_tokens/) or [group](https://docs.gitlab.com/user/group/settings/group_access_tokens/) access token) with `write_repository` scope and `Maintainer` role.
 
 #### Pip repositories
 
-When depending on Python packages published in [GitLab's packages registry](https://docs.gitlab.com/ee/user/packages/pypi_repository/), it could be useful to configure a group level Package.
+When depending on Python packages published in [GitLab's packages registry](https://docs.gitlab.com/user/packages/pypi_repository/), it could be useful to configure a group level Package.
 But such repository will require an authenticated access.
 
 To do so, simply set the `PIP_INDEX_URL` and use the CI job token.
@@ -558,7 +636,7 @@ In order to be able to communicate with the Vault server, the variant requires t
 | :lock: `VAULT_ROLE_ID`   | The [AppRole](https://www.vaultproject.io/docs/auth/approle) RoleID | _none_ |
 | :lock: `VAULT_SECRET_ID` | The [AppRole](https://www.vaultproject.io/docs/auth/approle) SecretID | _none_ |
 
-By default, the variant will authentifacte using a [JWT ID token](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html). To use [AppRole](https://www.vaultproject.io/docs/auth/approle) instead the `VAULT_ROLE_ID` and `VAULT_SECRET_ID` should be defined as secret project variables.
+By default, the variant will authentifacte using a [JWT ID token](https://docs.gitlab.com/ci/secrets/id_token_authentication/). To use [AppRole](https://www.vaultproject.io/docs/auth/approle) instead the `VAULT_ROLE_ID` and `VAULT_SECRET_ID` should be defined as secret project variables.
 
 #### Usage
 
@@ -580,9 +658,9 @@ With:
 ```yaml
 include:
   # main component
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.10.2
   # Vault variant
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python-vault@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python-vault@7.10.2
     inputs:
       vault-base-url: "https://vault.acme.host/v1"
       # audience claim for JWT
@@ -613,21 +691,21 @@ The variant requires the additional configuration parameters:
 
 | Input / Variable | Description                            | Default value     |
 | ----------------- | -------------------------------------- | ----------------- |
-| `gcp-oidc-aud` / `GCP_OIDC_AUD` | The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/))_ | `$CI_SERVER_URL` |
-| `gcp-oidc-provider` / `GCP_OIDC_PROVIDER` | Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/) | _none_ |
+| `gcp-oidc-aud` / `GCP_OIDC_AUD` | The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ci/cloud_services/google_cloud/))_ | `$CI_SERVER_URL` |
+| `gcp-oidc-provider` / `GCP_OIDC_PROVIDER` | Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ci/cloud_services/google_cloud/) | _none_ |
 | `gcp-oidc-account` / `GCP_OIDC_ACCOUNT` | Default Service Account to which impersonate with OpenID Connect authentication | _none_ |
 
 #### Example
 
 ```yaml
 include:
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.10.2
     # 2: set/override component inputs
     inputs:
       image: registry.hub.docker.com/library/python:3.12-slim
       pytest-enabled: true
 
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python-gcp@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python-gcp@7.10.2
     inputs:
       # common OIDC config for non-prod envs
       gcp-oidc-provider: "projects/<gcp_nonprod_proj_id>/locations/global/workloadIdentityPools/<pool_id>/providers/<provider_id>"
@@ -648,7 +726,7 @@ Most importantly, the variant sets the `pip global.index-url` to the CodeArtifac
 
 The variant supports two authentication methods:
 
-1. [federated authentication using OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/aws/) (**recommended method**),
+1. [federated authentication using OpenID Connect](https://docs.gitlab.com/ci/cloud_services/aws/) (**recommended method**),
 2. or basic authentication with AWS access key ID & secret access key.
 
 :warning: when using this variant, you must have created the CodeArtifact repository.
@@ -667,7 +745,7 @@ The variant *requires* the additional configuration parameters:
 
 ##### OIDC authentication config
 
-This is the recommended authentication method. In order to use it, first carefuly follow [GitLab's documentation](https://docs.gitlab.com/ee/ci/cloud_services/aws/),
+This is the recommended authentication method. In order to use it, first carefuly follow [GitLab's documentation](https://docs.gitlab.com/ci/cloud_services/aws/),
 then set the required configuration.
 
 | Input / Variable                                            | Description                                                                                    | Default value    |
@@ -687,13 +765,13 @@ then set the required configuration.
 
 ```yaml
 include:
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python@7.10.2
     # 2: set/override component inputs
     inputs:
       image: registry.hub.docker.com/library/python:3.12-slim
       pytest-enabled: true
 
-  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python-aws-codeartifact@7.8.0
+  - component: $CI_SERVER_FQDN/to-be-continuous/python/gitlab-ci-python-aws-codeartifact@7.10.2
     inputs:
       aws-region: "us-east-1"
       aws-codeartifact-domain: "acme"
diff --git a/kicker.json b/kicker.json
index 5484d7ccadc566a40c7aba8d8cc65135a47c8ec3..362ecc8cee0507ba8d630ecde4a5d144d491ecbb 100644
--- a/kicker.json
+++ b/kicker.json
@@ -21,7 +21,7 @@
       "name": "PYTHON_BUILD_SYSTEM",
       "description": "Python build-system to use to install dependencies, build and package the project",
       "type": "enum",
-      "values": ["auto", "setuptools", "poetry", "pipenv", "reqfile", "uv"],
+      "values": ["auto", "setuptools", "poetry", "pipenv", "reqfile", "uv", "hatch"],
       "default": "auto",
       "advanced": true
     },
@@ -86,7 +86,7 @@
     {
       "id":"publish",
       "name":"publish",
-      "description":"This job allows publishing the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/ee/user/packages/pypi_repository/) by default.",
+      "description":"This job allows publishing the built packages to a PyPI compatible repository ([GitLab packages](https://docs.gitlab.com/user/packages/pypi_repository/) by default.",
       "enable_with": "PYTHON_PUBLISH_ENABLED"
     },    
     {
@@ -227,6 +227,14 @@
       "description": "This job generates a file listing all dependencies using [syft](https://github.com/anchore/syft)",
       "disable_with": "PYTHON_SBOM_DISABLED",
       "variables": [
+        {
+          "name": "TBC_SBOM_MODE",
+          "type": "enum",
+          "values": ["onrelease", "always"],
+          "description": "Controls when SBOM reports are generated (`onrelease`: only on `$INTEG_REF`, `$PROD_REF` and `$RELEASE_REF` pipelines; `always`: any pipeline)",
+          "advanced": true,
+          "default": "onrelease"
+        },
         {
           "name": "PYTHON_SBOM_SYFT_URL",
           "description": "Url to the `tar.gz` package for `linux_amd64` of Syft to use\n\n_When unset, the latest version will be used_",
@@ -241,7 +249,7 @@
         {
           "name": "PYTHON_SBOM_OPTS",
           "description": "Options for syft used for SBOM analysis",
-          "default": "--override-default-catalogers python-package-cataloger",
+          "default": "--override-default-catalogers python-package-cataloger --select-catalogers -file",
           "advanced": true
         },
         {
@@ -308,7 +316,7 @@
         {
           "name": "PYTHON_REPOSITORY_URL",
           "type": "url",
-          "description": "Target PyPI repository to publish packages.\n\n_defaults to [GitLab project's packages repository](https://docs.gitlab.com/ee/user/packages/pypi_repository/)_",
+          "description": "Target PyPI repository to publish packages.\n\n_defaults to [GitLab project's packages repository](https://docs.gitlab.com/user/packages/pypi_repository/)_",
           "default": "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi"
         },
         {
@@ -461,7 +469,7 @@
       "variables": [
         {
           "name": "GCP_OIDC_AUD",
-          "description": "The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/))_",
+          "description": "The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ci/cloud_services/google_cloud/))_",
           "default": "$CI_SERVER_URL",
           "advanced": true
         },
@@ -471,7 +479,7 @@
         },
         {
           "name": "GCP_OIDC_PROVIDER",
-          "description": "Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/)"
+          "description": "Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ci/cloud_services/google_cloud/)"
         }
       ]
     },
@@ -493,13 +501,13 @@
         },
         {
           "name": "AWS_OIDC_AUD",
-          "description": "The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_",
+          "description": "The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ci/cloud_services/aws/))_",
           "default": "$CI_SERVER_URL",
           "advanced": true
         },
         {
           "name": "AWS_OIDC_ROLE_ARN",
-          "description": "Default IAM Role ARN associated with GitLab _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_"
+          "description": "Default IAM Role ARN associated with GitLab _(only required for [OIDC authentication](https://docs.gitlab.com/ci/cloud_services/aws/))_"
         },
         {
           "name": "AWS_ACCESS_KEY_ID",
diff --git a/templates/gitlab-ci-python-aws-codeartifact.yml b/templates/gitlab-ci-python-aws-codeartifact.yml
index 60baea0c05de835f801a4090f0b0db3977dc2641..81d1611dc5354452dfbe739fbca67be11e41aa56 100644
--- a/templates/gitlab-ci-python-aws-codeartifact.yml
+++ b/templates/gitlab-ci-python-aws-codeartifact.yml
@@ -16,11 +16,11 @@ spec:
       description: Default region (where the Codeartifact registry is located)
       default: ''
     aws-oidc-aud:
-      description: The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_
+      description: The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ci/cloud_services/aws/))_
       default: $CI_SERVER_URL
     aws-oidc-role-arn:
       description: Default IAM Role ARN associated with GitLab _(only required for [OIDC
-        authentication](https://docs.gitlab.com/ee/ci/cloud_services/aws/))_
+        authentication](https://docs.gitlab.com/ci/cloud_services/aws/))_
       default: ''
 ---
 variables:
diff --git a/templates/gitlab-ci-python-gcp.yml b/templates/gitlab-ci-python-gcp.yml
index 2ac425fbd57de08ef3956a99f54ea647ad4e239b..37aec7a2f965a8357ddd4a3be7ccf44ba48932bc 100644
--- a/templates/gitlab-ci-python-gcp.yml
+++ b/templates/gitlab-ci-python-gcp.yml
@@ -5,13 +5,13 @@
 spec:
   inputs:
     gcp-oidc-aud:
-      description: The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/)))_
+      description: The `aud` claim for the JWT token _(only required for [OIDC authentication](https://docs.gitlab.com/ci/cloud_services/google_cloud/)))_
       default: $CI_SERVER_URL
     gcp-oidc-account:
       description: Default Service Account to which impersonate with OpenID Connect authentication
       default: ''
     gcp-oidc-provider:
-      description: Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ee/ci/cloud_services/google_cloud/)
+      description: Default Workload Identity Provider associated with GitLab to [authenticate with OpenID Connect](https://docs.gitlab.com/ci/cloud_services/google_cloud/)
       default: ''
 ---
 variables:
@@ -19,12 +19,12 @@ variables:
   GCP_OIDC_ACCOUNT: $[[ inputs.gcp-oidc-account ]]
   GCP_OIDC_PROVIDER: $[[ inputs.gcp-oidc-provider ]]
 
-.gcp-provider-auth:
-  before_script:
-    - set -e
-    - echo -e "[\\e[1;94mINFO\\e[0m] Installing GCP authentication with env GOOGLE_APPLICATION_CREDENTIALS file"
-    - echo $GCP_JWT > "$CI_BUILDS_DIR/.auth_token.jwt"
-    - |-
+.python-gcp-adc:
+  - |
+    if [[ "$GCP_JWT" ]]
+    then
+      echo -e "[\\e[1;94mINFO\\e[0m] Installing GCP authentication with env GOOGLE_APPLICATION_CREDENTIALS file"
+      echo $GCP_JWT > "$CI_BUILDS_DIR/.auth_token.jwt"
       cat << EOF > "$CI_BUILDS_DIR/google_application_credentials.json"
       {
         "type": "external_account",
@@ -36,24 +36,25 @@ variables:
         },
         "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_OIDC_ACCOUNT}:generateAccessToken"
       }
-      EOF
-    - export GOOGLE_APPLICATION_CREDENTIALS="$CI_BUILDS_DIR/google_application_credentials.json"
-
+    EOF
+      export GOOGLE_APPLICATION_CREDENTIALS="$CI_BUILDS_DIR/google_application_credentials.json"
+    else
+      echo '[WARN] $GCP_JWT is not set: cannot setup Application Default Credentials (ADC) authentication'
+    fi
 
 .python-base:
   image: $PYTHON_IMAGE
   services:
     - name: "$TBC_TRACKING_IMAGE"
-      command: ["--service", "python", "7.8.0"]
+      command: ["--service", "python", "7.10.2"]
+  id_tokens:
+    GCP_JWT:
+      aud: "$GCP_OIDC_AUD"
   variables:
     GCP_JWT: $GCP_JWT
   before_script:
-    - !reference [.gcp-provider-auth, before_script]
     - !reference [.python-scripts]
+    - !reference [.python-gcp-adc]
     - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
     - cd ${PYTHON_PROJECT_DIR}
     - guess_build_system
-
-  id_tokens:
-    GCP_JWT:
-      aud: "$GCP_OIDC_AUD"
diff --git a/templates/gitlab-ci-python-vault.yml b/templates/gitlab-ci-python-vault.yml
index e9ec807653e98c977d562bd4247d5b8e6f43c5c7..1d49fdc6b3826b137f0c3fb4e97e3e60e1b98f21 100644
--- a/templates/gitlab-ci-python-vault.yml
+++ b/templates/gitlab-ci-python-vault.yml
@@ -22,7 +22,7 @@ variables:
 .python-base:
   services:
     - name: "$TBC_TRACKING_IMAGE"
-      command: ["--service", "python", "7.8.0"]
+      command: ["--service", "python", "7.10.2"]
     - name: "$TBC_VAULT_IMAGE"
       alias: "vault-secrets-provider"
   variables:
diff --git a/templates/gitlab-ci-python.yml b/templates/gitlab-ci-python.yml
index 0ab9be523dacc74a092e09a7dc718cbcc54639db..d26fa2b5d7835c391dd7965ce03190d808d9b9ea 100644
--- a/templates/gitlab-ci-python.yml
+++ b/templates/gitlab-ci-python.yml
@@ -30,6 +30,7 @@ spec:
       - pipenv
       - reqfile
       - uv
+      - hatch
       default: auto
     reqs-file:
       description: |-
@@ -125,7 +126,7 @@ spec:
       default: $CI_PROJECT_PATH/$PYTHON_PROJECT_DIR
     sbom-opts:
       description: Options for syft used for SBOM analysis
-      default: --override-default-catalogers python-package-cataloger
+      default: --override-default-catalogers python-package-cataloger --select-catalogers -file
     release-enabled:
       description: Enable Release
       type: boolean
@@ -157,7 +158,7 @@ spec:
       description: |-
         Target PyPI repository to publish packages.
 
-        _defaults to [GitLab project's packages repository](https://docs.gitlab.com/ee/user/packages/pypi_repository/)_
+        _defaults to [GitLab project's packages repository](https://docs.gitlab.com/user/packages/pypi_repository/)_
       default: ${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi
     black-enabled:
       description: Enable black
@@ -294,7 +295,18 @@ workflow:
     # else (Ready MR): auto & failing
     - when: on_success
 
+# software delivery job prototype: run on production and integration branches + release pipelines
+.delivery-policy:
+  rules:
+    # on tag with release pattern
+    - if: '$CI_COMMIT_TAG =~ $RELEASE_REF'
+    # on production or integration branch(es)
+    - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
+
 variables:
+  # Global TBC SBOM Mode (onrelease -> only generate SBOMs for releases, always -> generate SBOMs for all refs)
+  TBC_SBOM_MODE: "onrelease"
+
   # Default Docker image (can be overridden)
   PYTHON_IMAGE: $[[ inputs.image ]]
   # Default Python project root directory
@@ -327,7 +339,7 @@ variables:
   PYTHON_RELEASE_COMMIT_MESSAGE: $[[ inputs.release-commit-message ]]
 
   # By default, publish on the Packages registry of the project
-  # https://docs.gitlab.com/ee/user/packages/pypi_repository/#authenticate-with-a-ci-job-token
+  # https://docs.gitlab.com/user/packages/pypi_repository/#authenticate-with-a-ci-job-token
   PYTHON_REPOSITORY_URL: $[[ inputs.repository-url ]]
   PYTHON_REPOSITORY_USERNAME: gitlab-ci-token
   PYTHON_REPOSITORY_PASSWORD: $CI_JOB_TOKEN
@@ -382,6 +394,17 @@ variables:
       echo -e "[\\e[1;91mERROR\\e[0m] $*"
   }
 
+  function log_elapsed_time() {
+    _end_time=$(get_current_ts_ms)
+    _named=$1
+    _start_time=$2
+
+    _diff_ms=$((_end_time - _start_time))
+    _elapsed_sec="$((_diff_ms / 1000)).$(((_diff_ms % 1000) / 100))"
+
+    log_info " *** ${_named} took \\e[32m$_elapsed_sec\\e[0m seconds"  
+  }
+
   function fail() {
     log_error "$*"
     exit 1
@@ -395,6 +418,16 @@ variables:
     fi
   }
 
+  function get_current_ts_ms() {
+    if command -v busybox > /dev/null
+    then
+      # no easy way to retrieve ms in BusyBox
+      echo $(($(date +%s) * 1000))
+    else
+      echo $(($(date +%s%N) / 1000000))
+    fi
+  }
+
   function install_ca_certs() {
     certs=$1
     if [[ -z "$certs" ]]
@@ -635,12 +668,26 @@ variables:
     fi
   }
 
+  function enforce_python_cmd() {
+    _p3=$(command -v python3)
+    if [[ "$_p3" ]] && ! command -v python > /dev/null
+    then
+      _p3dir=$(dirname "$_p3")
+      ln -s "$_p3" "$_p3dir/python"
+      if [ -n "$TRACE" ]; then
+        log_info "python3 symlinked to $_p3dir/python"
+      fi
+    fi
+  }
   function guess_build_system() {
+    _start_time=$(get_current_ts_ms)
+
     case "${PYTHON_BUILD_SYSTEM:-auto}" in
     auto)
       ;;
-    poetry*|setuptools*|pipenv*|uv*)
-      log_info "--- Build system explicitly declared: ${PYTHON_BUILD_SYSTEM}"
+    poetry*|setuptools*|pipenv*|uv*|hatch*)
+      export PYTHON_BUILD_SYSTEM_CMD="${PYTHON_BUILD_SYSTEM%%[=<>]*}"
+      log_info "--- Build system explicitly declared: ${PYTHON_BUILD_SYSTEM} cmd=\\e[33m$PYTHON_BUILD_SYSTEM_CMD\\e[0m"
       return
       ;;
     reqfile)
@@ -665,6 +712,7 @@ variables:
       then
         log_info "--- Build system auto-detected: uv (uv.lock and pyproject.toml)"
         export PYTHON_BUILD_SYSTEM="uv"
+        export PYTHON_BUILD_SYSTEM_CMD="uv"
         return
       fi
       log_error "--- Build system auto-detected: uv (uv.lock) but no pyproject.toml found: please read template doc"
@@ -682,6 +730,13 @@ variables:
       poetry.core.masonry.api)
         log_info "--- Build system auto-detected: PEP 517 with Poetry backend"
         export PYTHON_BUILD_SYSTEM="poetry"
+        export PYTHON_BUILD_SYSTEM_CMD="poetry"
+        return
+        ;;
+      hatchling.build)
+        log_info "--- Build system auto-detected: PEP 517 with Hatch backend"
+        export PYTHON_BUILD_SYSTEM="hatch"
+        export PYTHON_BUILD_SYSTEM_CMD="hatch"
         return
         ;;
       setuptools.build_meta)
@@ -708,17 +763,12 @@ variables:
       log_error "--- Build system auto-detect failed: please read template doc"
       exit 1
     fi
-  }
 
-  function maybe_install_poetry() {
-    if [[ "$PYTHON_BUILD_SYSTEM" =~ ^poetry ]] && ! command -v poetry > /dev/null
-    then
-      # shellcheck disable=SC2086
-      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
-    fi
-  }
-  function maybe_install_uv() {
-    if [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]] && ! command -v uv > /dev/null
+    log_elapsed_time "guess_build_system" "$_start_time"
+    }
+
+  function maybe_install_build_system() {
+    if ! command -v "$PYTHON_BUILD_SYSTEM_CMD" > /dev/null
     then
       # shellcheck disable=SC2086
       pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
@@ -727,23 +777,23 @@ variables:
 
   # install requirements
   function install_requirements() {
+    _start_time=$(get_current_ts_ms)
+
     case "$PYTHON_BUILD_SYSTEM" in
     poetry*)
       if  [[ ! -f "poetry.lock" ]]; then
         log_warn "Using Poetry but \\e[33;1mpoetry.lock\\e[0m file not found: you shall commit it with your project files"
       fi
-      maybe_install_poetry
+      maybe_install_build_system
       poetry install ${PYTHON_EXTRA_DEPS:+--extras "$PYTHON_EXTRA_DEPS"}
       ;;
     setuptools*)
-      # shellcheck disable=SC2086
-      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
+      maybe_install_build_system
       # shellcheck disable=SC2086
       pip install ${PIP_OPTS} ".${PYTHON_EXTRA_DEPS:+[$PYTHON_EXTRA_DEPS]}"
       ;;
     pipenv*)
-      # shellcheck disable=SC2086
-      pip install ${PIP_OPTS} "$PYTHON_BUILD_SYSTEM"
+      maybe_install_build_system
       if  [[ ! -f "Pipfile.lock" ]]; then
         log_warn "Using Pipenv but \\e[33;1mPipfile.lock\\e[0m file not found: you shall commit it with your project files"
         pipenv install --dev --system
@@ -772,21 +822,31 @@ variables:
       if  [[ ! -f "uv.lock" ]]; then
         log_warn "Using uv but \\e[33;1muv.lock\\e[0m file not found: you shall commit it with your project files"
       fi
-      maybe_install_uv
+      maybe_install_build_system
       uv sync --frozen ${PYTHON_EXTRA_DEPS:+--extra "$PYTHON_EXTRA_DEPS"}
       ;;
+    hatch*)
+      maybe_install_build_system
+      hatch env create
+      ;;
     esac
+
+    log_elapsed_time "install_requirements" "$_start_time"
   }
 
   function _run() {
     if [[ "$PYTHON_BUILD_SYSTEM" =~ ^poetry ]]
     then
-      maybe_install_poetry
+      maybe_install_build_system
       poetry run "$@"
     elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
     then
-      maybe_install_uv
+      maybe_install_build_system
       uv run "$@"
+    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^hatch ]]
+    then
+      maybe_install_build_system
+      $PYTHON_BUILD_SYSTEM_CMD run "$@"
     else
       "$@"
     fi
@@ -797,31 +857,49 @@ variables:
   }
 
   function _pip() {
+    cmd=$1
+    shift
+
     if [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
     then
-      maybe_install_uv
+      maybe_install_build_system
       # shellcheck disable=SC2086
-      uv pip ${PIP_OPTS} "$@"
+      uv pip "$cmd" ${PIP_OPTS} "$@"
     else
       # shellcheck disable=SC2086
-      _run pip ${PIP_OPTS} "$@"
+      _run pip "$cmd" ${PIP_OPTS} "$@"
     fi
   }
 
   function py_package() {
+    _start_time=$(get_current_ts_ms)
+
+    # clean reports for this job but there not impact on other jobs
+    rm -fr "$PYTHON_PROJECT_DIR/reports"
+
     if [[ "$PYTHON_BUILD_SYSTEM" =~ ^poetry ]]
     then
-      maybe_install_poetry
-      poetry build
+      maybe_install_build_system
+      log_info "--- build packages (poetry)..."
+      poetry build ${TRACE+--verbose}
     elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
     then
-      maybe_install_uv
-      uv build
+      maybe_install_build_system
+      log_info "--- build packages (uv)..."
+      uv build ${TRACE+--verbose}
+    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^hatch ]]
+    then
+      log_info "--- build packages (hatch)..."
+      maybe_install_build_system
+      $PYTHON_BUILD_SYSTEM_CMD build
     else
+      log_info "--- build packages ..."
       # shellcheck disable=SC2086
       pip install ${PIP_OPTS} build
       python -m build
     fi
+
+    log_elapsed_time "py_package" "$_start_time"
   }
 
   function configure_scm_auth() {
@@ -847,7 +925,31 @@ variables:
     fi
   }
 
+  function py_bump_my_version() {
+    mkdir -p -m 777 tbc_tmp >&2
+    py_cur_version="$1"
+    py_release_part="$2"
+    echo "$py_cur_version" > tbc_tmp/version.txt
+    _pip install bump-my-version >&2
+    log_info "[bump-my-version] increase \\e[1;94m${py_release_part}\\e[0m (from current \\e[1;94m${py_cur_version}\\e[0m)" >&2
+    _run bump-my-version bump ${TRACE+--verbose} --current-version "$py_cur_version" "$py_release_part" tbc_tmp/version.txt  >&2
+    cat tbc_tmp/version.txt
+    rm -fr tbc_tmp/version.txt >&2
+  }
+
+  function py_commit_pyproject() {
+    py_cur_version="$1"
+    py_next_version="$2"  
+    # Git commit and tag
+    git add pyproject.toml
+    # emulate bump-my-version to generate commit message
+    py_commit_message=$(python -c "print('$PYTHON_RELEASE_COMMIT_MESSAGE'.format(current_version='$py_cur_version', new_version='$py_next_version'))")
+    git commit -m "$py_commit_message"
+    git tag "$py_next_version"  
+  }
+
   function py_release() {
+  log_info "PYTHON_RELEASE_ENABLED:$PYTHON_RELEASE_ENABLED  PYTHON_PUBLISH_ENABLED:$PYTHON_PUBLISH_ENABLED"
     # 1: retrieve next release info from semantic-release
     if [ "$SEMREL_INFO_ON" ] && [ "$PYTHON_SEMREL_RELEASE_DISABLED" != "true" ]
     then
@@ -866,7 +968,7 @@ variables:
     # 2: bump-my-version (+ Git commit & tag)
     if [[ "$PYTHON_BUILD_SYSTEM" =~ ^poetry ]]
     then
-      maybe_install_poetry
+      maybe_install_build_system
       if [[ -z "$py_next_version" ]]
       then
         py_cur_version=$(poetry version --short)
@@ -884,7 +986,7 @@ variables:
       git tag "$py_next_version"
     elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
     then
-      maybe_install_uv
+      maybe_install_build_system
       if [[ -z "$py_next_version" ]]
       then
         # quick version waiting for uv to manage bump
@@ -909,6 +1011,19 @@ variables:
       py_commit_message=$(python -c "print('$PYTHON_RELEASE_COMMIT_MESSAGE'.format(current_version='$py_cur_version', new_version='$py_next_version'))")
       git commit -m "$py_commit_message"
       git tag --force "$py_next_version"
+    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^hatch ]]
+    then
+      maybe_install_build_system
+      if [[ -z "$py_next_version" ]]
+      then
+        py_cur_version=$(hatch version)
+        py_next_version=$(py_bump_my_version "$py_cur_version" "$PYTHON_RELEASE_NEXT")
+      fi
+
+      log_info "[hatch] change version \\e[1;94m${py_cur_version}\\e[0m → \\e[1;94m${py_next_version}\\e[0m"
+      _pip install toml-cli
+      _run toml set --toml-path pyproject.toml project.version "$py_next_version"
+      py_commit_pyproject "$py_cur_version" "$py_next_version"
     else
       # Setuptools / bump-my-version
       # shellcheck disable=SC2086
@@ -920,12 +1035,19 @@ variables:
         # create cfg in case it doesn't exist - will be updated by bumpversion
         if [[ ! "$py_cur_version" && ! -f ".bumpversion.cfg" && ! -f ".bumpversion.toml" && ! -f "pyproject.toml" && ! -f "setup.cfg" ]]
         then
-          log_error "Current version not defined and not version file found, set initial version at least in .bumpversion.toml or pyproject.toml"
+          log_warn "Current version not defined and no version file found: please set initial version in .bumpversion.toml, .bumpversion.cfg, setup.cfg or pyproject.toml ([tool.bumpversion] section)"
         fi
         bump-my-version bump ${TRACE+--verbose} --current-version "${py_cur_version:-${PYTHON_RELEASE_START_VERSION:-0.0.0}}" --new-version "$py_next_version" --commit ${PYTHON_RELEASE_COMMIT_MESSAGE:+--message "$PYTHON_RELEASE_COMMIT_MESSAGE"} --tag --tag-name "{new_version}" "$py_release_part"
-      elif [[ -f ".bumpversion.cfg" ]]
+      elif [[ -f ".bumpversion.cfg" || -f ".bumpversion.toml" || -f "pyproject.toml" || -f "setup.cfg" ]]
       then
-        # current version shall be set in .bumpversion.cfg
+        if [[ ! -f ".bumpversion.cfg" && ! -f ".bumpversion.toml" && ! -f "setup.cfg"  ]] #  pyproject.toml case
+        then
+          if ! grep -q '^[[:space:]]*\[tool\.bumpversion\]' "pyproject.toml"
+          then
+            log_warn "Current version not defined and no version file found: please set initial version in pyproject.toml ([tool.bumpversion] section)"
+          fi
+        fi
+        # current version shall be set in bump-my-version config file
         py_release_part="$PYTHON_RELEASE_NEXT"
         log_info "[bump-my-version bump] increase \\e[1;94m${py_release_part}\\e[0m"
         bump-my-version bump ${TRACE+--verbose} --commit ${PYTHON_RELEASE_COMMIT_MESSAGE:+--message "$PYTHON_RELEASE_COMMIT_MESSAGE"} --tag --tag-name "{new_version}" "$py_release_part"
@@ -937,7 +1059,7 @@ variables:
         log_info "[bump-my-version] increase \\e[1;94m${py_release_part}\\e[0m (from current \\e[1;94m${py_cur_version}\\e[0m)"
         bump-my-version bump ${TRACE+--verbose} --current-version "$py_cur_version" --commit ${PYTHON_RELEASE_COMMIT_MESSAGE:+--message "$PYTHON_RELEASE_COMMIT_MESSAGE"} --tag --tag-name "{new_version}" "$py_release_part" setup.py
       else
-        log_error "--- setup.py or .bumpversion.cfg file required to retrieve current version: cannot perform release"
+        log_error "--- setup.py, .bumpversion.toml, pyproject.toml, .bumpversion.cfg(deprecated) or setup.cfg(deprecated) file required to retrieve current version: cannot perform release"
         exit 1
       fi
     fi
@@ -949,42 +1071,34 @@ variables:
   }
 
   function py_publish() {
-    if [[ "$PYTHON_BUILD_SYSTEM" =~ ^poetry ]]
+    if [[ "$PYTHON_PACKAGE_ENABLED" != "true" ]]
     then
-      maybe_install_poetry
+      py_package
+    fi
 
-      if [[ "$PYTHON_PACKAGE_ENABLED" != "true" ]]
-      then
-        log_info "--- build packages (poetry)..."
-        poetry build ${TRACE+--verbose}
-      fi
+    if [[ "$PYTHON_BUILD_SYSTEM" =~ ^poetry ]]
+    then
+      maybe_install_build_system
 
       log_info "--- publish packages (poetry) to $PYTHON_REPOSITORY_URL with user $PYTHON_REPOSITORY_USERNAME..."
       poetry config repositories.user_defined "$PYTHON_REPOSITORY_URL"
       poetry publish ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --repository user_defined
     elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^uv ]]
     then
-      maybe_install_uv
+      maybe_install_build_system
   
-      if [[ "$PYTHON_PACKAGE_ENABLED" != "true" ]]
-      then
-        log_info "--- build packages (uv)..."
-        uv build ${TRACE+--verbose}
-      fi
-
       log_info "--- publish packages (uv) to $PYTHON_REPOSITORY_URL with user $PYTHON_REPOSITORY_USERNAME..."
       uv publish ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --publish-url "$PYTHON_REPOSITORY_URL"
+    elif [[ "$PYTHON_BUILD_SYSTEM" =~ ^hatch ]]
+    then
+      maybe_install_build_system
+
+      log_info "--- publish packages (hatch) to $PYTHON_REPOSITORY_URL with user $PYTHON_REPOSITORY_USERNAME..."
+      hatch publish ${TRACE+--verbose} --no-prompt --yes --user "$PYTHON_REPOSITORY_USERNAME" --auth "$PYTHON_REPOSITORY_PASSWORD" --repo "$PYTHON_REPOSITORY_URL"
     else
       # shellcheck disable=SC2086
       pip install ${PIP_OPTS} build twine
 
-      if [[ "$PYTHON_PACKAGE_ENABLED" != "true" ]]
-      then
-        log_info "--- build packages (build)..."
-        rm -rf dist
-        python -m build
-      fi
-
       log_info "--- publish packages (twine) to $PYTHON_REPOSITORY_URL with user $PYTHON_REPOSITORY_USERNAME..."
       twine upload ${TRACE+--verbose} --username "$PYTHON_REPOSITORY_USERNAME" --password "$PYTHON_REPOSITORY_PASSWORD" --repository-url "$PYTHON_REPOSITORY_URL" dist/*
     fi
@@ -1029,7 +1143,7 @@ stages:
   image: $PYTHON_IMAGE
   services:
     - name: "$TBC_TRACKING_IMAGE"
-      command: ["--service", "python", "7.8.0"]
+      command: ["--service", "python", "7.10.2"]
   variables:
     # set local cache dir; most Python tools honour XDG specs
     XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache"
@@ -1037,6 +1151,8 @@ stages:
     POETRY_CACHE_DIR: "$CI_PROJECT_DIR/.cache/poetry"
     PIPENV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pipenv"
     UV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/uv"
+    HATCH_CACHE_DIR: "$CI_PROJECT_DIR/.cache/hatch/.cache/"
+    HATCH_DATA_DIR: "$CI_PROJECT_DIR/.cache/hatch/.local"
     POETRY_VIRTUALENVS_IN_PROJECT: "false"
   cache:
     key: "$CI_COMMIT_REF_SLUG-python"
@@ -1046,6 +1162,7 @@ stages:
   before_script:
     - !reference [.python-scripts]
     - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
+    - enforce_python_cmd
     - cd ${PYTHON_PROJECT_DIR}
     - guess_build_system
 
@@ -1092,7 +1209,7 @@ py-lint:
     - install_requirements
     - _pip install pylint_gitlab # codeclimate reports
     # run pylint and generate reports all at once
-    - _run pylint --output-format=colorized,pylint_gitlab.GitlabCodeClimateReporter:reports/py-lint.codeclimate.json,parseable:reports/py-lint.parseable.txt ${PYLINT_ARGS} ${PYLINT_FILES:-$(find -type f -name "*.py" -not -path "./.cache/*")}
+    - _run pylint --output-format=colorized,pylint_gitlab.GitlabCodeClimateReporter:reports/py-lint.codeclimate.json,parseable:reports/py-lint.parseable.txt ${PYLINT_ARGS} ${PYLINT_FILES:-$(find -type f -name "*.py" -not -path "./.cache/*" -not -path "./.venv/*")}
   artifacts:
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
     expire_in: 1 day
@@ -1255,7 +1372,7 @@ py-pytest:
     - mkdir -p -m 777 reports
     - install_requirements
     - _pip install pytest pytest-cov coverage
-    - _python -m pytest --junit-xml=reports/TEST-pytests.xml --cov --cov-report term  --cov-report xml:reports/py-coverage.cobertura.xml ${PYTEST_ARGS}
+    - _run pytest --junit-xml=reports/TEST-pytests.xml --cov --cov-report term  --cov-report xml:reports/py-coverage.cobertura.xml ${PYTEST_ARGS}
   rules:
     # skip if $PYTEST_ENABLED not set
     - if: '$PYTEST_ENABLED != "true"'
@@ -1348,9 +1465,14 @@ py-trivy:
           ;;
         uv*)
           log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt from uv.lock\\e[0m)"
-          maybe_install_uv
+          maybe_install_build_system
           uv export > ./reports/requirements.txt
           ;;
+        hatch*)
+          log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt\\e[0m)"
+          maybe_install_build_system
+          hatch run python -m pip freeze > reports/requirements.txt # hatch dep show requirements not working and complete
+          ;;
         *)
           log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt\\e[0m)"
           install_requirements
@@ -1408,9 +1530,14 @@ py-sbom:
           ;;
         uv*)
           log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt from uv.lock\\e[0m)"
-          maybe_install_uv
+          maybe_install_build_system
           uv export > ./reports/requirements.txt
           ;;
+        hatch*)
+          log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt from uv.lock\\e[0m)"
+          maybe_install_build_system
+          hatch run python -m pip freeze > reports/requirements.txt # hatch dep show requirements not working and complete
+          ;;
         *)
           log_info "$PYTHON_BUILD_SYSTEM build system used (\\e[32mmust generate pinned requirements.txt\\e[0m)"
           install_requirements
@@ -1447,7 +1574,13 @@ py-sbom:
     # exclude if disabled
     - if: '$PYTHON_SBOM_DISABLED == "true"'
       when: never
-    - !reference [.test-policy, rules]
+    # 'always' mode: run
+    - if: '$TBC_SBOM_MODE == "always"'
+    # exclude unsupported modes
+    - if: '$TBC_SBOM_MODE != "onrelease"'
+      when: never
+    # 'onrelease' mode: use common software delivery rules
+    - !reference [.delivery-policy, rules]
   tags: $[[ inputs.py-sbom-job-tags ]]
 
 # (manual from master branch): triggers a release (tag creation)