python

Ayer estuve haciendo algunas cosillas con gitpython y cairo para hacer unas gráficas chulas usando la información que nos ofrece un repositorio git. He creado un proyecto en gitorious con estos scripts.
Lo más chulo es el script gittimeline.py, que basandose en los commits y las estádisticas de estos commits muestra una galaxia de commits. Se van mostrando bolitas de colores que simbolizan a cada committer. El tamaño de la bola depende del número de cambios hechos y con el tiempo se van difuminando las bolas. Cuando un committer es regular, la bola que lo representa se va acercando al centro.
Aquí hay un vídeo de cómo se vería:
La forma de usar el script es la siguiente:
gittimeline.py path-to-repo [update-time-ms] [since-date]
Para repositorios grandes recomiendo usar el parámetro since-date para que no tarde una eternidad en empezar. La fecha se tiene que especificar con el formato 1970-01-01.

Desde principios de año estoy trabajando en yaco. Yaco es una pequeña empresa sevillana que se dedica a prestar servicios de consultoría y desarrollo de software libre y sobre todo desarrollo web con python tanto plone como django.
El funcionamiento de la empresa es parecido al de Emergya (empresa en la que estuve trabajando durante ocho meses) e incluso comparten una historia común, sin embargo tiene una serie de diferencias, por ejemplo emergya es prácticamente el doble de grande que yaco, y afronta casi todo tipo de proyectos, sin embargo yaco está más enfocada o especializada en el desarrollo de aplicaciones web.
Una empresa al fin y al cabo es un conjunto de empleados, y en esta empresa hay gente muy buena y muy inquieta, y por parte de la dirección se les permite pensar, opinar y desarrollarse. Un ejemplo de esto es el "día de proyectos personales" que es una iniciativa nueva de este año y que pretende brindar a los desarrolladores un día al mes para dedicar a desarrollar un proyecto propio. Es decir, que la empresa te dona parte de su tiempo para que lo dediques al proyecto software libre que a ti te parezca. Algo similar a lo que se hace en google, pero con menos recursos, por algo se empieza.
Pues bien, en principio voy a dedicar estos días de proyectos personales a seguir desarrollando sweetter y que no muera el proyecto. Mi idea principal es refactorizar todo el código, y corregir fallos que ahora que estoy desarrollando en django estoy descubriendo, y posteriormente surtir al proyecto con una serie de plugins útiles y empezar a buscarle rentabilidad o utilidad primero dentro de yaco, y después hacia nuestros clientes.
Y ya que estoy hablando de yaco, voy a contar las cosas que me gustan de trabajar aquí:
- Trabajo desarrollando en python y con software libre
- Hay gente con mucha experiencia e ideas interesantes, el equipo de trabajo es ideal y se puede trabajar de manera dinámica y ágil
- Los empleados están organizados, hay un delegado de personal que representa al conjunto de los empleados y todos conocen sus derechos
- Horario flexible y ajustable a tus necesidades
- La oficina está en el centro y puedo ir patinando
- La oficina es un lugar genial donde trabajar, está muy bien decorada, con las paredes pintadas con monigotes, hay una terraza, un sofá, en definitiva, se está como en casa
- Tenemos un futbolín y una wii en la oficina
- Hay comida y bebida gratuita en la oficina
- Cada vez que se consigue un proyecto nuevo hay desayuno gratis para todos, más conocido como campanazo, porque hay una campana que se toca cuando se consigue el proyecto.

