Mostrando entradas con la etiqueta python. Mostrar todas las entradas
Mostrando entradas con la etiqueta python. Mostrar todas las entradas

miércoles, 19 de febrero de 2014

Tutorial ¿Cómo compilar Python3 en Linux Debian?


Muchos ya lo han dicho, hay que dejar de lado Python2.X y aunque aun falta para ello he aquí un tutorial para compilar en nuestra maquina linux (debian para ser exactos) Python3.3.

Instalación de dependencias
Como super usuario (root) ejecutamos la siguiente instrucción en la linea de comandos:
apt-get install make build-essential  zlib1g-dev  libbz2-dev  libncurses5-dev  libreadline6-dev  libsqlite3-dev  libssl-dev  libgdbm-dev liblzma-dev tk8.5-dev

Descarga y compilación
Ahora bien, debemos descargar el código fuente y compilarlo, para ello ejecutamos las siguientes instrucciones:
wget -c http://python.org/ftp/python/3.3.4/Python-3.3.4.tgz
tar -zxf Python-3.3.4.tgz
cd Python-3.3.4
# Proceso de compilación
# primero se configuran los makefile para el sistema
./configure
# inicia compilación
make
# se hacen pruebas para saber que todo salió bien, 
# es recomendable pero no obligatorio.
make test
# instalación de los binarios en el sistema
make install

Finalmente solo queda probarlo ejecutando python3.3.

Espero que les haya gustado.

Si te gusto el post
compartelo... :D

martes, 4 de febrero de 2014

Django y las class based views


Cuando se trabaja en django resulta claro que hay muchas labores que son mas y mas fáciles, pero manejar la lógica de una petición mediante una función puede ser perjudicial a la hora de manejar muchas vistas. Es por eso que en este post vamos a ver cómo usar class bases views, una forma mas ordenada de encapsular la lógica.

¿Qué son?
Class-Based-Views (CBV en adelante) es una forma alternativa para crear vistas en django, no pretende reemplazar  las Function-Based-Views (FBV en adelante) , pero si tienen como objetivo permitir al desarrollador:
  • Organización del código relacionado con metodos HTTP específicos (GET, POST, PUT, etc) y que el router pueda acceder a estos sin tener que usar condicionales específicas.
  • Técnicas orientadas a objetos (Como los Mixins) para solucionar ciertos problemas.
¿Por qué usarlas?
La pregunta clave es ¿por qué? y ante esto hay varias cosas que la respaldan:

  • Orden.
  • Estética del Código.
  • Reciclaje del Código.
  • Uso de herramientas de OOP.
  • Simpleza.
  • Estructura bien definida (definición de acciones para cada protocolo, definición de respuestas para casos de éxito y falla con métodos como get_success_url).
¿Cómo funcionan?
Para entender mejor ciertos aspectos de cómo funcionan las CBV debemos ver un poco de FBV

# views.py
from django.shortcuts import render

def index(request):
    if request.method == 'GET':
        # TODO: GET ACTIONS
        return render(request,"template_get.html")
    elif request.method == 'POST':
        # TODO: POST ACTIONS
        return render(request,"template_POST.html")
    elif request.method == 'PUT':
        # TODO: PUT ACTIONS
        return render(request,"template_put.html")
    elif request.method == 'DELETE':
        # TODO: DELETE ACTIONS
        return render(request,"template_del.html")

# end views.py
# ----------------------
# urls.py

from django.conf.urls import patterns

urlpatterns = patterns('',
    (r'^$', "myapp.views.index"),
)
# end urls.py

Como vemos en el código anterior en una sola función la lógica de múltiples ti pos de respuestas, y esto no es adecuado porque restara mantenibilidad y escalabilidad a nuestra aplicación, para evitar eso están las CBV, veamos a continuación cómo sería esto:
# views.py
from django.views.generic.base import View
from django.shortcuts import render

class IndexView(View):

    def get(self, request):
        # TODO: GET ACTIONS
        return render(request,"template_get.html")

    def post(self, request):
        # TODO: POST ACTIONS
        return render(request,"template_POST.html")

    def put(self, request):
        # TODO: PUT ACTIONS
        return render(request,"template_put.html")

    def delete(self, request):
        # TODO: DELETE ACTIONS
        return render(request,"template_del.html")

# end views.py
# ----------------------
# urls.py

from django.conf.urls import patterns
from myapp.views import IndexView

urlpatterns = patterns('',
    (r'^$', IndexView.as_view()),
)

# end urls.py

