Skip to content

Latest commit

 

History

History
628 lines (484 loc) · 23.2 KB

File metadata and controls

628 lines (484 loc) · 23.2 KB

Publicación de software

La publicación de software parece siempre una meta inalcanzable para nuestros proyectos pero lo cierto es que sucede, y tiene que ser lo más fácil posible, para poder hacerlo a menudo, incorporando mejoras rápidamente y evolucionando la API de manera predecible, sin introducir incertidumbre en el usuario.

Aspectos generales de la publicación de software

Existen aspectos generales que caracterizan cualquier proceso de publicación de software, sin pertenecer a ningún ecosistema o estar escritos en ningún lenguaje en particular.

Licencias

Una de las primeras dudas a la hora de publicar software es qué licencia debería llevar: MIT, Apache y GPLv3 son algunas de las más famosas.

La licencia es un escrito donde se resume que es lo que un usuario puede hacer con un software, cuáles son sus derechos, las condiciones y las limitaciones.

En caso de no incluir una licencia, el software se publica con todos los derechos reservados al autor. De hecho, el usuario no debe asumir siquiera que pueda utilizar el software, aunque esto depende de la jurisdicción.

Incluso si no se elige una licencia, es conveniente hacer notar que es intencionado, incluyendo esta información en el readme del proyecto, por ejemplo.

Si piensas que es mejor no imponer restricciones, considera usar la licencia Unlicense.

Fichero README

Este característico fichero, presente en la mayoría de las publicaciones de software lleva aquí desde los años 70 e incluye información el nombre y la descripción del software, página web, instrucciones de la instalación, uso típico, contacto y autores. Puede incluir la licencia y la guía de contribución aunque estos documentos suelen ir en ficheros particulares.

Procura escribir un fichero README documentando el uso típico de tu software desde el comienzo del desarrollo.

El fichero README es normalmente un fichero de texto plano, legible por un usuario humano. Con la llegada de GitHub, se ha popularizado el uso de Markdown y los ficheros tienen extensión .md como en README.md. En el ecosistema Python, lo normal es usar reStructuredText como en README.rst.

Otra característica particular de los README de hoy día son los escudos (shields o badges).

shieldsIO

Los escudos muestran una pequeña información relevante en forma de una combinación de imagen y texto. Por ejemplo, el número de versión y si éste ha pasado todos los tests, o el tipo de licencia.

  • makeareadme.com es una guía para escribir un buen README*
  • shields.io es una relación de estos escudos y una herramienta para generarlos.

Versionado, control de cambios (changelog) y notas de la versión

El versionado es uno de los aspectos más importantes de cualquier desarrollo de software porque resume, de un vistazo, el estado del software respecto a publicaciones anteriores.

Existen diversos estándares de versionado aunque uno de los más extendidos es Semantic Versioning. En SemVer, la versión se divide en tres números: major.minor.patch:

  • El número de versión major se incrementa cuando se introducen cambios que rompen la retrocompatibilidad.
  • El número de versión minor se incrementa cuando se añaden características nuevas sin romper la retrocompatibilidad.
  • El número de versión patch se incrementa cuando se añaden otros cambios retrocompatibles.

Semantic Versioning considera la posibilidad de añadir marcas pre-publicación y metadatos de compilación.

Es normal ver también los sufijos a, b y rc denotando alpha, beta y release candidate. Por lo general, una versión alpha es una publicación inestable e interna, una versión beta es una publicación aún inestable pero externa y las release candidates son lo suficientemente estables como para considerarse listas para la publicación.

Python tiene su propio (complejo) sistema de versionado, recogido en el PEP-440.

Sea como sea, si sigues tu propio esquema de versionado, documéntalo en el README.

Relacionados con el versionado están los ficheros de control de cambios o CHANGELOG, y el fichero con las notas de la versión. Normalmente, el CHANGELOG es parte de las notas de la versión (release/version notes).

El control de cambios suele incluir información sobre lo qué ha cambiado de una versión a otra: adiciones, eliminación de características, cambios y corrección de errores y bugs. Las notas de la versión normalmente extienden esta información y la complementan con guías de actualización, o explicaciones más elaboradas sobre las principales características de la publicación.

Por último, publicar una nueva versión de software implica de alguna forma congelar el repositorio de control de versiones. Lo normal es que el último paso antes de publicar sea etiquetar la versión. En Git:

$ git tag 1.1.3

Para subir los tags:

$ git push --tags

A veces se precede el número de versión por la letra v.

Además de todas estas actividades, hay muchos otros procedimientos que se ponen en marcha cuando se realiza una publicación. Por ejemplo, publicar la nueva documentación, generar el instalador, escribir el anuncio, actualizar la página web del producto...