Ahora que estoy trabajando con django estoy viendo lo potente que es, pero su sintaxis y forma de hacer las cosas no me acaba de convencer del todo comparandolo con web.py.
Una de las cosas que me gustan mucho de web.py es la forma de definir las vistas, que consiste en una clase donde defines el método GET y el método POST (no son obligatorios) y estos métodos son llamados según sea la petición http. Esto es muy util a la hora de hacer vistas de formularios, que si reciben la petición por GET muestran el formulario y si la reciben por POST hacen lo que sea con los datos, y en django suele haber un if request.method == 'POST' que guarrea la vista completamente.
Por eso hoy me he puesto y he hecho una pequeña prueba de concepto para poder permitir una definición similar en django, y así poder tener una mejor reestructuración del código de las vistas.
Lo he puesto en django snippets, pero también lo voy a poner aquí y lo comento:
from django.http import HttpResponse as response from django.http import HttpResponseNotAllowed class ViewClass: def __call__(self, request, *args, **kwargs): self.request = request methods = ['POST', 'GET'] self.methods = [method for method in dir(self)\ if callable(getattr(self, method)) and method in methods] if request.method in self.methods: view = getattr(self, request.method) return view(*args, **kwargs) else: return HttpResponseNotAllowed(self.methods) class IndexView(ViewClass): def GET(self): return response("all ok %s" % self.request.method) def POST(self): return response("all ok %s" % self.request.method) index = IndexView()
¿Qué es lo que he hecho? He definido mi vista IndexView como una clase, que hereda de "ViewClass" (ahí está la mágia), donde me he definido mis dos métodos, GET y POST, igual que si lo hubiera hecho con web.py, suponiendo que GET se llamará cuando la petición sea GET y POST cuando sea POST, y el request en lugar de recibirlo como parámetro es un atributo de la clase, así que accedo a él con self.request.
Luego defino la vista como index = IndexView(), una instancia de la clase IndexView.
ViewClass es la clase de la que heredamos y la que implementa el método __call__. En python, si un objeto tiene el método __call__ es callable, y se puede llamar igual que una funcion. Es más, en python, las funciones son objetos que tienen definido el método __call__. Por tanto, como django espera que la vista sea una función, no una clase, django va a llamar al método call y le va a pasar los argumentos de la vista.
En el método __call__ lo que se hace es mirar si el tipo de petición HTTP está implementado en la clase y es uno de los válidos (methods) y si existe en la clase un método con ese nombre, lo llama pasandole los argumentos y poniendo previamente self.request = request. En caso de que el método no esté implementado devuelve responsenotallowed.
Para terminar, quiero comentar que otra de las cosas que no me gusta de django es el sistema de templates, con tantas llaves y porcentages por ahí, me parece mucho más elegante templetor, pero también es verdad que los templates de django son muy potentes y se pueden hacer muchas cosas.


Con el proyecto TBO pretendo hacer un editor fácil de usar para poder crear cómics, presentaciones guías de programas, etc.
La idea del proyecto es presentar una interfaz (hecha con gtk) más o menos sencilla que ofrezca un editor para que quién no sepa dibujar pueda hacer un cómic en cinco minutos para hacer una presentación de algo, un tutorial de cómo funciona algo o para dibujar un cómic gracioso.
Para ello el editor dispondrá de diferentes modelos prediseñados para ir añadiendo a las viñetas y el usuario tan sólo tendrá que colocar los personajes y escribir los bocadillos correspondientes para tener un cómic.
Quiero desarrollar el proyecto con python, gtk y cairo. Y tengo intención de que se pueda exportar a png y pdf y además quiero que añadir nuevos sets de personajes a añadir.

