Posts Tagged 'python'

Sistema para hacer test

Hoy estando en sugus, Virako ha preguntado por algo para hacer test en linux, para el stand de mañana de la asociación, y ha encontrado algo para hacerlo. Pero entonces a mí se me ha ocurrido implementar un script sencillo que haría más o menos la misma función.

La idea es simple, parsear un fichero con las preguntas y las respuestas, y mostrar con zenity una lista para seleccionar. Por supuesto para hacerlo más divertido, el orden de las respuestas es aleatorio.

El fichero a parsear sería de la forma:

El resultado de multiplicar 25*25 # 2500 # @625 # 825
¿Cuál es la mejor tira cómica de todas? # @la de linuxhispano # la tira ecol # bit & byte

Donde por cada linea se introducirían la pregunta y el conjunto de respuestas, separando cada una por una #. La pregunta siempre será la primera cadena, las respuestas se ordenarán de manera aleatoría. La respuesta correcta está marcada por una arroba al principio, entre # pueden existir tantos espacios como se quieran, ya que hago un strip de la cadena.

Aquí está el código:


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

import os, sys
import random
from datetime import datetime

acertadas = 0

comando = 'zenity --text "¿Cual es tu nombre?" --entry'
entrada, salida = os.popen2(comando)
nombre = salida.read()
nombre = nombre.strip()

n = 0
for line in open(sys.argv[1]):
    n += 1
    opts = line.split('#');
    pregunta = opts[0]
    opts = opts[1:]
    opts = map(str.strip ,opts)

    columnas = []
    correcta = ''
    while len(opts) > 0:
        opt = random.choice(opts)
        opts.remove(opt)
        if opt[0] == '@':
            correcta = opt[1:]
            opt = opt[1:]
        opt = 'FALSE "' + opt + '"'
        columnas.append(opt)

    comando = 'zenity --text "' + pregunta + '" --list --radiolist --width=500 --height=500 --column X --column "Respuestas" ' + ' '.join(columnas)
    entrada, salida = os.popen2(comando)
    respuesta = salida.read()
    if respuesta.strip() == correcta:
        acertadas += 1

comando = 'zenity --scale --value=%(acertadas)d --max-value=%(n)d --print-partial --text "tu puntación %(nombre)s %(acertadas)d/%(n)d"' % dict(acertadas=acertadas, n=n, nombre=nombre)
entrada, salida = os.popen2(comando)
salida.read()

print "%s | %s | %d" % (datetime.now().ctime(), nombre, acertadas)

Se ejecutaría el programa de la siguiente forma:
python test.py fichero_de_test1 >> estadisticas_de_test1

Al principio pedirá un nombre para las estadísticas, y al final mostrará una barra, indicando la puntuación que has sacado.

EventManager en python

Hoy he estado implementando un sistema de plugins para www.sweetter.net, y para ello he decidido utilizar un modelo de señales, de tal forma que se puedan enlazar funciones a señales, y cuando se lance esta señal, se hace una llamada a todas las funciones enlazadas a esta señal.

La idea es simple, pero la implementación lo es aún más. Solo es necesario implementar un par de clases, Event y EventManager.

La clase Event
Como su propio nombre indica, esta clase representa un evento, o señal. Como atributos de clase tiene:

  • (str)self.name, para nombrar a la señal.
  • (dict)self.listeners, para guardar todas las funciones conectadas a esta señal, junto con los posibles parametros de estas.

Como metodos tiene:

  • add(self, function, data=None)
  • , que conecta una funcion a este evento, en data se pueden pasar los parametros de la función, como una tupla, o un dict. Si no se pasan, se llama a la función sin parametros.

  • def delete(self, function), esta es la inversa a la función anterior, desconecta una función de una señal.
  • def called(self, data=None), en esta función es donde está el meollo de la cuestión, ya que se encarga de llamar a las funciones de este evento. Si se pasa el parametro data, pues se utilizan estos parametros para la llamada a todas las funciones conectadas a esta señal

