Skip to content

Latest commit

 

History

History
529 lines (381 loc) · 17.8 KB

File metadata and controls

529 lines (381 loc) · 17.8 KB

Documentación

En desarrollo de software moderno, documentar es dejar constancia de la funcionalidad de una API. El propósito de la documentación moderna no es explicar las líneas de código, el código debería ser autoexplicativo.

El propósito de la documentación cambia en función de la audiencia. Para el usuario, la documentación debe dejar claro cómo usar y qué esperar de un software. Para los desarrolladores, la documentación debe reducir la carga cognitiva que supone analizar una unidad de código, agilizando el proceso de evolución del software, como la creación de nueva funcionalidad o la "refactorización" de la ya existente.

  • Write The Docs es un portal web que pone énfasis en las buenas prácticas para escribir documentación.

Documentación integrada

La cadena de texto que encabeza un módulo, o la que sigue a una clase, un método o una función se denomina cadena de documentación o docstring. Normalmente, se delimitada por tres dobles comillas y es reconocida por el intérprete de Python que hace de ella un atributo del objeto al que documenta:

  1. Crea un proyecto nuevo, crea un nuevo paquete files, y dentro, añade el siguiente código al fichero memoryfiles.py (no olvides crear el fichero __init__.py dentro del paquete files):

    """Contain in-memory-files-related functionality."""
    
    class MemoryFile:
        """Represent a file that only exists in memory."""
    
        min_size = 4096
        """Minimum size of the memory file in bytes."""
    
        def __init__(self, name=''):
            """Create a new memory file with an optional name."""
    
            self.name = name
            """Hold the name of the memory file."""
    
        def read(self, count=1024):
            """Read some bytes from the file into a bytes object."""

    Observa cómo documentamos los distintos objetos del módulo:

    • La primera línea es una descripción corta del objeto.
    • El tiempo verbal es el imperativo puesto que la documentación prescribe lo que el objeto debe hacer.
    • Comienza por mayúscula y termina en punto.
    • Si hay más documentación, esta continúa tras dejar una línea en blanco y se sangra al mismo nivel que el las comillas iniciales.

    Las cadenas que acompañan a min_size y self.name = ... no son docstrings, pero el PEP 257, que introduce la idea de este tipo de documentación, recomienda este estilo de cara a que herramientas de extracción de documentación ascie las cadenas como la documentación de atributos de clase e instancia, respectivamente.

  2. Inicia un intérprete interactivo, importa el módulo e inspecciona las distintas cadenas de documentación:

    from files import memoryfiles
    memoryfiles.__doc__
    memoryfiles.MemoryFile.__doc__
    memoryfiles.MemoryFile.__init__.__doc__
    memoryfiles.MemoryFile.read.__doc__
  3. Haz lo mismo mediante la función help():

    help(memoryfiles)
    help(memoryfiles.MemoryFile)
    help(memoryfiles.MemoryFile.__init__)
    help(memoryfiles.MemoryFile.read)

    Pulsa q cuando quieras salir.

  4. Ahora sal del intérprete interactivo, y desde una terminal, ejecuta:

    $ pydoc files

    ¿Qué observas? Sal de la utilidad pulsando q.

  5. También puedes explorar la documentación con un navegador, con la opción -b:

    $ pydoc -b files

    La interfaz, de estética discutible, muestra una relación de todos los paquetes y módulos en las raíces de búsqueda. Bajo la sección punto, que representa el directorio actual, encontramos el módulo docs, sobre el que podemos hacer clic y encontrar una versión HTML de la misma ayuda que obtendríamos con la función help().

    Fíjate que esta UI incluye una utilidad de búsqueda en la esquina superior derecha.

La funcionalidad que inspecciona el fuente y extrae la documentación se encuentra en el módulo pydoc de la biblioteca estándar.

Sphinx

La función help() y el módulo/comando pydoc son insuficientes cuando se quiere mostrar ayuda más rica y estructurada. Para cubrir esta necesidad surgen los generadores de documentación.

Sphinx es un proyecto de generación de documentación a partir de ficheros reStructuredText o .rst. Sphinx es, precisamente, el software que genera la documentación de Python.