El otro día un amigo me preguntó como hacer consultas a una base de datos mysql desde python y le comenté que con sqlalchemy se podía hacer. Yo ya había hecho algunas cosas con sqlalchemy, pero no había tratado con bases de datos ya creadas, sino que siempre las creaba con sqlalchemy.
He estado mirando un poco en la web de sqlalchemy y me he encontrado SqlSoup (SqlAlchemy).
SqlSoup mapea una base de datos ya creada a objetos python, así que no es necesario crear las clases o declarar las tablas en código python, se infieren directamente de la base de datos ya creada.
Veamos como funciona con un simple ejemplo con sqlite.
-
Creamos la base de datos sqlite para las pruebas
- $ sqlite3 testdb.sqlite
- sqlite> create table users (name varchar(50), email varchar(100), primary key (name));
- sqlite> create table posts (id integer not_null auto_increment, user_id varchar(50),
- ...> text varchar, primary key (id), foreign key (user_id) references users(name));
-
Aquí un script de prueba para ejecutar
- # <a href="http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #" title="http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #">http://www.sqlalchemy.org/docs/05/reference/ext/sqlsoup.html<br />
- #</a> Mapeando una base de datos ya creada para usar desde python de
- # manera simple y rapida.
- # creamos la base de datos con sqlite3
- # sqlite3 testdb.sqlite
- # sqlite> create table users (name varchar(50), email varchar(100), primary key (name));
- # sqlite> create table posts (id integer not_null auto_increment, user_id varchar(50),
- # ...> text varchar, primary key (id), foreign key (user_id) references users(name));
- from sqlalchemy.ext.sqlsoup import SqlSoup, Session
- from sqlalchemy import or_, and_, desc
- #db = SqlSoup('mysql://scott:tiger@localhost/mydatabase')
- db = SqlSoup('sqlite:///testdb.sqlite')
- def add_users():
- user = db.users.insert(name='danigm', email='dani@exp.com')
- user2 = db.users.insert(name='dani', email='danigm@exp.com')
- user3 = db.users.insert(name='pepe', email='pepe@exp.com')
- user4 = db.users.insert(name='juan', email='juan@exp.com')
- db.flush()
- Session.commit()
- def add_posts():
- post1 = db.posts.insert(id=1, user_id='danigm', text='comentario1')
- post2 = db.posts.insert(id=2, user_id='danigm', text='comentario2')
- post3 = db.posts.insert(id=3, user_id='pepe', text='comentario3')
- post4 = db.posts.insert(id=4, user_id='juan', text='comentario4')
- db.flush()
- Session.commit()
- try:
- add_users()
- except:
- Session.rollback()
- try:
- add_posts()
- except:
- Session.rollback()
- print db.users.filter(db.users.name=='danigm').first().name
- where = or_(db.users.name=='danigm',
- db.users.email=='danigm@exp.com')
- users = db.users.filter(where).order_by(desc(db.users.name)).all()
- print users
- user = users[0]
- for post in db.posts.filter(db.posts.user_id == user.name):
- print user.name, post.text
-
Veamoslo paso a paso
- db = SqlSoup('sqlite:///testdb.sqlite')
- user = db.users.insert(name='danigm', email='dani@danigm.net')
- db.flush()
- Session.commit()
- print db.users.filter(db.users.name=='danigm').first().name
- where = or_(db.users.name=='danigm',
- db.users.email=='danigm@exp.com')
- users = db.users.filter(where).order_by(desc(db.users.name)).all()
- print users
- user = users[0]
- for post in db.posts.filter(db.posts.user_id == user.name):
- print user.name, post.text
Con esta simple línea tenemos el objeto db que está conectado con la base de datos y ya podemos empezar a hacer consultas u otras cosas.
Podemos acceder a las tablas por su nombre directamente, db.users, db.posts y estos objetos tienen métodos para insertar, borrar o filtrar de tal forma que podamos hacer la consulta deseada, en este ejemplo añadimos un usuario. Hay que tener en cuenta que para que los cambios se vean reflejados hay que ejecutar el commit().
se puede filtrar como en cualquier objeto de sqlite, en este ejemplo filtramos por nombre de usuario, nos quedamos con el primero y mostramos el nombre del mismo.
Y una vez dominado el uso de sqlalchemy se pueden hacer filtros más complejos con otras directivas. combinando filtros y usando toda la potencia que te da sqlalchemy.
Con este ejemplo vemos que acceder a bases de datos desde python es muy fácil con sqlalchemy y además puedes hacerlo independiente del motor de base de datos gracias a que sqlalchemy soporta mysql, postgresql, sqlite, oracle, firebird, MS-SQL, MSAccess.