La clase EventManager
Esta clase es la encargada de gestionar todos los eventos que tengamos, y que emite las señales.
Como atributos:

  • (dict)self.events, en este parametro se almacenan todos los eventos.

Como metodos:

  • def add_event(self, Event), este metodo añade un evento al EventManager.
  • def del_event(self, Event), este metodo es el inverso del anterior, borra un evento.
  • def connect(self, event, function, data=None), con este metodo se conecta una función con un evento, el evento debe existir en el event manager.
  • def disconnect(self, event, function), esta es la función inversa a la anterior.
  • def signal(self, event, data=None), esta es la función encargada de mandar señales, así que se llama cuando queramos generar un evento.

Ejemplo de uso

import events
em = events.EventManager()
em.add_event(events.Event('misenal1'))
...
em.add_event(events.Event('otrasenal'))
em.connect('misenal1', print)
em.connect('misenal1', str.join, [['uno', 'dos', 'tres']])
em.connect('otrasenal', str.split, "cadena dos")
...
em.signal('misenal1', 'esto se imprimiria')
em.signal('otrasenal')
...

Aquí el código completo

class Event():
    def __init__(self, name):
        self.name = name
        self.listeners = {}

    def add(self, function, data=None):
        self.listeners[function] = data
    
    def delete(self, function):
        self.listeners.pop(function)

    def called(self, data=None):
        for function, d in self.listeners.items():
            if data is None:
                if d is None:
                    function()
                else:
                    if type(d) == type([]):
                        function(*d)
                    elif type(d) == type({}):
                        function(**d)
                    else:
                        function(d)
            else:
                if type(data) == type([]):
                    function(*data)
                elif type(data) == type({}):
                    function(**data)
                else:
                    function(data)


class EventManager():
    def __init__(self):
        self.events = {}

    def add_event(self, Event):
        self.events[Event.name] = Event

    def del_event(self, Event):
        self.events.pop(Event.name)

    def connect(self, event, function, data=None):
        self.events[event].add(function, data)

    def disconnect(self, event, function):
        self.events[event].delete(function)

    def signal(self, event, data=None):
        if data is None:
            self.events[event].called()
        else:
            self.events[event].called(data)

Código sensor de presencia

En respuesta a este comentario de albertou, voy a poner aquí el código en python del sensor de presencia que tenemos en sugus:

# sensor.py
import os
import time
import pygame
from pygame.locals import *

# devuelve una media de luminosidad
def luminosidad_media(image):
    brillo = 0
    for i in range(0, image.get_width()):
        for j in range(0, image.get_height()):
            pix = image.get_at((i,j))
            brillo += 0.3*pix[0] + 0.59*pix[1] + 0.11*pix[2]

    brillo = brillo / (image.get_width()*image.get_height())

    return brillo

os.popen2("gqcam -b 100 -v /dev/video0 -d imagen.png")
image = pygame.image.load("imagen.png")
luminosidad = luminosidad_media(image)
print luminosidad

Tan simple como esto, se captura una imagen con gqcam, se carga la imagen, para ello uso pygame, y luego se calcula la luminosidad media, haciendo la media del nivel de gris (0,255) de la imagen.

Esto se pone en un script: python sensor.py > luminosidad.txt
y ese script se mete en el cron para que se haga periodicamente. En el fichero de luminosidad quedará un numero de 0 a 255, y tú decides cuando consideras que la luz está encendida.

Nada más, algo muy simple.

TrayIcon en python

En sugus hace poco hemos puesto un sensor de presencia, que basicamente es una webcam que está mirando hacia la pared, y con un programita hecho en python, con pygame, se mira una imagen capturada por la webcam, y se calcula la luminosidad media. Si la luminosidad media es superior a 100, es que la luz de la habitación está encendida. En otro caso es que está apagada, y por tanto no hay nadie. Gracias a este sensor, en la web se puede observar una bolita, que estará roja, o verde, según no haya nadie, o haya alguien en sugus.

