diff --git a/templates/Python.yaml b/templates/Python.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1d4b93cc182416bcba6fb16058c1c0ca7da2cbbb
--- /dev/null
+++ b/templates/Python.yaml
@@ -0,0 +1,241 @@
+# -*- coding: utf-8 -*-
+# vim: ft=yaml
+#
+# Hidden template jobs to be used in `.gitlab-ci.yml`
+#
+# - `.python:black:check`: verify formatting of code with `black`
+#
+---
+#
+# .python:black:check
+# ===================
+#
+# The Python code must match `black` standard.
+#
+# USAGE
+# =====
+#
+# include:
+#   - project: EOLE/Infra/ci-tools
+#     ref: stable
+#     file: /templates/Python.yaml
+#
+# stages:
+#   - lint
+#
+# python:black: {extends: '.python:black:check'}
+#
+# REQUIREMENTS
+# ============
+#
+# - a `lint` stage must be present in your pipeline or it must be
+#   overriden by the extending job to feet your need
+#
+# - the `.not-on-stable` rules templates
+#
+# OPTIONAL VARIABLES
+# ==================
+#
+# - `PYTHON_BLACK_OPTS`: additional options to pass to `black`
+#
+# - `PYTHON_SOURCE_DIR`: top level directory where to run `black`,
+#   defaults to `.`
+#
+# - `PYTHON_BLACK_IMAGE`: name of the black docker image to use
+#
+.python:black:check:
+  stage: lint
+  extends: .not-on-stable
+  image: "${PYTHON_BLACK_IMAGE}"
+  variables:
+    PYTHON_BLACK_IMAGE: "hub.eole.education/proxyhub/pyfound/black:latest_release"
+    PYTHON_BLACK_OPTS: ''
+    PYTHON_SOURCE_DIR: '.'
+  script:
+    - echo -e "\e[0Ksection_start:`date +%s`:python-black\r\e[0KExecute 'black --check ${PYTHON_BLACK_OPTS} ${PYTHON_SOURCE_DIR}'"
+    - black --check ${PYTHON_BLACK_OPTS} ${PYTHON_SOURCE_DIR}
+    - echo -e "\e[0Ksection_end:`date +%s`:python-black\r\e[0K"
+
+
+#
+# .python:build:sdist
+# ===================
+#
+# Build the python source `.tar.gz`
+#
+# USAGE
+# =====
+#
+# include:
+#   - project: EOLE/Infra/ci-tools
+#     ref: stable
+#     file: /templates/Python.yaml
+#
+# stages:
+#   - build
+#
+# python:build:source {extends: '.python:build:sdist'}
+#
+# REQUIREMENTS
+# ============
+#
+# - a `build` stage must be present in your pipeline or it must be
+#   overriden by the extending job to feet your need
+#
+# - the `.on-release-tag` rules templates
+#
+# - a `setup.py` file
+#
+# OPTIONAL VARIABLES
+# ==================
+#
+# - `PYTHON_DIST_FILES`: files to upload, defaults to `dist/*.tar.gz'
+#
+# - `PYTHON_SOURCE_DIR`: top level directory of python source where to
+#   find `setup.py`, defaults to `.'
+#
+# - `PYTHON_OPTS`: additional options to pass to the python command
+#   line
+#
+# - `PYTHON_IMAGE`: name of the python docker image to use
+#
+.python:build:sdist:
+  stage: build
+  extends: .python:base
+  variables:
+    PYTHON_DIST_FILES: dist/*.tar.gz
+  artifacts:
+    paths:
+      - ${PYTHON_SOURCE_DIR}/${PYTHON_DIST_FILES}
+  script:
+    - echo -e "\e[0Ksection_start:`date +%s`:python-build-sdist\r\e[0KBuild source distribution from '${PYTHON_SOURCE_DIR}'"
+    - cd ${PYTHON_SOURCE_DIR}
+    - python setup.py sdist ${PYTHON_OPTS}
+    - echo -e "\e[0Ksection_end:`date +%s`:python-build-sdist\r\e[0K"
+
+
+#
+# .python:build:wheel
+# ===================
+#
+# Build the python wheel package
+#
+# USAGE
+# =====
+#
+# include:
+#   - project: EOLE/Infra/ci-tools
+#     ref: stable
+#     file: /templates/Python.yaml
+#
+# stages:
+#   - build
+#
+# python:build:wheel {extends: '.python:build:wheel'}
+#
+# REQUIREMENTS
+# ============
+#
+# - a `build` stage must be present in your pipeline or it must be
+#   overriden by the extending job to feet your need
+#
+# - the `.on-release-tag` rules templates
+#
+# - a `setup.py` file
+#
+# OPTIONAL VARIABLES
+# ==================
+#
+# - `PYTHON_DIST_FILES`: files to upload, defaults to `dist/*.whl'
+#
+# - `PYTHON_SOURCE_DIR`: top level directory of python source where to
+#   find `setup.py`, defaults to `.'
+#
+# - `PYTHON_OPTS`: additional options to pass to the python command
+#   line
+#
+# - `PYTHON_IMAGE`: name of the python docker image to use
+#
+.python:build:wheel:
+  extends: .python:build:sdist
+  variables:
+    PYTHON_DIST_FILES: dist/*.whl
+  script:
+    - echo -e "\e[0Ksection_start:`date +%s`:python-build-wheel\r\e[0KBuild wheel distribution from '${PYTHON_SOURCE_DIR}'"
+    - cd ${PYTHON_SOURCE_DIR}
+    - python setup.py bdist_wheel ${PYTHON_OPTS}
+    - echo -e "\e[0Ksection_end:`date +%s`:python-build-wheel\r\e[0K"
+
+
+#
+# .python:upload
+# ==============
+#
+# Upload python package files to the registry
+#
+# USAGE
+# =====
+#
+# include:
+#   - project: EOLE/Infra/ci-tools
+#     ref: stable
+#     file: /templates/Python.yaml
+#
+# stages:
+#   - build
+#
+# python:upload {extends: '.python:upload'}
+#
+# REQUIREMENTS
+# ============
+#
+# - a `build` stage must be present in your pipeline or it must be
+#   overriden by the extending job to feet your need
+#
+# - the `.on-release-tag` rules templates
+#
+# OPTIONAL VARIABLES
+# ==================
+#
+# - `PYTHON_DIST_FILES`: files to upload, defaults to `dist/*'
+#
+# - `PYTHON_SOURCE_DIR`: top level directory of python source where to
+#   find `setup.py`, defaults to `.`
+#
+# - `PYTHON_REPOSITORY_URL`: pypi repository URL, defaults to
+#   `${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi`
+#
+# - `PYTHON_IMAGE`: name of the python docker image to use
+#
+.python:upload:
+  stage: release
+  extends: .python:base
+  variables:
+    PYTHON_REPOSITORY_URL: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi
+  script:
+    - echo -e "\e[0Ksection_start:`date +%s`:python-twine-upload\r\e[0KUpload '${PYTHON_SOURCE_DIR}/${PYTHON_DIST_FILES}' to repository '${PYTHON_REPOSITORY_URL}'"
+    - cd ${PYTHON_SOURCE_DIR}
+    - pip install twine
+    - 'TWINE_PASSWORD=${CI_JOB_TOKEN}
+       TWINE_USERNAME=gitlab-ci-token
+       python -m twine upload
+       --repository-url ${PYTHON_REPOSITORY_URL}
+       ${PYTHON_DIST_FILES}'
+    - echo -e "\e[0Ksection_end:`date +%s`:python-twine-upload\r\e[0K"
+
+
+#
+# .python:base
+# ============
+#
+# Base template for build and upload
+#
+.python:base:
+  extends: .on-release-tag
+  image: "${PYTHON_IMAGE}"
+  variables:
+    PYTHON_IMAGE: "hub.eole.education/proxyhub/library/python:latest"
+    PYTHON_OPTS: ''
+    PYTHON_SOURCE_DIR: '.'
+    PYTHON_DIST_FILES: 'dist/*'
+...