Mis primeros 4 scripts en Python.

Os presento mis primeros 4 scripts en Python. Me parece un lenguaje muy interesante con unas librerías muy potentes, creo que en parte es debido a que es fácil portar librerías de C a Python y en este lenguaje se ha programado de todo. Desde luego que no son para un manual de programación en Python, os los presento solo para que veáis la potencia del lenguaje.

Lo que hace éste script está comentado en el mismo código:

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

# El script espera dos argumentos:
# 1 - Una fecha en formato '%d-%m-%Y %H:%M', por ejemplo '21-01-2011 11:40'
# Mostrará sólo los ficheros que se hayan modificado después de esta fecha.
# 2 - Un path en formato *nix, por ejemplo '/home/vic/python'. No acepta '~' para referirse a 'home'.
# Buscará recursivamente, subdirectorios incluidos, sin profundizar en enlaces simbólicos, todos los ficheros a partir de ese path.
#
# os.path.getmtime -> Da el número de segundos desde epoch (medianoche UTC desde el 1 de enero de 1970).
# time.strptime devuelve una estructura time (struct_time).
# time.mktime(struct_time) devuelve el nº de segundos desde epoch. time.localtime(segundos_desde_epoch) hace la función inversa.
# El nº de segundos desde epoch se expresa en coma flotante.
#
#Para la fecha de creación de un fichero o dir: stat -c %w nombre
#Para saber la fecha de la última molificación: stat -c %y nombre
#Cuando se crea un fichero dentro de un directorio se modifica solo la fecha de modificacion del dir y no la de creación.
#Cuando se modifica un fichero dentro de un directorio no se altera la fecha de modificación ni de creación del dir.

import os, time, sys
from sys import argv

stModDesde = time.strptime(argv[1], '%d-%m-%Y %H:%M')
fModDesde = time.mktime(stModDesde)

top = argv[2]
for root, dirs, files in os.walk(top, topdown=False):
    for name in files:
        elemPath = os.path.join(root, name)
        fModificado = os.path.getmtime(elemPath)
        if (fModificado > fModDesde):
            print 'Fichero ' + elemPath + ' Modificado:' + time.ctime(fModificado)
#     for name in dirs:
#         elemPath = os.path.join(root, name)
#         Si queremos trabajar también con los directorios descomentar este for / in

¡Daros cuenta de que tan solo son 11 líneas! Aquí una función en Object Pascal que hice hará unos 7 años que aun teniendo que hacer algo más sencillo (buscar un fichero en el directorio actual y subdirectorios) era más compleja: recursiva y con más líneas de código.

unit Llibreria;

interface
uses
SysUtils, Classes, Forms;

procedure busca(path: string; carpet: string; coincidencias: TStrings);

implementation

procedure busca(path: string; carpet: string; coincidencias: TStrings);
    var
    sr: TSearchRec;
    res: byte;
    lg: integer;
    begin
        lg:=strLen(PChar(path)); //lg:=length(path);
        path:=path+carpet;
        res:=FindFirst(path, faAnyFile, sr);
        if (res=0) then coincidencias.Add(copy(path,1,lg) + sr.Name);
        FindClose(sr);
        path:=copy(path,1,lg) + '*.*';
        res:=FindFirst(path, faAnyFile, sr);
        while (res=0) do begin
            if (((sr.Attr and faDirectory)<>0) and (sr.Name[1]<>'.')) then begin
                path:=copy(path,1,lg);
                path:=path + sr.Name + '\';
                application.ProcessMessages;
                busca(path,carpet,coincidencias);
            end;
            res:=FindNext(sr);
        end;
    FindClose(sr);
    path:=copy(path,1,lg);
    end;
end.

Aquí un script para criptografía simétrica, usa el algoritmo Blowfish. El modo de cifrado es CTR, pero no le hagáis mucho caso a la función del contador. Una vez más creo que los comentarios ya explican bastante. (Python es el primer lenguaje en que me veo obligado a usar más líneas de comentarios que de código)

#! /usr/bin/env python
# -*- coding: latin-1 -*-

# El script espera 3 argumentos:
# 1º: Encriptar / Desencriptar: e / d
# 2º y 3º: paths a ficheros.
#     Con 'e' el 2º parámetro es el fichero a encriptar y el 3º el del encriptado (el resultado).
#     Con 'd' el 2º parámetro es el fichero encriptado y el 3º el desencriptado (el resultado).
#     ATENCIÓN: ¡en ambos casos el fichero resultante se sobreescribirá si ya existe!
#
# A continuación pedirá la clave.

from Crypto.Cipher import Blowfish
from os import path
from sys import argv, exit
import getpass

if not path.isfile(argv[2]):
    exit(argv[2] + " no existe o no es un fichero.")

# Añadir 'b' (binary) lo hace portable a Windows que sí distingue entre ficheros de texto y binarios,
# permitiendo encriptar tanto ficheros de texto como binarios.
f1 = open(argv[2], 'rb')