A partir de ahí, surgió la idea de llevar eso al escritorio, y como primera aproximación he hecho un icono del sistema en python. Para esto me he basado en uno que tenía por aquí, de un programa que gestiona la conexión a la red, el wicd.

Para poder hacerlo había que tener un acceso al estado de manera rápida y simple, por lo que hice este webservice:

http://sugus.eii.us.es/webservice/hayalguien.php

Que devuelve 1 cuando hay alguien, y 0 cuando no.

Aquí está el código:

import os,sys
import socket
import gtk, gobject, time

tr=None

dir = sys.argv[0]
final = len(dir)
for i in range(len(dir)):
    if dir[len(dir) -1 - i] == '/':
        final = len(dir) - i
        break
dir = dir[0:final]

def set_signal_image():
    s = socket.socket()
    try:
        s.connect(("sugus.eii.us.es", 80))
        s.send("GET /webservice/hayalguien.php\r\n")
        valor = s.recv(1)
        s.close()
    except:
        valor = '0'

    if valor == '1':
        tr.set_from_file(dir+"verde.png")
    else: tr.set_from_file(dir+"rojo.png")
    return True


class TrackerStatusIcon(gtk.StatusIcon):
    def __init__(self):
        gtk.StatusIcon.__init__(self)
        menu = '''
                <ui>
                <menubar name="Menubar">
                <menu action="Menu">
                <menuitem action="About"/>
                <menuitem action="Quit"/>
                </menu>
                </menubar>
                </ui>
        '''
        actions = [
                ('Menu',  None, 'Menu'),
                ('About', gtk.STOCK_ABOUT, '_About...', None, 'About wicd-tray-icon', self.on_about),
                ('Quit',gtk.STOCK_QUIT,'_Quit',None,'Quit wicd-tray-icon', self.on_quit),

                ]
        ag = gtk.ActionGroup('Actions')
        ag.add_actions(actions)
        self.manager = gtk.UIManager()
        self.manager.insert_action_group(ag, 0)
        self.manager.add_ui_from_string(menu)
        self.menu = self.manager.get_widget('/Menubar/Menu/About').props.parent
        self.current_icon_path = ''
        self.set_from_file(dir+"rojo.png")
        self.set_visible(True)
        self.connect('popup-menu', self.on_popup_menu)

    def on_quit(self,widget):
        sys.exit()

    def on_popup_menu(self, status, button, time):
        self.menu.popup(None, None, None, button, time)

    def on_about(self, data):
        dialog = gtk.AboutDialog()
        dialog.set_name('SUGUS sensor')
        dialog.set_version('1.0')
        dialog.set_comments('Este icono te dice si hay alguien en SUGUS')
        dialog.set_website('http://sugus.eii.us.es')
        dialog.run()
        dialog.destroy()

    def set_from_file(self,path):
        if path != self.current_icon_path:
            self.current_icon_path = path
            gtk.StatusIcon.set_from_file(self,path)

tr=TrackerStatusIcon()
gobject.timeout_add(3000,set_signal_image)
gtk.main()

El código con las imagenes, se puede encontrar aquí http://sugus.eii.us.es/~danigm/trayicon.tgz

Para que arranque por defecto, vete a las aplicaciones de arranque de tu escritorio favorito, y añade el comando personalizado:

python /ruta/te/hayas/bajado/esto/sugus.py

Y ya estarás informado en todo momento de cuando hay alguien en sugus, para pasarte por allí a hacer una visita.

Extensión para Epiphany-browser

Desde hace ya algún tiempo vengo usando epiphany-browser, porque es mucho más ligero que firefox. Tampoco se puede comparar con firefox, porque para firefox hay un montón de plugins, para hacer todo lo que quieras, pero yo no necesito tanto de un navegador.