Para aliviar la carga manual de algunos de estos procedimientos, existen herramientas de automatización. También GitHub permite gestionar publicaciones de versiones.

Publicación de software en Python

Pasamos ahora a la publicación de software en Python. La biblioteca estándar incluye el módulo distutils para la integración de nuevos paquetes en los entornos de ejecución de Python.

Sin embargo, en la actualidad nadie usa distutils directamente sino otra biblioteca más simple setuptools. El paquete setuptools, instalable desde pip y mantenido por la Python Package Authority (PyPA), es la biblioteca central de la utilidad easy_install, el "antiguo pip".

La herramienta easy_install hacía fácil la instalación de paquetes con dependencias.

PyPI y Testing PyPI

PyPI son las siglas de Python Package Index que, como su propio nombre indica, es el índice de paquetes de Python. Para poder subir software a PyPI es necesario registrarse y crear una cuenta como desarrollador de software.

El ecosistema de Python también ofrece Test PyPI, que es otra instancia del mismo software que gestiona PyPI pero completamente separada, para que podamos probar los procesos de distribución de nuestro software, sin afectar el índica principal. Al estar completamente separado de PyPI, es necesario registrarse aquí para poder subir paquetes. Regístrate y ten tu nombre de usuario a mano.

PyPI no provee de ninguna interfaz Web para subir nuestras distribuciones, sino que lo hace programaticamente a través de herramientas como twine (que, por cierto, se instala a través de pip).

Tampoco permite instalar software en nuestro ordenador directamente, ni descargar "instalables" sino que sirve de repositorio para herramientas como pip.

El módulo setuptools y el fichero setup.py

No es necesario que nuestro software esté en el PyPI para poder instalarse. Por ejemplo, puedes ejecutar el siguiente comando para instalar desde un repositorio de control de versiones:

pip install git+https://github.com/delapuente/[email protected]#egg=mcpy

Pero sí que es necesario que tu proyecto cumpla ciertas condiciones para poder instalarse. Una de ellas es contar con un fichero setup.py en la raíz del proyecto:

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

import setuptools
import inspect
import sys
import os

long_description = """<a href="https://qiskit.org/aqua" rel=nofollow>Qiskit Aqua</a> is an extensible,
 modular, open-source library of quantum computing algorithms.
 Researchers can experiment with Aqua algorithms, on near-term quantum devices and simulators,
 and can also get involved by contributing new algorithms and algorithm-supporting objects,
 such as optimizers and variational forms. Qiskit Aqua is used by Qiskit Aqua Chemistry,
 Qiskit Aqua Artificial Intelligence, and Qiskit Aqua Optimization to experiment with real-world applications to quantum computing."""

requirements = [
    "qiskit-terra>=0.9.0,<0.10.0",
    "qiskit-ignis>=0.2.0,<0.3.0",
    "scipy>=1.0",
    "sympy>=1.3",
    "numpy>=1.13",
    "psutil>=5",
    "jsonschema>=2.6,<2.7",
    "scikit-learn>=0.20.0",
    "cvxopt",
    "dlx",
    "docplex",
    "fastdtw",
    "quandl",
    "setuptools>=40.1.0",
    "h5py",
    "networkx>=2.2",
    "pyscf; sys_platform != 'win32'",
]

if not hasattr(setuptools, 'find_namespace_packages') or not inspect.ismethod(setuptools.find_namespace_packages):
    print("Your setuptools version:'{}' does not support PEP 420 (find_namespace_packages). "
          "Upgrade it to version >='40.1.0' and repeat install.".format(setuptools.__version__))
    sys.exit(1)

VERSION_PATH = os.path.join(os.path.dirname(__file__), "qiskit", "aqua", "VERSION.txt")
with open(VERSION_PATH, "r") as version_file:
    VERSION = version_file.read().strip()

setuptools.setup(
    name='qiskit-aqua',
    version=VERSION,
    description='Qiskit Aqua: An extensible library of quantum computing algorithms',
    long_description=long_description,
    long_description_content_type="text/markdown",
    url='https://github.com/Qiskit/qiskit-aqua',
    author='Qiskit Aqua Development Team',
    author_email='[email protected]',
    license='Apache-2.0',
    classifiers=(
        "Environment :: Console",
        "License :: OSI Approved :: Apache Software License",
        "Intended Audience :: Developers",
        "Intended Audience :: Science/Research",
        "Operating System :: Microsoft :: Windows",
        "Operating System :: MacOS",
        "Operating System :: POSIX :: Linux",
        "Programming Language :: Python :: 3.5",
        "Programming Language :: Python :: 3.6",
        "Topic :: Scientific/Engineering"
    ),
    keywords='qiskit sdk quantum aqua',
    packages=setuptools.find_namespace_packages(exclude=['test*']),
    install_requires=requirements,
    include_package_data=True,
    python_requires=">=3.5",
    extras_require={
        'torch': ["torch; sys_platform != 'win32'"],
    }
)

