Creando un nuevo proyecto Python

¿Así que querés comenzar un nuevo proyecto usando Python? ¡Felicitaciones! ¿Querés que tenga la estructura recomendada para proyectos modernos? ¿Querés que sea instalable fácilmente? ¿Querés que no se te arme lio de dependencias que colisionan con las de otros proyectos ? Bien, te propongo esta receta.

Las herramientas

  • Virtualenv: es una herramienta para aislar tu entorno de desarrollo python. Es muy pero muy útil para evitar conflictos entre las dependencias de tus distintos proyectos.
  • virtualenvwrapper: es un conjunto de extensiones que hacen la vida del usuario virtualenv aun más feliz, permitiendo crear y borrar entornos virtuales, asociarlos a un proyecto, automatizar tareas al activar o desactivar uno, etc.
  • pip: es la herramienta moderna, correcta y recomendada para administrar los paquetes python instalados en tu sistema/virtualenv. Es un reemplazo de easy_install
  • Distribute: es la herramienta moderna y recomendada para distribuir tu paquete python. Es un fork de setuptools (que es, a su vez, una mejora sobre el módulo estándar distutils)
  • skeleton es una herramienta que define plantillas para iniciar un proyecto, generando la estructura básica necesaria. Es similar a PasteScript pero enfocado en esta tarea concreta, sin dependencias y compatible con Python 3.x

La receta

  1. Instalá pip:

    $ sudo apt-get install python-pip
    

    o bien directamente:

    $ sudo curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
    
  2. Instalá virtualenwrapper y skeleton:

    $ sudo pip install virtualenwrapper
    $ sudo pip install git+git://github.com/stumitchell/skeleton.git#egg=skeleton
    

Nota

