PYTHONPATHGuia oficial de embalagem Python
Um módulo no Python é um único arquivo (com uma extensão .py ) que contém código Python. Normalmente, consiste em classes, funções e variáveis que podem ser usadas por outro código Python. Os módulos são usados para organizar o código em unidades lógicas e facilitar a reutilização do código.
Por exemplo, considere um módulo chamado math_operations.py que contém funções para executar operações matemáticas como adição, subtração, multiplicação etc. Você pode importar e usar essas funções em outros scripts Python.
# math_operations.py
def add ( x , y ):
return x + y
def subtract ( x , y ):
return x - y Um pacote no Python é uma coleção de módulos agrupados em um diretório. Um pacote é normalmente representado por um diretório que contém um arquivo __init__.py (que pode estar vazio) e um ou mais módulos Python. O arquivo __init__.py indica ao Python que o diretório deve ser tratado como um pacote.
Por exemplo, considere um pacote chamado my_package :
my_package/
├── __init__.py
├── module1.py
└── module2.py
Aqui, my_package é um pacote que contém module1.py e module2.py , que pode ser importado usando a notação de ponto ( my_package.module1 , my_package.module2 ).
Uma sub-pacote no Python é um pacote aninhado em outro pacote. Isso significa que um pacote pode conter outros pacotes e módulos. Os sub-pacotes são criados organizando os diretórios e adicionando arquivos __init__.py adequadamente para definir a estrutura do pacote.
Por exemplo:
my_parent_package/
├── __init__.py
└── my_sub_package/
├── __init__.py
├── module3.py
└── module4.py
Nesta estrutura, my_sub_package é uma sub-pacote de my_parent_package e pode conter seus próprios módulos ( module3.py , module4.py ). O sub-pacote pode ser importado usando a notação de ponto ( my_parent_package.my_sub_package.module3 ).
Um pacote de distribuição (ou simplesmente distribuição ) no Python refere -se a uma coleção embalada de código Python e recursos que são disponibilizados para instalação. Normalmente, inclui módulos, pacotes, arquivos de dados, arquivos de configuração e outros recursos necessários para uma finalidade específica (por exemplo, uma biblioteca ou aplicativo).
Os pacotes de distribuição geralmente são distribuídos e instalados usando gerentes de pacotes Python, como pip , e podem ser carregados para repositórios de embalagem como Pypi (Python Package Index) para facilitar a distribuição e a instalação de outros desenvolvedores.
Por exemplo, os pacotes de distribuição populares incluem numpy , fast-api , pandas , etc., que são instalados usando pip e fornecem funcionalidades que podem ser usadas em projetos Python.
sdistsetup.pypython setup.py build sdist para construir a distribuição de origempip install ./dist/<package_name>.tar.gz para instalar o pacotepip list para ver se o pacote está instalado. Se as alterações forem feitas no pacote, use pip install . que criará e instalará o pacote mais recente em tempo real.
Ou simplesmente use editável para que você nem sempre precise reconstruir o pacote que novas alterações são feitas:
pip install --editable . sdist é abreviado para distribuição de origem, um arquivo .tar que contém nosso código chamado "SDIST". O que isso significa é que o pacote de distribuição contém apenas um subconjunto do nosso código -fonte.
Uma "distribuição de origem" é essencialmente uma pasta com zíper que contém nosso código -fonte .
wheel Distribuição de origem ( sdist ) :
*.py ), arquivos de configuração e outros ativos relacionados ao projeto.python setup.py sdist . Rodas ( bdist_wheel ) :
sdist ..whl .python setup.py bdist_wheel . Instalação mais rápida :
*.whl ) é tipicamente mais rápida do que a instalação das distribuições de origem ( *.tar.gz ).Facilidade de uso para usuários :
sdist e bdist_wheelsdist e bdist_wheel para Python para acomodar diferentes casos e plataformas de uso.python setup.py sdist bdist_wheelRequisitos de compilação :
Infelizmente, a construção de rodas para todos os sistemas operacionais fica difícil se você tiver uma etapa de compilação necessária, para que alguns mantenedores de OSS construam apenas rodas para um único sistema operacional.
pip realmente executa o setup.py (ou arquivos equivalentes) em sua máquina, logo após o download do SDIST.Construir a roda pode exigir código de compilação
gcc se o código -fonte estiver em C, mas outros idiomas exigirem seus próprios compiladores. O usuário deve instalá -los na máquina ON ou na pip install ... simplesmente falhará. Isso pode acontecer quando você instala numpy , pandas , scipy , pytorch , tensorflow , etc. setup.py pode conter código arbitrário. Isso é altamente inseguro. Um setup.py pode conter código malicioso.
Gerenciamento de dependência :
gcc , etc.) para instalar com sucesso pacotes que requerem compilação.Preocupações de segurança :
setup.py para instalação do pacote pode ser inseguro, pois executa o código arbitrário.wheelUm nome de arquivo de roda é dividido em partes separadas por hífens:
{dist}-{version}(-{build})?-{python}-{abi}-{platform}.whl Cada seção em {brackets} é uma tag ou um componente do nome da roda que carrega algum significado sobre o que a roda contém e onde a roda funcionará ou não funcionará.
Por exemplo: dist/packaging-0.0.0-py3-none-any.whl
packaging é o nome do pacote0.0.0 é o número da Verisonpy3 denota é compilação para python3abi . ABI significa interface binária do aplicativo.any significa que este pacote esteja construído para funcionar em qualquer plataforma.Outros exemplos:
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.whlA razão para isso é que o arquivo da roda contém o código binário complicado que ajuda a instalar o pacote rapidamente.
Manutenção de pacotes :
sdist e bdist_wheel para maximizar a compatibilidade e a facilidade de uso para os usuários.Considere a experiência do usuário :
build ferramenta CLI e pyproject.toml"Construir dependências" são qualquer coisa que deve ser instalada no seu sistema para criar seu pacote de distribuição em um SDIST ou roda.
Por exemplo, precisávamos pip install wheel para executar python setup.py bdist_wheel para que wheel seja uma dependência de construção para as rodas de construção.
Os arquivos setup.py podem ficar complexos.
Pode ser necessário pip install ... bibliotecas externas e importá -las para o seu arquivo setup.py para acomodar processos de construção complexos.
A palestra mostra pytorch e airflow como exemplos de pacotes com arquivos complexos setup.py .
De alguma forma, você precisa documentar dependências de construção fora do setup.py .
Se eles estivessem documentados no arquivo setup.py ... você não poderá executar o arquivo setup.py para ler as dependências documentadas (como se fossem especificadas em uma list em algum lugar do arquivo).
Este é o problema original pyproject.toml foi feito para resolver.
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute.
requires = [ " setuptools>=62.0.0 " , " wheel " ] pyproject.toml fica ao lado do setup.py na árvore de arquivos
A ferramenta CLI build ( pip install build ) é um projeto especial da Python Packaging Authority (PYPA), que
[build-system] no 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 para setup.cfg arquivo de configuraçãoMovendo -se 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
# ],
)PARA
# 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"Além disso, a configuração adicional é passada para o arquivo
pyproject.toml. Aqui, especificamosbuild-systemsemelhante aosetup_requiresemsetup.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 " ,
] Temos tratado setup.py como um arquivo de configuração glorificado, não aproveitando o fato de ser um arquivo python adicionando lógica a ele.
Isso é mais comum do que não. Além disso, houve uma mudança geral do uso de arquivos Python para configuração, porque isso adiciona complexidade ao uso dos arquivos de configuração (como ter que instalar bibliotecas para executar o arquivo de configuração).
setup.cfg é um arquivo complementar para setup.py que nos permite definir nossa configuração de pacote em um arquivo de texto estático - especificamente um arquivo de formato ini.
Quaisquer valores que não passamos diretamente como argumentos para Setup () serão procurados pela Invocation Setup () em um arquivo setup.cfg, que deve sentar -se adjacente ao Setup.py na árvore de arquivos, se usado.
Agora estamos acumulando muitos arquivos!
setup.pysetup.cfgpyproject.tomlREADME.md.pylintrc , .flake8 , .blackrc , ruff.toml , .mypy , pre-commit-config.yaml , etc.CHANGELOG ou CHANGELOG.mdVERSION ou version.txt Acontece que quase todos esses arquivos podem ser substituídos pelo pyproject.toml . Quase todas as ferramentas de qualidade de linha / código suportam a análise pyproject.toml uma seção chamada [tool.black] [tool.<name>]
Os documentos de cada ferramenta individual devem dizer como fazer isso.
Acima, está um pyproject.toml com configurações para muitas das ferramentas de linha que usamos no curso.
setup.cfgesetup.pytambém podem ser substituídos?
setup.cfg para 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"PARA
# 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 .- Executa perfeitamente, nos livramos de outro arquivo de configuração (setup.cfg)
setup.py pelo build-backend O PEP 517 adicionou um argumento build-backend ao pyproject.toml como assim:
[ 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 O build-backend define um ponto de entrada (módulo Python executável nesse caso) que a CLI build usa para realmente fazer o trabalho de analisar pyproject.toml e construir a roda e o SDIST.
Isso significa que você pode implementar seu próprio back-end de compilação hoje, escrevendo um programa que faz isso e você pode usá-lo adicionando seu pacote a requires = [...] e especificando o ponto de entrada em build-backend = ...
Se você não especificar um build-backend em pyproject.toml , o SetupTools será assumido e o pacote ficará com prefeitura de bulit.
setup.py e executarmos python -m build --sdist --wheel . Ele é executado perfeitamente sem ele, porque o valor padrão do build-system é definido como build-backend = "setuptools.build_meta" na CLI build que constrói nosso pacote. Mas você ainda pode declarar explicitamente setuptools como seu back -end de construção como este
# pyproject.toml
...
[ build-system ]
requires = [ " setuptools>=61.0.0 " , " wheel " ]
build-backend = " setuptools.build_meta "
... Cada back -end de construção normalmente estende o arquivo pyproject.toml com suas próprias opções de configuração. Por exemplo,
# pyproject.toml
...
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]
[ tool . setuptools . dynamic ]
version = { file = " version.txt " }
long_description = { file = " README.md " }
... Se você optar por usar setuptools em seu projeto, poderá adicionar essas seções ao pyproject.toml . Você pode ler mais sobre isso na documentação setuptools
Muitas vezes, é benéfico incluir arquivos não-python, como arquivos de dados ou arquivos binários dentro do seu pacote, porque muitas vezes seu código Python se baseia nesses arquivos não-python.
E então vimos que, se vamos incluir esses arquivos, precisamos fazer com que esses arquivos acabem dentro da pasta de pacotes, porque é a nossa pasta de pacotes que acaba dentro de nossos usuários ambientes virtuais após a instalação.
Também vimos que, por padrão, todos os arquivos não-python não chegam a essa pasta final de ambiente virtual. Isto é, na verdade não entre em nossa pasta de pacotes durante o procedimento de construção.
Então, como garantemos que esses arquivos acabem em nossa compilação de roda/dist em nossa embalagem? Por exemplo, aqui demonstramos o arquivo cities.json que queremos em nosso pacote, pois é usado pelo arquivo states_info.py .
Docros oficiais
setuptoolspara suporte de dados
# 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 Portanto, o SetupTools, por padrão, possui esse valor include-package-data definido como true , como mostrado nos documentos oficiais, mas precisamos criar um MANIFEST.in de arquivo extra.in e especificar os dados que desejamos incultar em nosso pacote presente no ROOT DIR.
IMP: É importar todas as pastas no diretório do pacote devem ter um arquivo
__init__.pyinculando o diretório de dados que queremos incluir porque o processofind_packages()Recusrive que o setupTools não entrará em FO; DERS que possuem arquivo__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
Documentos para configurar o manifesto.in arquivo
MANIFEST.in Nos documentos do setupTools, podemos adicionar isso em nosso arquivo 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 não são setuptools Além do setuptools , podemos usar esses sistemas de back -end. O ponto a ser observado é ao usar outros sistemas, a cofiguração pyproject.toml deve seguir seus padrões.
Chocar
[ build-system ]
requires = [ " hatchling " ]
build-backend = " hatchling.build "Poesia
[ build-system ]
requires = [ " poetry-core>=1.0.0 " ]
build-backend = " poetry.core.masonry.api " Documentando as versões exatas de nossas dependências e suas dependências e assim por diante.
É aconselhável ter o mínimo possível de dependências associadas ao nosso pacote, pois pode levar ao inferno de dependência ou conflito de dependência com outro pacote, conforme explicado nas palestras.
Quanto mais complexa a árvore de dependência maior a chance de conflitos com a versão futura de outras bibliotecas.
Análise de gráfico de dependência por Eric
Manter as versões fixadas de dependências e versões Python é aconselhável para fins de solução de problemas:
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] ' Podemos usar o SNYK para verificar o quão estável, bem suportado, se houver problemas de segurança etc.
Para pusblish nosso pacote para Pypi [Python Packaging Index], conforme declarado no guia oficial, usamos a ferramenta CLI twine .
pip install twine
twine upload --helpGerar token de API para teste Pypi ou POPI PROD
Construa seu pacote Python: python -m build --sdist --wheel "${PACKAGE_DIR}" , aqui estamos construindo SDIST e Wheel, conforme recomendado.
Execute a ferramenta Twine: twine upload --repository testpypi ./dist/* , Uplading to test-pypi
CMake e Makefile
sudo apt-get install makeTaskfile
justfile
Pyinvoke
Dev
$ rightarrow $ QA/estadiamento$ rightarrow $ Prod



# .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 --tagsRequisitos de bloqueio
Cache de dependência
Sempre que as ações do GitHub são executadas no Github CI, toda vez que é executado em um recipiente fresco. Assim, toda vez que teremos que baixar e reinstalar as dependências do PIP repetidamente; O que não é bom, pois é inefício e diminui nosso fluxo de trabalho.
Assim, gostaríamos de instalar todas as dependências quando o fluxo de trabalho foi executado primeiro e usá -lo toda vez que um novo fluxo Wor é executado.
As ações do GitHub fornecem essa funcionalidade armazenando em cache as dependências, ele armazena as dependências instaladas ( ~/.cache/pip ) e baixam sempre que um novo fluxo de trabalho é executado. Documentos
- 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.txtParalelização
# 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
...