Como vemos en el código anterior la legibilidad del código mejoraría en caso de que existiesen muchas líneas de código por cada método, además de que ya sería posible usar ciertas técnicas de programación orientada a objetos como los mixins.

Espero que les sea de utilidad.

Si te gusto el post 
compartelo... :D

miércoles, 29 de enero de 2014

Haciendo una API REST en 20 minutos con Python y Django


Hace poco vi en uno de los blogs que leo un muy interesante artículo acerca de cómo hacer una API REST en una hora, pero ¿Por qué no hacer una en 15 minutos? Para esto vamos a usar python, dajngo y dajngo-rest-framework.

Instalando los componentes
En un artículo pasado habíamos hablado de virtualenv y pip, en este caso usaremos virtualenvwrapper para crear un nuevo entorno y pip para instalar lo necesario:

mkvirtualenv entorno_rest --no-site-packages
workon entorno_rest
pip install django djangorestframework
django-admin.py startproject test_rest
cd test_rest
django-admin.py startapp apprest

Hora de codificar
Primero que todo es necesario configurar las preferencias del proyecto, es decir, editar el archivo settings dentro de la carpeta test_rest
En éste archivo es necesario incluir en las aplicaciones instaladas nuestra aplicación rest y el framework así:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'apprest',
)
Además de esto si quieren que la aplicación solicite usuario y contraseña (mediante Basic Auth) agregamos al archivo:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
    ),
}

Muy bien, luego creamos los modelos de nuestra aplicación, para ello editamos el archivo models.py de la carpeta apprest y definimos lo necesario, yo solo voy a definir dos modelos, pero podrían ser cuantos quisieran:
class Autor(models.Model):
    nombre = models.TextField(max_length=100)
    apellido = models.TextField(max_length=100)
    
class Libro(models.Model):
    nombre = models.TextField(max_length=100)
    editorial = models.TextField(max_length=100)
    genero = models.TextField(max_length=100)
    autor = models.ForeignKey(Autor)

Ahora bien, es necesario crear 3 elementos auxiliares,  serializadores, que convierten los datos recuperados de la base de datos en json, viewsets, que son los elementos que de acuerdo al tipo de petición renderizan los datos y un router, el cual se encarga de indicar al proyecto django cuáles son las URLs de nuestra API. El serializer se creará en un nuevo archivo que está destinado para todos los serializers, estará dentro de apprest y se llamará serializers.py, veamos:

from rest_framework import serializers
from .models import Libro, Autor

class LibroSerializer(serializers.ModelSerializer):
    class Meta:
        model = Libro
        fields = ('id', 'nombre', 'editorial', 'genero', 'autor',)

class AutorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Autor
        fields = ('id', 'nombre', 'apellido',)

Ahora es necesario definir los viewsets, esto se hace en un archivo llamado viewsets.py, que esta ubicado en apprest. A continuación su contenido:

from .models import Libro, Autor
from .serializers import LibroSerializer, AutorSerializer
from rest_framework import viewsets

class LibroViewSet(viewsets.ModelViewSet):
 
    serializer_class = LibroSerializer
    queryset = Libro.objects.all()

class AutorViewSet(viewsets.ModelViewSet):
 
    serializer_class = AutorSerializer
    queryset = Autor.objects.all()
    
Finalmente es necesario crear el router y enlazarlo a nuestro proyecto, en esta oportunidad lo haremos directamente en el archivo urls.py del proyecto, ubicado en test_rest, en este definiremos un router, el cual tendrá referencias a los elementos creados anteriormente. Esto se hará así:

from apprest.viewsets import LibroViewSet, AutorViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'libros', LibroViewSet)
router.register(r'autores', AutorViewSet)
   
y en la tupla urlpatterns agregamos:
    ...
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    ...
para obtener finalmente algo así:
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()

from apprest.viewsets import LibroViewSet, AutorViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'libros', LibroViewSet)
router.register(r'autores', AutorViewSet)

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'test_rest.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

    url(r'^admin/', include(admin.site.urls)),
)

para obtener finalmente:
Pueden encontrar el código fuente en github, espero que les sirva de ayuda.

Si te gusto el post
compartelo... :D

lunes, 25 de noviembre de 2013

Puesta en producción de una aplicación web con Django y Nginx


