Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Video de la charla Entendiendo decoradores en Python

Orfi se tomó el trabajo de editar una filmación de mi charla en el PyDay de Rafaela con mis slides para armar este video. Muchas gracias!

entendiendo decoradores from Orfx Sch on Vimeo.

Al final, durante las preguntas, escribo algo de código Python en la terminal. Lo siguiente es una reproducción:

>>> def f(a, b):

...     print a, b

...

>>> f(1, 2)

1 2

>>> def f(*a, **kw):

...     print a

...     print kw

...

>>> f(1)

(1,)

{}

>>> f(1, parametro=2)

(1,)

{'parametro': 2}

>>> def f(p1, *a, **kw):

...     print kw['param']

...

>>> f()

Traceback (most recent call last):

  File "", line 1, in

TypeError: f() takes at least 1 argument (0 given)

>>> f(1, 2, 3, param=0)

0

>>> def deco(f):

...     def _deco(*a, **kw):

...             if kw.get('p'):

...                     return f(*a, **kw)

...             else:

...                     print "No ejecuto."

...     return _deco

...

>>> @deco

... def saludo(*a, **kw):

...     print "hola"

...

>>> saludo()

No ejecuto.

>>> saludo(p=0)

No ejecuto.

>>> saludo(p=1)

hola


Decorando decoradores

Una de las preguntas que aveces me hacen luego de hablar sobre Taint Mode es: una vez que la aplicación pasa de desarrollo a producción, ¿hay alguna forma de deshabilitar los decoradores?

La biblioteca usa decoradores para marcar entradas no confiables, sumideros sensibles y funciones limpiadoras con la idea de encontrar posibles vulnerabilidades en tiempo de desarrollo. Si en producción no los requerimos más, esos decoradores producen overhead.

Entonces... qué se puede hacer? Editar el código comentando los decoradores no escala[0]. Una alternativa es utilizar un nuevo decorador: llamemoslo @apply_decorator y vamos a utilizarlo para controlar mediante alguna condición (por ejemplo una variable en el archivo de configuración) si se debe usar o no el decorador.

COND = True

def apply_decorator(d):

if COND:

    return d

else:

    return lambda f: f

Si la condición es verdadera, se retorna el decorador original, sino una función fake (implementada utilizando lambda) que recibe una función y retorna la misma función: un decorador que no hace nada.

Un ejemplo de su uso en el REPL de Python:


>>> @apply_decorator

... def mydeco(f):

...     def inner(*a, **kw):

...             print "decorado"

...             return f(*a, **kw)

...     return inner

... 

>>> mydeco



>>> COND = False

>>> @apply_decorator

... def mydeco(f):

...     def inner(*a, **kw):

...             print "decorado"

...             return f(*a, **kw)

...     return inner

... 

>>> mydeco

 at 0xb7753dbc>

Podemos aplicarlo directamente a la definición de nuestros decoradores


@apply_decorator

def mi_decorador(f):

    ...

o al principio de nuestro programa.


mi_decorador = apply_decorator(mi_decorador)

[0] nessita trademark.


Quoted

Fillipo has just published Implementing Erasure Policies Using Taint Analysis.

With this in mind, and inspired by Conti and Russo’s work, we implement a mechanism to perform taint propagation, i.e. how to mark as erasure-aware data that is computed from other erasure-aware values. From now on, we use taint and erasure-aware as interchangeable terms.

Algunos problemas y soluciones al levantar bases de datos legacy con Django

Con Django podemos levantar una base de datos legacy en lugar de definir nuestro modelo de datos y arrancar una aplicación desde cero. No es necesariamente una base de datos vieja, sino una base de datos heredada de otro sistema; puede ser de un sistema anterior que se está reemplazando o incluso de otro sistema que queremos usar de forma paralela.

Luego de configurar nuestra base de datos y ejecutar:

python manage.py inspectdb > models.py

obtenemos una definición de modelos basada en en las tablas de la base de datos. Lo siguiente es ver si anda.

Lo más probable es que obtengamos un NameError. Esto pasa cuando en la definición de alguno de los modelos se hace referencia a otro modelo aún no definido! La solución indicada es empezar a reordernar los modelos, pero una forma más fácil es cambiar los nombres por strings. Un ejemplo:

jefe = models.ForeingKey(Empleado)

por

jefe = models.ForeingKey('Empleado')