Sphinx es increiblemente potente, incluye soporte para múltiples lenguages, tematización, extensiones, internacionalización... En este tema, vamos a limitarnos al uso más sencillo.

  1. Para comenzar a utilizar Sphinx, instálalo desde pip:

    $ pip install sphinx
  2. Crea la carpeta docs dentro de tu proyecto y entra en ella:

    $ mkdir docs
  3. Ahora ejecuta el comando sphinx-quickstart y contesta a las preguntas pertinentes. Contesta no a querer tener los ficheros fuentes separados del directorio de salida de la documentación.

    El comando habrá creado un fichero conf.py y un documento index.rst.

  4. Desde el directorio docs, ejecuta:

    $ sphinx-build -b html source _build/html

    La documentación se habrá generado en el directorio build. Alternativamente, puedes correr el comando:

    $ make html

    Que también generará la documentación sin necesidad de indicar explícitamente dónde se encuentran los fuentes y el destino.

  5. Entra en la carpeta _build y levanta un servidor estático para ver la documentación en un navegador:

    $ python -m http.server

    Abre el navegador y comprueba qué tal luce.

  6. Ahora vuelve a docs, crea una carpeta nueva con el nombre de usage y añade un documento installation.rst con el siguiente texto:

    Installation
    ============
    
    In order to install this software, execute::
    
        $ pip install files
  7. Crea otro documento quickstart.rst con el siguiente texto:

    Quickstart
    ==========
    
    To start using memory files, use the `memoryfiles` module:
    
    .. code-block::
    
        from files import memoryfiles
        memfile = memoryfiles.MemoryFile()

    No te olvides del espacio tras los dos puntos seguidos y de la línea en blanco a continuación de .. code-block::.

  8. Ahora vuelve al nivel dónde se encuentra el fichero maestro index.rst, ábrelo, busca la directiva .. toctree:: y déjala de la siguiente forma:

    .. toctree::
        :maxdepth: 2
        :caption: Contents:
    
        usage/installation
        usage/quickstart

    No olvides la línea en blanco a continuación de :caption: Contents:.

  9. Genera la documentación otra vez y comprueba los resultados en un navegador.

Autodocumentando código fuente de Python

Como verás más formalmente, Sphinx usa un formato de documento llamado reStructuredText, que es una suerte de lenguaje de marcado extensible. Las directivas son la forma de extender el lenguaje. Un ejemplo de algunas directivas son .. code-block:: o .. toctree::.

Existen directivas para documentar objetos de Python. Por ejemplo, para documentar la función de Python enumerate(), podrías usar:

.. py:function:: enumerate(sequence[, start=0])

    Return an iterator that yields tuples of an index and an item of the
    *sequence*.

O la simplificación:

.. function:: enumerate(sequence[, start=0])

    Return an iterator that yields tuples of an index and an item of the
    *sequence*.

Esta forma omite el dominio de la directiva (el fragmento py: que precedía a function).

Afortunadamente para nosotros, no es necesario escribir estas directivas a mano. Sphinx es extensible y la extensión autodoc permite generar estas directivas a partir del fuente.

  1. Activa la extensión modificando el fichero conf.py. Busca la variable extensions y añade la cadena 'sphinx.ext.autodoc':

    extensions = [
        'sphinx.ext.autodoc'
    ]
    
    autoclass_content = 'both'

    La opción autoclass_content puesta a 'both' indica que se debe documentar el método __init__ como parte de la clase.

  2. La extensión importa los módulos que necesita documentar por lo que estos deben ser alcanzables desde el directorio donde se lanza la utilidad. Esto suele ser dentro de la carpeta docs por lo que hay que añadir el padre de esta carpeta a sys.path manualmente. Busca una sección en conf.py que se llama Path setup, descomenta el código de ejemplo y modifícalo para que quede así:

    from os.path import dirname
    import sys
    sys.path.insert(0, dirname(dirname(__file__)))
  3. Ve a la carpeta raíz del proyecto y utiliza el programa sphinx-apidoc para generar automáticamente las directivas de autodoc:

    $ sphinx-apidoc -o docs/autodoc --separate --module-first files

    Entra en la carpeta docs/autodoc y explora los ficheros generados.

    La extensión autodoc no es tan automática como pudiera parecer. Necesita que se escriban ciertas directivas. La herramienta sphinx-apidoc genera estas directivas recursivamente para todo un árbol de paquetes y módulos.

  4. Ahora tenemos que añadir los nuevos contenidos a la tabla de contenidos. Busca la directiva .. toctree:: en el fichero index.rst y asegúrate de que queda así:

    .. toctree::
        :maxdepth: 2
        :caption: Contents:
    
        usage/installation
        usage/quickstart
        API documentation <autodoc/modules>
  5. Ahora puedes generar una nueva versión de la documentación. Dentro de la carpeta docs, lanza en una terminal:

    $ make html

    Recuerda que la extension autodoc está realmente importando los módulos así que si hay código (más allá de definiciones) que se ejecute durante la carga del módulo, asegúrate que esté bajo la guarda:

    if __name__ == '__main__':
        ...

    O sé consciente de que se ejecutará.

reStructuredText

El formato utilizado por Sphinx para documentar es reStructuredText o .rst. reStructuredText es considerablemente más rico que Markdown, por ejemplo, y está soportado en GitHub. Lo normal, es que los proyectos Python utilicen .rst como su lenguaje de marcado por defecto.

Puedes consultar la sintáxis en esta guía de reStructuredText o en la página de referencia rápida .rst de Sphinx.

.rst es, además, extensible. La extensión del lenguaje tiene lugar a través de las directivas. Una directiva tiene la siguiente forma:

.. nombre-de-la-directiva:: argumento-1 argumento-2
    :parametro 1:
    :parametro 2:

    Contenido

A veces parámetros y contenido se invierten.