Últimamente sí que he visto que tenía una necesidad, y es la papelera de pestañas cerradas. Cuando estuve trabajando en el proyecto konqueror con esteriodes, para el concurso universitario de software libre, trabajamos en la implementación de la papelera de pestañas para konqueror, (Todavía está Edulix intentando que incluyan el parche). Así que me he decidido a hacer una extensión para epiphany.

Las extensiones para epiphany se pueden hacer en C, y también en python. Como a mí me encanta python, pues ayer implementé la extensión epitab.

El mayor problema con el que me he encontrado a la hora de implementarla, ha sido la falta de documentación. Pude encontrar algo en gnome.org, pero me costó mucho encontrarlo. Otra cosa que ayuda mucho es la extesión consola de python para epiphany, donde puedes probar todo lo que quieras hacer.

Lo único que hago es controlar cada vez que una pestaña es cerrada, y añadir la url y el titulo a una lista. Luego muestro esa lista en el menú, dentro de Tabs, justo debajo de los tabs abiertos, de tal forma que si pulsas sobre algún elemento de tabs cerrados, se vuelve a abrir. Evitando así el tener que navegar por el historial si hemos cerrado una pestaña por error.

La extensión tiene un problema, y es que si hay varias pestañas con el mismo título, sólo se guarda la última, ya que están almacenadas por título. Una posible mejora sería almacenarlas por url, o añadirle un indice al título.

Se puede descargar de aquí.

Voy a comentar todo lo que he aprendido, por si hay alguien que se quiera aventurar en la implementación de alguna extensión que vea que necesite.

Una extensión en python se compone básicamente de dos ficheros, uno que es el que especifica lo que es la extensión: epitab.ephy-extension, que lo copié de otro, y cambié los campos necesaríos. Y otro es el modulo en python, que yo he llamado epitab.py. Voy a comentar este último, que es el que realmente importa.

Primero se importan todos los modulos necesarios, epyphany, pygtk y gtk, yo añadí también pdb, porque tuve algunos problemas y estuve depurando, pero no es necesarío.

import epiphany
import pygtk
pygtk.require('2.0')
import gtk
import pdb

Luego está la definición de unas cadenas, para pintar el menu. Los tabs cerrados los voy a añadir en el menú Tabs, así que irán como menuitem entre _ui_str1 y _ui_str2.

_ui_str1 = """
<ui>
  <menubar name="menubar">
    <menu name="TabsMenu" action="Tabs">
"""
_ui_str2 = """
    </menu>
  </menubar>
</ui>
"""

Las extensiones en epiphany se cargan y descargan de manera dinámica, por lo tanto tienen unas funciones que hay que definir, y que son las que se ejecutan cuando se carga el módulo, y cuando se descarga. Cuando se descarga hay que liberar todos los recursos que se hayan utilizado.

attach_window, se ejecuta al iniciar el plugin, en este caso, tan solo defino una variable dentro de window, que inicializo a None, y donde despues voy a guardar el id del menu, para poder borrarlo y redibujarlo.

def attach_window(window):
    window._epitab_window_data = None

detach_window se ejecuta cuando se descarga la extensión, por lo tanto libero todos los recursos reservados. Esta función la he copiado de otro plugin, y la he modificado, porque yo creo un action_group por cada pestaña cerrada, por lo que tengo que hacer un bucle para borrarlos todos. Además se borra el menú.

def detach_window(window):
    if hasattr(window, 'epitab'):
        window._epitab.destroy()
        del window._epitab_window_data

    ui_id = window._epitab_window_data
    del window._epitab_window_data

    ui_manager = window.get_ui_manager()
    if ui_id is not None:
        ui_manager.remove_ui(ui_id)

    for i in closed_tabs.keys():
        titulo, grupo = closed_tabs.pop(i)
        ui_manager.remove_action_group(grupo)
    ui_manager.ensure_update()
    

También declaro una variable global, donde se van a guardar todos los tabs cerrados. Uso un diccionario, donde indexo por el título de la pestaña, y guardo una lista con la url, y el action_group.