Cuando se usan frameworks para desarrollo web todo se hace más fácil, excepto la puesta en producción, una muestra clara de esto es django, un framework que permite que cosas como hacer un entorno de administración parezca juego de niños y que realizar consultas que parecen concebidas en la maligna mente del mismo Satanás sea una tarde de picnic. Desafortunadamente a la hora de poner en producción un proyecto empezamos a ver dificultades. Este post tiene como objetivo mostrar una sencilla manera de poner en producción nuestros proyectos con Django mediante el servidor Nginx.

Nota: Todo el proceso de configuración esta preparado para Linux Debian y derivados, si usas una distribución diferente es probable que cambien las rutas de los archivos de configuración.

Primero que todo, ¿Qué es Nginx?

Nginx es un servidor web y proxy inverso ligero de muy alto rendimiento, es software libre de código abierto y multiplataforma, tiene las siguientes características:
  • Servidor de archivos estáticos, índices y autoindexado.
  • Proxy inverso con opciones de caché.
  • Permite realizar balanceo de carga.
  • Tolerancia a fallos.
  • Soporte de HTTP sobre SSL.
  • Soporte para FastCGI con opciones de caché.
  • Servidores virtuales basados en nombre y/o en dirección IP.
  • Streaming de archivos FLV y MP4.
  • Soporte para autenticación.
  • Compatible con IPv6.
  • Soporte para protocolo SPDY.
  • Soporte para compresión gzip de paquetes.
  • Habilitado para soportar más de 10.000 conexiones simultáneas.
Muy bien, pero ¿Qué pasa con lo clásico?
Muchos se preguntaran ¿Por qué Nginx y no Apache2?, pienso que hay varias cosas al respecto:
  • La cantidad de recursos que demanda Apache2 son mucho mayores que los demandados por Nginx.
  • La velocidad alcanzada por Nginx es mucho mayor que la de Apache2 a la hora de responder peticiones.
  • Nginx es muchisimo más fácil de configurar que Apache2 puesto que Apache2 usa engorrosos XMLs, Nginx no.
La información mencionada la obtuve de aquí, aquí y aquí.

Muy bien, entonces, manos a la obra
En este momento me parece adecuado explicar cómo funciona la conexión entre django y Nginx:

Nginx es el encargado de atender las peticiones externas por el puerto 80 (protocolo HTTP), éste a su vez envía los datos de la petición a uWSGI mediante un puerto auxiliar, quien hace de intermediario en el proceso de comunicación y finalmente uWSGI entrega los datos de petición a nuestro proyecto django para que éste ejecute la acción correspondiente e inicie el proceso inverso para responder a la petición. Escogí uWSGI porque me pareció muy fácil de usar y en éste enlace hay una comparativa de rendimiento en la cual el tiempo de respuesta es mejor que el de Gunicorn.

Configuremos Nginx
Debemos crear el archivo de configuración para nuestro proyecto, recuerda que si el puerto 80 está siendo usado por Apache2, por alguna otra aplicación o por el mismo Nginx por otra configuración debes liberar primero el puerto, en caso de ser Apache2 debes editar el archivo /etc/apache2/ports.conf y cambiar el puerto 80 por uno diferente, luego reiniciar el servicio Apache2 así:
$ service apache2 restart
Después de esto debes crear el archivo de configuración en la carpeta /etc/nginx/sites-avaliable, el nombre del archivo lo eliges tú, yo elegí como nombre archivo.conf. El contenido del archivo debe ser el siguiente:
# archivo.conf
server {
    listen      80; # puerto por el que escucha la aplicación
    server_name midominio.co; # nombre o ip del servidor
    charset     utf-8;

    # tamaño maximo de subida
    client_max_body_size 75M;

    # Ruta de la carpeta media
    location /media  {
        alias /var/www/proyecto/media; # reemplace por su ruta a media
    }

    # Ruta de la carpeta static
    location /static {
        alias /var/www/proyecto/static; # reemplace por su ruta a media
    }

    # Configuración de las peticiones que no son resueltas con las
    # reglas anteriores. Estas son reenviadas al servidor django
    location / {
        uwsgi_pass 127.0.0.1:8001; # 127.0.0.1:PUERTO_AUXILIAR
        include     uwsgi_params;
    }
}
Cabe resaltar que el puerto auxiliar seleccionado es el 8001, este dato es importante puesto que debemos tenerlo en cuenta para iniciar la labor de uWSGI.
Después de esto es necesario habilitar la nueva configuración creando un enlace simbólico del archivo en la carpeta sites-avaliable de Nginx así:
$ cd /etc/nginx/sites-enabled
$ ln -s ../sites-avaliable/archivo.conf .
Finalmente reiniciamos el servidor Nginx
$ service nginx restart

