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.
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.
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.
- choosealicense.com es una herramienta online para ayudarte a elegir una licencia.*
- Relación de licencias de la Open Source Inititative
- Definición de Open Source de acuerdo a la Open Source Initiative.
- Definición de Software Libre de acuerdo a la Free Software Foundation.
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).
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.
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
.
- keepachangelog.com* contiene buenas prácticas acerca de cómo escribir el control de cambios.
- El extensivo control de cambios de Python.
- Y las notas de la versión 3.8
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.
- Automate Release por Kiko Beats.*
- Guía sobre cómo publicar versiones en GitHub*
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 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.
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.
-
Inicia un nuevo proyecto (utiliza el nombre
test-package-<test.pypi username>
). -
Asegúra que tienes instalados los paquetes
setuptools
:$ pip install --upgrade setuptools
-
En la raíz del mismo, crea un paquete
awesome
, y en su__init__.py
, pon el siguiente contenido:print('Awesome!')
-
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.
-
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). -
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 asetup.py
y otros campos que hemos omitido. setup.cfg
son opciones para el comandosetup.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.
- El directorio
-
Elimina el directorio descomprimido antes de continuar. Asegúrate que dentro de
dist
sólo está el fichero.tar.gz
. -
Prueba a instalar tu software desde el propio paquete:
$ pip install dist/test-package-lodr-0.1.0.tar.gz
-
Desinstala tu paquete después:
$ pip uninstall test-package-lodr
-
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
-
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')
-
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.
-
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.
-
Genera una nueva distribución:
$ python3 setup.py sdist
-
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
- Tutorial de Python para empaquetar un proyecto.*
- La lista completa de argumentos de la función
setup
. - La lista completa de clasificadores.
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
.
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
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.
Vas a publicar tu proyecto personal. Para ello:
- Comienza eligiendo una licencia con la ayuda de https://choosealicense.com
y añádela al fichero
LICENSE
- Escribe un fichero
README.rst
siguiendo los consejos de https://www.makeareadme.com/ - Elije un esquema de versionado y decide la versión de tendrá tu software.
Ponlo en un fichero
VERSION
- Puedes escribir un fichero
CHANGELOG
siguiendo las recomendaciones de https://keepachangelog.com/en/1.0.0/ pero no le dediques mucho esfuerzo. ConInitial release
bajo la primera versión es suficiente. - Escribe un fichero
MANIFEST.in
y añade los ficherosREADME.rst
,LICENSE
yVERSION
. - Escribe el fichero
setup.py
. Lee programaticamente los contenidos deVERSION
yREADME.rst
para pasar los parametrosversion
ylong_description
. No olvides el parámetrolong_description_content_type
para señalar que estás leyendo un.rst
. Trata de ser tan exhaustivo como puedas. - Repasa que hayas dado todos los pasos y genera una distribución.
- En un entorno virtual limpio, prueba a instalar tu paquete desde el
.tar.gz
. Asegúrate de que se instala correctamente y quepip show
muestra la información correcta. - 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.
- Publica tu proyecto en Test PyPI. Prueba a instalarla desde aquí.
- Bonus: genera una nueva versión en Git.
- Bonus: si te sientes con confianza, publica en PyPI.