Este fichero le dice a setuptools qué queremos que incluya la distribución de nuestro software y sus metadatos.

La guía de empaquetado y distribución de proyectos de PyPA recomienda crear también un fichero README, otro LICENSE.txt, además de documentar otros ficheros como setup.cfg y MANIFEST.in. Este último resulta útil para incluir otros ficheros que no sean módulos en el paquete.

  1. Inicia un nuevo proyecto (utiliza el nombre test-package-<test.pypi username>).

  2. Asegúra que tienes instalados los paquetes setuptools:

    $ pip install --upgrade setuptools
  3. En la raíz del mismo, crea un paquete awesome, y en su __init__.py, pon el siguiente contenido:

    print('Awesome!')
  4. También en la raíz del proyecto, crea un fichero setup.py con el siguiente contenido:

    from setuptools import setup
    
    setup(
        name='test-package-lodr',
        description='A test package from lodr user',
        version='0.1.0',
        packages=['awesome']
    )

    Fíjate cómo listamos explícitamente los paquetes que queremos añadir a la distribución. Los listamos mediante su nombre de paquete, no ruta en el sistema de archivos.

  5. Crea una versión del software con el siguiente comando:

    $ python3 setup.py sdist

    El subcomando sdist crea un paquete de distribución de fuentes (sources).

  6. Visita el directorio dist (que deberías añadir a tu fichero .gitignore) y descomprime el fichero .tar.gz para echar un vistazo a la estructura de archivos y directorios de su interior:

    • El directorio awesome está tal cual lo teníamos en nuestro proyecto.
    • El fichero PKG-INFO contiene parte de los metadatos que hemos incluído en la llamada a setup.py y otros campos que hemos omitido.
    • setup.cfg son opciones para el comando setup.py.
    • setup.py es nuestro fichero, tal cual.
    • El directorio acabado en .egg-info contiene, entre otros, un fichero con las dependencias del software y el fichero de fuentes, SOURCES.txt, necesario para desinstalar el software.
  7. Elimina el directorio descomprimido antes de continuar. Asegúrate que dentro de dist sólo está el fichero .tar.gz.

  8. Prueba a instalar tu software desde el propio paquete:

    $ pip install dist/test-package-lodr-0.1.0.tar.gz
  9. Desinstala tu paquete después:

    $ pip uninstall test-package-lodr
  10. Vamos a añadir algunas dependencias. Instala termcolor:

    $ pip install termcolor art

    Puedes especificar una versión en concreto usando la sintáxis de especificación de versiones de pip. Por ejemplo:

    $ pip install termcolor==1.0.1

    Pero esto desinstalará la versión que tuvieras instalada antes. Puedes experimentar con otros especificadores:

    $ pip install termcolor<1.0.0

    Asegúrate que vuelves a la versión más reciente antes de continuar:

    $ pip install -U termcolor
  11. Edita awesome/__init__.py para que luzca así:

    from art import text2art
    from termcolor import colored, cprint
    
    def aformat(msg):
        ascii_art = text2art(msg, font="bulbhead")
        return colored(ascii_art, 'magenta')
  12. Y crea una interfaz de línea de comandos en el módulo awesome/cli.py con el siguiente contenido:

    import sys
    from awesome import aformat
    
    def main():
        message = sys.argv[1] if len(sys.argv) > 1 else 'WoW!'
        print(aformat(message))
    
    if __name__ == '__main__':
        main()

    Prueba a ejecutarlo:

    $ PYTHONPATH=. ./awesome/cli.py 'I am Ziltoid!'

    ¿Por qué necesitamos incluir el directorio raíz al Python path.

  13. Ahora modifica el fichero setup.py para que incluya los siguientes parámetros:

    from setuptools import setup, find_packages
    
    setup(
        name='test-package-lodr',
        description='A test package from lodr user',
        version='0.2.0',
        packages=find_packages(),
        install_requires=['art', 'termcolor'],
        entry_points = {
            'console_scripts': ['awesomeness=awesome.cli:main'],
        }
    )

    Fíjate en que hemos realizado varios cambios:

    • Hemos aumentado la versión.
    • Hemos añadido el parámetro install_requires con valor una lista de las dependencias, en el formato de especificación de versiones que acabamos de ver.
    • Hemos usado la función find_packages() para simplificar el listar todos los paquetes de nuestro proyecto. Esta función puede tomar parámetros que excluyan ciertos directorios y otras utilidades.
    • Hemos utilizado el parámetro entry_points para indicar los scripts que queremos tener a disposición como comandos.
  14. Genera una nueva distribución:

    $ python3 setup.py sdist
  15. Crea un entorno virtual nuevo en un directorio vecino y prueba a instalar el fichero desde el .tar.gz:

    $ pip install ../test-package-lodr/dist/test-package-lodr-0.2.0.tar.gz

    Comprueba que puedes lanzar el comando awesomeness:

    $ awesomeness "I am Ziltoid!"

    Antes de pasar a la siguiente lección, desinstala tu paquete de prueba:

    $ pip uninstall test-package-lodr