Configuremos nuestro entorno
Primero es necesario crear un entorno para el proyecto con la orden mkvirtualenv vista en el post anterior y luego activamos el entorno creado con la orden workon:
$ mkvirtualenv entorno
$ workon entorno
después de ésto es necesario instalar las dependencias de nuestro proyecto django, personalmente pienso que es bueno trabajar con django 1.5 o superior, puesto que aasi tendremos muchas de las nuevas características, además django 1.6 fue liberado hace un par de semanas.
$ pip install django
$ pip install ....
$ pip install ....
Ahora necesitamos instalar uWSGI:
$ pip install uwsgi
Nota: Para instalar uWSGI es necesario tener instalados en el sistema el compilador gcc y los paquetes de desarrollo de python, para el caso de linux debian y derivados con python2.7 deben instalarse como superusuario así:
$ apt-get install gcc-4.5 python2.7-dev
Finalmente creamos el archivo para iniciar el proceso de conexión entre Nginx y django, este archivo es un script bash al que yo llame start.sh, este debe tener el siguiente contenido:
#!/bin/bash
uwsgi --socket :8001 --wsgi-file wsgi.py -d mensajes.log

Aquí viene la parte importante, cuando django genera un proyecto él crea una carpeta de proyecto, dentro de ésta hay una carpeta con los archivos de configuración, éstos son settings.py, urls.py, __init__.py y wsgi.py


debe hacer una copia del archivo wsgi.py a la raíz del proyecto y en éste mismo lugar poner el archivo start.sh así:

Después es necesario permitir la ejecución de este archivo mediante la orden:
$ chmod +x start.sh
Luego podemos iniciar el proceso así:
$ ./start.sh
Si tienes alguna duda o comentario puedes hacerla en la sección de comentarios, espero que sea útil.

Si te gusto el post
compartelo... :D

martes, 19 de noviembre de 2013

Virtualenv, qué, por qué y cómo.


Cuando alguien empieza en el mundo de python lo que hace es crear sus programas e instalar librerías indiscriminadamente, sin tener en cuenta que puede que más adelante tenga conflictos a causa de las versiones de las librerías necesarias para sus proyecto. Es aquí donde Virtualenv salva el día.



¿Qué es Virtualenv?:
Virtualenv es una herramienta para crear entornos de python aislados, es decir entornos donde las librerías o las versiones de python no interfieren con las carpetas que python tiene por defecto en la maáquina. Haciendo una analogía con un edificio, un entorno vendría siendo como una planta, usa ciertos recursos como el agua o la energía eléctrica (para el caso de python usa la misma máquina) y a su vez cada planta tiene sus propios recursos, tales como los muebles, las habitaciones y demás (para el caso de python hablamos de librerías.).

¿Por qué?:
Cuando tienes python en tu máquina probablemente debas usar django 1.5 para un proyecto que surgio hace tiempo, y django1.6 para poder usar nuevas funciones, por esto es necesario usar Virtualenv, porque con ésta herramienta puedes crear espacios de trabajo separados con diferentes librerías o diferentes versiones de las mismas.

¿Cómo?:
Instalación:
primero debes tener python, luego instalas setuptools para poder usar easy_install, con easy_install instalas pip mediante la orden
easy install pip
y finalmente con pip instalas virtualenv
pip install virtualenv

nota: si usas Windows deberías agregar la carpeta de scripts de Python al path del sistema para poder usarlo desde la línea de comandos, mira este enlace.

Modo de empleo:

  • Crear un entorno 
    • virtualenv nombre_del_entorno 
  • Crear un entorno totalmente aislado virtualenv --no-site-packages nombre_del_entorno 
  • Usar un entorno especifico:
    • Hay que tener en cuenta que si es windows se hace de una manera un tanto diferente, puesto que en la carpeta del entono el genera una subcarpeta llamada Scripts y ahí está ubicado un archivo llamado activate.bat, mediante este se activa el entorno, entonces:
      • Para Windows (mediante la linea de comandos):
        • C:\.....\carpeta> ruta\a\la\carpeta\del\entorno\Scripts\activate.bat 
      • Para *nix [Mac, Linux] (desde la consola):
        • source /ruta/a/la/carpeta/del/entono/bin/activate 
    • nota: si el entorno quedo bien activado al principio de la linea de la consola debe aparecer entre parentesis el nombre del entorno, mas o menos así: 
      • (entorno)ogranada@maquina:~/python$ 
  • Para buscar paquetes 
    • pip search 
  • Instalar un paquete 
    • pip install paquete 
  • Eliminar paquete 
    • pip uninstall paquete 
  • Listar paquetes instalados (sirve para replicar el entono) 
    • solo listar 
      • pip freeze 
    • hacer archivo para replicar entorno 
      • pip freeze > requirements.txt 
  • Salir del entorno
    • deactivate



