6. Ejemplos de distutils
************************

Nota:

  Este documento se conserva únicamente hasta que la documentación de
  "setuptools" en
  https://setuptools.readthedocs.io/en/latest/setuptools.html cubra de
  forma independiente toda la información relevante que se incluye
  actualmente aquí.

Este capitulo provee varios ejemplos básicos para ayudar a comenzar
con distutils. Información adicional sobre el uso de distutils puede
ser encontrado en el Distutils Cookbook.

Ver también:

  Distutils Cookbook
     Colección de recetas que muestran como lograr mayor control sobre
     distutils.


6.1. Distribución de Python pura (por módulo)
=============================================

Si solo distribuyes un par de módulos, especialmente si no viven en un
paquete en particular, puedes especificarlos individualmente usando la
opción "py_modules" en el script de instalación.

En el caso más simple, tendrás dos archivos de los cuales preocuparte:
un script de instalación y el único modulo que estás distribuyendo, en
este ejemplo "foo.py":

   <root>/
           setup.py
           foo.py

(En todos los diagramas en esta sección, *<root>* se referirá al
directorio raíz de la distribución.) Un script de instalación mínimo
para describir esta situación seria:

   from distutils.core import setup
   setup(name='foo',
         version='1.0',
         py_modules=['foo'],
         )

Observe que el nombre de la distribución esta especificada de forma
independiente con la opción "name", y no hay ninguna regla que diga
que tiene que ser el mismo que el nombre del único modulo de la
distribución (aunque probablemente sea una buena convención a seguir).
Sin embargo, el nombre de la distribución es usado para generar
nombres de archivo, así que deberías limitarte a letras, dígitos,
guión bajo, y guiones.

Dado que "py_modules" es una lista, puedes por supuesto especificar
múltiples módulos, por ejemplo, si estás distribuyendo los módulos
"foo" y "bar", tu configuración podría verse así:

   <root>/
           setup.py
           foo.py
           bar.py

y el script de instalación podría ser:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         py_modules=['foo', 'bar'],
         )

Puedes poner los archivos fuente de los módulos en otro directorio,
pero si tienes suficientes módulos para hacerlo, probablemente sea mas
fácil especificar los módulos por paquete en lugar de enumerarlos
individualmente.


6.2. Distribución de Python pura (por paquete)
==============================================

Si tienes más de un par de módulos para distribuir, especialmente si
están en múltiples paquetes, probablemente sea más fácil especificar
paquetes completos en lugar de módulos individuales. Esto funciona
incluso si sus módulos no están en un paquete; solo puedes decirle a
los Distutils que procese los módulos desde el paquete raíz, y eso
funciona igual que cualquier otro paquete (excepto que no tiene que
tener un archivo "__init__.py".

El script de instalación del último ejemplo también podría ser escrito
como:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         packages=[''],
         )

(La cadena de caracteres vacía representa el paquete raíz.)

Si esos dos archivos son movidos a un subdirectorio, pero permanecen
en el paquete raíz, por ejemplo:

   <root>/
           setup.py
           src/      foo.py
                     bar.py

entonces seguirías especificando el paquete raíz, pero tienes que
decirle a los Distutils dónde viven los archivos fuente del paquete
raíz:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         package_dir={'': 'src'},
         packages=[''],
         )

No obstante, lo mas típico es que quieras distribuir múltiples módulos
en el mismo paquete (o en subpaquetes). Por ejemplo, si los módulos
"foo" y "bar" pertenecen al paquete "foobar", una forma de diseñar su
estructura fuente es:

   <root>/
           setup.py
           foobar/
                    __init__.py
                    foo.py
                    bar.py

Este es, de hecho, la distribución por defecto esperada por los
Distutils, y la que requiere menos trabajo para describir en su script
de instalación:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         packages=['foobar'],
         )

Si quieres poner módulos en directorios no nombrados por su paquete,
entonces necesitas usar la opción "package_dir" otra vez. Por ejemplo,
si el directorio "src" contiene los módulos en el paquete "foobar":

   <root>/
           setup.py
           src/
                    __init__.py
                    foo.py
                    bar.py

un script de instalación apropiado sería:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         package_dir={'foobar': 'src'},
         packages=['foobar'],
         )

O, podrías poner módulos de tu paquete principal justo en la raíz de
la distribución:

   <root>/
           setup.py
           __init__.py
           foo.py
           bar.py

en cuyo caso tu script de instalación sería:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         package_dir={'foobar': ''},
         packages=['foobar'],
         )

(La cadena de caracteres vacía también representa el directorio
actual.)

