Tutorial avanzado: Cómo escribir apps reusables

Este tutorial avanzado comienza donde dejó el Tutorial 6. Vamos a convertir nuestra app en un paquete Python standalone de tal manera que se pueda reusar en nuevos proyectos y compartir con otra gente.

Si todavía no completaste los Tutoriales 1-5, te alentamos a hacerlo, ya que nos basaremos en ese proyecto durante este tutorial.

La reusabilidad importa

Es mucho trabajo diseñar, construir, testear y mantener una aplicación web. Varios proyectos Python y Django comparten problemas comunes. No sería bueno poder ahorrarse algo de este trabajo repetido?

Reusabilidad es el estilo de vida en Python. The Python Package Index (PyPI) tiene un amplio abanico de paquetes que uno puede usar en sus propios programas Python. También vale la pena chequear Django Packages para apps reusables que se pueden incorporar en nuestros proyectos. Django mismo es también un paquete Python. Esto significa que uno puede partir de paquetes Python o Django apps existentes y componerlos en un proyecto web propio. Solamente es necesario escribir las partes que hacen nuestro proyecto único.

Digamos que estamos por comenzar un nuevo proyecto que necesita una app de encuestas como la que hemos estado desarrollando. Cómo hacemos que sea reusable? Afortunadamente, estamos en el buen camino. En el Tutorial 3 vimos como desacoplar la app polls del URLconf a nivel proyecto usando include. En este tutorial vamos a ir más allá para lograr que nuestra app sea fácil de usar en nuevos proyectos y quede lista para publicarla y que otros puedan instalarla y usarla.

Paquete? App?

Un paquete Python provee una manera de agrupar código Python relacionado para facilitar su reuso. Un paquete contiene uno o más archivos de código Python (también conocidos como “módulos”).

Un paquete se puede importar con import foo.bar o from foo import bar. Para que un directorio (como polls) sea un paquete, debe contener un archivo especial, __init__.py, que incluso puede estar vacío.

Una app Django es sólo un paquete Python que está pensado específicamente para usarse en un proyecto Django. Una app puede también usar algunas convenciones comunes de Django, como tener un archivo models.py.

Más adelante usamos el término empaquetar para describir el proceso de hacer que un paquete Python sea fácil de instalar para otros. Puede resultar un poco confuso, lo sabemos.

Nuestro proyecto y nuestra app reusable

Después de los tutoriales anteriores, nuestro proyecto debería verse así:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    polls/
        __init__.py
        admin.py
        migrations/
            __init__.py
            0001_initial.py
        models.py
        static/
            polls/
                images/
                    background.gif
                style.css
        templates/
            polls/
                detail.html
                index.html
                results.html
        tests.py
        urls.py
        views.py
    templates/
        admin/
            base_site.html

Creamos mysite/templates en el Tutorial 2, y polls/templates en el Tutorial 3. Ahora quizás es más claro por qué elegimos tener directorios separados de templates para el proyecto y la aplicación: todo lo que es parte de la app polls está en polls. Esto permite que la aplicación esté auto-contenido y sea más fácil reusarla en otro proyecto.

Ahora el directorio polls podría copiarse en un nuevo proyecto Django y ser reusado inmediatemente. Todavía no está listo para publicarse, sin embargo. Para ello necesitamos empaquetar la app y hacer fácil su instalación para otros.

Instalando algunos prerrequisitos

El estado actual del empaquetado en Python está un poco confuso con varias herramientas. Para este tutorial vamos a usar setuptools para construir nuestro paquete. Es la herramienta recomendada (unida al fork de distribute). Vamos a usar también pip para instalarlo y desinstalarlo. Deberías instalar estos dos paquetes ahora. Si necesitaras ayuda, podés chequear cómo instalar Django con pip. De la misma manera se puede instalar setuptools.

Empaquetando nuestra app

