Apartados


10. Empaquetando librerías en Python

Nivel de dificultad:4 sobre 5

``Descubrirás que la vergüenza es como el dolor; solo lo sientes una vez.''
--Marquesa de Merteuil, Las amistades peligrosas10.1

10.1 Inmersión

Los verdaderos artistas lanzan productos. O eso dice Steve Jobs. ¿Quieres sacar una versión de un script, librería, framework o aplicación? Excelente. El mundo necesita más código en Python. Python 3 viene con un framework de empaquetado denominado Distutils, que sirve para muchas cosas:

Se integra con el Índice de Paquetes de Python ``(PyPI)''10.2, un repositorio central para las librerías de Python de código abierto.

Todos estos aspectos de Distutils giran en torno al script de setup, que tradicionalmente se ha denominado setup.py. De hecho, ya has visto varios scripts de setup (configuración) a lo largo de este libro. Utilizaste Distutils para instalar httplib2 en el apartado 14.5, Introducción a httplib2. También lo utilizaste para instalar chardet en el capítulo 15, Caso de estudio: migrar chardet a Python 3.

En este capítulo aprenderás cómo funcionan los scripts de configuración para las librerías chardet y httplib2, y recorrerás los pasos necesarios para generar una versión de tu propio software en Python.

# chardet's setup.py
from distutils.core import setup
setup(
    name = "chardet",
    packages = ["chardet"],
    version = "1.0.2",
    description = "Universal encoding detector",
    author = "Mark Pilgrim",
    author_email = "mark@diveintomark.org",
    url = "http://chardet.feedparser.org/",
    download_url = "http://chardet.feedparser.org/download/python3-chardet-1.0.1.tgz",
    keywords = ["encoding", "i18n", "xml"],
    classifiers = [
        "Programming Language :: Python",
        "Programming Language :: Python :: 3",
        "Development Status :: 4 - Beta",
        "Environment :: Other Environment",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
        "Operating System :: OS Independent",
        "Topic :: Software Development :: Libraries :: Python Modules",
        "Topic :: Text Processing :: Linguistic",
        ],
    long_description = """ Universal character encoding detector
-------------------

Detects - ASCII, UTF-8, UTF-16 (2 variants), UTF-32 (4 variants) - Big5, GB2312, EUC-TW, HZ-GB-2312, ISO-2022-CN (Traditional and Simplified Chinese) - EUC-JP, SHIFT_JIS, ISO-2022-JP (Japanese) - EUC-KR, ISO-2022-KR (Korean) - KOI8-R, MacCyrillic, IBM855, IBM866, ISO-8859-5, windows-1251 (Cyrillic) - ISO-8859-2, windows-1250 (Hungarian) - ISO-8859-5, windows-1251 (Bulgarian) - windows-1252 (English) - ISO-8859-7, windows-1253 (Greek) - ISO-8859-8, windows-1255 (Visual and Logical Hebrew) - TIS-620 (Thai)

This version requires Python 3 or later; a Python 2 version is available separately. """ )

chardet y httplib2 son librerías de código abierto, pero no existe ningún requisito para que tú liberes tus propias librerías Python bajo la licencia que quieras. El proceso descrito en este capítulo funcionará para cualquier software Python, independientemente del tipo de licencia.

10.2 Cosas que Distutils no puede hacer por ti

Distribuir tu primer paquete Python es un proceso intimidante (la segunda es un poco más sencillo). Distutils intenta automatizarlo al máximo, pero hay algunas cosas que tienes que hacer por ti mismo.

10.3 La estructura de directorios

Para comenzar a empaquetar un software Python necesitas colocar tus ficheros y directores en el orden adecuado. La estructura de directorios de httplib2 es la siguiente:

httplib2/                 
|
+-README.txt            
|
+-setup.py             
|
+-httplib2/           
   |
   +-__init__.py
   |
   +-iri2uri.py

  1. Línea 1: Crea un directorio raíz para contenerlo todo. Dale el mismo nombre que el de tu módulo Python.
  2. Línea 3: Para acomodar a tus usuarios de windows, el fichero ``README'' debería incluir la extensión .txt, y debería utilizar el estilo de retorno de caracteres de Windows. Solo porque utilices un moderno editor de texto que se ejecuta desde la línea de comando e incluya su propio lenguaje de macros, eso no significa que tengas que hacerle la vida más difícil a tus usuarios (Tus usuarios usan el cuaderno de notas de Windows, triste pero cierto). Incluso aunque estés en Linux o en Mac OS X, tu editor de textos seguro que tiene una opción para grabar los ficheros con retornos de carro al estilo de Windows.

  3. Línea 5: Tu script de configuración de Distutils debería llamarse setup.py, a menos que tengas una buena razón para no hacerlo así. Pero no la tienes.
  4. Línea 7: Si tu paquete Python es un único fichero .py, deberías ponerlo en el directorio ``raíz'' junto al fichero ``README'' y tu script de configuración. Pero la librería httplib2 no es un único fichero; es un módulo multifichero. Simplemente añade el directorio httplib2 dentro del directorio raíz. De este modo tendrás el fichero __init__.py dentro de un directorio httplib2 que, a su vez, está dentro del directorio raíz httplib2. Esto no es un problema; de hecho, simplifica el proceso de empaquetado.

La estructura de directorios de chardet es un poco diferente. Como httplib2 es un módulo multifichero, así que hay un directorio chardet dentro del directorio ``raíz'' chardet. Además del fichero README.txt, chardet dispone de una documentación formateada en HTML, en el directorio docs. Este contiene varios ficheros .html y css y un subdirectorio images que contiene varios ficheros .png y .gif (esto será importante después). También, manteniendo la convención del software liberado bajo licencia gpl, tiene un fichero separado denominado COPYING.txt que contiene el texto completo de la licencia GPL.

chardet/
|
+-COPYING.txt
|
+-setup.py
|
+-README.txt
|
+-docs/
|  |
|  +-index.html
|  |
|  +-usage.html
|  |
|  +-images/ ...
|
+-chardet/
   |
   +-__init__.py
   |
   +-big5freq.py
   |
   +-...

10.4 Escribiendo el script de configuración

El script de configuración de Distutils es un script en Python. En teoría puede hacer cualquier cosa que se pueda hacer en Python. En la práctica, debería hacer lo mínimo posible. Debería ser un script ``aburrido''. Cuanto más exótico sea tu proceso de instalación, tanto más complejo serán los mensajes de error durante el mismo.

La primera línea de cualquier fichero de script de configuración es:

form distutils.core import setup

Esta línea importa la función setup(), que es el punto de entrada principal a Distutils. El 95% de los ficheros de configuración de Distutils constan simplemente de una llamada al método setup() y nada más (me he inventado la estadística, pero si tu script hace más cosas, deberías tener una buena razón. ¿La tienes? Seguramente no).

La función setup() puede tomar docenas de parámetros10.4. Para mantener la cordura de los que tengan que leerla, deberías usar argumentos con nombre para cada uno que uses. Esto no es solamente una convención; es un requisito. Tu script de configuración fallará si intentas llamar a la función con argumentos sin nombre.

Se requieren, al menos, los siguientes:

Aunque no se requieren, recomiendo que también incluyas los siguientes parámetros en tu script de configuración:

Los metadatos del script de configuración están definidos en la PEP 314 http://www.python.org/dev/peps/pep-0314/

Ahora veamos el script de configuración de chardet. Contiene todos los parámetros requeridos y los recomendados, más uno que no he mencionado aún: packages.

from distutils.core import setup
setup(
    name = 'chardet',
    packages = ['chardet'],
    version = '1.0.2',
    description = 'Universal encoding detector',
    author='Mark Pilgrim',
    ...
)

El parámetro packages destaca un solapamiento desafortunado en el vocabulario del proceso de distribución. Hemos estado hablando del ``paquete'' como la cosa que estamos construyendo (y potencialmente, listándolo en el Índice de Paquetes de Python). Pero eso no es lo que el parámetro packages quiere expresar. Se refiere al hecho de que el módulo chardet es un módulo multifichero, algunas veces conocido como...un ``paquete''. El parámetro packages le indica a Distutils que incluya el directorio chardet, su fichero __init__.py y todos los ficheros .py que constituyen el módulo chardet. Esto es muy importante; toda esta parrafada sobre la documentación y los metadatos es irrelevante si te olvidas de incluir el código propiamente dicho.