Si tienes subpaquetes, deben ser listados explícitamente en
"packages", pero cualquier entrada en "package_dir" se extiende
automáticamente a los subpaquetes. (En otras palabras, los Distutils
*no* escanean tu estructura fuente, intentando descubrir que
directorios corresponden a los paquetes de Python buscando por
archivos "__init__.py".) Por lo tanto, si la distribución por defecto
hace crece un subpaquete:

   <root>/
           setup.py
           foobar/
                    __init__.py
                    foo.py
                    bar.py
                    subfoo/
                              __init__.py
                              blah.py

entonces el script de instalación correspondiente sería:

   from distutils.core import setup
   setup(name='foobar',
         version='1.0',
         packages=['foobar', 'foobar.subfoo'],
         )


6.3. Módulo de extensión única
==============================

Los módulos de extensión son especificados usando la opción
"ext_modules". "package_dir" no tiene efecto sobre donde se encuentren
los archivos fuente de la extensión; solo afecta a la fuente de los
módulos de Python puro. El mas simple caso, un único modulo de
extensión en un único archivo fuente de C, es:

   <root>/
           setup.py
           foo.c

Si la extensión "foo" pertenece al paquete raíz, el script de
instalación para este podría ser:

   from distutils.core import setup
   from distutils.extension import Extension
   setup(name='foobar',
         version='1.0',
         ext_modules=[Extension('foo', ['foo.c'])],
         )

Si la extensión realmente pertenece a un paquete, digamos "foopkg",
entonces

Con exactamente la misma distribución del árbol fuente, esta extensión
puede ser puesta en el paquete "foopkg" simplemente cambiando el
nombre de la extensión:

   from distutils.core import setup
   from distutils.extension import Extension
   setup(name='foobar',
         version='1.0',
         ext_modules=[Extension('foopkg.foo', ['foo.c'])],
         )


6.4. Verificando un paquete
===========================

El comando "check" le permite verificar si los metadatos de su paquete
cumplen los requisitos mínimos para construir la distribución.

Para ejecutarlo, sólo tienes que llamarlo usando tu script "setup.py".
Si falta algo, "check" mostrará una advertencia.

Tomemos un ejemplo con un script simple:

   from distutils.core import setup

   setup(name='foobar')

La ejecución del comando "check" mostrará algunas advertencias:

   $ python setup.py check
   running check
   warning: check: missing required meta-data: version, url
   warning: check: missing meta-data: either (author and author_email) or
            (maintainer and maintainer_email) should be supplied

Si usas la sintaxis "reStructuredText" en el campo "long_description"
y docutils esta instalado puedes comprobar si la sintaxis está bien
con el comando "check", usando la opción "restructuredtext".

Por ejemplo, si el script "setup.py" es cambiado así:

   from distutils.core import setup

   desc = """\
   My description
   ==============

   This is the description of the ``foobar`` package.
   """

   setup(name='foobar', version='1', author='tarek',
       author_email='tarek@ziade.org',
       url='http://example.com', long_description=desc)

Donde se rompa la descripción larga, "check" será capaz de detectarla
usando el analizador "docutils":

   $ python setup.py check --restructuredtext
   running check
   warning: check: Title underline too short. (line 2)
   warning: check: Could not finish the parsing.


6.5. Leyendo los metadatos
==========================

La función "distutils.core.setup()" provee una interfaz de línea de
comandos que te permite consultar los campos de metadatos de un
proyecto a través del script "setup.py" de un proyecto dado:

   $ python setup.py --name
   distribute

Esta llamada lee los metadatos de "name" ejecutando la función
"distutils.core.setup()". Aunque, cuando se crea una distribución
fuente o binaria con Distutils, los campos de metadatos son escritos
en un archivo estático llamado "PKG-INFO". Cuando un proyecto basado
en Distutils es instalado en Python, el archivo "PKG-INFO" es copiado
junto con los módulos y paquetes de la distribución en "NAME-VERSION-
pyX.X.egg-info", donde "NAME" es el nombre del proyecto, "VERSION" su
versión como se define en los metadatos, y "pyX.X" la versión mayor y
menor de Python como "2.7" o "3.2".

Puedes leer de nuevo este archivo estático usando la clase
"distutils.dist.DistributionMetadata" y su método "read_pkg_file()":

   >>> from distutils.dist import DistributionMetadata
   >>> metadata = DistributionMetadata()
   >>> metadata.read_pkg_file(open('distribute-0.6.8-py2.7.egg-info'))
   >>> metadata.name
   'distribute'
   >>> metadata.version
   '0.6.8'
   >>> metadata.description
   'Easily download, build, install, upgrade, and uninstall Python packages'

Note que la clase también puede ser instanciada con una ruta de
archivo de metadatos para cargar sus valores:

   >>> pkg_info_path = 'distribute-0.6.8-py2.7.egg-info'
   >>> DistributionMetadata(pkg_info_path).name
   'distribute'