El empaquetar Python se refiere a preparar nuestra app en un formato específico que pueda ser fácilmente instalable para su uso. Django mismo viene empaquetado de forma muy similar. Para una app pequeña como polls, el proceso no es muy complicado.

  1. Primero, creamos un directorio padre para polls, fuera del proyecto Django. Llamaremos a este directorio django-polls.

    Eligiendo un nombre para nuestra app

    Cuando uno elige un nombre para un paquete es buena idea chequear fuentes como PyPI para evitar conflictos de nombre con paquetes existentes. A menudo es útil usar el prefijo django- para el nombre cuando uno crea un paquete para distruirlo. Esto ayuda a que aquellos que buscan apps de Django identifiquen la app como específica para Django.

    El nombre de una aplicación (es decir, la parte final del path de importación) debe ser único en INSTALLED_APPS. Evitá usar el mismo nombre que cualquiera de los paquetes contrib de Django, como auth, admin, o messages.

  2. Movemos el directorio polls dentro de django-polls.

  3. Creamos un archivo django-polls/README.rst con el siguiente contenido:

    django-polls/README.rst
    =====
    Polls
    =====
    
    Polls is a simple Django app to conduct Web-based polls. For each
    question, visitors can choose between a fixed number of answers.
    
    Detailed documentation is in the "docs" directory.
    
    Quick start
    -----------
    
    1. Add "polls" to your INSTALLED_APPS setting like this::
    
        INSTALLED_APPS = (
            ...
            'polls',
        )
    
    2. Include the polls URLconf in your project urls.py like this::
    
        url(r'^polls/', include('polls.urls')),
    
    3. Run `python manage.py migrate` to create the polls models.
    
    4. Start the development server and visit http://127.0.0.1:8000/admin/
       to create a poll (you'll need the Admin app enabled).
    
    5. Visit http://127.0.0.1:8000/polls/ to participate in the poll.
    
  4. Creamos un archivo django-polls/LICENSE. Elegir una licencia está más allá del alcance de este tutorial, pero vale decir que código liberado públicamente sin una licencia es inútil. Django y muchas aplicaciones Django se distribuyen bajo la licencia BSD; sin embargo, uno puede elegir su propia licencia, teniendo en cuenta que ésta afecta quién puede utilizar nuestro código.

  5. Luego vamos a crear un archivo setup.py que provee los detalles sobre cómo construir e instalar la app. Escapa a este tutorial una explicación más detallada sobre este archivo, pero la documentación de setuptools tiene una buena explicación. Creamos el archivo django-polls/setup.py con el siguiente contenido:

    django-polls/setup.py
    import os
    from setuptools import setup
    
    with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
        README = readme.read()
    
    # allow setup.py to be run from any path
    os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
    
    setup(
        name='django-polls',
        version='0.1',
        packages=['polls'],
        include_package_data=True,
        license='BSD License',  # example license
        description='A simple Django app to conduct Web-based polls.',
        long_description=README,
        url='http://www.example.com/',
        author='Your Name',
        author_email='yourname@example.com',
        classifiers=[
            'Environment :: Web Environment',
            'Framework :: Django',
            'Intended Audience :: Developers',
            'License :: OSI Approved :: BSD License', # example license
            'Operating System :: OS Independent',
            'Programming Language :: Python',
            # Replace these appropriately if you are stuck on Python 2.
            'Programming Language :: Python :: 3',
            'Programming Language :: Python :: 3.2',
            'Programming Language :: Python :: 3.3',
            'Topic :: Internet :: WWW/HTTP',
            'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
        ],
    )
    
  6. Solamente módulos y paquetes Python se incluyen por defecto en el paquete. Para incluir archivos adicionales, necesitamos crear un archivo MANIFEST.in. La documentación sobre distribute referida en el paso anterior revisa este archivo en más detalle. Para incluir los templates y nuestro archivo LICENSE, creamos el archivo django-polls/MANIFEST.in con el siguiente contenido:

    django-polls/MANIFEST.in
    include LICENSE
    include README.rst
    recursive-include polls/static *
    recursive-include polls/templates *
    
  7. Es opcional, pero se recomienda, incluir documentación detallada con una app. Creamos un directorio vacío django-polls/docs para la futura documentación. Agregamos una línea adicional al archivo django-polls/MANIFEST.in:

    recursive-include docs *
    

    Notemos que el directorio docs no se va a incluir en nuestro paquete a menos que agreguemos algunos archivos dentro. Muchas apps Django también proveen su documentación de forma online mediante sitios como readthedocs.org.

  8. Intentemos construir nuestro paquete con python setup.py sdist (corriendo desde dentro de django-polls). Esto crea un directorio llamado dist y crea un nuevo paquete, django-polls-0.1.tar.gz.

Para más información sobre empaquetar, podés ver el Tutorial sobre empaquetar y distribuir proyectos.

Usando nuestro paquete

Como movimos el directorio polls fuera de nuestro proyecto, no funciona más. Vamos a solucionar esto instalando nuestro nuevo paquete, django-polls.

Instalando como librería de usuario

Los siguientes pasos instalan django-polls como una librería de usuario. Las instalaciones por usuario tienen muchas ventajas sobre las instalaciones a nivel sistema, como por ejemplo permitir usar un paquete en sistemas donde no se tiene acceso de administrador o también prevenir que un paquete afecte servicios de sistema y/o otros usuarios en la máquina.

Notar que las instalaciones por usuario pueden incluso afectar herramientas del sistema que corren bajo ese usuario, entonces virtualenv es un solución más robusta (ver más abajo).

  1. Para instalar el paquete usamos pip (ya lo instalamos, no?):

    pip install --user django-polls/dist/django-polls-0.1.tar.gz
    
  2. Con suerte, nuestro proyecto Django debería funcionar correctamente de nuevo. Levantamos el servidor de desarrollo para confirmarlo.

  3. Para desinstalar el paquete, usamos pip:

    pip uninstall django-polls
    

Publicando nuestra app

Ahora que ya empaquetamos y testeamos django-polls, está lista para compartirla con el mundo! Si este no fuera sólo un ejemplo, uno podría:

Instalando paquetes Python con virtualenv

Instalar la app polls como librería de usuario tiene algunas desventajas:

  • Modificar las librerías de usuario puede afectar otro software Python de nuestro sistema.

  • No podemos correr múltiples versiones de nuestro paquete (u otros con el mismo nombre).

Típicamente, estas situaciones sólo se presentan si uno mantiene varios proyectos Django. En ese caso, la mejor solución es usar virtualenv. Esta herramienta permite mantener múltiples ambientes Python aislados, cada uno con su propia copia de librerías y paquetes.