Otra queja que nos puede hacer Django es que tengamos atributos llamados id que no sean primary key. Solución: les cambiamos el nombre.

Eventualmente vamos a necesitar habilitar la aplicación admin (ya que estamos usando Django para levantar datos, nada mejor que usar su ABM estrella para ahorrarnos mucho trbajo). Los pasos para hacer con éxito son:

  1. Habilitar la aplicaccion admin en settings.py.
  2. Ejecutar syncdb para que se creen las tablas de esta aplicación.
  3. Agregar los modelos de la base de datos heredada al archivo admin.py de la aplicación.

Por supuesto, si los nuevos modelos son cientos, es bastante engorroso hacer esto a mano. Les paso un hack; así luce mi admin.py:

from django.contrib import admin

from django.db.models import base

from my_app.models import *



for k,v in locals().items():

    if isinstance(v, base.ModelBase):

        admin.site.register(v)

Tip final. Si obtenemos un error de este tipo:

OperationalError at /admin/mango/datapointusers/

(1054, "Unknown column 'dataPointUsers.id' in 'field list'")

es por que alguna de las tablas levantadas no tenía una columna que sea clave primaria. Django necesita esto para poder distinguir los objetos entre si. Para cada tabla dónde tengamos este problema, podemos ejecutar el siguiente comando SQL para agregarle una columna llamada id; clave primaria y auto numérico.

