Python Packaging

Empaquetamiento y distribución de código en Python

images/python.svg
Autor:Carlos Jenkins, KuraLabs S.R.L
Email:carlos.jenkins@kuralabs.io
Fecha:30 de Enero, 2020

Introducción

Ok, hice un código bien chiva.

Cómo hago para que otras personas puedan usarlo?

Agenda

Cómo empaqueto?

Respuesta: setup.py y setuptools.

from setuptools import setup

setup(
    name='mypackage',
    version='1.0.0',
    author='KuraLabs S.R.L',
    author_email='info@kuralabs.io',
    description='Such an awesome package!',
    long_description='Place here what will be shown in project page!',
    url='https://github.com/kuralabs/mypackage/',
    packages=[
        'mypackage',
        'mypackage.args',
        'mypackage.hello',
    ],
    install_requires=[
        'requests',
        'colorlog',
    ],
)

Cómo empaqueto? : Versión

Dónde pongo la versión?

4 lugares:

Debe haber un único lugar.

Cómo empaqueto? : Versión

Dónde pongo la versión? En el __init__.py:

__author__ = 'KuraLabs S.R.L'
__email__ = 'info@kuralabs.io'
__version__ = '1.10.0'

Cómo la cargo?

Cómo empaqueto? : Versión

Parseo?

def find_version(filename):
    import re
    content = read(filename)
    version_match = re.search(
        r"^__version__ = ['\"]([^'\"]*)['\"]", content, re.M
    )
    if not version_match:
        raise RuntimeError('Unable to find version string.')
    return version_match.group(1)

Cómo empaqueto? : Versión

setup(
    version=find_version('lib/mypackage/__init__.py'),
)

Cómo empaqueto? : Descripción

Descripción en el setup.py se mostrará en la página del proyecto.

Y mi README?

setup(
    long_description=read('README.rst'),
)

No olvidar poner el README en el MANIFEST.in!

Cómo empaqueto? : Paquetes

setup(
    packages=[
        'mypackage',
        'mypackage.args',
        'mypackage.hello',
    ],
)

Especificar cada uno de los módulos?!!!!!

Cómo empaqueto? : Paquetes

Setuptools ayuda:

from setuptools import find_packages

setup(
    packages=find_packages('.'),
)

Cómo empaqueto? : Paquetes

Mejor los paquetes en su propio directorio?

from setuptools import find_packages

setup(
    package_dir={'': 'lib'},
    packages=find_packages('lib'),
)

Cómo empaqueto? : Requerimientos

setup(
    install_requires=[
        'requests',
        'colorlog',
    ],
)

Y mi requirements.txt?

Cómo empaqueto? : Requerimientos

Lo parseo :D

def find_requirements(filename):
    import string
    content = read(filename)
    requirements = []
    ignored = []
    for line in content.splitlines():
        line = line.strip()
        if line.startswith('#') or not line:
            continue
        if line[:1] not in string.ascii_letters:
            ignored.append(line)
            continue
        requirements.append(line)
    return requirements

Cómo empaqueto? : Requerimientos

Y lo puedo usar así:

setup(
    install_requires=find_requirements('requirements.txt'),
)

No olvidar poner el requirements.txt en el MANIFEST.in!

Cómo empaqueto? : Assets

Dónde los pongo?

lib/
└── mypackage
    ├── __init__.py
    ├── args.py
    └── data
        └── config.json

Cómo empaqueto? : Assets

Se empaquetan con package_data.

setup(
    package_data={
        'mypackage': ['data/*'],
    },
)

Cómo empaqueto? : Assets

Se cargan con pkg_resources.

Dos modos:

Cómo empaqueto? : Assets

Obtener path:

from pathlib import Path
from pkg_resources import resource_filename

filepath = Path(resource_filename(
    __package__, 'data/images/myimage.png'
))

Cómo empaqueto? : Assets

Obtener contenido:

from json import loads
from pkg_resources import resource_string

content = loads(resource_string(
    __package__, 'data/defaults.json'
).encode('utf-8'))

Cómo empaqueto? : Ejecutables

Usando entrypoints! Así usamos el __main__.py también!

setup(
     entry_points={
         'console_scripts': [
             'myexec=mypackage.__main__:main'
         ],
     },
)

Cómo empaqueto? : Ejecutables

O bien old school:

setup(
     scripts=['bin/myexec'],
)

Más información aquí:

https://python-packaging.readthedocs.io/en/latest/command-line-scripts.html

En qué formato publico?

Respuesta: Wheel y Source.

python3 setup.py bdist_wheel
python3 setup.py sdist

Ojo con el MANIFEST.in!!!!

En qué formato publico?

images/flowbber.png

En qué formato publico?

images/pyzmq.png

En dónde lo publico?

Respuesta: PyPI, o un PyPI propio! Cómo pypiserver.

En dónde lo publico?

Servidor PyPI local? Fácil:

docker run -p 8080:8080 -d pypiserver/pypiserver:latest

Cómo lo uso?

pip3 install --index-url http://localhost:8080/simple/ PACKAGE [PACKAGE2...]

En dónde lo publico?

Cómo lo configuro global para mi usuario?

pip3 config --user set global.index-url http://localhost:8080/simple/

Lo anterior escribe la configuración en:

~/.config/pip/pip.conf

Cómo lo publico?

Listo. Tengo mi paquete, cómo lo subo a PyPI?

Respuesta: Twine.

sudo pip3 install twine
twine upload --username myuser dist/mypackage-x.y.z.tar.gz
twine upload --username myuser dist/mypackage-x.y.z-py3-none-any.whl

Cómo lo publico?

Si uso un PyPI propio?

nano ~/.pypirc

Escribimos:

[distutils]
index-servers =
    local

[local]
repository : http://localhost:8080/legacy
username : YOUR.EMAIL@EMAIL.COM
password : YOUR.MYPYPI.PASSWORD

Cómo lo publico?

Subimos a nuestro PyPI propio con:

twine upload -r local dist/mypackage-x.y.z-py3-none-any.whl

¿Preguntas?

Muchas gracias!

https://carlos.jenkins.co.cr/presentations/packaging

Autor:Carlos Jenkins, KuraLabs S.R.L
Email:carlos.jenkins@kuralabs.io
Web:https://kuralabs.io/