Notar que skeleton se está instalando desde un fork del proyecto original (aparentemente desmantenida), que resuelve bugs importantes

  1. Configurá virtualenvwrapper

    $ mkdir ~/.virtualenvs           # acá se van a guardar tus entornos virtuales
    $ mkdir ~/proyectos              # acá se van a guardar tus proyectos
    $ mkdir ~/.pip_download_cache     # para no bajar paquetes cada vez
    

    Luego editá tu ~/.bashrc agregando las siguientes líneas

    export WORKON_HOME=$HOME/.virtualenvs
    export PROJECT_HOME=$HOME/proyectos
    export VIRTUALENV_DISTRIBUTE=true
    export PIP_DOWNLOAD_CACHE=$HOME/.pip_download_cache
    source /usr/local/bin/virtualenvwrapper.sh
    

    y recargá tus cambios:

    $ source ~/.bashrc
    
  2. Inicializá tu proyecto. Por ejemplo el proyecto zaraza

    $ mkproject -t package zaraza
    

    Se te solicitarán algunos datos (nombre del proyecto, autor, licencia, etc.) y ¡(casi) listo! Estarás trabajando en tu proyecto zaraza. Tu prompt se verá así

    (zaraza)tin@morocha:~/proyectos/zaraza$
    

    ¿Qué sucedió? Se creó un directorio ~/proyectos/zaraza para tu proyecto, asociado a un virtualenv ubicado en ~/.virtualenvs/zaraza. skeleton automáticamente creó una estructura básica de paquete python en ~/proyectos/zaraza/src incluyendo un setup.py basado en distribute.

  3. Instalá tu paquete en el virtualenv, para poder importarlo desde cualquier lado:

    (zaraza) $ cd  ~/proyectos/zaraza/src
    (zaraza) $ pip install -e .
    

    Esto agrega el directorio de desarrollo de tu proyecto al PYTHONPATH del virtualenv, de modo que puedes importar zaraza desde cualquier lado dentro del virtualenv (por ejemplo, cuando hagas una carpeta src/test al nivel de '/src/zaraza'

¿Y ahora?

Cada vez que quieras trabajar en tu proyecto zaraza ejecutás:

$ workon zaraza

Para salir del virtualenv:

(zaraza) $ deactivate

Algunos tips más a modo de despedida

Virtualenwrapper es totalmente hookeable y extensible. Esta receta propone usar skeleton (que funciona como plugin de virtualenvwrapper.project) para crear una estructura de paquete estándar básica, pero hay plugins para proyectos más específicos. Por ejemplo virtualenwrapper.django

El comando mkproject es un wrapper sobre el comando principal de virtualenvwrapper mkvirtualenv, que acepta muchos parámetros opcionales. Ejecutá mkproject_help o mkvirtualenv --help para saber más.

Y ya sabés ...

http://python-distribute.org/pip_distribute.png

Este artículo lo escribí originalmente para el Recetario de PyAr

Migrando issues entre proyectos de Bitbucket

Hace un tiempo conté como migré un repositorio Mercurial a Git . Se trataba de un proyecto hospedado en Bitbucket y para cambiar de DVCS tuve que crear un proyecto nuevo, que tambien hospedamos allí porque somos pobretones y nos da repos privados gratis. En la mudanza se me quedaron varios issues que necesitaba migrar. Y no era el único.

Buscando un rato encontré scriptcitos para migrar desde o hacia GitHub pero no había para migrar entre proyectos de Bitbucket, algo bastante común desde que empezaron a ofrecer soporte Git.

Decidí entonces que debía hacer mi propio scriptcito migrador. La cosa se complicaba porque el par de bibliotecas python que interactuan con la API de Bitbucket no tenian, hasta el momento, soporte para "postear" issues

Pero no hay darse por vencido: se me ocurrió mirar el par de forks de cada proyecto y encontré justo lo que estaba buscando.

Entonces bastó con instalar el fork de David Paz Reyes

$ pip install git+https://github.com/davidmpaz/BitBucket-api.git

he hice un script que migra todo los issues en estado new del repo original (gpec) al nuevo (gpec) y los potenciales comentarios que tenga. Como el autor se pierde (dado que el nuevo quedará publicado con mi usuario) agrego un comentario avisando que es un issue migrado.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bitbucket import bitbucket

gpec = bitbucket.Bitbucket('tin_nqn', '***', 'gpec')
gpec3 = bitbucket.Bitbucket('tin_nqn', '***', 'gpec3')

# request original ISSUES
_, result = gpec.get_issues()

for issue in result['issues'][:]:
    original_id = issue['local_id']
    if issue['status'] != 'new':
        continue

    # and post to the new repo
    ok, new_issue = gpec3.add_issue(**issue)

    if not ok:
        print 'Fail migrating #%d' % original_id
        continue

    new_id = new_issue['local_id']

    print 'Migrated #%d as #%d in the new project' % (original_id, new_id)

    # add a comment to mark the migration
    who = issue.get('reported_by', None)
    who = who['username'] if who else 'anonymous'
    gpec3.add_issue_comment(new_id, content="Issue migrated from the original repo. "
            "Was #%d reported by %s" % (original_id, who))

    # migrate comments
    result, comments = gpec.get_issue_comments(original_id)
    for c in comments:
        if not c['content']:
            continue
        gpec3.add_issue_comment(new_id, **c)

¡Disfruten!

Jquery, una introducción

jQuery, una librería javascript ligera y sumamente útil, desarrollada por el talentoso programador John Resig.

Como se explica en el sitio web:

jQuery es una biblioteca javascript ligera y concisa que simplifica como atraviesas tus documentos HTML, manejas eventos, realizas animaciones, y agregas interacción Ajax a tus páginas web. jQuery está diseñada para cambiar la manera en que escribes JavaScript

¿Necesitamos otra biblioteca?

El Toolkit Dojo está maduro e incluye hasta el los hielos del whisky. Es ampliamente popular y muchas interfaces UI fueron construidas sobre este toolkit, incluyendo script.aculo.us, Rico, y otros. Por supuesto, no podemos dejar afuera a mootools. Su biblioteca UI, moo.fx, puede apoyarse sobre Prototype si lo deseas. Tambiñen está Y!UI the Yahoo! User Interface library. Y tenemos ExtJs, un robusto framework de interfaz que se basa en Y!UI, Prototype, o jQuery.

jQuery

Lo que nos trae a... jQuery. Sí, otro framework JavaScript. jQuery, sin embargo, es a mi criterio el más rápido y elegante del montón. Soporta CSS3, detección del navegador, encadenamiento de métodos, plugins, Ajax, selectores flexibles, y efectos UI basicos. Todo en menos de 30Kb.

Pues bien, engolosinemos los ojos un poco. Hacé click acá para ver lo que jQuery puede hacer con un par de líneas de código.

Wow! Jquery en acción. ¿Lo ves? Ese efecto se logra con este sencillo código:

$("a.intro").click( function() {
    $("div.introtarget").toggle(100);return false;