Sphinx añade múltiples directivas para la documentación de código a las que agrupa por lenguaje, llamadas "dominios". El dominio por defecto es Python.

En realidad, documentar un módulo sería hacer algo como:

Module `files.memoryfiles`
==========================

.. py:module:: files.memoryfiles

Contain in-memory-files-related functionality.

.. py:class:: files.memoryfiles.MemoryFile(name='')
    Represent a file that only exists in memory.

    :param name: The name of the memory file
    :type name: str

    .. py:attribute:: min_size

        Minimum size of the memory file in bytes.

    .. py:attribute:: name

        Hold the name of the memory file.

    .. py:method:: read(count=1024)

        Read some bytes from the file into a bytes object.

        :param count: The number of bytes to be read.
        :type count: int
        :return: The bytes read.
        :rtype: bytes

Prueba a añadir el texto anterior a un fichero manual_file_doc.rst, dentro de la carpeta docs y enlázalo en la tabla de contenidos. Genera una nueva versión de la documentación y comparar la documentación generada por autodoc con la escrita por tí manualmente. Cuando termines, borra la entrada en la tabla de contenidos.

Afortunadamente, no nos tenemos que preocupar de la generación de estos documentos .rst porque autodoc ya lo hace por nosotros.

Enlaces y referencias cruzadas

Una de las características más importantes de la buena documentación es el cruce de referencias que permite saltar al usuario entre los objetos relacionados. Además de los múltiples estilos de enlace, también podemos referenciar definiciones de Python.

Por ejemplo, modifiquemos la documentación del módulo para ilustrar el ejemplo:

"""Contain in-memory-files-related functionality.

The class :py:class:`MemoryFile` is the main class of the
module.
"""

Genera de nuevo la documentación para ver los resultados. Si quisieras referirte a una clase o definición fuera del módulo, tendrías que especificar el nombre completo de la definición (en este ejemplo files.memoryfiles.MemoryFile).

Estilos de documentación

El modo por defecto para documentar un proyecto de Python es utilizar las directivas del dominio Python. La extensión autodoc simplifica considerablemente el proceso pero no lo evita totalmente, aun es necesario, por ejemplo, documentar la signatura de la función.

La forma por defecto de documentar con Sphinx es incrustando los parámetros de las directivas en las docstrings de Python. Así, la documentación del método read sería:

"""Read some bytes from the file into a bytes object.

:param count: The number of bytes to be read.
:type count: int
:return: The read bytes.
:rtype: bytes
"""

Prueba a actualizar la documentación del módulo files.memoryfiles y lanza la generación de la documentación de nuevo.

Existen muchas opciones a la hora de documentar funciones y métodos. Al fin y al cabo, estos son los puntos de entrada a la funcionalidad de una API.

La sintáxis de Sphinx es un poco engorrosa. Podemos deshacernos de parte los parámetros con las anotaciones de nuestras funciones, métodos y atributos. Prueba a anotar los parámetros de los métodos de la clase, y sus atributos y lanza una nueva generación de la documentación.

Existen dos alternativas a la sintáxis de Sphinx que podemos utilizar gracias a la extensión napoleon: el estilo NumPy y el estilo Google.

Para activar el módulo tendrás que añadirlo a la lista de extensiones:

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.napoleon'
]

Ahora puedes optar entre:

Mira, por ejemplo, cómo queda la documentación del módulo siguiendo el estilo de Google, con anotaciones:

"""Contain in-memory-files-related functionality.

The class :py:class:`files.memoryfiles.MemoryFile` is the main class of the
module.
"""

class MemoryFile:
    """Represent a file that only exists in memory.

    Attributes:
        min_size: Minimum size of the memory file in bytes.
        name: Hold the name of the memory file.
    """

    min_size: str = 4096

    def __init__(self, name: str =''):
        """Create a new memory file with an optional name.

        Args:
            name: The name of the memory file.
        """

        self.name = name  # type: str

    def read(self, count: str = 1024) -> bytes:
        """Read some bytes from the file into a bytes object.

        Args:
            count: Number of bytes to read.
        """

La extensión napoleon es realmente un paso de pre-procesamiento que traduce al dominio Python la sintaxis de cada uno de los estilos.

Documenta tu proyecto personal

Antes de terminar, documenta lo que lleves de tu proyecto. Sigue los siguientes pasos, a modo de guía:

  1. Crea una carpeta docs en tu proyecto.
  2. Ejecuta sphinx-quickstart como hiciste al comienzo de la lección.
  3. Modifica .gitignore para que omita el directorio docs/_build.
  4. Lanza una primera generación de la documentación.
  5. Añade documentación en ficheros rst con un corto tutorial de uso.
  6. Lanza otra generación para comprobar que los nuevos cambios se han añadido.
  7. Elige un estilo de documentación: Sphinx, Google o NumPy.
  8. Documenta la API de tu proyecto, la privada y la pública.
  9. Utiliza autodoc para generar automáticamente la documentación de tu API.
  10. Recuerda usar napoleon si elegiste un estilo distinto a Sphinx.