Últimamente he estado jugando con JQuery y llamadas ajax a servcios json y me he encontrado con un pequeño problema. Cuando intentas acceder a un servicio que te ofrece json plano jquery peta y te devuelve un error como este en la consola de javascript del navegador:
Error: uncaught exception: [Exception... "Access to restricted URI denied" code: "1012" nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI)" location: "js/jquery-1.3.2.min.js Line: 19"]
Esto ocurre porque el navegador implementa una política de seguridad mediante la cual no permite que se acceda a urls diferentes a la que hace la petición. Osea que no puedes hacer peticiones a un servidor externo de json pelado.
Para realizar este tipo de peticiones existen callbacks y jsonp. La idea es que en la url de la petición jquery debes añadir "http://.../?callback=?" y jquery rellenará el resto. Para cada petición jquery pasará como callback un nombre de función "jsonp1234" donde los números son diferentes por petición y el servidor ha de responder con el código json envuelto en esa función "jsonp1234({...});".
¿Qué haces cuando el servidor no ofrece el servicio jsonp?
Aquí es donde viene el proxy guarrón que me he implementado. La idea es pillar todas las peticiones al servidor, pillar el json plano y modificarlo para que sea jsonp.
Para ello lo primero que desarrollé fué un proxy http plano y feo:
# python proxy.py twitter.com 80 import sys import socket s = socket.socket() mserver = 'localhost' mport = 8080 s.bind((mserver, mport)) s.listen(10) server = sys.argv[1] port = int(sys.argv[2]) while True: try: print "waiting for requests at %s:%s" % (mserver, mport) s1 = s.accept() except KeyboardInterrupt: s.close() print "bye" break s2 = socket.socket() s2.connect((server, port)) s1 = s1[0] v = '' r = 0 while not r or len(r) == 1024: r = s1.recv(1024) v += r v = v.replace('Host: %s:%s' %(mserver, mport), 'Host: %s:%s' % (server, port)) print v s2.send(v) v2 = '' r = 0 while True: s2.settimeout(0.3) try: r = s2.recv(1024) except: break v2 += r s1.send(v2) s2.close() s1.close()
¿Qué hace? pues monta un servidor en el puerto 8080 y cada petición a este servidor la hace directamente al servidor remoto y la respuesta de este es lo que devuelve. Simple ¿verdad?.
Pero yo quiero modificar lo que me devuelve, así que hay que tratar la respuesta del servidor remoto:
#!/usr/bin/python # proxy.py twitter.com 8080 import sys import socket import gzip import StringIO import re pet = re.compile('(GET) (?P<url>.*)\?(?P<args>.*) (HTTP/1.1)') js = re.compile('(\{.*\})') def html(out): ret = '' ret += "Content-type: text/html\r\n" ret += 'Content-Encoding: gzip\r\n' ret += 'Content-Length: %s\r\n' % len(out) ret += '\r\n' ret += out return ret def compressBuf(buf): zbuf = StringIO.StringIO() zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 9) zfile.write(buf) zfile.close() return html(zbuf.getvalue()) def json(cad, callback="json"): if not callback: return cad else: return "%s(%s);" % (callback, cad) s = socket.socket() mserver = 'localhost' mport = 8080 s.bind((mserver, mport)) s.listen(10) server = sys.argv[1] port = int(sys.argv[2]) while True: try: print "waiting for requests at %s:%s" % (mserver, mport) s1 = s.accept() except KeyboardInterrupt: s.close() print "bye" break s2 = socket.socket() s2.connect((server, port)) s1 = s1[0] v = '' r = 0 while not r or len(r) == 1024: r = s1.recv(1024) v += r v = v.replace('Host: %s:%s' %(mserver, mport), 'Host: %s:%s' % (server, port)) callback='' args = pet.search(v) if args: args = args.group('args').split('&') for a in args: c,val = a.split('=') if c == 'callback': callback = val break v = pet.sub(r'\1 \2 \4', v) print v s2.send(v) v2 = '' r = True while r: s2.settimeout(1) try: r = s2.recv(1024) except: break v2 += r v3 = v2.split('\r\n') print v3 encoding = '' for i in v3: if i.startswith('Content-Encoding: '): encoding = i[len('Content-Encoding: '):] if encoding == 'gzip': st = StringIO.StringIO(v3[-1]) content = gzip.GzipFile(fileobj=st).read() else: try: content = js.search(v2).groups()[0] except: content = '' print content s1.send(json(content, callback)) s2.close() s1.close()
Puedes probar con twitter por ejemplo, lanzando el proxy "python proxy.py twitter.com 80" y haciendo la petición desde el navegador "http://localhost:8080/status/user_timeline/danigm.json?callback=jsonp123" o "http://localhost:8080/status/user_timeline/danigm.json?callback=holaaaaa".
Y jugando con esto puedes hacer un proxy que modifique toda respuesta, por lo que te puedes divertir mucho.
Por supuesto habrá muchos proxys http hechos en python, pero este lo he hecho yo, a bajo nivel y de mala manera :P

