Publicar paquetes Python en PyPi

Por Arrecio


De nuevo una entrada en el blog sobre Python. En esta ocasión hablaré de su repositorio oficial de paquetes PyPi. Se trata de un repositorio de libre acceso a la comunidad, tan libre que me da la sensación que se basa en la confianza de todos los miembros o usuarios ya que parece bastante desatendida o ausente la fiscalización de lo que se sube. Indicar que existe un repositorio paralelo de testing llamado Test PyPi al que resulta totalmente recomendable subir nuestros paquetes antes de publicarlo en el repositorio principal.

Ambos repositorios están completamente separados y requieren registrarse de manera separada. No debemos pensar que son un sólo lugar y que el proceso consiste en crear un único proyecto con el que trabajaríamos con dos versiones (test y release) a la vez. Me parece un diseño acertado aunque haya gente que pueda no compartir mi opinión.

La página de entrada de ambos repositorios incluyen enlaces a los manuales oficiales tanto para instalar paquetes como para publicarlos. Otros enlaces interesantes dentro de la misma web son estas guías. No son muy extensas pero parecen complicar más el asunto de lo que finalmente es.

Antes de nada comentar que el estándar para el sistema público de paquetes Python es responsabilidad de la Python Packaging Authority o PyPa.

Lo habitual es que casi todo el mundo mantenga el código fuente también en la nube de GitHub. Debes saber que en tal servicio se incluye la posibilidad de automatizar las publicaciones de los paquetes. Se trata de una opción que no tengo intención de utilizar pero aquí el enlace que lo explica de manera oficial y aquí un pequeño tutorial.

Como siempre aquí va el dibujo de lo que esta entrada pretende mostrar:

Proceso de publicación de paquetes Python

Estructura de directorio local

En primer lugar debemos almacenar nuestro paquete en una estructura de directorios concreta. Las fuentes irán al directorio src, en es donde se encuentran los distintos módulos y que se incorporarán al raiz del python path que es el lugar o lugares a partir de los cuales se buscan los módulos cuando hacemos uso de import. Para conocer que lugares son estos puedes ejecutar este simple script:

import sys
print(sys.path)

La salida del programa anterior podrá variar dependiendo del entorno utilizado (ver también algo sobre entornos virtuales).

Cada módulo estará alojado en un directorio distinto con el nombre del mismo y contendrá un archivo init.py que puede estar vacío y que indica que el directorio contiene un módulo python.

Además del directorio src hay que incluir el archivo project.toml en el que se describen algunos aspecto del paquete que serán utilizados por los scripts de generación de la versión distribuible y para la subida al repositorio. Lo normal es partir de un modelo preescrito, o reutilizar alguno de los que hayas utilizado antes, pero una descripción de su formato debería estar disponible en este enlace. Aunque existen métodos alternativos este es a día de escribir esta entrada el procedimiento estándar establecido por la PyPi y descrito en el PEP 621.

PEP es el acrónimo de Python Enhancement Proposals que son una colección de documentos por el que se describen diversos aspectos del lenguaje y buenas prácticas en el uso del mismo. Los primeros PEP son precisamente los que describen el propósito de estos documentos empezando por el PEP 0 – Index of Python Enhancement Proposals. El PEP 20, el llamado Zen de Python lista una serie de principios aplicables prácticamente a todos los proyectos de cualquier lenguaje y si me apuras a cualquier aspecto de la vida.

En el diagrama aparecen dos archivos adicionales que aunque no sean obligatorios siempre conviene tener presentes. Se trata del README que se especifica en el campo readme del project.toml y que admite además del texto plano dos tipos de formatos, uno de ellos es el markdown (.md) y el reStructuredText (.rst). Mejor seguir este enlace para más info.

El archivo de licencia o LICENSE debería incluirse siempre y en el incluir la licencia de uso para los usuarios que decida utilizar el paquete o librería. Python es una contribución libre al mundo y lo ideal es que todos los paquetes siguieran la misma filosofía pero incluir licencias especialmente restrictivas es una opción válida, incluso las de uso privativo aunque únicamente tienen sentido declarativo una vez que cualquier paquete que forme parte del repositorio puede ser descargado libremente. Se referencia en el proyecto.toml con el campo license-files. El campo license se utiliza para indicar una referencia rápida al tipo de licencia siguiendo lo descrito en el PEP 639.

Construcción del paquete

Para construir el paquete necesitaremos hacer uso del paquete build el cual puede ser instalado mediante el siguiente comando:

pip install build

Tras esto la mera ejecución del siguiente comando debería construir el paquete en formato wheel (ver también ¿Qué son las Python wheel y por qué debería importarte?).

Para construir el paquete nada más sencillo que ejecutar el paquete build que acabas de instalar en modo módulo desde el directorio raíz del proyecto:

python -m build

Tras un corto proceso que puede incluir la instalación del paquete buildtools se generarán los binarios distribuibles wheel en el directorio dist.

Una vez empaquetado ya podríamos instalar en nuestro python path el paquete usando la carpeta del proyecto como fuente local, lo podemos hacer ejecutando el siguiente comando:

pip install -e .

Subida del paquete

Vamos a dar por hecho que aún no tenemos cuentas tanto en PyPi como en el repositorio de Testing. Empezaremos por utilizar el repositorio de prueba pero antes hay que instalar el paquete twine que instalará el script del mismo nombre por lo que para utilizarlo desde cualquier lugar siempre que el directorio de scripts de python se incluya en el PATH.

pip install twine

Aunque no es obligatorio forma parte de las buenas prácticas, y así se describe en el tutorial, que cuando se trabaja con Testing PyPi el nombre del módulo principal del paquete, es decir el directorio que pende de src y donde se aloja nuestro código fuente, contenga una coletilla con - o _ seguido del nombre del usuario.

twine es una aplicación que automatiza operaciones de comprobación y subida de paquetes utilizando el project.toml visto anteriormente. Para comprobar que los archivos construidos con el paso anterior lo están correctamente y están por tanto preparados para una subido debemos ejecutar lo siguiente:

twine check dist/*

Si no hay problemas ya podríamos ejecutar lo siguiente:

twine upload --repository testpypi dist/*

Si todo ha ido bien se nos preguntará por un API token que no tendremos ya que ni tan siquiera somos usuario del repositorio. La creación del usuario es bastante trivial y no dista en absoluto técnicamente hablando de cualquier otra creación de usuario en otro tipo de web.

Creado el usuario tendremos acceso a algunas opciones de la web lo que incluye un apartado de automatizaciones en la publicación usando servicios de terceros que yo particularmente no tengo intención de utilizar tal y como indiqué en una nota anterior.

Llegados a este punto el usuario puede sentirse extrañado al no encontrar ninguna opción dentro de la web para crear un nuevo proyecto. Esta situación se produce automáticamente a través de la subida del paquete por primera vez.

Antes de subir la aplicación vamos a necesitar ese API token, la opción para crearla está en el apartado de Configuración de Cuenta y el enlace directo es este. Al no tener subidos proyectos previamente sólo permitirá crear un token global. Estableciendo el nombre que queramos y dándole a generar el token lo obtendremos en forma de cadena larga que comienza por pypi- y que deberemos guardar para no olvidarla ya que será la única vez que se mostrará. No obstante podemos crear tantos tokens como queramos por lo que resulta tan sencillo como crear uno nuevo cuando olvidemos el anterior. Esa cadena largar será la que tengamos que ingresar cuando twine nos pida el API Token.

Introducido el token si todo ha ido bien se nos suministrará la dirección web para el paquete recién subido donde a su vez se mostrará el comando para instalar con pip y hacer las pruebas que estimemos conveniente para comprobar que el paquete está bien construido y se descarga e instala adecuadamente.

A partir de la correcta subida del paquete, si acudimos de nuevo a la página para crear tokens veremos que ya podemos crear tokens específicos para el proyecto recién subido lo que nos permitirá compartirlos con colaboradores.

El fichero .pypirc

Cuando creamos el token, en la misma página en la que este se muestra, se informa de la forma en la que podemos utilizarlo de manera automática mediante la creación de un archivo .pypirc en el raíz del sistema de archivos de nuestro usuario en el sistema operativo y que twine utilizará para no tener necesidad de introducir manualmente el token en cada subida.

Lo anoto porque me resulta importante. En mi caso incorporé la primera vez el .pypirc en el raíz del proyecto lo que no obtuvo ningún resultado. Debe estar en la carpeta de usuario asignada en el sistema operativo. En linux se tiende a tener este tipo de archivos dentro de la carpeta .config pero no es así en este caso, debe estar en el HOME del usuario.

Subida al repositorio público principal del PyPi

Aprendido todo lo anterior, la subida al repositorio principal se convierte en algo trivial ya que el procedimiento a seguir es el mismo con excepción de que omitiremos la parte que dice --repository testpypi cuando usemos el comando twine.

Además debemos tener en cuenta que si utilizamos la convención para el nombre del paquete en Testing debemos eliminar la coletilla con el nombre del usuario, pero antes de poner un nombre definitivo debemos estar seguros de que este no está siendo ya utilizado. Nos obligará a construir de nuevo el paquete.

Y con esto otra cuestión importante, si usamos el comando:

twine upload dist/*

twine procurará subir todos los archivos del directorio dist que puede contener archivos obsoletos. Además contiene tanto el archivo binario wheel como las fuentes. Si quieres mantener las fuentes al margen, por ejemplo para forzar a quienes visiten el sitio a que acudan a GitHub (si las tienes allí alojadas) si quieren consultarlas, indica a twine que suba únicamente un archivo en particular que en este caso será el .whl.

Y con todo esto ya tendremos nuestro paquete publicado y a disposición de toda la comunidad. Y recuerda que la comunidad no sabe absolutamente nada del nuevo paquete disponible por lo que escribir un buen README es una de las partes más importantes en el proceso de crear paquetes públicos que estarán al alcance de todos.