10.5 Clasificando tu paquete

El Índice de Paquetes Python (``PyPI'') contiene miles de librerías Python. Una correcta clasificación utilizando metadatos permite encontrarlas con más facilidad. PyPI te permite mostrar los paquetes filtrando por clasificador10.6. Puedes seleccionar varios para acotar la búsqueda. ¡Estos clasificadores no son metadatos invisibles que puedas ignorar!

Para clasificar tu software debes pasar un parámetro classifiers a la función setup de Distutils. Este parámetro es una lista de cadenas de caracteres que no son texto libre. Todas las cadenas de clasificación deben salir una lista que existe en PyPI que está accesible en el siguiente enlace http://pypi.python.org/pypi?:action=list_classifiers.

Aquí puedes ver una parte de ella:

...
Topic :: Terminals
Topic :: Terminals :: Serial
Topic :: Terminals :: Telnet
Topic :: Terminals :: Terminal Emulators/X Terminals
Topic :: Text Editors
Topic :: Text Editors :: Documentation
Topic :: Text Editors :: Emacs
Topic :: Text Editors :: Integrated Development Environments (IDE)
Topic :: Text Editors :: Text Processing
Topic :: Text Editors :: Word Processors
...

Los clasificadores son opcionales. Puedes escribir un script de configuración de Distutils sin ellos. No lo hagas. Siempre deberías incluir, al menos, los siguientes clasificadores:

También te recomiendo que incluyas los siguientes:

10.5.1 Ejemplos de unos buenos clasificadores de paquete

Como ejemplo, aquí están los clasificadores de Django10.9, un framework para el desarrollo de aplicaciones web bajo licencia BSD, multiplataforma y listo para producción10.10.

Programming Language :: Python
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Development Status :: 5 - Production/Stable
Environment :: Web Environment
Framework :: Django
Intended Audience :: Developers
Topic :: Internet :: WWW/HTTP
Topic :: Internet :: WWW/HTTP :: Dynamic Content
Topic :: Internet :: WWW/HTTP :: WSGI
Topic :: Software Development :: Libraries :: Python Modules

A continuación se muestran los clasificadores de chardet, la librería de detección de la codificación de caracteres que se muestra en el capítulo 15. chardet es una beta, multiplataforma, compatible con Python 3, con licencia LGPL, y sus usuarios potenciales son los desarrolladores para uso en sus propios productos.

Programming Language :: Python
Programming Language :: Python :: 3
License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Operating System :: OS Independent
Development Status :: 4 - Beta
Environment :: Other Environment
Intended Audience :: Developers
Topic :: Text Processing :: Linguistic
Topic :: Software Development :: Libraries :: Python Modules

Y estos son los clasificadores de httplib2, la librería que se mostraba en el capítulo 14. httplib2 es calidad beta, multiplataforma, con licencia MIT, y su audiencia es la de los desarrolladores Python.

Programming Language :: Python
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Development Status :: 4 - Beta
Environment :: Web Environment
Intended Audience :: Developers
Topic :: Internet :: WWW/HTTP
Topic :: Software Development :: Libraries :: Python Modules

10.6 Especificando ficheros adicionales con un ``Manifiesto''

Por defecto, Distutils incluirá los siguientes ficheros en tu paquete de distribución:

Eso es suficiente para los ficheros del proyecto httplib2. Pero para el proyecto chardet necesitamos incluir también el fichero de licencia COPYING.txt y el directorio docs/ completo, que contiene las imágenes y los ficheros HTML. Para decirle a Distutils que incluya estos ficheros y directorios adicionales cuando construya el paquete de distribución, necesitas un fichero manifest.

Un fichero de manifiesto es un fichero de texto denominado MANIFEST.in. Debe encontrarse en el directorio raíz del proyecto, junto al fichero README.txt y setup.py. Los ficheros de manifiesto no son scripts de Python: son ficheros de texto que contienen una lista de ``comandos'' en un formato definido por Distutils. Estos comandos del manifiesto te permiten incluir o excluir determinados ficheros y directorios.