Cada vez que hago un desarrollo comienzo creando una carpeta y a partir de esa carpeta raíz del proyecto comienzo a generar todo lo necesario para que se pueda ejecutar. Hasta hace bien poco GECO estaba así y se podían ejecutar cada una de sus partes, tanto el servidor como el cliente de forma simple tan solo entrando en el directorio correspondiente y lanzando el script.
Pero cuando comienzas a utilizar un programa de manera más o menos habitual, no es cómodo el tener que entrar en el directorio correspondiente y lanzar el script, para un uso habitual es más conveniente el tener el programa instalado como uno más del sistema. Y esto es lo último que he realizado, un instalador sencillo que te deja todo donde "debe" estar para facilitar el uso.
Python ofrece una forma sencilla de hacer instaladores de módulos y programas, es el conocido módulo distutils. Normalmente los programas en python cuando te los descargas vienen con un fichero setup.py el cual hay que ejecutar para que se instale en el sistema, es una especie de make, pero para python.
¿Cómo crear un fichero setup.py?
El fichero setup.py no es más que un pequeño script en python en el cual tendremos que definir qué queremos que se instale, autor, descripción y otros datos.
Veamoslo con ejemplos:
# setup.py de gecoc from distutils.core import setup setup(name = 'gecoc', version = '1.0', description = 'geco client lib', author = 'Daniel Garcia Moreno', author_email = '<dani@danigm.net>', url = 'http://bzr.danigm.net/geco', license = 'GPLv3', scripts = ['terminal-geco'], packages = ['gecoc'] )
$sudo python setup.py install
Con este script se puede instalar la librería gecolib que está en el paquete gecoc y el script terminal-geco. La mayoría de las opciones se entienden perfectamente, son datos opcionales que dan información sobre el programa. Las partes importantes en este ejemplo son las opciones scripts y packages. Scripts instala la lista de scripts en /usr/bin y packages instala un paquete python (directorio con __init__.py) en /usr/lib/pythonX.X/site-packages/.
El directorio destino de la instalación se puede cambiar y poner otro en lugar de /usr/, por ejemplo /usr/local/
$sudo python setup.py install --prefix=/usr/local/
# setup.py de gtk-geco from distutils.core import setup import os datafiles = [] for i in os.listdir('glade'): datafiles.append(('share/gtk-geco/', ['glade/'+i])) datafiles.append(('share/pixmaps', ['geco.png'])) datafiles.append(('share/applications', ['gtkgeco.desktop'])) setup(name = 'gtk-geco', version = '1.0', description = 'gtk client for geco', author = 'Daniel Garcia Moreno', author_email = '<dani@danigm.net>', url = 'http://bzr.danigm.net/geco', license = 'GPLv3', data_files = datafiles, scripts = ['gtk-geco'], py_modules = ['gtkgeco'], packages = ['gecoc'] )
Este otro ejemplo es el instaldor de la aplicación de escritorio gtk-geco. En este caso además de instalar un script y el paquete gecoc se instala un módulo python, que es un simple fichero .py y una serie de datos, que se hace con data_files. Con datafiles se puede instalar cualquier tipo de archivo en cualquier ruta del sistema. Si la ruta es relativa se instala a partir de La ruta /usr (o la que se haya especificado), y si la ruta es absoluta se usa esa. La ruta define un directorio y la lista de ficheros siguiente serán los que se copien a dicha ruta.
Facilitando un poco más la instalación
Tengo el código dividido en dos carpetas dentro de src, gecod y gecoc y dentro de gecoc está gtk-geco y web-geco que son dos clientes diferentes. Para facilitar la instalación he creado un script en bash que se encarga de instalar el módulo que deseemos de manera sencilla:
#!/bin/bash function install_help(){ echo "Ayuda del instalador $(basename $0)" echo " Con este instalador puedes instalar:" echo " gecod" echo " gecoc" echo " gtk-geco" echo " web-geco" echo "" echo " Usalo pasando como argumento el nombre de lo que quieras instalar" echo " por ejemplo:" echo " sudo ./$(basename $0) gecod" } root=$PWD if [ "$#" -eq 0 ] then install_help exit 0 fi while [ "$#" -gt 0 ] do case $1 in -h | --help) install_help shift ;; gecod) cd $root cd src/gecod/ echo "instalando gecod" python setup.py install shift ;; gecoc) cd $root cd src/gecoc/ echo "instalando gecoc" python setup.py install shift ;; gtk-geco) cd $root cd src/gecoc/gtk-geco echo "instalando gtk-geco" python setup.py install shift ;; web-geco) cd $root cd src/gecoc/web-geco echo "instalando web-geco" python setup.py install shift ;; *) install_help shift ;; esac done
Y con este instalador instalar por ejemplo el cliente de escritorio es tan fácil como ejecutar:
$sudo ./install.sh gtk-geco