Espero que les sirva de ayuda.

Si te gusto el post
compartelo... :D

viernes, 2 de agosto de 2013

Muy rápido, muy fácil, reproductor de audio con Python (Ahora con video)

En el articulo pasado hicimos un reproductor de audio, este únicamente reproducía pistas de audio, en esta entrega vamos a hacer que mediante un modulo nuevo y unas cuantas lineas extra sea posible reproducir vídeo.

Qt es un framework que ofrece herramientas muy poderosas, una de estas es el modulo Phonon, el cual nos da la posibilidad de reproducir audio y vídeo, para este fin usaremos el widget VideoWidget, el cual viene incluido dentro del grupo de widgets de la librería Phonon. el VideoWidget será enlazado a un AudioObject  y a un MediaObject, este ultimo sera el encargado de cambiar el archivo fuente e iniciar la reproducción.
Todo lo anterior sera agrupado en una clase para intentar hacer esto un poco más modular y poder reusarlo de ser necesario.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
from PySide.QtGui import QApplication
from PySide.phonon import Phonon
from PySide import QtCore, QtGui


class player(Phonon.VideoWidget):

    def __init__(self, file_path):
        super(player, self).__init__(parent=None)
        self.setWindowTitle("Video")
        QtGui.QApplication.addLibraryPath(
            os.path.join(os.path.dirname(__file__), 'plugins'))
        media_src = Phonon.MediaSource(file_path)
        self.file_path = file_path
        media_obj = Phonon.MediaObject()
        media_obj.setCurrentSource(media_src)
        Phonon.createPath(media_obj, self)
        self.media_obj = media_obj
        audio_out = Phonon.AudioOutput(Phonon.VideoCategory)
        Phonon.createPath(media_obj, audio_out)
        self.audio_out = audio_out
        self.media_obj.setTickInterval(1000)
        self.media_obj.tick.connect(self.alAvanzar)
        self.media_obj.stateChanged.connect(self.alCambiarEstado)

    def alAvanzar(self, tiempo):
        pass

    def alCambiarEstado(self, new, old):
        pass

    def play(self):
        self.media_obj.play()

    def pause(self):
        self.media_obj.pause()

    def stop(self):
        self.media_obj.stop()

    def state(self):
        return self.media_obj.state()

En el código anterior vemos como una la clase player hereda de la clase Phonon.VideoWidget y tiene como atributos un MediaObject y un AudioOutput, los cuales sirven como control de reproducción y salida de audio respectivamente, y los tres son enlazados en el constructor de la clase en la linea 21 y 24.

Ahora veremos la nueva versión del reproductor, con las mejoras respectivas para implementar nuestro player de vídeo:
from PySide import QtCore, QtGui, phonon, QtUiTools
from PySide.phonon import Phonon

from videopanel import player