ALTER TABLE `dataPointUsers` ADD `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY;


Entendiendo *args y **kwargs en Python

Me llegó un mail consultando sobre args y *kwargs en Python. ¿Qué es eso? Vamos por partes.

En Python existen varias formas de llamar a una función. Por ejemplo, en su definición puede tener valores por defecto, entonces no es necesario utilizarlos todos al llamarla:

>>> def f(a, b, c=3):

... print a, b, c

...

>>> f(1,2)

1 2 3

Se pueden utilizar palabras claves como argumento, por supuesto, respetando algunas reglas:

>>> def f(a, b, c):

...     print a, b, c

...

>>> f(a=1, b=2, c=3)

1 2 3

>>> f(a=1, 2, 3)

  File "", line 1

SyntaxError: non-keyword arg after keyword arg

>>> f(1, 2, b=3)

Traceback (most recent call last):

  File "", line 1, in

TypeError: f() got multiple values for keyword argument 'b'

>>> f(1, b=2, c=3)

1 2 3

Y finalmente, se puede llamar con listas arbitrarias de argumentos. Como en los casos anteriores, poder hacerlo depende de cómo hayamos definido la función:

>>> def f(*args, **kwargs):

...     print "args:", args

...     print "kwargs:", kwargs

...

Con args se indica, mapear todos los argumentos posicionales a una tupla llamada args. Y con *kwargs se indica, mapear todos los argumentos de palabra clave a un diccionario llamado kwargs.

>>> f(1,2,3)

args: (1, 2, 3)

kwargs: {}

>>> f(1,2,3, cuatro=4)

args: (1, 2, 3)

kwargs: {'cuatro': 4}

>>> f(cuatro=4)

args: ()

kwargs: {'cuatro': 4}

Notemos que se pueden definir argumentos con nombre propio antes de los argumentos agrupados y que no es necesario usar exactamente los nombres args y kwargs. También puede usarse solo uno de los dos:

>>> def f(p, *a, **kw):

...     print "p:", p

...     print "a:", a

...     print "kw:", kw

...

>>> f(1,2,3)

p: 1

a: (2, 3)

kw: {}

>>> f(1,2,3, cuatro=4)

p: 1

a: (2, 3)

kw: {'cuatro': 4}

>>> f(cuatro=4)

Traceback (most recent call last):

  File "", line 1, in

TypeError: f() takes at least 1 non-keyword argument (0 given)

Espero que los ejemplos sirvan. Para más detalle, se pueden consultar las secciones del Tutorial de Python que enlacé.


Twitter updates desde Twisted

Este artículo es una re-edición del publicado el 24/08/2010 ya que cuando lo publiqué, Twitter estaba terminando su proceso apagar el sistema de autenticación básica para pasar al más complejo sistema de la danza oAuth; lo cual convirtió al artículo en inservible. El artículo anterior no está más disponible. Gracias a Pupeno por revisar esta versión.

¿Tenés un servidor escrito en Twisted? ¿Tenés eventos críticos o importantes que mandás por mail o a celulares? ¿Qué tal publicarlos en Twitter?

La versión original de este artículo utilizaba la biblioteca Twitty Twister, la única pensada para usar Twitter desde Twisted. Lamentablemente no funciona bien con el nuevo sistema de autenticación de Twitter por lo que voy a usar Tweepy, una librería con la cual hacer la danza de oAuth es muy sencillo.

1) Registrar un usuario dónde publicar las notificaciones.

2) Con ese usuario, registrar una aplicación. Es muy importante usar el mismo usuario para ahorrarnos algunos pasos en la danza oAuth.

3) De la página de la aplicación creada, tomar la siguiente información: consumer key, consumer secret (desde la home de la aplicación), access token y access token secret (de la página My Access Token).

4) Código:

Con los datos del punto 3, creamos el módulo twitterupdates.py:

import tweepy



TWITTER_KEY = 'xxxxxxxxxxxxxxxx'

TWITTER_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

MY_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxx'

MY_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxx'



auth = tweepy.OAuthHandler(TWITTER_KEY, TWITTER_SECRET)

auth.set_access_token(MY_KEY, MY_SECRET)

twitter = tweepy.API(auth)

y desde nuestra aplicación Twisted lo importamos:

from twitterupdates import twitter

Postear una actualización en Twitter o cualquier otra petición a un servidor externo tiene una naturaleza bloqueante y en un framework de concurrencia colaborativa, como es Twisted, no podemos darnos ese lujo. Para solucionar esto, utilizamos deferToThread:

deferToThread(twitter.update_status, "Mensaje a postear en Twitter")

Esta llamada retorna un objeto Deferred al cual se le pueden añadir callbacks o errbacks. La llamada bloqueante es corrida en un hilo aparte.

Enjoy!


Cambiando el formato de los logs en Twisted

En Twisted se puede tener un log de todo lo que pasa en un programa de forma bastante fácil:

from twisted.python import log

from twisted.python.logfile import DailyLogFile

log.startLogging(DailyLogFile('log.txt', LOGDIR))

Todos los prints que anden dando vuelta ahora son entradas en el log y de yapa estoy usando DailyLogFile en lugar de un archivo común y corriente para que al final del día rote a un nuevo archivo en disco. Hay varias opciones, esta es la que me sirve a mí.

Por defecto los logs se ven algo así:

2010-05-28 07:41:00-0500 [__main__.TModBusFactory] Nuevo cliente: 190.136.29.16:12101

2010-05-28 07:41:00-0500 [__main__.TModBusFactory] Total: 1

2010-05-28 07:41:01-0500 [TModBus,0,190.136.29.16] G24 dice:  :0090SFE00

2010-05-28 07:41:01-0500 [TModBus,0,190.136.29.16] SITIO SFE

2010-05-28 07:41:01-0500 [TModBus,0,190.136.29.16] Nuevo sitio registrado en MBProxy SFE

2010-05-28 07:45:49-0500 [__main__.TModBusFactory] Nuevo cliente: 190.136.29.16:30519

2010-05-28 07:45:49-0500 [__main__.TModBusFactory] Total: 2

2010-05-28 07:45:49-0500 [TModBus,1,190.136.29.16] G24 dice:  :0090SFE00

2010-05-28 07:45:49-0500 [TModBus,1,190.136.29.16] SITIO SFE

2010-05-28 07:45:49-0500 [TModBus,1,190.136.29.16] SFE ya estaba conectado. Borrando anterior.

2010-05-28 07:45:51-0500 [TModBus,1,190.136.29.16] Nuevo sitio registrado en MBProxy SFE

¿Cómo podemos cambiar el formato de salida? Algunos tips.

Para cambiar el formato de la fecha y hora:

log.FileLogObserver.timeFormat = '%Y-%m-%d %H:%M:%S'

Para cambiar lo que aparece entre corchetes: utilizar log.msg y el keyword system:

log.msg("Nuevo cliente: %s:%d" % (self.peer.host, self.peer.port), system=' - ')

Con estos dos cambios se puede tener un log como este:

2010-05-28 07:41:00 [ - ] Nuevo cliente: 190.136.29.16:12101

2010-05-28 07:41:00 [ - ] Total: 1

2010-05-28 07:41:01 [SFE] G24 dice:  :0090SFE00

2010-05-28 07:41:01 [SFE] SITIO SFE

2010-05-28 07:41:01 [SFE] Nuevo sitio registrado en MBProxy SFE

2010-05-28 07:45:49 [ - ] Nuevo cliente: 190.136.29.16:30519

2010-05-28 07:45:49 [ - ] Total: 2

2010-05-28 07:45:49 [ - ] G24 dice:  :0090SFE00

2010-05-28 07:45:49 [SFE] SITIO SFE

2010-05-28 07:45:49 [SFE] SFE ya estaba conectado. Borrando anterior.

2010-05-28 07:45:51 [SFE] Nuevo sitio registrado en MBProxy SFE

Para cambiar aún más el formato de la salida, la única forma que encontré es extender FileLogObserver y sobreescribir su método emit. Discutimos un poco esto en StackOverflow.

nota: En Twisted también podemos usar el sistema de logging de Python, esto tiene la ventaja de que podemos trabajar con niveles de log y controlar mejor el formato y la forma en que se generan los logs, pero el problema de que no está preparado para funcionar en forma asincrónica y esto puede traer algunos problemas con Twisted.


Un servidor web con pocas líneas de Python

Hoy un amigo necesitaba un servidor web para engañar a un programa. Cada vez que el programa iniciaba, se conectaba con un servidor web para verificar si había actualizaciones disponibles.

El nombre del host a dónde se hacía la petición era leído de un archivo de configuración, por lo que lo podíamos cambiar. El resto solo era levantar un servidor web que responda con la información apropiada.

En la librería estándar de Python tenemos todos los elementos necesarios para realizar la tarea. Luego de probar un poco, el resultado final fue algo como esto:

PORT = 8090

from BaseHTTPServer import BaseHTTPRequestHandler

import SocketServer

import cgi

class MyHandler(BaseHTTPRequestHandler):

def do_GET(self):

    self.send_response(200)

    self.end_headers()

        self.wfile.write('1.9.1')



def do_POST(self):

    form = cgi.FieldStorage(

        fp=self.rfile,

        headers=self.headers,

        environ={'REQUEST_METHOD':'POST',

        'CONTENT_TYPE':self.headers['Content-Type'],

        })

    print form

    self.send_response(200)

    self.end_headers()

    self.wfile.write('1.9.1')

httpd = SocketServer.TCPServer(("", PORT), MyHandler)

print "serving at port", PORT

httpd.serve_forever()

Levanta en localhost y responde la cadena '1.9.1' al ser consultado tanto por GET como por POST y en el caso de POST, también imprime los valores recibidos.


El hosting para Django más barato

Hace un tiempo recomendé Webfaction como el mejor hosting para Django (debe aún serlo). Pero algunas veces no queremos el mejor, con el más barato nos alcanza :D

Tal vez son un adolecente aprendiendo a programar, o simplemente rata. Para un programador PHP es bastante fácil encontrar un hosting en Internet que le de un servicio gratuito, subir sus cosas y mostrarlas al mundo. Cuando programás en Django... es más difícil.

Hasta hoy; les paso el dato: AlwaysData.com

Hosting Django gratuito.

Tengo 2 cuentas funcionando muy bien. Y no se asusten por que está en francés (eso me detuvo la primer vez que lo vi); luego de sacar la cuenta tenés acceso al panel de administración que está en muchos idiomas; incluyendo español. Tenés para elegir distintas versiones de Python y Djando, MySQL o PosgreSQL, acceso SHELL, FTP y WebDAV. Qué más querés?

También podés tener PHP o Ruby on Rails.

Espero les sirva.


Salió la revista de PyAr

Hoy salió a la calle (del cyberespacio) el número 1 de PET, Python Entre Todos, la revista de la comunidad Python Argentina.

Cuando empezaron a organizarla no tenía tiempo para prestarle mucha atención, pero un par de semanas antes de que esté lista pude colaborar un poco más y mandé un artículo sobre Taint Mode, un desafío y e hice un poquito de revisor. Espero en la próxima edición colaborar un poco más; en esta primera vuelta todo el trabajo de edición recayó sobre Roberto Alsina y Emiliano Dalla Verde Marcozzi.

Destaco hasta ahora de los que leí, un artículo sobre la historia del grupo, pero tengo muchos más en la cola de lectura!

¿Qué hacés que todavía no fuiste a leer? Está disponible en varios formatos!