# clave -> titulo
# (url, actiongroup)
closed_tabs = {}

La función detach_tab, es una de las más importantes de la extensión, ya que es la que se llama cuando un tab es cerrado. También está attach_tab, pero en esta extensión no la uso. Tabién se pueden enlazar funciones callback con señales, pero en esta extensión no lo uso. Esto viene explicado en el manual que he enlazado arriba, de gnome.org.

Cómo se puede ver en el código, cuando se cierra una pestaña lo que hago es crear una nueva acción, que llamará a la función epitab_cb, y le pasará como argumentos window, y title, siendo title el título de la pestaña cerrada, por el cual indexo en el diccionario closed_tabs, por lo que puedo buscar el cerrado de forma fácil y abrirlo.

def detach_tab(window, tab):
    title = tab.get_title()
    address = tab.get_address()
    
    ui_manager = window.get_ui_manager()
    ui_id = window._epitab_window_data # cogemos el menu que tenemos guardado
    if ui_id is not None:
        ui_manager.remove_ui(ui_id) # borramos el menu
        
    action = [(title, None, title, None, None, epitab_cb)] # creamos la accion a hacer
    group = gtk.ActionGroup(title)
    group.add_actions(action, (window, title)) # anadimos la accion a la ventana
    ui_manager.insert_action_group(group, 0)
       
    closed_tabs[title] = (address, group)
    
    menu = genera_menu() # se genera el nuevo menu, con la nueva entrada
    ui_id = ui_manager.add_ui_from_string(menu) # se vuelve a anadir el menu
    window._epitab_window_data = ui_id # se guarda el nuevo id
    
    ui_manager.ensure_update()

Para generar el menú, he creado una función, que añade cada elemento del diccionario closed_tabs al menú, y lo asocia con su acción. Utilizo las variables _ui_str1 y _ui_str2, y entre estas pongo tantos menuitem como entradas haya en closed_tabs. Asociando cada uno a su acción, que hemos creado anteriormente con el nombre del título.

def genera_menu():
    menu = ''
    menu += _ui_str1
    for i in closed_tabs.keys():
        menu += '<menuitem name="'+i+'" action="'+i+'"/>'
    menu += _ui_str2
    
    return menu

Finalmente está la función epitab_cb, que es la que se encarga de abrir la pestaña que queremos, sobre la que hemos pulsado en el título. Se crea una pestaña nueva, con la url del seleccionado, y se añade a la ventana. Como ya lo hemos vuelto a abrir, hay que volver a generar el menú, en el cual ya no estará esta entrada, puesto que la hemos eliminado de closed_tabs.

def epitab_cb(action, window, title):

    ui_manager = window.get_ui_manager()
    ui_id = window._epitab_window_data # cogemos el menu que tenemos guardado
    if ui_id is not None:
        ui_manager.remove_ui(ui_id) # borramos el menu

    try:
        to_load = closed_tabs[title][0]
        action_group = closed_tabs[title][1]
        closed_tabs.pop(title)
    except:
        return 0
        
    tab = epiphany.Tab()
    embed = tab.get_embed()
    embed.load_url(to_load)
    tab.show()
    window.add_tab(tab, len(window.get_tabs()), len(window.get_tabs()))
    
    menu = genera_menu() # se genera el nuevo menu, con la nueva entrada
    ui_id = ui_manager.add_ui_from_string(menu) # se vuelve a anadir el menu
    window._epitab_window_data = ui_id # se guarda el nuevo id
    
    ui_manager.remove_action_group(action_group) # borramos la accion
    
    ui_manager.ensure_update()

Y eso es todo. Aquí se ve que no es muy complicado hacer una extensión para epiphany, si sabes como. Yo me he dado cuenta de que la mayor dificultad está en encontrar la documentación necesaría, y por eso he puesto este post explicandolo todo al detalle.