PYTHONPATHGuía oficial de embalaje de Python
Un módulo en Python es un solo archivo (con una extensión .py ) que contiene código Python. Por lo general, consiste en clases, funciones y variables que pueden ser utilizadas por otro código de Python. Los módulos se utilizan para organizar el código en unidades lógicas y facilitar la reutilización del código.
Por ejemplo, considere un módulo llamado math_operations.py que contiene funciones para realizar operaciones matemáticas como suma, resta, multiplicación, etc. Puede importar y usar estas funciones en otros scripts de Python.
# math_operations.py
def add ( x , y ):
return x + y
def subtract ( x , y ):
return x - y Un paquete en Python es una colección de módulos agrupados en un directorio. Un paquete generalmente está representado por un directorio que contiene un archivo __init__.py (que puede estar vacío) y uno o más módulos de Python. El archivo __init__.py indica a Python que el directorio debe tratarse como un paquete.
Por ejemplo, considere un paquete llamado my_package :
my_package/
├── __init__.py
├── module1.py
└── module2.py
Aquí, my_package es un paquete que contiene module1.py y module2.py , que se puede importar usando notación de puntos ( my_package.module1 , my_package.module2 ).
Un subgrupo en Python es un paquete anidado dentro de otro paquete. Esto significa que un paquete puede contener otros paquetes y módulos. Los subgrupos se crean organizando directorios y agregando archivos __init__.py adecuadamente para definir la estructura del paquete.
Por ejemplo:
my_parent_package/
├── __init__.py
└── my_sub_package/
├── __init__.py
├── module3.py
└── module4.py
En esta estructura, my_sub_package es un subackage de my_parent_package , y puede contener sus propios módulos ( module3.py , module4.py ). El subpackaje se puede importar usando notación DOT ( my_parent_package.my_sub_package.module3 ).
Un paquete de distribución (o simplemente distribución ) en Python se refiere a una colección envasada de código y recursos de Python que se pone a disposición para la instalación. Por lo general, incluye módulos, paquetes, archivos de datos, archivos de configuración y otros recursos necesarios para un propósito específico (por ejemplo, una biblioteca o aplicación).
Los paquetes de distribución a menudo se distribuyen e instalan utilizando los administradores de paquetes de Python como pip y se pueden cargar en repositorios de paquetes como PYPI (Python Package Index) para una fácil distribución e instalación de otros desarrolladores.
Por ejemplo, los paquetes de distribución populares incluyen numpy , fast-api , pandas , etc., que se instalan utilizando pip y proporcionan funcionalidades que pueden usarse en proyectos de Python.
sdistsetup.pypython setup.py build sdist para construir la distribución de la fuentepip install ./dist/<package_name>.tar.gz para instalar el paquetepip list para ver si el paquete está instalado. Si se realizan cambios en el paquete, use pip install . que construirá e instalará el último paquete en la marcha.
O simplemente use editable para que no siempre tenga que reconstruir el paquete que se realizan nuevos cambios:
pip install --editable . sdist es la abreviatura de distribución de origen, un archivo .tar que contiene nuestro código llamado "SDIST". Lo que eso significa es que el paquete de distribución solo contiene un subconjunto de nuestro código fuente.
Una "distribución de origen" es esencialmente una carpeta con cremallera que contiene nuestro código fuente .
wheel Distribución de fuente ( sdist ) :
*.py ), archivos de configuración y otros activos relacionados con el proyecto.python setup.py sdist . Ruedas ( bdist_wheel ) :
sdist ..whl .python setup.py bdist_wheel . Instalación más rápida :
*.whl ) es típicamente más rápida que la instalación de las distribuciones de origen ( *.tar.gz ).Facilidad de uso para los usuarios :
sdist y bdist_wheelsdist y bdist_wheel para los paquetes de Python para acomodar diferentes casos y plataformas de uso.python setup.py sdist bdist_wheelRequisitos de compilación :
Desafortunadamente, la construcción de ruedas para todos los sistemas operativos se vuelve difícil si tiene un paso de compilación requerido, por lo que algunos mantenedores de OSS solo construyen ruedas para un solo sistema operativo.
pip realmente ejecuta el setup.py (o archivos equivalentes) en su máquina, justo después de descargar el SDIST.Construir la rueda puede requerir código de compilación
gcc si el código fuente está en C, pero otros idiomas requieren sus propios compiladores. El usuario debe instalarlos en su máquina o la pip install ... simplemente fallará. Esto puede suceder cuando instala numpy , pandas , scipy , pytorch , tensorflow , etc. setup.py puede contener código arbitrario. Esto es muy inseguro. Un setup.py puede contener código malicioso.
Gestión de dependencia :
gcc , etc.) para instalar con éxito paquetes que requieren compilación.Preocupaciones de seguridad :
setup.py para la instalación del paquete puede ser inseguro ya que ejecuta código arbitrario.wheelUn nombre de archivo de la rueda se descompone en partes separadas por guiones:
{dist}-{version}(-{build})?-{python}-{abi}-{platform}.whl Cada sección en {brackets} es una etiqueta, o un componente del nombre de la rueda que lleva algún significado sobre lo que contiene la rueda y dónde funcionará o no la rueda.
Por ejemplo: dist/packaging-0.0.0-py3-none-any.whl
packaging es el nombre del paquete0.0.0 es el número de versopy3 denota su construcción para Python3abi . ABI significa interfaz binaria de aplicación.any significa que este paquete sea construido para funcionar en cualquier plataforma.Otros ejemplos:
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 razón de esto es que el archivo de la rueda contiene el código binario previamente complicado que ayuda a instalar el paquete rápidamente.
Mantenimiento del paquete :
sdist y bdist_wheel para maximizar la compatibilidad y la facilidad de uso para los usuarios.Considere la experiencia del usuario :
build herramienta CLI y pyproject.tomlLas "dependencias de compilación" son cualquier cosa que debe instalarse en su sistema para construir su paquete de distribución en un SDIST o rueda.
Por ejemplo, necesitábamos pip install wheel para ejecutar python setup.py bdist_wheel para que wheel sea una dependencia de compilación para las ruedas de construcción.
Los archivos setup.py pueden volverse complejos.
Es posible que pip install ... bibliotecas externas e importarlas a su archivo setup.py para acomodar procesos complejos de compilación.
La conferencia muestra pytorch y airflow como ejemplos de paquetes con archivos complejos setup.py .
De alguna manera, debe poder documentar las dependencias de compilación fuera de setup.py .
Si se documentaran en el archivo setup.py ... no podría ejecutar el archivo setup.py para leer las dependencias documentadas (como si se especificaran en una list en algún lugar del archivo).
Este es el problema original pyproject.toml estaba destinado a resolver.
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute.
requires = [ " setuptools>=62.0.0 " , " wheel " ] pyproject.toml se sienta adyacente a setup.py en el árbol de archivos
La herramienta build CLI ( pip install build ) es un proyecto especial de la Autoridad de Empaque de Python (PYPA) que
[build-system] en 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 a setup.cfg Config FilePasar 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
# ],
)A
# 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"También se pasa la configuración adicional al archivo
pyproject.toml. Aquí hemos especificadobuild-systemsimilar alsetup_requiresensetup.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 " ,
] Hemos estado tratando setup.py como un archivo de configuración glorificado, no aprovechando realmente el hecho de que es un archivo de Python al agregarle lógica.
Esto es más común que no. Además, ha habido un cambio general de usar Python para archivos de configuración porque hacerlo agrega complejidad al uso de los archivos de configuración (como tener que instalar bibliotecas para ejecutar el archivo de configuración).
setup.cfg es un archivo complementario a setup.py que nos permite definir nuestra configuración de paquete en un archivo de texto estático, específicamente un archivo de formato INI.
Cualquier valor que no pase directamente como argumentos para configurar () será buscado por la invocación configuración () en un archivo setup.cfg, que está destinado a sentarse adyacente a setup.py en el árbol de archivos si se usa.
¡Ahora estamos acumulando muchos archivos!
setup.pysetup.cfgpyproject.tomlREADME.md.pylintrc , .flake8 , .blackrc , ruff.toml , .mypy , pre-commit-config.yaml , etc.CHANGELOG o CHANGELOG.mdVERSION o version.txt Resulta que casi todos estos archivos se pueden reemplazar con pyproject.toml . Casi todas las herramientas de calidad del código / código admiten la analización de una sección llamada [tool.<name>] Eg [tool.black] Sección de pyproject.toml para leer su configuración!
Los documentos de cada herramienta individual deben decirle cómo lograr esto.
Arriba se muestra un pyproject.toml con configuraciones para muchas de las herramientas de pelusa que hemos utilizado en el curso.
¿Se pueden reemplazar
setup.cfgysetup.py?
setup.cfg a 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"A
# 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 .- Se ejecuta perfectamente, nos deshacemos de otro archivo de configuración (setup.cfg)
setup.py con build-backend PEP 517 agregó un argumento build-backend a pyproject.toml como así:
[ 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 El build-backend define un punto de entrada (módulo de Python ejecutable en este caso) que la CLI build usa para hacer el trabajo de analizar pyproject.toml y construir la rueda y el SDIST.
Esto significa que podría implementar su propio backend de compilación hoy escribiendo un programa que lo haga, y podría usarlo agregando su paquete a requires = [...] y especificando el punto de entrada en build-backend = ...
Si no especifica un build-backend en pyproject.toml , se supone que SetupTools y el paquete se volverá bien prefecto.
setup.py y ejecutamos python -m build --sdist --wheel . Se ejecuta perfectamente sin él porque el valor predeterminado del build-system se establece como build-backend = "setuptools.build_meta" en build cli que crea nuestro paquete. Pero aún puede declarar explícitamente setuptools como su backend de compilación como este
# pyproject.toml
...
[ build-system ]
requires = [ " setuptools>=61.0.0 " , " wheel " ]
build-backend = " setuptools.build_meta "
... Cada backend de compilación generalmente extiende el archivo pyproject.toml con sus propias opciones de configuración. Por ejemplo,
# pyproject.toml
...
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]
[ tool . setuptools . dynamic ]
version = { file = " version.txt " }
long_description = { file = " README.md " }
... Si elige usar setuptools en su proyecto, puede agregar estas secciones a pyproject.toml . Puede leer más sobre esto en la documentación setuptools
A menudo es beneficioso incluir archivos que no son de Python como archivos de datos o archivos binarios dentro de su paquete debido a que a menudo su código Python se basa en estos archivos que no son Python.
Y luego vimos que si vamos a incluir esos archivos, necesitamos que esos archivos terminen dentro de nuestra carpeta de paquetes porque es nuestra carpeta de paquetes que termina dentro de los entornos virtuales de nuestros usuarios tras la instalación.
También vimos que de forma predeterminada, todos los archivos que no son de Python no llegan a esa carpeta de entorno virtual final. Es decir, no lo convierta en nuestra carpeta de paquete durante el procedimiento de compilación.
Entonces, ¿cómo nos aseguramos de que estos archivos terminen en nuestra construcción de ruedas/DIS de nuestro paquete? Por ejemplo, aquí demostramos con el archivo cities.json que queremos en nuestro paquete como lo usa el archivo states_info.py .
Documentos oficiales
setuptoolspara soporte de datos
# 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 Por lo tanto, SetupTools de forma predeterminada tiene este valor include-package-data establecido en true como se muestra en los documentos oficiales, pero necesitamos crear un archivo adicional MANIFEST.in y especificar los datos que queremos inculde en nuestro paquete presente en Root Dir.
Imp: Es importar todas las carpetas en el directorio de paquetes debe tener un archivo
__init__.pyque inculca el directorio de datos que queremos incluir porque el proceso de recusaciónfind_packages()que SetupTools no entrará en Fo; Ders que tienen un archivo__init__.pyen él.
# 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 sobre la configuración de manifest.in archivo
MANIFEST.in De los documentos de SetupTools podemos agregar esto en nuestro archivo 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 Aparte de setuptools podemos usar estos sistemas de backend de compilación. El punto a tener en cuenta es que cuando se usa otros sistemas, la cofiguración pyproject.toml debe seguir a sus standerds.
Escotilla
[ build-system ]
requires = [ " hatchling " ]
build-backend = " hatchling.build "Poesía
[ build-system ]
requires = [ " poetry-core>=1.0.0 " ]
build-backend = " poetry.core.masonry.api " Documentando las versiones exactas de nuestras dependencias y sus dependencias, etc.
Es aconsejable tener la menor cantidad posible de dependencias asociadas con nuestro paquete, ya que puede conducir al infierno de dependencia o un conflicto de dependencia con otro paquete como se explica en las conferencias.
Cuanto más complejo sea el árbol de dependencia mayor que las posibilidades de conflictos con la versión futura de otras bibliotecas.
Análisis de gráficos de dependencia por Eric
Mantener las versiones fijadas de las dependencias y las versiones de Python es adultible para fines de solución 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 SNYK para verificar cómo estable, bien admitido, si hay problemas de seguridad, etc., están presentes para las dependencias que vamos a usar para nuestro paquete y luego tomar la decisión de usarlo en nuestro proyecto.
Para hacer nuestro paquete a Pypi [Python Packaging Index], como se indica en la guía oficial, utilizamos la herramienta twine CLI.
pip install twine
twine upload --helpGenere el token API para la prueba PYPI o PYPI Prod
Construya su paquete Python: python -m build --sdist --wheel "${PACKAGE_DIR}" , aquí estamos construyendo Sdist y Wheel, como se recomienda.
Ejecute la herramienta Twine: twine upload --repository testpypi ./dist/* , aumentando a Test-Pypi
CMake y Makefile
sudo apt-get install makeArchivo de tareas
justfile
pyinvoke
Enchufe
$ rectarrow $ Qa/puesta en escena$ rectarrow $ Pinchar



# .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 bloqueo
Almacenamiento en caché de dependencia
Cada vez que las acciones de Github se ejecutan en el Github CI, cada vez que se ejecuta en un recipiente nuevo. Por lo tanto, cada vez que tendremos que descargar y reinstalar dependencias de PIP una y otra vez; Lo cual no es bueno, ya que es inefficeint y ralentiza nuestro flujo de trabajo.
Por lo tanto, nos gustaría instalar todas las dependencias cuando el flujo de trabajo se ejecute primero y usarlo cada vez que se ejecuta un nuevo Worflow.
Las acciones de GitHub proporcionan esta funcionalidad al almacenar en caché las dependencias, almacena las dependencias instaladas ( ~/.cache/pip ) y la descarga cada vez que se ejecuta un nuevo flujo de trabajo. Documento
- 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.txtParalelización
# 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
...