PYTHONPATHОфициальное руководство по упаковке Python
Модуль в Python - это единый файл (с расширением .py ), который содержит код Python. Обычно он состоит из классов, функций и переменных, которые могут использоваться другим кодом Python. Модули используются для организации кода в логические единицы и облегчение повторного использования кода.
Например, рассмотрим модуль с именем math_operations.py , который содержит функции для выполнения математических операций, таких как добавление, вычитание, умножение и т. Д. Вы можете импортировать и использовать эти функции в других сценариях Python.
# math_operations.py
def add ( x , y ):
return x + y
def subtract ( x , y ):
return x - y Пакет в Python - это коллекция модулей, сгруппированных вместе в каталоге. Пакет обычно представлен каталогом, содержащим файл __init__.py (который может быть пустым) и одним или несколькими модулями Python. Файл __init__.py указывает на Python, что каталог следует рассматривать как пакет.
Например, рассмотрим пакет с именем my_package :
my_package/
├── __init__.py
├── module1.py
└── module2.py
Здесь my_package - это пакет, содержащий module1.py и module2.py , который может быть импортирован с использованием точечной нотации ( my_package.module1 , my_package.module2 ).
Подпада в Python-это пакет, вложенная в другой пакет. Это означает, что пакет может содержать другие пакеты, а также модули. Подпакинги создаются путем организации каталогов и добавления файлов __init__.py соответствующим образом для определения структуры пакета.
Например:
my_parent_package/
├── __init__.py
└── my_sub_package/
├── __init__.py
├── module3.py
└── module4.py
В этой структуре my_sub_package -это sOp-Package of my_parent_package , и он может содержать свои собственные модули ( module3.py , module4.py ). Подпада может быть импортирован с использованием точечной нотации ( my_parent_package.my_sub_package.module3 ).
Распределительный пакет (или простое распределение ) в Python относится к упакованной коллекции кода и ресурсов Python, которая доступна для установки. Обычно он включает в себя модули, пакеты, файлы данных, файлы конфигурации и другие ресурсы, необходимые для определенной цели (например, библиотека или приложение).
Распределительные пакеты часто распределяются и устанавливаются с использованием менеджеров пакетов Python, таких как pip , и могут быть загружены в пакетные репозитории, такие как PYPI (индекс пакетов Python) для удобного распространения и установки другими разработчиками.
Например, популярные распределительные пакеты включают numpy , fast-api , pandas и т. Д., Которые устанавливаются с использованием pip и предоставляют функции, которые можно использовать в проектах Python.
sdistsetup.pypython setup.py build sdist для создания исходного распределенияpip install ./dist/<package_name>.tar.gz для установки пакетаpip list , чтобы увидеть, установлен ли пакет. Если в пакете внесены изменения, используйте pip install . который построит и установит последний пакет на лету.
Или просто используйте редактируемые, чтобы вам не всегда приходилось перестроить пакет Evrerytime Новые изменения внесены:
pip install --editable . sdist короткий для распределения источников, файл .tar содержащий наш код, называемый «SDIST». Это означает, что распределительный пакет содержит только подмножество нашего исходного кода.
«Распределение источников» - это по сути папка с застегиванием, содержащую наш исходный код.
wheel Распределение источников ( sdist ) :
*.py ), файлы конфигурации и другие связанные с проектом активы.python setup.py sdist . Колеса ( bdist_wheel ) :
sdist ..whl .python setup.py bdist_wheel . Быстрая установка :
*.whl ) обычно быстрее, чем установка из распределений источников ( *.tar.gz ).Простота использования для пользователей :
sdist , так и bdist_wheelsdist , так и bdist_wheel для Python Packages, чтобы размещать различные варианты использования и платформы.python setup.py sdist bdist_wheelТребования к компиляции :
К сожалению, строительство колес для всех операционных систем становится трудным, если у вас требуется шаг компиляции, поэтому некоторые активисты OSS строят колеса только для одной ОС.
pip фактически выполняет setup.py .Построение колеса может потребовать компиляции кода
gcc если исходный код находится в C, но другим языкам требуются свои собственные компиляторы. Пользователь должен установить их на машине или pip install ... просто пройдет сбой. Это может произойти, когда вы устанавливаете numpy , pandas , scipy , pytorch , tensorflow и т. Д. setup.py может содержать произвольный код. Это очень небезопасно. setup.py может содержать вредоносный код.
Управление зависимостями :
gcc и т. Д.), Чтобы успешно установить пакеты, которые требуют компиляции.Проблемы безопасности :
setup.py для установки пакета может быть небезопасным, когда она выполняет произвольный код.wheelИмя файла колеса разбивается на части, разделенные дефисами:
{dist}-{version}(-{build})?-{python}-{abi}-{platform}.whl Каждый раздел в {brackets} представляет собой тег или компонент имени колеса, которое имеет некоторое значение о том, что содержит колесо и где колесо будет или не будет работать.
Например: dist/packaging-0.0.0-py3-none-any.whl
packaging - это название пакета0.0.0 - номер Verisonpy3 обозначает его сборку для Python3abi Tag. ABI означает двоичный интерфейс приложения.any стенды, которые этот пакет создает для работы на любой платформе.Другие примеры:
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.whlПричина этого в том, что колесный файл содержит предварительный двоичный код, который помогает быстро установить пакет.
Техническое обслуживание пакета :
sdist , так и bdist_wheel , чтобы максимизировать совместимость и простоту использования для пользователей.Рассмотрим пользовательский опыт :
build инструмент CLI и pyproject.toml«Строительные зависимости» - это все, что должно быть установлено в вашей системе, чтобы построить ваш распределительный пакет в SDIST или колесо.
Например, нам нужно было pip install wheel , чтобы запустить python setup.py bdist_wheel , поэтому wheel является зависимостью для строительства колес.
Файлы setup.py могут стать сложными.
Вам может потребоваться pip install ... внешние библиотеки и импортировать их в свой файл setup.py , чтобы приспособить сложные процессы сборки.
В лекции показаны pytorch и airflow в качестве примеров пакетов со сложными файлами setup.py .
Каким -то образом вам нужно иметь возможность документировать зависимости от сборки за пределами setup.py .
Если бы они были задокументированы в файле setup.py … вы не смог бы выполнить файл setup.py , чтобы прочитать документированные зависимости (например, если они были указаны в list где -то в файле).
Это оригинальная проблема pyproject.toml предназначен для решения.
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute.
requires = [ " setuptools>=62.0.0 " , " wheel " ] pyproject.toml сидит рядом с setup.py в дереве файлов
Инструмент build CLI ( pip install build ) - это специальный проект Управления упаковки Python (PYPA), который
[build-system] в 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Переезжая от
# 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"Также настройка добавления передается в файл
pyproject.toml. Здесь мы определилиbuild-systemпохожая наsetup_requiresinsetup.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 " ,
] Мы рассматривали setup.py как прославленный файл конфигурации, не используя преимущества того факта, что это файл Python, добавив в него логику.
Это чаще, чем нет. Кроме того, произошел общий переход от использования Python для файлов конфигурации, потому что это добавляет сложности к использованию файлов конфигурации (например, необходимость установить библиотеки для выполнения файла конфигурации).
setup.cfg - это файл компаньона для setup.py , который позволяет нам определить конфигурацию нашей пакета в статическом текстовом файле - в частности, файл формата INI.
Любые значения, которые мы не передаем непосредственно в качестве аргументов в setup (), будут искать вызов Setup () в файле setup.cfg, который призван сесть рядом с setup.py в дереве файлов, если используется.
Теперь мы накапливаем много файлов!
setup.pysetup.cfgpyproject.tomlREADME.md.pylintrc , .flake8 , .blackrc , ruff.toml , .mypy , pre-commit-config.yaml и т. Д.CHANGELOG или CHANGELOG.mdVERSION или version.txt Оказывается, что почти все эти файлы могут быть заменены на pyproject.toml . Почти pyproject.toml инструмент качества вкладки / кода поддерживает анализ раздела [tool.<name>] [tool.black]
Документы каждого отдельного инструмента должны рассказать вам, как это сделать.
Выше показано, это pyproject.toml с конфигурациями для многих инструментов для линирования, которые мы использовали в курсе.
Можно ли также заменить
setup.cfgиsetup.py?
setup.cfg в pyproject.tomlОт
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 .- Запускается отлично, мы избавились от другого файла конфигурации (setup.cfg)
setup.py на build-backend PEP 517 добавил аргумент build-backend в pyproject.toml как 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 build-backend определяет точку входа (в данном случае исполняемый модуль Python), которую CLI build использует для фактического выполнения работы по анализу pyproject.toml и строительства колеса и SDIST.
Это означает, что вы можете реализовать свой собственный бэкэнд настройки сегодня, написав программу, которая делает это, и вы можете использовать ее, добавив свой пакет к requires = [...] и указав точку входа в build-backend = ... ....
Если вы не указаете build-backend в pyproject.toml , предполагается Setuptools, и пакет будет префектно хорошо.
setup.py и запустим python -m build --sdist --wheel . Он работает отлично без него, потому что значение по умолчанию build-system установлено как build-backend = "setuptools.build_meta" в CLI build , которая строит наш пакет. Но вы все равно можете явно объявить setuptools как ваш бэкэнд с сборкой
# pyproject.toml
...
[ build-system ]
requires = [ " setuptools>=61.0.0 " , " wheel " ]
build-backend = " setuptools.build_meta "
... Каждый бэкэнд сборки обычно расширяет файл pyproject.toml с помощью собственных параметров конфигурации. Например,
# pyproject.toml
...
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]
[ tool . setuptools . dynamic ]
version = { file = " version.txt " }
long_description = { file = " README.md " }
... Если вы решите использовать setuptools в своем проекте, вы можете добавить эти разделы в pyproject.toml . Вы можете прочитать больше об этом в документации setuptools
Часто полезно включать непитоны, такие как файлы данных или двоичные файлы в ваш пакет, потому что ваш код Python опирается на эти не питоны.
А потом мы увидели, что если мы собираемся включить эти файлы, нам нужно получить эти файлы, чтобы оказаться внутри нашей папки пакета, потому что это наша папка пакета, которая заканчивается в виртуальных средах наших пользователей при установке.
Мы также видели, что по умолчанию все непитоны не попадают в эту окончательную папку виртуальной среды. То есть, на самом деле не вступайте в нашу папку с пакетами во время процедуры сборки.
Итак, как мы можем убедиться, что эти файлы оказываются в нашей сборке колеса/дистий в нашем пакете? Например, здесь мы демонстрируем файл cities.json , который мы хотим в нашем пакете, поскольку он используется в файле states_info.py .
Официальные документы
setuptoolsдля поддержки данных
# 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 Таким образом, у Setuptools по умолчанию это include-package-data установленные true , как показано в официальных документах, но нам нужно создать дополнительный файл MANIFEST.in и указать данные, которые мы хотим привить в нашем пакете, присутствующих в Root Dir.
IMP: он импортирует все папки в каталоге пакетов, должен иметь
__init__.py-файл, внедряющий каталог данных, который мы хотим включить, потому что процесс переполненияfind_packages(), который делает Setuptools, не будет входить в FO; ders, которые имеют в нем файл__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
Документы при настройке файла manifest.in
MANIFEST.in файл От документов setuptools мы можем добавить это в нашем файле 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 чем setuptools Кроме setuptools , мы можем использовать эти бэкэнд -системы сборки. Следует отметить, что при использовании других систем pyproject.toml Cofiguration должна следовать за их стойкой.
Люк
[ build-system ]
requires = [ " hatchling " ]
build-backend = " hatchling.build "Поэзия
[ build-system ]
requires = [ " poetry-core>=1.0.0 " ]
build-backend = " poetry.core.masonry.api " Документирование точных версий наших зависимостей и их зависимостей и так далее.
Желательно иметь как можно меньше, чем возможное количество зависимостей, связанных с нашим пакетом, поскольку это может привести к конфликту ада зависимости или конфликта зависимости с другими пакетами, как объяснено в лекциях.
Чем сложнее дерево зависимостей выше вероятности конфликтов с будущей версией других библиотек.
Анализ графа зависимостей от Eric
Сохранение закрепленных версий зависимостей и версий Python рекомендуется для целей устранения неполадок:
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] ' Мы можем использовать SNYK, чтобы проверить, насколько стабильно, хорошо поддерживается, если какие -либо проблемы безопасности и т. Д. Присутствуют зависимости, которые мы собираемся использовать для нашего пакета, а затем принять решение об использовании в нашем проекте.
Чтобы Pusblish нашего пакета в PYPI [Индекс упаковки Python], как указано в официальном руководстве, мы используем инструмент twine CLI.
pip install twine
twine upload --helpГенерировать токен API для теста PYPI или PYPI PROD
Создайте свой Python Package: python -m build --sdist --wheel "${PACKAGE_DIR}" , здесь мы строим как SDIST, так и колесо, как рекомендовано.
Запустите инструмент Twine: twine upload --repository testpypi ./dist/* , подъем к тестируемому pypi
CMake и Makefile
sudo apt-get install makeЗадача файла
justfile
Pyinvoke
Девчонка
$ rightarrow $ QA/постановка$ rightarrow $ Продлевать



# .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 --tagsТребования блокировки
Кэширование зависимости
Всякий раз, когда Действия GitHub выполняются в GitHub CI, каждый раз, когда он работает на свежем контейнере. Таким образом, каждый раз, когда нам приходится скачать и переустановить зависимости от PIP снова и снова; Что не очень хорошо, так как это неэффективное и замедляет наш рабочий процесс.
Таким образом, мы хотели бы установить все зависимости, когда рабочий процесс работает первым, и использовать его каждый раз, когда запускается новый worflow.
Действия GitHub обеспечивают эту функциональность, кэшируя зависимости, он хранит установленные зависимости ( ~/.cache/pip ) и загружает его каждый раз, когда запускается новый рабочий процесс. Док
- 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.txtПараллелизация
# 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
...