passw = getpass.getpass("Contraseña:")

class contador(object):
    def __init__(self, inicio):
        self.cont = inicio
    def next(self):
        self.cont = self.cont + 1
        sCont = str(self.cont)
        while len(sCont) < 8:
            sCont = sCont + '0'
        return sCont

c = contador(0)

oBlow = Blowfish.new(passw, Blowfish.MODE_CTR, counter=c.next)
# El primer param. es la clave. El segundo es el modo.
# Parece que el modo ECB es el más inseguro y CTR el más seguro. Ver http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
# El tercer parámetro no es opcional en el caso de CTR: requiere el contador. Debe ser una cadena de 8 carácteres.
# Se instancia el objeto antes del if pues el contador debe ser el mismo para la encriptación y la desencriptación.
# Usar algo como "sAleatoria = os.urandom(8)" tiene el inconveniente de tener que almacenar en el fichero ese contador para luego poder desencriptar.

if argv[1] == 'e':
    texto = f1.read() # Texto a encriptar.

    # La cadena a encriptar debe tener una longitud que sea múltiple de 8. Independientemente de la extensión de la clave.
    while (len(texto) % 8 != 0):
        texto = texto + chr(3) # El ASCII 3 es End of TeXt (ETX).

        textoEncriptado = oBlow.encrypt(texto)
        #print "Texto encriptado: " + textoEncriptado
        #textoDesencriptado = oBlow.decrypt(textoEncriptado)
        #print "Texto desencriptado: " + textoDesencriptado
        f2 = open(argv[3], 'wb') # Sobreescribe el fichero si ya existe.
        f2.write(textoEncriptado)
        f1.close()
        f2.close()
elif argv[1] == 'd':
    textoEncriptado = f1.read()
    texto = oBlow.decrypt(textoEncriptado)
    f2 = open(argv[3], 'wb')
    f2.write(texto)
    f1.close()
    f2.close()
else:
    print "Opción desconocida. 'e' para encriptar y 'd' para desencriptar"

¿Y Python para la web? He aquí un pequeño script que lee todas las referencias de productos de un fichero csv para después buscar en la web de la empresa las imágenes que les corresponden y guardarlas en el disco duro.

Creo que con las librerías «mechanize» y «BeautifulSoup» podemos construir fantásticas aplicaciones de testeo web, de una manera más rápida, eficiente y consumiendo muchos menos recursos que con herramientas como Selenium. Aquí el código:

#! /usr/bin/env python
# coding=utf-8

import mechanize
import cookielib
from BeautifulSoup import BeautifulSoup
import urllib

br = mechanize.Browser()

cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
#Inicio configuración browser
br.set_handle_equiv(True)
br.set_handle_gzip(False)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)

#Para debug:
#br.set_debug_http(True)
#br.set_debug_redirects(True)
#br.set_debug_responses(True)

# User-Agent (no es Linux! pero lo engañamos O:-)
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

br.set_proxies({'http' : '192.168.4.4:3128'})

# Fin configuración browser
# Abre fichero de referencias:
f = open('/cygdrive/c/users/vic/documents/Empresa/Empresa.csv', 'r')
fContent = f.readlines()
for ref in fContent:
    ref = ref.strip()
    print "'" + ref + "'"
    #Busca por cada referencia su imagen:
    response1 = br.open("http://www.empresa.com/en/search/node/" + urllib.quote(ref))
    #print br.title() Título de la página
    #print response1.info() # Cabeceras
    #print response1.read() # HTML
    # Si no obtenemos HTML da un error:
    if not br.viewing_html():
        continue
    sHtml = response1.read()
    if sHtml.find('http://www.empresa.com/en/products/') == -1:
        continue
    #Sigue el link del resultado de la búsqueda:
    response1 = br.follow_link(url_regex = "http://www.empresa.com/en/products/")
    if not response1:
        continue;
    sHtml = response1.read()
    #Pone el HTML en la sopa para procesarlo:
    soup = BeautifulSoup(sHtml)
    #Busca la imagen y obtiene su valor src
    bsTagImg = soup.find(id='productimage')
    src = bsTagImg['src']
    #Descarga la imagen en /tmp
    fTemp = br.retrieve(src)[0]
    #La copiamos al directorio que queramos:
    fh = open(fTemp, 'rb')
    fContentImg = fh.read()
    f2 = open('/home/vic/python/ref_' + ref + '.jpg', 'wb')
    f2.write(fContentImg)
    fh.close()
    f2.close()
f.close()

Realmente un lenguaje potente, ¿verdad?

Sitio web para un amigo

Acabo de realizar un sitio web para que un amigo dé a conocer los Yorkshires que cría y pone a la venta. Aquí pueden ver el sitio. Dispone de un sencillo back office a modo de gestor de contenidos para añadir, modificar y borrar los contenidos de la web. Debido a su sencillez y también para experimentar, quise desarrollarlo íntegramente en PHP y MySQL partiendo de cero.