Hace ya algún tiempo que hice la interfaz gráfica de GECO y esto lo hice usando glade para generar un fichero xml que posteriormente se carga desde python y se trabaja con los controles. Así se consigue separar la capa de presentación de la lógica del programa y es muy simple cambiar casi cualquier aspecto gráfico sin tener que tocar código.
Para hacer aplicaciones de escritorio con gtk lo más simple es utilizar glade-3, en casi todas las distribuciones hay varios paquetes, glade o glade-2 y glade-3. En las versiones anteriores el editor glade presentaba varias ventanas dispersas, al estilo gimp, pero glade-3 presenta una interfaz unificada y mucho más intuitiva.
Es muy sencillo crear una interfaz con este programa, sólo tienes que ir pinchando en los controles que quieras añadir y se van dibujando sobre la marcha, luego cambias sus propiedades y le das el aspecto visual que prefieras.
La mayoría de los manuales de pygtk y glade indican que hay que hacer lo siguiente para cargar una interfaz creada con glade desde código python:
import gtk.glade class Ventana: def __init__(self): self.gladefile = "window.glade" self.glade = gtk.glade.XML(self.gladefile) #accediendo a los controles self.button = self.glade.get_widget('button1') self.button.connect('clicked', action)
¿Qué simple verdad? pues en principio no funciona, cuando lo intentas hacer no funciona, a no ser que en el glade le des a guardar como libglade. Esto es porque desde hace algún tiempo, y yo no me había enterado, se utiliza otro formato que es gtkbuilder. Para utilizar interfaces generadas con glade y guardadas como gtkbuilder (que es como debería ser) hay que hacerlo de la siguiente manera:
import gtk class Ventana: def __init__(self): self.gladefile = "window.glade" self.glade = gtk.Builder() self.glade.add_from_file(self.gladefile) #accediendo a los controles self.button = self.glade.get_object('button1') self.button.connect('clicked', action)
Como podrás observar no ha cambiado mucho la cosa, solo cambiar get_widget por get_object y poco más.
Una vez cargado un objeto ya se puede trabajar con él, modificándolo, obteniendo información de él, etc. Para conocer todos los métodos y señales disponibles lo mejor es mirar la documentación.
Más facilidades, connect_signals, antiguo autoconnect
Desde glade, además de poner los controles en su sitio y definir los nombres se pueden definir funciones asociadas a los diferentes eventos que el objeto en cuestión pueda recibir. Así podemos evitarnos el tener que enlazar todos los botones con sus correspondientes funciones con el método connect, como se puede ver en los ejemplos anteriores y se pueden enlazar todas las funciones con una serie de funciones o métodos definidos.
Supongamos que tenemos una interfaz con un par de botones y hemos enlazado la señal clicked de cada botón con dos funciones llamadas boton1 y boton2 respectivamente.
Para que estos botones hagan algo, tendríamos un código así:
import gtk class Ventana: def __init__(self): self.builder = gtk.Builder() self.builder.add_from_file('test.glade') self.window = self.builder.get_object('window1') self.window.show_all() # Magia :P self.builder.connect_signals(self) def boton1(self, widget): print "boton1" def boton2(self, widget): print "boton2" if __name__ == '__main__': v = Ventana() gtk.main()
Si al método connect_signals se le pasa una instancia, buscará el nombre de las funciones en sus métodos, sin embargo también puede recibir un diccionario, y en tal caso, buscará las funciones a enlazar en ese diccionario.
También es posible pasarle argumentos a las funciones, pasándole otro argumento al método connect_signals:
... self.builder.connect_signals(self, 'mas info') ... def boton1(self, widget, data): ... def boton2(self, widget, data): ...
Y con esto y un bizcocho, hacer interfaces gráficas con pygtk y glade es mucho más simple de lo que nunca pudieras imáginar :P