class Reproductor(QtGui.QWidget):

    def __init__(self):
        super(Reproductor, self).__init__(parent=None)
        self.setWindowTitle("Python Simple Player")
        self.gridLayout = QtGui.QGridLayout(self)
        loader = QtUiTools.QUiLoader()
        file = QtCore.QFile("repro.ui")
        file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(file, self)
        file.close()
        self.gridLayout.addWidget(self.ui, 0, 0, 1, 1)
        # seccion multimedia
        self.audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
        self.mediaObject = Phonon.MediaObject(self)
        Phonon.createPath(self.mediaObject, self.audioOutput)
        self.mediaObject.setTickInterval(1000)
        #
        self.ui.lcdAvance.display("00:00")
        QtCore.QObject.connect(
            self.ui.btnArchivo,
            QtCore.SIGNAL("clicked()"),
            self.openFile)
        QtCore.QObject.connect(
            self.ui.btnPlay,
            QtCore.SIGNAL("clicked()"),
            self.play)
        self.mediaObject.tick.connect(self.alAvanzar)
        self.mediaObject.stateChanged.connect(self.alCambiarEstado)
        #----------------------
        self.player = None

    def alAvanzar(self, tiempo):
        displayTime = QtCore.QTime(
            0, (tiempo / 60000) %
            60, (tiempo / 1000) %
            60)
        self.ui.lcdAvance.display(displayTime.toString('mm:ss'))

    def alCambiarEstado(self, new, old):
        if self.mediaObject.state() == Phonon.State.StoppedState:
            self.ui.btnPlay.setEnabled(True)
        elif self.mediaObject.state() == Phonon.State.PlayingState:
            self.ui.btnPlay.setText("||")  # .setEnabled(False)

    def play(self):
        if self.player:
            if self.player.state() == Phonon.State.PlayingState:
                self.player.pause()
                self.player.show()
                self.ui.btnPlay.setText(">")
            else:
                self.player.show()
                self.player.play()
                self.ui.lblNombre.setText(self.player.file_path)
                self.ui.btnPlay.setText("||")
        else:
            if self.mediaObject.state() == Phonon.State.PlayingState:
                self.mediaObject.pause()
                self.ui.btnPlay.setText(">")
            else:
                self.mediaObject.play()
                #///////////////////////////////////
                meta = self.mediaObject.metaData()
                print(meta)
                meta["TITLE"] = meta[
                    "TITLE"] if "TITLE" in meta else "Indefinido"
                meta["ARTIST"] = meta[
                    "ARTIST"] if "ARTIST" in meta else "SinNombre"
                label = "{TITLE} by {ARTIST}".format(**meta)
                self.ui.lblNombre.setText(label)
                #///////////////////////////////////
                self.ui.btnPlay.setText("||")

    def openFile(self):
        fileName = QtGui.QFileDialog.getOpenFileName(
            self,
            u"Abrir Archivo",
            u".",
            u"Archivos de Audio (*.mp3 *.wav *.ogg);;Archivos de Video (*.mp4 *.mpg *.avi)")
        if fileName[1]:
            self.path = fileName[0]
            self.mediaObject.setCurrentSource(
                Phonon.MediaSource(self.path))
            self.ui.btnPlay.setText(">")
            self.ui.lblNombre.setText("...")
            self.ui.lcdAvance.display("00:00")
            if fileName[0][-3:] in "mp4 mpg avi".split():
                self.player = player(fileName[0])
                self.player.alAvanzar = self.alAvanzar
                self.player.alCambiarEstado = self.alCambiarEstado
            else:
                self.player = None

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    rep = Reproductor()
    rep.show()
    sys.exit(app.exec_())

Ahora lo primero que vemos es una modificación en el constructor, en el cual creamos una variable de clase llamada player que es nula (linea 38). Luego vemos en el método play una condición en la cual si el player es diferente de nulo llama al objeto player para que este inicie la reproducción o la detenga. Finalmente en el método openFile vemos que se verifica la extensión del archivo a reproducir, si es un archivo de vídeo la variable de clase player deja de ser nula y se convierte en una instancia de la clase player que ya habíamos hecho antes, de lo contrario sigue siendo nula.

Espero que les guste y que les sirva, dejo el código en GitHub, si tienen sugerencias o demás comenten.

martes, 30 de julio de 2013

Muy rápido, muy fácil, reproductor de audio con Python


En la entrada anterior pudimos ver cómo con muy poco código fue posible obtener un simple pero poderoso navegador web con el lenguaje de programación Python, PySide (Los bindings de Qt, uno de los  frameworks gráficos del lenguaje C++) y poco más de 70 líneas de código. En esta entrega veremos cómo usando el diseñador de interfaces y algo de código podemos hacer un muy simple reproductor de audio, el cual será extendido en próximas entregas.
Primero que todo es necesario decir que Qt ofrece una herramienta para diseñar interfaces de usuario. Esta se llama QtDesigner y viene incluida en el instalador de la libreria PySide de windows (para los entornos linux hay que instalarlo a mano). Esta herramienta es muy útil, puesto que con ella podemos hacer la interfaz y luego exportarla al lenguaje que vayamos a usar para realizar la aplicación (en la actualidad Qt tiene bindings para varios lenguajes, entre ellos Java, Ruby, Python y Php, entre otros), además de proveer un sistema de enlace entre objetos y métodos mediante eventos (en Qt se habla de slots y signals), lo cual facilita en cierta manera la tarea del programador. Esta herramienta es una herramienta WYSIWYG, la cual nos permite arrastrar, soltar y acomodar hasta obtener la interfaz como se desea. A continuación la imagen de la interfaz gráfica del reproductor:


La interfaz gráfica contiene unos cuantos QLabel para mostrar información, un QLCDNumber para mostrar cuánto tiempo ha transcurrido, un par de botones y unos espaciadores para conservar las proporciones al cambiar el tamaño de la ventana. A pesar de que sería posible "compilar" la interfaz a el lenguaje nativo con herramientas como el pyside-uic no lo vamos a hacer.

Ahora vamos a ver el código Python:

#!/usr/bin/env python

from PySide import QtCore, QtGui, phonon, QtUiTools
from PySide.phonon import Phonon

class Reproductor(QtGui.QWidget):

    def __init__(self):
        super(Reproductor, self).__init__(parent=None)
        self.setWindowTitle("Python Simple Player")
        self.gridLayout = QtGui.QGridLayout(self)
        loader = QtUiTools.QUiLoader()
        file = QtCore.QFile("repro.ui")
        file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(file, self)
        file.close()
        self.gridLayout.addWidget(self.ui, 0, 0, 1, 1)
        # seccion multimedia
        self.audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
        self.mediaObject = Phonon.MediaObject(self)
        Phonon.createPath(self.mediaObject, self.audioOutput)
        self.mediaObject.setTickInterval(1000)
        #
        self.ui.lcdAvance.display("00:00")
        QtCore.QObject.connect(
            self.ui.btnArchivo,
            QtCore.SIGNAL("clicked()"),
            self.openFile)
        QtCore.QObject.connect(
            self.ui.btnPlay,
            QtCore.SIGNAL("clicked()"),
            self.play)
        self.mediaObject.tick.connect(self.alAvanzar)
        self.mediaObject.stateChanged.connect(self.alCambiarEstado)

    def alAvanzar(self, tiempo):
        displayTime = QtCore.QTime(
            0, (tiempo / 60000) %
            60, (tiempo / 1000) %
            60)
        self.ui.lcdAvance.display(displayTime.toString('mm:ss'))

    def alCambiarEstado(self, new, old):
        if self.mediaObject.state() == Phonon.State.StoppedState:
            self.ui.btnPlay.setEnabled(True)
        elif self.mediaObject.state() == Phonon.State.PlayingState:
            self.ui.btnPlay.setText("||")  # .setEnabled(False)

    def play(self):
        if self.mediaObject.state() == Phonon.State.PlayingState:
            self.mediaObject.pause()
            self.ui.btnPlay.setText(">")
        else:
            self.mediaObject.play()
            #///////////////////////////////////
            meta = self.mediaObject.metaData()
            meta["TITLE"] = meta["TITLE"] if "TITLE" in meta else "Indefinido"
            meta["ARTIST"] = meta[
                "ARTIST"] if "ARTIST" in meta else "SinNombre"
            label = "{TITLE} by {ARTIST}".format(**meta)
            self.ui.lblNombre.setText(label)
            #///////////////////////////////////
            self.ui.btnPlay.setText("||")

    def openFile(self):
        fileName = QtGui.QFileDialog.getOpenFileName(
            self,
            u"Abrir Archivo",
            u"/",
            u"Archivos de Audio (*.mp3 *.wav *.ogg)")
        if fileName[1]:
            self.path = fileName[0]
            self.mediaObject.setCurrentSource(
                Phonon.MediaSource(self.path))
            self.ui.btnPlay.setText(">")
            self.ui.lblNombre.setText("...")
            self.ui.lcdAvance.display("00:00")

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    rep = Reproductor()
    rep.show()
    sys.exit(app.exec_())

En el código anterior podemos ver cómo mediante el uso del modulo QtUiTools es posible usar la clase QUiLoader para cargar en tiempo de ejecución el archivo de interfaz gráfica (repro.ui), y obtenemos un objeto QWidget, el cual contiene toda la jerarquía de objetos que habíamos hecho en el diseñador de interfaces, los cuales quedan listos para ser usados a gusto. Después de esto acoplamos el QWidget obtenido mediante un gridLayout. Seguidamente creamos las instancias de la salida de audio (audioOutput) y del reproductor (mediaObject), los cuales son enlazados mediante la instrucción:
Phonon.createPath(self.mediaObject, self.audioOutput)
Esta instrucción enlaza el reproductor con la salida de audio. Después de esto se establece cada cuánto se va a lanzar una señal de actualización (es para que pueda medirse el tiempo transcurrido y hacer cosas de acuerdo a este) mediante el tickInterval, el cual se establece cada 1000 milisegundos (un segundo) y se conectan objetos como botones a los eventos correspondientes para que antes de finalizar el constructor se enlacen las señales del reproductor a los métodos encargados de modificar el avance y cambiar la etiqueta del botón de reproducción.
Los métodos siguientes se encargan de cambiar el valor de las etiquetas, de iniciar o detener la reproducción o cambiar el título de la pista.

 El código correspondiente esta en GitHub para quienes estén interesados.