Publicando en PyPI

Una vez has generado los paquetes de la distribución, subir un paquete es tan fácil como instalar twine:

$ pip install -U twine

Y ejecutar el siguiente comando:

$ twine upload --repository-url https://test.pypi.org/legacy/ dist/*

Que indica a twine que suba al repositorio pasado al parámetro --repository-url el contenido del directorio dist/*.

Puedes comprobar que tu paquete se ha subido correctamente en PyPI o probar a instalarlo con pip:

$ pip install --index-url https://test.pypi.org/simple/ --no-deps test-package-lodr

El modificador --no-deps hace que no se instalen las dependencias porque, probablemente, no esté en Test PyPI. No obstante, para simular una instalación más realista, podríamos haber hecho:

$ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple test-package-lodr

Tienes que tener en cuenta que pip, por defecto, busca los paquetes en PyPI por lo que es necesario indicarle el repositorio de prueba específicamente.

Si nuestro paquete tiene dependencias externas, tendrás que indicar el repositorio oficial como "segundo repositorio", con la opción --extra-index-url.

Wheels

El PEP-427 presenta una nueva forma de empaquetar software en Python, para reemplazar a los antiguos eggs.

Estos formatos no requieren compilación (si la hubiera) en la plataforma de destino puesto que es el mantenedor del paquete el que crea y mantiene las versiones de las distintas plataformas.

Una distribución por wheel se denomina binaria y toma, en general, menos tiempo que una distribución de fuentes. Para crear una distribución binaria debemos instalar el paquete wheel:

$ pip install -U wheel

Que extiende los comandos que acepta setup.py para que podamos hacer:

$ python3 setup.py bdist_wheel

El fichero requirements.txt y el fichero setup.py

Existe una discusión en el ecosistema de Python acerca de si se deben mantener dos ficheros de requisitos en lugar de uno, más si mantenemos la misma lista de dependencias en ambos. La confusión surge de no entender a qué propósito sirven cada uno.

  • Si queremos replicar un entorno, utilizaremos requirements.txt y lo pasaremos a pip con la opción -r:

    $ pip install -r requirements.txt

    Esto es útil, por ejemplo, para replicar entornos de desarrollo o de ejecución. Para generar estas descripciones detalladas del entorno, utiliza el comando freeze:

    $ pip install freeze > requirements.txt
  • Si queremos que nuestro software se integre con otro software, utilizaremos el fichero setup.py, que debe incluir dependencias lo menos restringidas posibles.

  • En caso de necesitar, por la razón que fuera, mantener las mismas dependencias en ambos ficheros, considera usar un fichero requirements con el contenido:

    -e .
    

El artículo setup.py vs requirements.txt, de Donal Stufft, arroja algo más de luz a este respecto.

Caso de estudio: publicación del proyecto personal

Vas a publicar tu proyecto personal. Para ello:

  1. Comienza eligiendo una licencia con la ayuda de https://choosealicense.com y añádela al fichero LICENSE
  2. Escribe un fichero README.rst siguiendo los consejos de https://www.makeareadme.com/
  3. Elije un esquema de versionado y decide la versión de tendrá tu software. Ponlo en un fichero VERSION
  4. Puedes escribir un fichero CHANGELOG siguiendo las recomendaciones de https://keepachangelog.com/en/1.0.0/ pero no le dediques mucho esfuerzo. Con Initial release bajo la primera versión es suficiente.
  5. Escribe un fichero MANIFEST.in y añade los ficheros README.rst, LICENSE y VERSION.
  6. Escribe el fichero setup.py. Lee programaticamente los contenidos de VERSION y README.rst para pasar los parametros version y long_description. No olvides el parámetro long_description_content_type para señalar que estás leyendo un .rst. Trata de ser tan exhaustivo como puedas.
  7. Repasa que hayas dado todos los pasos y genera una distribución.
  8. En un entorno virtual limpio, prueba a instalar tu paquete desde el .tar.gz. Asegúrate de que se instala correctamente y que pip show muestra la información correcta.
  9. Si todo ha ido bien, haz commit de los nuevos cambios y etiqueta tu proyecto con la versión. Acuérdate de hacer push de las tags al repositorio.
  10. Publica tu proyecto en Test PyPI. Prueba a instalarla desde aquí.
  11. Bonus: genera una nueva versión en Git.
  12. Bonus: si te sientes con confianza, publica en PyPI.