Esta mañana me he levantado con una idea en la cabeza, sería genial: poder importar módulos de una ruta...
Pues bien después de haber pensado en la implementación y todo me ha dado por buscar en la web y da la casualidad de que no soy demasiado original y ya alguien tenía alguna que otra implementación.
He encotrado el módulo urlimport:
http://urlimport.codeshift.net/
Ejemplo de código:
import urlimport import sys sys.path += ['http://www.crummy.com/software/BeautifulSoup/download/'] import BeautifulSoup BeautifulSoup <module 'BeautifulSoup' from 'http://www.crummy.com/software/BeautifulSoup/download/BeautifulSoup.py'>
Con esto se podrían ejecutar aplicaciones gtk por ejemplo de manera que el código esté en remoto y nos lo bajemos sólo para ejecutarlo. Así tenemos aplicaciones de escritorio remotas, es lo mismo que pasa con el javascript pero a nivel de python y sin navegador. Y con esto si por ejemplo cambiamos nuestro código no tenemos que ir instalando la nueva versión en cada cliente, sólo en el servidor, sino que la próxima vez que se ejecuten estarán actualizados.
Esto tiene un pequeño fallo de seguridad, y es que si alguien consigue mandarte una respuesta con codigo malicioso podría ejecutar casi cualquier cosa en tu propia máquina.
Por eso me he puesto a modificar un poco el código y le he añadido firma con RSA. Para esto he utilizado un módulo python puro http://stuvel.eu/rsa.
El servidor en el cual está el código tiene una clave pública y otra privada, con la clave privada se cifra el sha256 del código y se incluye tanto el código como el hash firmado en un zip. Por otra parte el cliente se descarga el fichero zip y teniendo la clave pública verifica que el hash que va incluido en el zip es el que se corresponde con el código y además está firmado por el servidor.
Para crear el fichero .zip he utilizado el módulo zipfile y hashlib para generar el hash.
Cosas por hacer
Aún me quedan un par de mejoras que quiero añadirle, además de código python quiero poder ponerle dependencias a los módulos .zip de otros "módulos" pero de datos para por ejemplo poder añadir las imágenes de una aplicación o los ficheros .glade, etc. También quiero estudiar la posibilidad de que los datos como imágenes y los ficheros .glade se los pueda descargar por petición, es decir que se los descargue si intenta cargar un fichero y falla, aunque no tengo mucha confianza en la viabilidad de esta solución.
El código lo estoy versionando en mi repositorio: http://bzr.danigm.net/urlimport/. Quiero hacer que sweetgtk se ejecute así.


