# 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'