PYTHONPATH VariableGuide officiel d'emballage Python
Un module de Python est un seul fichier (avec une extension .py ) qui contient du code Python. Il se compose généralement de classes, de fonctions et de variables qui peuvent être utilisées par un autre code Python. Les modules sont utilisés pour organiser le code en unités logiques et faciliter la réutilisabilité du code.
Par exemple, considérons un module nommé math_operations.py qui contient des fonctions pour effectuer des opérations mathématiques comme l'addition, la soustraction, la multiplication, etc. Vous pouvez importer et utiliser ces fonctions dans d'autres scripts Python.
# math_operations.py
def add ( x , y ):
return x + y
def subtract ( x , y ):
return x - y Un package dans Python est une collection de modules regroupés dans un répertoire. Un package est généralement représenté par un répertoire contenant un fichier __init__.py (qui peut être vide) et un ou plusieurs modules Python. Le fichier __init__.py indique à Python que le répertoire doit être traité comme un package.
Par exemple, considérez un package nommé my_package :
my_package/
├── __init__.py
├── module1.py
└── module2.py
Ici, my_package est un package contenant module1.py et module2.py , qui peut être importé à l'aide de la notation de points ( my_package.module1 , my_package.module2 ).
Un sous-package de Python est un package imbriqué dans un autre package. Cela signifie qu'un package peut contenir d'autres packages ainsi que des modules. Les sous-packages sont créés en organisant des répertoires et en ajoutant des fichiers __init__.py de manière appropriée pour définir la structure du package.
Par exemple:
my_parent_package/
├── __init__.py
└── my_sub_package/
├── __init__.py
├── module3.py
└── module4.py
Dans cette structure, my_sub_package est un sous-package de my_parent_package , et il peut contenir ses propres modules ( module3.py , module4.py ). Le sous-package peut être importé à l'aide de la notation de points ( my_parent_package.my_sub_package.module3 ).
Un package de distribution (ou simplement une distribution ) dans Python fait référence à une collection emballée de code Python et de ressources rendues disponibles pour l'installation. Il comprend généralement des modules, des packages, des fichiers de données, des fichiers de configuration et d'autres ressources nécessaires dans un but spécifique (par exemple, une bibliothèque ou une application).
Les packages de distribution sont souvent distribués et installés à l'aide de gestionnaires de packages Python tels que pip et peuvent être téléchargés dans des référentiels de packages comme PYPI (Python Package Index) pour une distribution et une installation faciles par d'autres développeurs.
Par exemple, les packages de distribution populaires incluent numpy , fast-api , pandas , etc., qui sont installés à l'aide pip et fournissent des fonctionnalités qui peuvent être utilisées dans des projets Python.
sdistsetup.pypython setup.py build sdist pour construire la distribution de la sourcepip install ./dist/<package_name>.tar.gz pour installer le packagepip list pour voir si le package est installé. Si les modifications sont effectuées dans le package, utilisez pip install . qui créera et installera le dernier package à la volée.
Ou utilisez simplement modifiable afin que vous n'ayez pas toujours à reconstruire le package que de nouvelles modifications sont apportées:
pip install --editable . sdist est abrégé pour la distribution des sources, un fichier .tar contenant notre code appelé "SDIST". Cela signifie que le package de distribution ne contient qu'un sous-ensemble de notre code source.
Une "distribution source" est essentiellement un dossier zippé contenant notre code source .
wheel Distribution des sources ( sdist ) :
*.py ), des fichiers de configuration et d'autres actifs liés au projet.python setup.py sdist . Roues ( bdist_wheel ) :
sdist ..whl .python setup.py bdist_wheel . Installation plus rapide :
*.whl ) est généralement plus rapide que l'installation à partir de distributions source ( *.tar.gz ).Facilité d'utilisation pour les utilisateurs :
sdist et bdist_wheelsdist et bdist_wheel pour les packages Python pour accueillir différents cas d'utilisation et plates-formes.python setup.py sdist bdist_wheelExigences de compilation :
Malheureusement, la construction de roues pour tous les systèmes d'exploitation devient difficile si vous avez une étape de compilation requise, de sorte que certains responsables OSS ne construisent que des roues pour un seul système d'exploitation.
pip exécute réellement le setup.py (ou les fichiers équivalents) sur votre machine, juste après avoir téléchargé le SDIST.La construction de la roue peut nécessiter du code de compilation
gcc si le code source est en C, mais d'autres langues nécessitent leurs propres compilateurs. L'utilisateur doit les installer sur sa machine ou l' pip install ... échouera simplement. Cela peut se produire lorsque vous installez numpy , pandas , scipy , pytorch , tensorflow , etc. setup.py peut contenir du code arbitraire. C'est très peu sûr. Un setup.py peut contenir du code malveillant.
Gestion des dépendances :
gcc , etc.) pour installer avec succès des packages qui nécessitent une compilation.Présentations de sécurité :
setup.py pour l'installation de package peut être peu sûre car elle exécute du code arbitraire.wheelUn nom de fichier de roue est décomposé en pièces séparées par des traits de traits:
{dist}-{version}(-{build})?-{python}-{abi}-{platform}.whl Chaque section dans {brackets} est une balise, ou un composant du nom de la roue qui a un sens sur ce que la roue contient et où la roue fonctionnera ou ne fonctionnera pas.
Par exemple: dist/packaging-0.0.0-py3-none-any.whl
packaging est le nom du package0.0.0 est le numéro de Verisonpy3 indique qu'il est construit pour Python3abi Tag. ABI signifie Interface binaire d'application.any représente que ce package est construit pour fonctionner sur n'importe quelle plate-forme.Autres exemples:
cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whlchardet-3.0.4-py2.py3-none-any.whlPyYAML-5.3.1-cp38-cp38-win_amd64.whlnumpy-1.18.4-cp38-cp38-win32.whlscipy-1.4.1-cp36-cp36m-macosx_10_6_intel.whlLa raison en est que le fichier de roue contient le code binaire pré-contenu qui aide à installer le package rapidement.
Entretien des emballages :
sdist et bdist_wheel pour maximiser la compatibilité et la facilité d'utilisation pour les utilisateurs.Considérez l'expérience utilisateur :
build l'outil CLI et pyproject.tomlLes «dépendances de construction» sont tout ce qui doit être installé sur votre système afin de construire votre package de distribution dans un SDIST ou une roue.
Par exemple, nous devions pip install wheel afin d'exécuter python setup.py bdist_wheel pour que wheel est une dépendance de construction pour les roues de construction.
Les fichiers setup.py peuvent devenir complexes.
Vous devrez peut-être pip install ... des bibliothèques externes et les importer dans votre fichier setup.py pour accueillir des processus de construction complexes.
La conférence montre pytorch et airflow comme exemples de packages avec des fichiers setup.py complexes.
D'une manière ou d'une autre, vous devez être en mesure de documenter les dépendances de construction en dehors de setup.py .
S'ils étaient documentés dans le fichier setup.py … vous ne pourriez pas exécuter le fichier setup.py pour lire les dépendances documentées (comme s'ils étaient spécifiés dans une list quelque part dans le fichier).
Ceci est le problème d'origine que pyproject.toml devait résoudre.
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute.
requires = [ " setuptools>=62.0.0 " , " wheel " ] pyproject.toml se trouve à côté de setup.py dans l'arborescence de fichiers
L'outil CLI build ( pip install build ) est un projet spécial de la Python Packaging Authority (PYPA) qui
[build-system] dans le pyproject.toml ,pip install build
# both setup.py and pypproject.toml should be together, ideally in the root directory
# python -m build --sdist --wheel path/to/dir/with/setup.py/and/pyproject.toml
python -m build --sdist --wheel . setup.py à setup.cfg Fichier de configurationSe déplaçant de
# setup.py
from pathlib import Path
from setuptools import find_packages , setup
import wheel
# Function to read the contents of README.md
def read_file ( filename : str ) -> str :
filepath = Path ( __file__ ). resolve (). parent / filename
with open ( filepath , encoding = "utf-8" ) as file :
return file . read ()
setup (
name = "packaging-demo" ,
version = "0.0.0" ,
packages = find_packages (),
# package meta-data
author = "Amit Vikram Raj" ,
author_email = "[email protected]" ,
description = "Demo for Python Packaging" ,
license = "MIT" ,
# Set the long description from README.md
long_description = read_file ( "README.md" ),
long_description_content_type = "text/markdown" ,
# install requires: libraries that are needed for the package to work
install_requires = [
"numpy" , # our package depends on numpy
],
# setup requires: the libraries that are needed to setup/build
# the package distribution
# setup_requires=[
# "wheel", # to build the binary distribution we need wheel package
# ],
)À
# setup.py
from setuptools import setup
# Now setup.py takes it's configurations from setup.cfg file
setup () # setup.cfg
[metadata]
name = packaging-demo
version = attr: packaging_demo.VERSION
author = Amit Vikram Raj
author_email = [email protected]
description = Demo for Python Packaging
long_description = file: README.md
keywords = one, two
license = MIT
classifiers =
Framework :: Django
Programming Language :: Python :: 3
[options]
zip_safe = False
include_package_data = True
# same as find_packages() in setup()
packages = find:
python_requires = >=3.8
install_requires =
numpy
importlib-metadata ; python_version<"3.10"Le paramètre addtional est également passé au fichier
pyproject.toml. Ici, nous avons spécifiébuild-systemsimilaire àsetup_requiresdanssetup.py
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute
requires = [ " setuptools " , " wheel " , " numpy<1.24.3 " ]
# Adding ruff.toml to pyproject.toml
[ tool . ruff ]
line-length = 99
[ tool . ruff . lint ]
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
select = [ " E " , " F " , " B " , " ERA " ]
# 2. Avoid enforcing line-length violations (`E501`)
ignore = [ " E501 " ]
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = [ " B " ]
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
[ tool . ruff . lint . per-file-ignores ]
"__init__.py" = [ " E402 " ]
"**/{tests,docs,tools}/*" = [ " E402 " ]
# copying isort configurations from .isort.cfg to pyproject.toml
[ tool . isort ]
profile = " black "
multi_line_output = " VERTICAL_HANGING_INDENT "
force_grid_wrap = 2
line_length = 99
# copying balck config from .black.toml to pyproject.toml
[ tool . black ]
line-length = 99
exclude = " .venv "
# copying flake8 config from .flake8 to pyproject.toml
[ tool . flake8 ]
docstring-convention = " all "
extend-ignore = [ " D107 " , " D212 " , " E501 " , " W503 " , " W605 " , " D203 " , " D100 " ,
" E305 " , " E701 " , " DAR101 " , " DAR201 " ]
exclude = [ " .venv " ]
max-line-length = 99
# radon
radon-max-cc = 10
# copying pylint config from .pylintrc to pyproject.toml
[ tool . pylint . "messages control" ]
disable = [
" line-too-long " ,
" trailing-whitespace " ,
" missing-function-docstring " ,
" consider-using-f-string " ,
" import-error " ,
" too-few-public-methods " ,
" redefined-outer-name " ,
] Nous avons traité setup.py comme un fichier de configuration glorifié, ne profitant pas vraiment du fait qu'il s'agit d'un fichier python en y ajoutant une logique.
Ceci est plus courant que non. De plus, il y a eu un décalage général de l'utilisation de Python pour les fichiers de configuration, car cela ajoute de la complexité à l'utilisation des fichiers config (comme avoir à installer des bibliothèques afin d'exécuter le fichier de configuration).
setup.cfg est un fichier compagnon de setup.py qui nous permet de définir notre configuration de package dans un fichier texte statique - en particulier un fichier de format INI.
Toutes les valeurs que nous ne transmettons pas directement en tant qu'arguments à SetUp () seront recherchées par l'invocation de configuration () dans un fichier setup.cfg, qui est destinée à s'asseoir à côté de setup.py dans l'arborescence de fichier si elle est utilisée.
Maintenant, nous accumulons beaucoup de fichiers!
setup.pysetup.cfgpyproject.tomlREADME.md.pylintrc , .flake8 , .blackrc , ruff.toml , .mypy , pre-commit-config.yaml , etc.CHANGELOG ou CHANGELOG.mdVERSION ou version.txt Il s'avère que presque tous ces fichiers peuvent être remplacés par pyproject.toml . Presque tous les outils de qualité de liaison / code prennent en charge l'analyse d'une section appelée [tool.<name>] EG [tool.black] section de pyproject.toml pour lire sa configuration!
Les documents de chaque outil individuel devraient vous dire comment y parvenir.
Ci-dessus est indiqué un pyproject.toml avec des configurations pour de nombreux outils de liaison que nous avons utilisés dans le cours.
setup.cfgetsetup.pypeuvent-ils également être remplacés?
setup.cfg à pyproject.tomlDe
setup.cfg
# setup.cfg
[metadata]
name = packaging-demo
version = attr: packaging_demo.VERSION
author = Amit Vikram Raj
author_email = [email protected]
description = Demo for Python Packaging
long_description = file: README.md
keywords = one, two
license = MIT
classifiers =
Programming Language :: Python :: 3
[options]
zip_safe = False
include_package_data = True
# same as find_packages() in setup()
packages = find:
python_requires = >=3.8
install_requires =
numpy
importlib-metadata ; python_version<"3.10"À
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute
requires = [ " setuptools>=61.0.0 " , " wheel " ]
# Adding these from setup.cfg in pyproject.toml file
[ project ]
name = " packaging-demo "
authors = [{ name = " Amit Vikram Raj " , email = " [email protected] " }]
description = " Demo for Python Packaging "
readme = " README.md "
requires-python = " >=3.8 "
keywords = [ " one " , " two " ]
license = { text = " MIT " }
classifiers = [ " Programming Language :: Python :: 3 " ]
dependencies = [ " numpy " , ' importlib-metadata; python_version<"3.10" ' ]
dynamic = [ " version " ]
# version = "0.0.3"
# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata
[ tool . setuptools . dynamic ]
# every while making changes in package, you can change the verison in one of these files
# version = {attr = "packaging_demo.VERSION"} # version read by 'packaging_demo/__init__.py' file
version = { file = [ " version.txt " ]} # version read by 'version.txt' file in root folder
python -m build --sdist --wheel .- s'exécute parfaitement, nous nous sommes débarrassés d'un autre fichier de configuration (setup.cfg)
setup.py par build-backend PEP 517 a ajouté un argument build-backend à pyproject.toml comme SO:
[ build-system ]
# Defined by PEP 518:
requires = [ " flit " ]
# Defined by this PEP:
build-backend = " flit.api:main " # The above toml config is equivalent to
import flit . api
backend = flit . api . main Le build-backend définit un point d'entrée (module Python exécutable dans ce cas) que la CLI build utilise pour effectuer réellement le travail de l'analyse pyproject.toml et de la construction de la roue et du sdist.
Cela signifie que vous pouvez implémenter votre propre backend de build aujourd'hui en écrivant un programme qui le fait, et vous pouvez l'utiliser en ajoutant votre package à requires = [...] et en spécifiant le point d'entrée dans build-backend = ...
Si vous ne spécifiez pas de build-backend dans pyproject.toml , SetUptools est supposé et le package deviendra très bien.
setup.py et exécutons python -m build --sdist --wheel . Il fonctionne parfaitement sans lui car la valeur par défaut du build-system est définie comme build-backend = "setuptools.build_meta" dans build CLI qui construit notre package. Mais vous pouvez toujours déclarer explicitement setuptools comme votre backend de construction comme celui-ci
# pyproject.toml
...
[ build-system ]
requires = [ " setuptools>=61.0.0 " , " wheel " ]
build-backend = " setuptools.build_meta "
... Chaque backend de construction étend généralement le fichier pyproject.toml avec ses propres options de configuration. Par exemple,
# pyproject.toml
...
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]
[ tool . setuptools . dynamic ]
version = { file = " version.txt " }
long_description = { file = " README.md " }
... Si vous choisissez d'utiliser setuptools dans votre projet, vous pouvez ajouter ces sections à pyproject.toml . Vous pouvez en savoir plus à ce sujet dans la documentation setuptools
Il est souvent avantageux d'inclure des fichiers non python comme des fichiers de données ou des fichiers binaires à l'intérieur de votre package, car souvent votre code Python repose sur ces fichiers non python.
Et puis nous avons vu que si nous voulons inclure ces fichiers, nous devons faire en sorte que ces fichiers se terminent à l'intérieur de notre dossier de package, car c'est notre dossier de package qui se termine à l'intérieur de nos utilisateurs virtuels lors de l'installation.
Nous avons également vu que par défaut, tous les fichiers non-python n'apparaissent pas dans ce dossier d'environnement virtuel final. Autrement dit, ne faites pas réellement notre dossier de package pendant la procédure de construction.
Alors, comment pouvons-nous nous assurer que ces fichiers se retrouvent dans notre version de roue / dist de notre package? Par exemple ici, nous démo avec le fichier cities.json que nous voulons dans notre package tel qu'il est utilisé par le fichier states_info.py .
Docs officiels
setuptoolspour la prise en charge des données
# pyprject.toml
[ tool . setuptools ]
# ...
# By default, include-package-data is true in pyproject.toml, so you do
# NOT have to specify this line.
include-package-data = true Ainsi, SetUtuptools a par défaut cette valeur include-package-data définie sur true comme indiqué dans les documents officiels, mais nous devons créer un fichier supplémentaire MANIFEST.in et spécifier les données que nous voulons inculde dans notre package présent à Root Dir.
IMP: Il s'agit d'importer tous les dossiers du répertoire des packages devraient avoir un fichier
__init__.pyinculant le répertoire de données que nous voulons inclure parce que le processus de RecusRivefind_packages()que fait SetUtuptools ne sera pas dans FO; Ders qui a un fichier__init__.py.
# MANIFEST.in
include packaging_demo/*.json
include packaging_demo/my_folder/*.json
OR
Recursive include all json files in the package directory
recursive-include packaging_demo/ *.json
Docs sur la configuration du fichier manifeste.in
MANIFEST.in À partir des docs setuptools, nous pouvons l'ajouter dans notre fichier pyproject.toml :
# this is by default true so no need to explicitly add it
# but as mentioned in the docs, it is false for other methods like setup.py or setup.cfg
[ tool . setuptools ]
include-package-data = true
# add the data here, it's finding the files recursively
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]build-backend que setuptools Outre setuptools , nous pouvons utiliser ces systèmes backend de construction. Le point à noter est lors de l'utilisation d'autres systèmes, la cofiguration pyproject.toml doit suivre leurs autandissements.
Trappe
[ build-system ]
requires = [ " hatchling " ]
build-backend = " hatchling.build "Poésie
[ build-system ]
requires = [ " poetry-core>=1.0.0 " ]
build-backend = " poetry.core.masonry.api " Documenter les versions exactes de nos dépendances et leurs dépendances, etc.
Il est conseillé d'avoir le plus peu de nombre possible de dépendances associées à notre package, car cela peut entraîner un enfer de dépendance, ou un conflit de dépendance avec un autre package comme expliqué dans les conférences.
Plus l'arbre de dépendance est complexe, les chances de conflit avec la version future d'autres bibliothèques.
Analyse des graphiques de dépendance par Eric
Garder les versions épinglées des dépendances et des versions Python est acquise à des fins de dépannage:
pip freeze > requirements.txt pip install pipdeptree graphviz
sudo apt-get install graphviz
# generate the dependency graph
pipdeptree -p packaging-demo --graph-output png > dependency-graph.png[ project . optional-dependencies ]
dev = [ " ruff " , " mypy " , " black " ] # installing our package with optional dependencies
pip install ' .[dev] ' [ project . optional-dependencies ]
# for developement
dev = [ " ruff " , " mypy " , " black " ]
# plugin based architecture
colors = [ " rich " ] # plugin based installation
pip install ' .[colors] '
# here we demo with rich library, if user wants the output to be
# colorized then they can install our package like this.
# we can add multiple optional dependencies like:
pip install ' .[colors, dev] ' [ project . optional-dependencies ]
# for developement
dev = [ " ruff " , " mypy " , " black " ]
# plugin based architecture
colors = [ " rich " ]
# install all dependencies
all = [ " packaging-demo[dev, colors] " ] # Installing all dependencies all at once
pip install ' .[all] ' Nous pouvons utiliser SNYK pour vérifier à quel point les problèmes de sécurité sont stables, bien pris en charge, etc. pour les dépendances que nous allons utiliser pour notre package, puis prendre la décision de l'utiliser dans notre projet.
Pour pusblish notre package à PYPI [Python Packaging Index], comme indiqué dans le guide officiel, nous utilisons l'outil twine CLI.
pip install twine
twine upload --helpGénérer un jeton API pour le test PYPI ou PYPI prod
Créez votre package Python: python -m build --sdist --wheel "${PACKAGE_DIR}" , nous construisons ici SDIST et Wheel, comme recommandé.
Exécutez l'outil Twine: twine upload --repository testpypi ./dist/* , Uplemling to Test-PYPI
CMake et Makefile
sudo apt-get install makeFichier de tâche
justfile
pyinvoke
Dev
$ rightarrow $ QA / mise en scène$ rightarrow $ Faire un coup de pouce



# .github/workflows/publish.yaml
name : Build, Test, and Publish
# triggers: whenever there is new changes pulled/pushed on this
# repo under given conditions, run the below jobs
on :
pull_request :
types : [opened, synchronize]
push :
branches :
- main
# Manually trigger a workflow
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
workflow_dispatch :
jobs :
build-test-and-publish :
runs-on : ubuntu-latest
steps :
# github actions checksout, clones our repo, and checks out the branch we're working in
- uses : actions/checkout@v3
with :
# Number of commits to fetch. 0 indicates all history for all branches and tags
# fetching all tags so to aviod duplicate version tagging in 'Tag with the Release Version'
fetch-depth : 0
- name : Set up Python 3.8
uses : actions/setup-python@v3
with :
python-version : 3.8
# tagging the release version to avoid duplicate releases
- name : Tag with the Release Version
run : |
git tag $(cat version.txt)
- name : Install Python Dependencies
run : |
/bin/bash -x run.sh install
- name : Lint, Format, and Other Static Code Quality Check
run : |
/bin/bash -x run.sh lint:ci
- name : Build Python Package
run : |
/bin/bash -x run.sh build
- name : Publish to Test PyPI
# setting -x in below publish:test will not leak any secrets as they are masked in github
if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
run : |
/bin/bash -x run.sh publish:test
env :
TEST_PYPI_TOKEN : ${{ secrets.TEST_PYPI_TOKEN }}
- name : Publish to Prod PyPI
if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
run : |
/bin/bash -x run.sh publish:prod
env :
PROD_PYPI_TOKEN : ${{ secrets.PROD_PYPI_TOKEN }}
- name : Push Tags
if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
run : |
git push origin --tagsExigences de verrouillage
Cache de dépendance
Chaque fois que les actions GitHub sont exécutées dans le GitHub CI, chaque fois, il est exécuté sur un nouveau conteneur. Ainsi, chaque fois, nous devrons télécharger et réinstaller les dépendances de PIP encore et encore; Ce qui n'est pas bon car il est inefficent et ralentit notre flux de travail.
Ainsi, nous voudrions installer toutes les dépendances lorsque le flux de travail s'est déroulé en premier et l'utiliser chaque fois qu'un nouveau worflow est exécuté.
Les actions GitHub fournissent cette fonctionnalité en mettant en cache les dépendances, il stocke les dépendances installées ( ~/.cache/pip ) et le télécharge à chaque fois qu'un nouveau workflow est exécuté. Docs
- uses : actions/cache@v3
with :
path : ~/.cache/pip
key : ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys : |
${{ runner.os }}-pip-setup-python steps :
- uses : actions/checkout@v4
- uses : actions/setup-python@v5
with :
python-version : ' 3.9 '
cache : ' pip ' # caching pip dependencies
- run : pip install -r requirements.txtParallélisation
# See .github/workflows/publish.yaml
jobs :
check-verison-txt :
...
lint-format-and-static-code-checks :
....
build-wheel-and-sdist :
...
publish :
needs :
- check-verison-txt
- lint-format-and-static-code-checks
- build-wheel-and-sdist
...