Este es el fichero de manifiesto definido para el proyecto chardet:

include COPYING.txt
recursive-include docs *.html *.css *.png *.gif

  1. Línea 1: La primera línea es autoexplicativa: incluir el fichero COPYING.txt del directorio raíz del proyecto.
  2. Línea 2: La segunda línea es un poco más complicada. El comando recursive-include toma el nombre de un directorio y de un o más ficheros. Los ficheros pueden incluir caracteres comodín. Esta línea significa ``¿Ves el directorio docs/ en el directorio raíz del proyecto? Mira dentro, recursivamente, y busca ficheros .html, .css, .png, y .gif. Inclúyelos a todos en la distribución de mi paquete''.

Todos los comandos de manifiesto conservan la estructura de directorios que tengas en el directorio de tu proyecto. Este comando recursive-include no va a poner un puñado de ficheros .html y .png en el directorio raíz del paquete. Va a mantener la estructura de directorios de docs/, pero solamente incluirá los ficheros que coincidan con los comodines especificados10.11.

Los ficheros de manifiesto tienen su propio formato. Puedes consultar ``Especificando los ficheros a distribuir''
(http://docs.python.org/3.1/distutils/sourcedist.html#manifest)
y ``los comandos de la plantilla de manifiesto''
(http://docs.python.org/3.1/distutils/commandref.html#sdist-cmd),
para conocer los detalles del mismo.

Por insistir: solo necesitas incluir un fichero de manifiesto si quieres incluir ficheros que Distutils no incluye por defecto. Si necesitas el fichero, solamente deberías incluir los ficheros y directorios que Distutils no encontraría por sí mismo.

10.7 Revisando errores en tu script de configuración

Hay mucho que tener en cuenta. Distutils viene con un comando de validación que permite revisar si todos los metadatos obligatorios están presentes en tu script de configuración. Por ejemplo, si te olvidas de incluir el parámetro version, Distutils te lo recordará:

c:> c:31.exe setup.py check
running check
warning: check: missing required meta-data: version

Una vez incluyas el parámetro version (y los restantes metadatos obligatorios), el comando check se ejecutará así:

c:> c:31.exe setup.py check
running check

10.8 Creando un distribución de código fuente

Distutils permite la construcción de diversos tipos de paquetes de distribución. Como mínimo, deberías construir una ``distribución de código fuente'' que incluiría tu código fuente, el fichero de configuración de Distutils, el fichero README, y los ficheros adicionales que quieras incluir. Para construirla, pasa el comando sdist a tu script de configuración de Distutils.

c:> c:31.exe setup.py sdist
running sdist
running check
reading manifest template 'MANIFEST.in'
writing manifest file 'MANIFEST'
creating chardet-1.0.2
creating chardet-1.0.2
creating chardet-1.0.2
creating chardet-1.0.2
copying files to chardet-1.0.2...
copying COPYING -> chardet-1.0.2
copying README.txt -> chardet-1.0.2
copying setup.py -> chardet-1.0.2
copying chardet__init__.py -> chardet-1.0.2
copying chardet5freq.py -> chardet-1.0.2
...
copying chardet.py -> chardet-1.0.2
copying chardet8prober.py -> chardet-1.0.2
copying docs.html -> chardet-1.0.2
copying docs.html -> chardet-1.0.2
copying docs-it-works.html -> chardet-1.0.2
copying docshtml -> chardet-1.0.2
copying docs.html -> chardet-1.0.2
copying docs-encodings.html -> chardet-1.0.2
copying docs.html -> chardet-1.0.2
copying docs.png -> chardet-1.0.2
copying docs.png -> chardet-1.0.2
copying docs.png -> chardet-1.0.2
copying docs.gif -> chardet-1.0.2
copying docs.png -> chardet-1.0.2
copying docs.png -> chardet-1.0.2
creating dist
creating 'dist-1.0.2.zip' and adding 'chardet-1.0.2' to it
adding 'chardet-1.0.2'
adding 'chardet-1.0.2-INFO'
adding 'chardet-1.0.2.txt'
adding 'chardet-1.0.2.py'
adding 'chardet-1.0.25freq.py'
adding 'chardet-1.0.25prober.py'
...
adding 'chardet-1.0.2.py'
adding 'chardet-1.0.28prober.py'
adding 'chardet-1.0.2__init__.py'
adding 'chardet-1.0.2.html'
adding 'chardet-1.0.2.html'
adding 'chardet-1.0.2-it-works.html'
adding 'chardet-1.0.2html'
adding 'chardet-1.0.2.html'
adding 'chardet-1.0.2-encodings.html'
adding 'chardet-1.0.2.html'
adding 'chardet-1.0.2.png'
adding 'chardet-1.0.2.png'
adding 'chardet-1.0.2.png'
adding 'chardet-1.0.2.gif'
adding 'chardet-1.0.2.png'
adding 'chardet-1.0.2.png'
removing 'chardet-1.0.2' (and everything under it)

Hay varias cosas a destacar aquí:

c:> dir dist
 Volume in drive C has no label.
 Volume Serial Number is DED5-B4F8

Directory of c:

07/30/2009 06:29 PM <DIR> . 07/30/2009 06:29 PM <DIR> .. 07/30/2009 06:29 PM 206,440 chardet-1.0.2.zip 1 File(s) 206,440 bytes 2 Dir(s) 61,424,635,904 bytes free

10.9 Creando un instalador gráfico

En mi opinión, toda librería Python merece un instalador gráfico para usuarios de Windows. Es fácil de hacer (incluso aunque tú no trabajes en Windows), y los usuarios de Windows lo apreciarán.

Distutils puede crear el instalador gráfico por ti10.12, si le pasas el comando bdist_wininst a tu script de configuración de Distutils.

c:> c:31.exe setup.py bdist_wininst
running bdist_wininst
running build
running build_py
creating build
creating build
creating build
copying chardet5freq.py -> build
copying chardet5prober.py -> build
...
copying chardet.py -> build
copying chardet8prober.py -> build
copying chardet__init__.py -> build
installing to build.win32
running install_lib
creating build.win32
creating build.win32
creating build.win32
creating build.win32
copying build5freq.py -> build.win32
copying build5prober.py -> build.win32
...
copying build.py -> build.win32
copying build8prober.py -> build.win32
copying build__init__.py -> build.win32
running install_egg_info
Writing build.win32-1.0.2-py3.1.egg-info
creating 'c:2f4h7e.zip' and adding '.' to it
adding 'PURELIB-1.0.2-py3.1.egg-info'
adding 'PURELIB5freq.py'
adding 'PURELIB5prober.py'
...
adding 'PURELIB.py'
adding 'PURELIB8prober.py'
adding 'PURELIB__init__.py'
removing 'build.win32' (and everything under it)
c:> dir dist
c:>dir dist
 Volume in drive C has no label.
 Volume Serial Number is AADE-E29F

Directory of c:

07/30/2009 10:14 PM <DIR> . 07/30/2009 10:14 PM <DIR> .. 07/30/2009 10:14 PM 371,236 chardet-1.0.2.win32.exe 07/30/2009 06:29 PM 206,440 chardet-1.0.2.zip 2 File(s) 577,676 bytes 2 Dir(s) 61,424,070,656 bytes free

10.9.1 Construyendo paquetes de instalación para otros sistemas operativos

Distutils de puede ayudar a construir paquetes de instalación para usuarios linux10.13. En mi opinión, probablemente no te merezca la pena. Si quieres distribuir software para Linux, puedes dedicar el tiempo con miembros de la comunidad que se han especializado en software de empaquetado para las distribuciones Linux mayoritarias.

Por ejemplo, mi librería chardet está en los repositorios de Debian GNU/linux10.14 (Y, por lo tanto, en los de Ubuntu). No tuve nada que ver con ello; los paquetes aparecieron allí un día. La comunidad de Debian tiene sus propias políticas de empaquetado de librerías Python10.15, y el paquete python-chardet está diseñado para cumplirlas. Y puesto que el paquete está en los repositorios de Debian, sus usuarios recibirán las actualizaciones de seguridad y las versiones nuevas, dependiendo de la configuración que hayan elegido en sus ordenadores.

Los paquetes de Linux que Distutils construye no ofrecen ninguna de estas ventajas. Puedes dedicar tu tiempo de mejor manera.

10.10 Añadiendo tu software al Índice de Paquetes de Python

Subir software al Índice de Paquetes de Python es un proceso de tres fases:

  1. Primero, te debes registrar.
  2. Luego, debes registrar tu software.
  3. Por último, debes subir los paquetes que creaste con setup.py sdist y setup.py bdist_*.

Para registrarte tienes que ir a la página de registro de usuarios de PyPI10.16. Debes introducir el nombre de usuario y clave deseados, proporcionar una dirección de correo electrónico válida, y pulsar el botón Register (Si dispones de una clave PGP o GPG, también puedes incluirla. Si no tienes una, o no sabes lo que es, no te preocupes). Revisa tu correo; en unos minutos deberías recibir un mensaje de PyPI con un enlace de validación. Pulsa sobre él para completar el proceso de registro.

Ahora necesitas registrar tu software en PyPI y subirlo. Puedes hacerlo en un único paso:

c:> c:31.exe setup.py register sdist bdist_wininst upload
running register
We need to know who you are, so please choose either:
 1. use your existing login,
 2. register as a new user,
 3. have the server generate a new password for you (and email it to you), or
 4. quit
Your selection [default 1]:  1
Username: MarkPilgrim
Password:
Registering chardet to http://pypi.python.org/pypi  
Server response (200): OK
running sdist
... output trimmed for brevity ...
running bdist_wininst
... output trimmed for brevity ...
running upload
Submitting dist-1.0.2.zip to http://pypi.python.org/pypi
Server response (200): OK
Submitting dist-1.0.2.win32.exe to http://pypi.python.org/pypi
Server response (200): OK
I can store your PyPI login so future submissions will be faster.
(the login will be stored in c:pypirc)
Save your login (y/N)?n

  1. Línea 1: Cuando distribuyes tu proyecto por vez primera, Distutils lo añade al Índice de Paquetes de Python y le da su propia URL. A partir de ese momento, solo actualizará los metadatos del proyecto con los cambios que hayas introducido en tus parámetros de setup.py. Después, construye una distribución de código fuente (sdist) y un instalador para Windows (bdist_wininst). Luego los sube a PyPI (upload).
  2. Línea 8: Teclea 1 o pulsa ENTER para seleccionar que quieres usar un usuario existente.
  3. Línea 9: Teclea el usuario y la clave que seleccionaste en la página de registro. La clave no se mostrará en pantalla; ni siquiera con asteriscos en lugar de los caracteres. Solamente teclea la clave y pulsa ENTER.
  4. Línea 11: Distutils registra tu paquete en PyPI...
  5. Línea 13: ...construye tu distribución de código fuente...
  6. Línea 15: ...construye el instalador para Windows...
  7. Línea 17: ...y los sube al Índice de Paquetes de Python.
  8. Línea 24: Si quieres automatizar el proceso de distribución de nuevas versiones, necesitas salvar tus credenciales en un fichero local. Esto es inseguro y totalmente opcional.

¡Enhorabuena! Ya tienes su própia página en el Índice de Paquetes de Python. La dirección es http://pypi.python.org/pypi/NAME, en donde NAME es la cadena de caracteres que utilizaste en el parámetro name del fichero setup.py.

Si quieres distribuir una nueva versión, solo tienes que actualizar el fichero setup.py con el nuevo número de versión y volver a ejecutar el comando anterior:

c:> c:31.exe setup.py register sdist bdist_wininst upload

10.11 Los muchos futuro posibles del empaquetado de Python

Distutils no es el único sistema de empaquetado de software Python, pero en el momento de escritura de este libro (Agosto de 2009), es el único que funciona con Python 3. Hay otros sistemas que funcionan en Python 2; algunos se enfocan en la instalación, otros en las pruebas y distribución. Algunos de ellos, o todos, puede que acaben migrando a Python 3.

Los siguientes frameworks se enfocan en la instalación:

Los siguientes se enfocan en las pruebas y distribución:

10.12 Lecturas complementarias

Sobre Distutils:

Sobre otros sistemas de empaquetado:

José Miguel González Aguilera 2016-08-18