viernes, 26 de julio de 2013

Muy rápido, muy fácil, navegador web con Python

Python es un lenguaje que por su simpleza y versatilidad ha llegado a ser usado en muchos y muy buenos proyectos. A continuación veremos en este post el código de un navegador web muy simple, con soporte para la mayoría de las características de HTML5. Para poder hacer esto usamos Python2.7 y PySide de 32 bits si el sistema operativo es Windows, si es Linux podemos instalarlo usando los repositorios inestables o de pruebas mediante el clásico apt-get así:

apt-get install python-pyside

Aquí vamos:
# -*- coding: utf-8 -*-
# Tested in Python2.7

from PySide import QtCore, QtGui
from PySide import QtWebKit
from PySide import QtNetwork


class Navegador(QtGui.QWidget):

    def __init__(self):
        # constructor de la clase
        super(Navegador, self).__init__(parent=None)
        self.setWindowTitle("Python Navigator")
        self.showMaximized()
        self.gridLayout = QtGui.QGridLayout(self)
        self.btnAtras = QtGui.QPushButton(self, text='atras')
        self.gridLayout.addWidget(self.btnAtras, 0, 0, 1, 1)
        self.btnAdelante = QtGui.QPushButton(self, text='adelante')
        self.gridLayout.addWidget(self.btnAdelante, 0, 1, 1, 1)
        self.label = QtGui.QLabel(u"Dirección", self)
        self.gridLayout.addWidget(self.label, 0, 2, 1, 1)
        self.txtUrl = QtGui.QLineEdit(self)
        self.gridLayout.addWidget(self.txtUrl, 0, 3, 1, 1)
        self.wvNavegador = QtWebKit.QWebView(self)
        self.gridLayout.addWidget(self.wvNavegador, 1, 0, 1, 4)

        QtNetwork.QNetworkProxyFactory.setUseSystemConfiguration(True)
        QtWebKit.QWebSettings.globalSettings().setAttribute(
            QtWebKit.QWebSettings.PluginsEnabled, True)
        QtWebKit.QWebSettings.globalSettings().setAttribute(
            QtWebKit.QWebSettings.JavascriptCanOpenWindows, True)
        QtWebKit.QWebSettings.globalSettings().setAttribute(
            QtWebKit.QWebSettings.DeveloperExtrasEnabled, True)

        QtCore.QObject.connect(
            self.txtUrl,
            QtCore.SIGNAL("returnPressed()"),
            self.cargarUrl)
        QtCore.QObject.connect(
            self.btnAtras,
            QtCore.SIGNAL("clicked()"),
            self.wvNavegador.back)
        QtCore.QObject.connect(
            self.btnAdelante,
            QtCore.SIGNAL("clicked()"),
            self.wvNavegador.forward)

        _url = "http://www.python.org"
        self.wvNavegador.load(QtCore.QUrl(_url))
        self.txtUrl.setText(_url)

    def cargarUrl(self):
        url = self.txtUrl.text()
        url = url if url.startswith(
            "http://") or url.startswith(
                "https://") else "http://{url}".format(
                    url=url)
        self.wvNavegador.load(QtCore.QUrl(url))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    nav = Navegador()
    nav.show()
    sys.exit(app.exec_())

Podemos ver que en el bloque de código anterior se crea la clase navegador, en la cual se crean los controles básicos (la caja de texto para la URL, botones de atrás y adelante, el espacio de navegación), después de esto se "empaquetan" por así decirlo en un gridlayout, el cual nos permite hacer que los controles se ajusten de manera dinámica cuando cambie el tamaño de la ventana. Después se habilitan los plugins del elemento mediante el cual se navega para que sea posible ver vídeos en youtube e inspeccionar elementos (muestra el DOM, los scripts y demás de manera muy similar a la de Google Chrome) mediante click derecho, luego se enlazan los botones al navegador mediante los eventos de click y la barra de direcciones al navegador mediante el evento returnPressed y después se carga la URL inicial ("http://www.python.org").
Finalmente se instancia los objetos necesarios para iniciar nuestro navegador.

Fácil, efectivo y contundente, un navegador muy simple con menos de 70 lineas de código.

Aquí esta el enlace al repositorio en GitHub (siéntanse libres de modificarlo) y abajo les dejo una imagen de como se ve.