Diferencia entre revisiones de «Curso Python DGA 2011/acceso a datos/slides»

De WikiEducator
Saltar a: navegación, buscar
 
(15 revisiones intermedias por el mismo usuario no mostrado)
Línea 253: Línea 253:
 
cursor = db.cursor()
 
cursor = db.cursor()
  
# create the query  
+
# create and exe the query  
 
query = "SELECT * FROM foo"
 
query = "SELECT * FROM foo"
 
# execute the query
 
 
cursor.execute(query)
 
cursor.execute(query)
  
Línea 266: Línea 264:
 
</source></div>
 
</source></div>
 
<div class="slide">
 
<div class="slide">
 +
 
=== Ampliación MySQL ===
 
=== Ampliación MySQL ===
 
[[../MySQL python| Acceso a MySQL con Python]]
 
[[../MySQL python| Acceso a MySQL con Python]]
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
=== Persistencia de objetos ===</div>
+
=== Persistencia de objetos ===
 +
</div>
 
<div class="slide">
 
<div class="slide">
 +
 
=== pickle===
 
=== pickle===
 
Pickle: convierte un objeto python en secuencia de bytes
 
Pickle: convierte un objeto python en secuencia de bytes
Línea 359: Línea 360:
 
=== Instalar ===
 
=== Instalar ===
 
  $ sudo easy_install elixir  # pip install elixir
 
  $ sudo easy_install elixir  # pip install elixir
 +
 +
Podemos descargar el fuente de: http://elixir.ematia.de/trac/wiki/Download
 +
$ svn checkout http://elixir.ematia.de/svn/elixir/trunk/ elixir
 +
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
 +
 
=== Un modelo sencillo ===
 
=== Un modelo sencillo ===
 
La clase '''Entity''' define clases, tablas y mapper en un sólo paso.
 
La clase '''Entity''' define clases, tablas y mapper en un sólo paso.
Línea 383: Línea 389:
 
<div class="slide">
 
<div class="slide">
 
=== Creación de la bases de datos ===
 
=== Creación de la bases de datos ===
 
 
<source lang="python">
 
<source lang="python">
 
>>> from modelo import *
 
>>> from modelo import *
>>> setup_all()  # crea la el objeto Tabla SQLAlchemy y el objeto Mapper para la clase Película.
+
>>> setup_all()  # crea el objeto Tabla SQLAlchemy y el objeto Mapper para la clase Película.
 
>>> create_all()  # crea las tablas SQL correspondientes a la Tabla SQLAlchemy
 
>>> create_all()  # crea las tablas SQL correspondientes a la Tabla SQLAlchemy
 
</source>
 
</source>
 
  
 
<source lang="sql">
 
<source lang="sql">
Línea 400: Línea 404:
 
)
 
)
 
</source>
 
</source>
 
+
</div>
Por defecto la tabla se llama '''<nombre_del_modulo>_<nombre_de_la_clase>'''.
+
<div class="slide">
Si ningún campo tiene el parámetro '''primary_key=True''', crea un atributo '''id'''.
+
=== Creación de la bases de datos II===
 
+
* Por defecto la tabla se llama '''<nombre_del_modulo>_<nombre_de_la_clase>'''.
 +
* Si ningún campo tiene el parámetro '''primary_key=True''', crea un atributo '''id'''.
 
<source lang="python">
 
<source lang="python">
 
>>> Pelicula(titulo=u"Blade Runner", year=1982)
 
>>> Pelicula(titulo=u"Blade Runner", year=1982)
Línea 409: Línea 414:
 
>>> session.commit()
 
>>> session.commit()
 
</source>
 
</source>
 
+
</div>
 +
<div class="slide">
 +
=== Búsquedas ===
 
<source lang="python">
 
<source lang="python">
 
>>> Pelicula.query.all()
 
>>> Pelicula.query.all()
Línea 451: Línea 458:
 
         return '<Director "%s">' % self.nombre
 
         return '<Director "%s">' % self.nombre
 
</source>
 
</source>
 
+
</div>
 +
<div class="slide">
 +
=== Relaciones II ===
 
<source lang="python">
 
<source lang="python">
 
>>> from model import *
 
>>> from model import *
Línea 473: Línea 482:
 
)
 
)
 
</source>
 
</source>
 
+
</div>
 +
<div class="slide">
 +
=== Relaciones III===
 
<source lang="python">
 
<source lang="python">
 
# directores
 
# directores
Línea 491: Línea 502:
 
>>> session.commit()
 
>>> session.commit()
 
</source>
 
</source>
 
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
Línea 504: Línea 514:
 
>>> Pelicula.query.filter(Pelicula.director.has(Director.nombre.endswith(u'Scott'))).all()
 
>>> Pelicula.query.filter(Pelicula.director.has(Director.nombre.endswith(u'Scott'))).all()
 
[<Pelicula "Alien" (1979)>, <Pelicula "Blade Runner" (1982)>]
 
[<Pelicula "Alien" (1979)>, <Pelicula "Blade Runner" (1982)>]
 
+
</source>
# Búsquedas generativas (usando partes de otras búsquedas)
+
</div>
 
+
<div class="slide">
 +
=== Búsquedas generativas ===
 +
(usando partes de otras búsquedas)
 +
<source lang="python">
 
>>> d = Director.get_by(nombre=u'Ridley Scott') # Class.get_by(xxx) es un atajo para Class.query.filter_by(xxx).first()
 
>>> d = Director.get_by(nombre=u'Ridley Scott') # Class.get_by(xxx) es un atajo para Class.query.filter_by(xxx).first()
 
>>> q = Pelicula.query.filter_by(director=d)
 
>>> q = Pelicula.query.filter_by(director=d)
Línea 518: Línea 531:
 
<div class="slide">
 
<div class="slide">
 
===Relaciones muchos a muchos===
 
===Relaciones muchos a muchos===
 
 
<source lang="python">
 
<source lang="python">
# modifica ...
 
 
 
class Genero(Entity):
 
class Genero(Entity):
 
     nombre = Field(Unicode(15), primary_key=True)
 
     nombre = Field(Unicode(15), primary_key=True)
 
     peliculas = ManyToMany('Pelicula')
 
     peliculas = ManyToMany('Pelicula')
   
 
 
     def __repr__(self):
 
     def __repr__(self):
 
         return '<Genero "%s">' % self.nombre
 
         return '<Genero "%s">' % self.nombre
Línea 539: Línea 548:
 
         return '<Pelicula "%s" (%d)>' % (self.titulo, self.year)
 
         return '<Pelicula "%s" (%d)>' % (self.titulo, self.year)
 
</source>
 
</source>
 
+
</div>
 +
<div class="slide">
 +
===Relaciones muchos a muchos (cont.)===
 
<source lang="python">
 
<source lang="python">
 
>>> from model import *
 
>>> from model import *
Línea 553: Línea 564:
 
[<Pelicula "Alien" (1979)>]
 
[<Pelicula "Alien" (1979)>]
 
</source>
 
</source>
</div>
 
<div class="slide">
 
=== Opciones ===</div>
 
<div class="slide">
 
===Nombre de la tabla===
 
<source lang="python">
 
class Pelicula(Entity):
 
    using_options(tablenombre='peliculas')
 
 
    titulo = Field(Unicode(30))
 
    year = Field(Integer)
 
    descripcion = Field(UnicodeText)
 
</source>
 
 
Más opciones: ver API.
 
</div>
 
<div class="slide">
 
===Sesiones===
 
La sesión implementa el patrón '''unidad de trabajo'''.
 
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
 
===Herencia===
 
===Herencia===
 
 
Si pensamos en introducir actores y directores:
 
Si pensamos en introducir actores y directores:
 
 
<source lang="python">
 
<source lang="python">
 
class Persona(Entity):
 
class Persona(Entity):
Línea 584: Línea 574:
 
     def __repr__(self):
 
     def __repr__(self):
 
         return '<Persona "%s">' % self.nombre
 
         return '<Persona "%s">' % self.nombre
 
+
</source>
 +
</div>
 +
<div class="slide">
 +
===Herencia (cont.)===
 +
<source lang="python">
 
class Actor(Persona):
 
class Actor(Persona):
 
     using_options(inheritance='multi')
 
     using_options(inheritance='multi')
 
     peliculas = ManyToMany('Pelicula')
 
     peliculas = ManyToMany('Pelicula')
 
 
     def __repr__(self):
 
     def __repr__(self):
 
         return '<Actor "%s">' % self.nombre
 
         return '<Actor "%s">' % self.nombre
Línea 595: Línea 588:
 
     using_options(inheritance='multi')
 
     using_options(inheritance='multi')
 
     peliculas = OneToMany('Pelicula')
 
     peliculas = OneToMany('Pelicula')
 
 
     def __repr__(self):
 
     def __repr__(self):
 
         return '<Director "%s">' % self.nombre
 
         return '<Director "%s">' % self.nombre
 
</source>
 
</source>
 
+
</div>
 +
<div class="slide">
 +
===Herencia (cont.)===
 
<source lang="python">
 
<source lang="python">
 
>>> rscott = Director(nombre=u"Ridley Scott")
 
>>> rscott = Director(nombre=u"Ridley Scott")
Línea 607: Línea 601:
 
>>> sweaver = Actor(nombre=u"Sigourney Weaver")
 
>>> sweaver = Actor(nombre=u"Sigourney Weaver")
 
>>> session.commit()
 
>>> session.commit()
 
+
</source>
 +
</div>
 +
<div class="slide">
 +
===Herencia===
 +
<source lang="python">
 
>>> Persona.query.all()
 
>>> Persona.query.all()
 
[<Director "Ridley Scott">,  <Director "George Lucas">,
 
[<Director "Ridley Scott">,  <Director "George Lucas">,
Línea 639: Línea 637:
 
  know, I’ll use regular expressions.” Now they have two
 
  know, I’ll use regular expressions.” Now they have two
 
  problems.
 
  problems.
   – Jamie Zawinski
+
   – Jamie Zawinski  
 
<source lang="python">
 
<source lang="python">
 
from urllib import urlopen
 
from urllib import urlopen
Línea 645: Línea 643:
 
doc = urlopen(URL).read()
 
doc = urlopen(URL).read()
 
</source>
 
</source>
 +
</div>
 +
<div class="slide">
 +
=== Alternativas (cont.)===
 
* Usar parsers de html/xml. Estos parsers tiene que poder leer '''tagsoup''' porque se encontrarán con código no válido:
 
* Usar parsers de html/xml. Estos parsers tiene que poder leer '''tagsoup''' porque se encontrarán con código no válido:
 
** BeautifulSoup, lxml, '''amara'''.
 
** BeautifulSoup, lxml, '''amara'''.
 +
 +
Nosotros usaremos Amara: http://wiki.xml3k.org/Amara ([http://wiki.xml3k.org/Amara/Tutorial Tutorial])
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
 
=== Amara ===
 
=== Amara ===
* http://wiki.xml3k.org/Amara ([http://wiki.xml3k.org/Amara/Tutorial Tutorial])
+
* Interfaz rápida con API más cercana al xml  
* Tiene dos interfaces:
+
<ul><li>
+
Una muy rápida y con una sintaxis más cercana al xml  
+
 
<source lang="python">
 
<source lang="python">
 
import amara
 
import amara
Línea 659: Línea 659:
 
doc = amara.parse(URL)
 
doc = amara.parse(URL)
 
</source>
 
</source>
</li><li>
+
* API más pythonica:
Otra más amigable y fácil de utilizar, que convierte el xml en objetos python.
+
 
<source lang="python">
 
<source lang="python">
 
from amara import bindery
 
from amara import bindery
Línea 666: Línea 665:
 
doc = bindery.parse(URL)
 
doc = bindery.parse(URL)
 
</source>
 
</source>
 +
</div>
 +
<div class="slide">
 +
=== Tagsoup===
 +
* Para usar html no válido:
 
<source lang="python">
 
<source lang="python">
# si el html o xml puede no ser válido:
 
 
from amara.bindery import html
 
from amara.bindery import html
 
URL = '....'   
 
URL = '....'   
 
doc = html.parse(URL)
 
doc = html.parse(URL)
 
</source>
 
</source>
</li></ul></div>
+
</div>
 
<div class="slide">
 
<div class="slide">
 +
 
=== Instalar ===
 
=== Instalar ===
 
Para instalar la última versión:
 
Para instalar la última versión:
 
  $ sudo pip install http://files.akara.info/00-amara-latest.tar.bz2
 
  $ sudo pip install http://files.akara.info/00-amara-latest.tar.bz2
Es necesario tener instalado un compilador de C y la cabeceras de python (en debian/ubuntu hay que instalar '''python-dev'''
+
También puedes descargar el código del repositorio:
 +
$ git clone https://github.com/zepheira/amara.git
 +
 
 +
Es necesario tener instalado un compilador de C y la cabeceras de python (en debian/ubuntu hay que instalar '''python-dev''') Para windows hay versiones precompiladas.
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
=== Ejemplos habituales de uso ===</div>
+
=== Ejemplos habituales de uso ===
<div class="slide">
+
 
===Búsqueda por expresiones XPATH===
 
===Búsqueda por expresiones XPATH===
 
{{Tip | Hay herramientas como [http://getfirebug.com/ firebug] que permite copiar el XPATH de un elemento.}}</div>
 
{{Tip | Hay herramientas como [http://getfirebug.com/ firebug] que permite copiar el XPATH de un elemento.}}</div>
 
<div class="slide">
 
<div class="slide">
====Búsqueda de las imágenes de un artículo====
+
===Ej.: Búsqueda de las imágenes de un artículo===
 
<source lang="python">
 
<source lang="python">
 
>>> from amara.bindery import html
 
>>> from amara.bindery import html
Línea 694: Línea 699:
 
65
 
65
 
>>> primera_imagen = imagenes[0]
 
>>> primera_imagen = imagenes[0]
 +
</source>
 +
</div>
 +
<div class="slide">
 +
===Ej.: Búsqueda de las imágenes (cont.) ===
 +
<source lang="python">
 
>>> print primera_imagen.xml_encode()
 
>>> print primera_imagen.xml_encode()
 
<img src="/MODULOS/global/publico/interfaces/img/logo-Heraldo.png" alt="Últimas noticias de Aragón, Zaragoza, Huesca y Teruel del periódico digital Heraldo.es"/>
 
<img src="/MODULOS/global/publico/interfaces/img/logo-Heraldo.png" alt="Últimas noticias de Aragón, Zaragoza, Huesca y Teruel del periódico digital Heraldo.es"/>
Línea 705: Línea 715:
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
====Búsqueda de las entradas de una revista====
+
===Búsqueda de las entradas de una revista===
 
Barrapunto publica sus entradas como
 
Barrapunto publica sus entradas como
 
<source lang="html4strict">
 
<source lang="html4strict">
Línea 723: Línea 733:
 
</div>
 
</div>
 
</source>
 
</source>
 +
</div>
 +
<div class="slide">
 +
===Búsqueda de las entradas de una revista (cont.)===
 
Para extraer los nombres de los artículos de la primera página:
 
Para extraer los nombres de los artículos de la primera página:
 
 
<source lang="python">
 
<source lang="python">
 
>>> from amara.bindery import html
 
>>> from amara.bindery import html
Línea 741: Línea 753:
 
...
 
...
 
</source>
 
</source>
 
+
</div>
'''Más ejemplos''' en http://wiki.xml3k.org/Amara/Recipes</div>
+
<div class="slide">
 +
===Búsqueda de las entradas de una revista (cont.) ===
 +
'''Más ejemplos''' en http://wiki.xml3k.org/Amara/Recipes
 +
</div>
 
<div class="slide">
 
<div class="slide">
====Expresiones XPATH útiles====
+
===Expresiones XPATH útiles===
 
<source lang="python">
 
<source lang="python">
 
# Nodo que contenga una cadena de texto:
 
# Nodo que contenga una cadena de texto:
Línea 750: Línea 765:
 
expresion = u'.//text()[contains(., "%s")]' % cadena.decode('utf-8')
 
expresion = u'.//text()[contains(., "%s")]' % cadena.decode('utf-8')
 
# Nodos o atributos que contengan una cadena:
 
# Nodos o atributos que contengan una cadena:
expresion = expr = u'.//@*[contains(., "%s")]'
+
expresion = u'.//@*[contains(., "%s")]'
 
</source>
 
</source>
 
 
</div>
 
</div>
 
<div class="slide">
 
<div class="slide">
 +
 
===Inyección de marcado ===
 
===Inyección de marcado ===
 
Se puede transformar un documento para añadirle o quitarle información.
 
Se puede transformar un documento para añadirle o quitarle información.

Última revisión de 11:05 7 sep 2011

Contenido

Acceso a datos

y gestión de información

Luis Miguel Morillas <lmorillas at xml3k.org>

identi.ca: lmorillas

Introducción

  • Una de las tareas más frecuentes que tenemos es el tratamiento de información.
  • Muchas veces esa información está bien estructurada y almacenada en herramientas estándar (bases de datos relacionales),
  • pero otras veces los datos están en hojas de cálculo, páginas web y formatos menos estructurados.

Hojas de cálculo

Mucha información está almacenada en hojas de cálculo.

Ficheros CSV

La información de una hoja de cálculo se puede exportar/importar desde un fichero csv. Python tiene soporte para tratar ficheros csv. Un fichero csv es un fichero de texto.

Lectura

import csv
with open('zaragoza_2010_10.csv') as fin:
    reader = csv.reader(fin, delimiter=";")
    for fila in reader:
        print fila
['Países', 'HOMBRES', 'MUJERES', 'TOTAL']
['Rumania', '16704', '14487', '31191']
['Ecuador', '5612', '5713', '11325']
['Marruecos', '5042', '3007', '8049']

Escritura

with open('codigo_ascii.txt', 'w') as fout:
    writer = csv.writer(fout)
    ascii_a = ord('a')
    for n in range(26):
        writer.writerow((chr(ascii_a+n), ascii_a + n))

Quoting

writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)

Dialectos

>>> csv.list_dialects()
['excel-tab', 'excel']

Se pueden crear dialectos:

csv.register_dialect(nombre, delimiter="|")

Usando nombres de campos

with open('zaragoza_2010_10.csv') as fin:
    # cuidado, toma la primera fila como títulos
    # usad parámetro fieldnames si no
    reader = csv.DictReader(fin, delimiter=";")

Hojas de cálculo Excel

Algunas librerías interesantes:

Hojas de cálculo Excel (II)

Sin embargo es necesario usar COM para :

  • manipular gráficos
  • celdas con texto enriquecido
  • leer fórmulas
  • trabajar con macros y nombres

xlrd

# ejemplo lectura
import xlrd
datos = {} # Dicc. vacío
book = xlrd.open_workbook('sampledata.xls')
hoja = book.sheet_by_index(0)
for i in range(1,sh.nrows): #salta la primera línea
    datos[hoja.cell_value(rowx=i, colx=1)] = hoja.cell_value(rowx=i, colx=2)

xlwt

# ejemplo escritura
import xlwt
lista1 = [1,2,3,4,5]
lista2 = [234,267,281,301,331]
wb = xlwt.Workbook()
ws = wb.add_sheet('Primera hoja')
ws.write(0,0,'Column A')
ws.write(0,1,'Column B')
i = 1
for x,y in zip(lista1,lista2): # Recorre las dos listas a la vez
    ws.write(i,0,x) # Fila, Columna, Datos.
    ws.write(i,1,y)
    i += 1
wb.save('hoja.xls')

Bases de Datos Relacionales

  • Una base de datos relacional es una colección de tablas, cada una tien un número fijo de columnas y un número variable de filas. Las columnas tienen un nombre y contienen datos del mismo tipo.
  • Muchos sistemas de bases de datos: comerciales (Oracle, DB2, SQL Server, ...) y libres (MySQL, PostgreSQL, SQLite ...)
  • SQLite está incluida en Python

Bases de Datos Relacionales en Py

Usar bases de datos relacionales con Python es muy fácil. Python proporciona un estándar para acceder a bases de datos. La DB API 2.0 es la versión vigente (PEP 249) Módulos compatibles:

  • MySQLdb (MySQL)
  • psycopg2 (PostgreSQL)
  • cx_Oracle (Oracle)
  • mxODBC (SQL Server, DB2, Sybase, Oracle, etc.)

Estructura

La DB API usa dos conceptos para realizar los procesos:

  • Objeto Conexión
    • conexión con la base de datos
    • Transacciones
  • Objeto Cursor
    • Ejecuta las sentencias
    • Accede a los resultados

Conexión

  • El objeto conexión se encarga de conectar con la base de datos
  • Proporciona acceso (red/RPC) a la base de datos.
  • Este objeto no permite lanzar sentencias.
  • Gestiona las transacciones (grupos lógicos de sentencias)

Cursor

  • Creado a partir de una conexión
  • Sentencias de manipulación y de consulta en la bbdd.
  • Método execute(), que acepta una secuencia de parámetros.
  • Almacena los datos del result set depués de lanzar la consulta.
  • Método fetch*() que lee los datos del result set

Transacciones

  • DB API 2.0 soporta transacciones (si el motor las soporta) desde el objeto conexión.
  • conexión: commit() / rollback()

Introspección del esquema

  • Busca el tipo de las columnas de una tabla:
  • Método sencillo:
    cursor.execute(select * from testtable where 1=0)
    # mira el atributo cursor.description
  • Método avanzado:

    cursor.columns(table='testtable')
    rows = cursor.fetchall()

Muy importante: Paso de parámetros

  • No hay que hacer nunca sustitución de cadenas de caracteres para evitar inyección de código.

Paso de parámetros

  • paramstyle: define cómo se pasan los parámetros.
  • Todos los módulos admiten al menos uno de:
    • 'qmark': Signo de interrogación, ej. '...WHERE name=?'
    • 'numeric': Numerico, posicional, ej. '...WHERE name=:1'
    • 'named': por Nombre, ej. '...WHERE name=:name'
    • 'format': Formato ANSI C, ej. '...WHERE name=%s'
    • 'pyformat': Formato Python, ej. '...WHERE name=%(name)s'

Ejemplo con sqlite (I)

# Fuente: http://mundogeek.net/archivos/2008/06/25/bases-de-datos-en-python
import sqlite3 as dbapi
 
# 1. Creamos objeto conexión
bbdd = dbapi.connect("bbdd.dat")
 
# 2. Creamos un cursor
cursor = bbdd.cursor()
 
# 3. Usamos cursor para acceder a la  base de datos
# 3.1. create
cursor.execute("""create table empleados (dni text,
                  nombre text,
                  departamento text)""")
 
# 3.2. insert
cursor.execute("""insert into empleados
                  values ('12345678-A', 'Manuel Gil', 'Contabilidad')""")
 
bbdd.commit()

Ejemplo con sqlite (cont.)

# 3.3 select
cursor.execute("""select * from empleados
                  where departamento='Contabilidad'""")
 
# extraer resultados de select --> están almacenados en cursor
for tupla in cursor.fetchall():
    print tupla

Ejemplo mysql

import MySQLdb
 
dbusername = "user" 
dbname = 'user_private' 
dbpassword = 'some_password'
 
# connect to the database 
db = MySQLdb.Connect(db = dbname, user = dbusername, passwd = dbpassword)
 
#To perform a query, you first need a cursor, and then you can execute queries on it. 
cursor = db.cursor()
 
# create and exe the query 
query = "SELECT * FROM foo"
cursor.execute(query)
 
# retrieve the result 
results = cursor.fetchall()
 
for firstname, age, city in results: 
    print firstname, age, city

Ampliación MySQL

Acceso a MySQL con Python

Persistencia de objetos

pickle

Pickle: convierte un objeto python en secuencia de bytes Funciones de pickle:

  • dumps(objeto, proto): serializa a una string
  • dump(objeto, archivo, proto): guarda en archivo
  • loads(cadena, proto): des-serializa una string
  • load(archivo, proto): carga desde archivo
>>> import pickle
>>> s = pickle.dumps({1:'a',2:'b'},0) >>> s
"(dp0\nI1\nS'a'\np1\nsI2\nS'b'\np2\ns."
>>> print pickle.loads(s) {1: 'a', 2: 'b'}

Shelve

Shelve: objeto similar a un diccionario persistente

  • open(filename, flag='c', protocol=None, writeback=False) #crea un diccionario persistente
  • flag= 'r': solo lectura, 'w': lectura/escritura, 'c':creación y lectura/escritura, 'n': nuevo
  • shelve.sync(): sincronizar (writeback=True)
  • shelve.close(): grabar y cerrar diccionario
>>>import shelve
>>> d = shelve.open('alumnos.dat') # abrir archivo 
>>> d['12cd'] = {'nombre': 'Luis', 'apellido': 'Pérez'}
>>> data = d['12cd'] # leer una COPIA de los datos 
>>> del d['12cd'] # borra los datos almacenados

ORM



Icon inter.gif

ORM

El mapeo objeto-relacional (Object-Relational mapping, o sus siglas) es una técnica de programación para convertir datos entre el sistema de tipos utilizado en un lenguaje de programación orientado a objetos y el utilizado en una base de datos relacional, utilizando un motor de persistencia (http://es.wikipedia.org/wiki/Mapeo_objeto-relacional)



En un modelo tradicional de bases de datos (ver charla de pycamp.orm:

MySQL <------------> dialecto_sql_1 <---> MySQLdb <----> Python
PostgreSQL <-------> dialecto_sql_2 <---> psycopg2 <---> python
Oracle <-----------> dialecto_sql_3 <---> cx_Oracle <--> Python

Esto dificulta las migraciones y la escalabilidad de las aplicaciones:

  • Cada base de datos usa un dialecto.
  • Al usar el driver de Python tenemos que usar ese dialecto. Una migración supone cambiar muchas sentencias al nuevo dialecto.

Cuando trabajamos con un ORM sólo usamos objetos Python. El ORM se encarga de traducir las sentencias al dialecto de la base de datos:

BASE_DE_DATOS <-----> ORM (SQLAlchemy, Elixir ...) <------> PYTHON
El mismo código Python funcionará en PostgreSQL, Oracle o MySQL.

Un ejemplo

Incrementar la edad de las personas que tienen 20 años: Sin ORM

cursor.execute(“SELECT * FROM personas WHERE edad=20")
for row in cursor.fetchall():
    id = row[0]
    edad = row[1]
    cursor.execute(“UPDATE personas WHERE id=%s SET edad=%d” % (id, edad+1))

Con ORM

for p in Personas.listado(edad=20): # ejecuta SELECT
    p.edad = p.edad + 1
    p.update() # ejecuta UPDATE

Elixir

(Tutorial basado en http://elixir.ematia.de/trac/wiki/TutorialDivingIn)

  • Capa declarativa sobre SQLAlchemy
  • SQLAlchemy usa el patrón "Data Mapper" (no hay relación uno a uno de tablas y clases: las clases se pueden mapear a selects arbitrarias)
  • Elixir usa el patrón "Active Record": relación uno a uno de clases y tablas.
  • Soporta claves primarias compuestas.

Instalar

$ sudo easy_install elixir  # pip install elixir

Podemos descargar el fuente de: http://elixir.ematia.de/trac/wiki/Download

$ svn checkout http://elixir.ematia.de/svn/elixir/trunk/ elixir

Un modelo sencillo

La clase Entity define clases, tablas y mapper en un sólo paso.

modelo.py

# -*- coding: utf-8 -*-
from elixir import *
 
metadata.bind = "sqlite:///peliculas.sqlite"
metadata.bind.echo = True
 
class Pelicula(Entity):
    titulo = Field(Unicode(30))
    year = Field(Integer)
    descripcion = Field(UnicodeText)
 
    def __repr__(self):
        return u'<Peli: "%s" (%d)>' % (self.titulo, self.year)

Creación de la bases de datos

>>> from modelo import *
>>> setup_all()   # crea el objeto Tabla SQLAlchemy y el objeto Mapper para la clase Película.
>>> create_all()  # crea las tablas SQL correspondientes a la Tabla SQLAlchemy
CREATE TABLE modelo_pelicula (
    id INTEGER NOT NULL, 
    titulo VARCHAR(30), 
    YEAR INTEGER, 
    descripcion TEXT, 
    PRIMARY KEY (id)
)

Creación de la bases de datos II

  • Por defecto la tabla se llama <nombre_del_modulo>_<nombre_de_la_clase>.
  • Si ningún campo tiene el parámetro primary_key=True, crea un atributo id.
>>> Pelicula(titulo=u"Blade Runner", year=1982)
<Peli: "Blade Runner" (1982)>
>>> session.commit()

Búsquedas

>>> Pelicula.query.all()
[<Peli: "Blade Runner" (1982)>]
>>> peli = Pelicula.query.first() # .first() equivalente a .all()[0]
>>> peli.year = 1983
>>> session.commit()
>>> Pelicula.query.all()
[<Peli: "Blade Runner" (1983)>]

Eliminar

>>> movie.delete()
>>> session.commit()
>>> Pelicula.query.all()
[]

Relaciones

class Pelicula(Entity):
    titulo = Field(Unicode(30))
    year = Field(Integer)
    descripcion = Field(UnicodeText)
    director = ManyToOne('Director')    # <-- Añade esta línea
 
    def __repr__(self):
        return '<Peli: "%s" (%d)>' % (self.titulo, self.year)
 
class Director(Entity):
    nombre = Field(Unicode(60))
    peliculas = OneToMany('Pelicula')         # <-- Añade esta línea
 
    def __repr__(self):
        return '<Director "%s">' % self.nombre

Relaciones II

>>> from model import *
>>> setup_all(True)
CREATE TABLE model_director (
    id INTEGER NOT NULL, 
    nombre VARCHAR(60), 
    PRIMARY KEY (id)
)
 
CREATE TABLE model_movie (
    id INTEGER NOT NULL, 
    titulo VARCHAR(30), 
    YEAR INTEGER, 
    descripcion TEXT, 
    director_id INTEGER, 
    PRIMARY KEY (id), 
    CONSTRAINT model_movie_director_id_fk FOREIGN KEY(director_id) REFERENCES model_director (id)
)

Relaciones III

# directores
>>> rscott = Director(nombre=u"Ridley Scott")
>>> glucas = Director(nombre=u"George Lucas")
# películas
>>> alien = Pelicula(titulo=u"Alien", year=1979)
>>> swars = Pelicula(titulo=u"Star Wars", year=1977)
>>> brunner = Pelicula(titulo=u"Blade Runner", year=1982)
# Añadimos películas a los directores
>>> rscott.peliculas.append(brunner) 
>>> rscott.peliculas.append(alien)
>>> swars.director = glucas
# comprobación
>>> glucas.peliculas
[<Pelicula "Star Wars" (1977)>]
>>> session.commit()

Búsquedas

>>> Pelicula.query.filter_by(titulo=u"Alien").one()
<Pelicula "Alien" (1979)>
>>> Pelicula.query.filter(Pelicula.year > 1980).all()
[<Pelicula "Blade Runner" (1982)>]
>>> Pelicula.query.filter(Pelicula.director.has(nombre=u'Ridley Scott')).all()
[<Pelicula "Alien" (1979)>, <Pelicula "Blade Runner" (1982)>]
>>> Pelicula.query.filter(Pelicula.director.has(Director.nombre.endswith(u'Scott'))).all()
[<Pelicula "Alien" (1979)>, <Pelicula "Blade Runner" (1982)>]

Búsquedas generativas

(usando partes de otras búsquedas)

>>> d = Director.get_by(nombre=u'Ridley Scott') # Class.get_by(xxx) es un atajo para Class.query.filter_by(xxx).first()
>>> q = Pelicula.query.filter_by(director=d)
>>> q.filter_by(year=1979).all()
[<Pelicula "Alien" (1979)>]
>>> from sqlalchemy import desc
>>> q.order_by(desc(Pelicula.year)).all()
[<Pelicula "Blade Runner" (1982)>, <Pelicula "Alien" (1979)>]

Relaciones muchos a muchos

class Genero(Entity):
    nombre = Field(Unicode(15), primary_key=True)
    peliculas = ManyToMany('Pelicula')
    def __repr__(self):
        return '<Genero "%s">' % self.nombre
 
class Pelicula(Entity):
    titulo = Field(Unicode(30), primary_key=True)   # <-- modifica esta línea
    year = Field(Integer, primary_key=True)        # <-- y ésta
    descripcion = Field(UnicodeText)
    director = ManyToOne('Director')
    generos = ManyToMany('Genero')                   # <-- añade esta línea
 
    def __repr__(self):
        return '<Pelicula "%s" (%d)>' % (self.titulo, self.year)

Relaciones muchos a muchos (cont.)

>>> from model import *
>>> setup_all(True)
>>> scifi = Genero(nombre=u"Science-Fiction")
>>> rscott = Director(nombre=u"Ridley Scott")
>>> glucas = Director(nombre=u"George Lucas")
>>> alien = Pelicula(titulo=u"Alien", year=1979, director=rscott, generos=[scifi, Genero(nombre=u"Horror")])
>>> brunner = Pelicula(titulo=u"Blade Runner", year=1982, director=rscott, generos=[scifi])
>>> swars = Pelicula(titulo=u"Star Wars", year=1977, director=glucas, generos=[scifi])
>>> session.commit()
>>> Pelicula.query.filter(Pelicula.generos.any(nombre=u"Horror")).all()
[<Pelicula "Alien" (1979)>]

Herencia

Si pensamos en introducir actores y directores:

class Persona(Entity):
    using_options(inheritance='multi')
    nombre = Field(Unicode(60))
    def __repr__(self):
        return '<Persona "%s">' % self.nombre

Herencia (cont.)

class Actor(Persona):
    using_options(inheritance='multi')
    peliculas = ManyToMany('Pelicula')
    def __repr__(self):
        return '<Actor "%s">' % self.nombre
 
class Director(Persona):
    using_options(inheritance='multi')
    peliculas = OneToMany('Pelicula')
    def __repr__(self):
        return '<Director "%s">' % self.nombre

Herencia (cont.)

>>> rscott = Director(nombre=u"Ridley Scott")
>>> glucas = Director(nombre=u"George Lucas")
>>> hford = Actor(nombre=u"Harrison Ford")
>>> mhamill = Actor(nombre=u"Mark Hamill")
>>> sweaver = Actor(nombre=u"Sigourney Weaver")
>>> session.commit()

Herencia

>>> Persona.query.all()
[<Director "Ridley Scott">,  <Director "George Lucas">,
 <Actor "Harrison Ford">,  <Actor "Mark Hamill">,
 <Actor "Sigourney Weaver">]
>>> Actor.query.all()
[<Actor "Harrison Ford">, <Actor "Mark Hamill">, <Actor "Sigourney Weaver">]

Profundización

Web scraping: la web como fuente de información

La web se está transformando en una web de datos, pero muy poca información se sirve de forma estructurada y abierta:

Dificultades

  • Información poco o mal estructurada
  • Etiquetado no válido

Alternativas

  • Leer la información de la web y parsearla con herramientas de análisis textual (expresiones regulares, etc.)
Some people, when confronted with a problem, think “I
know, I’ll use regular expressions.” Now they have two
problems.
 – Jamie Zawinski 
from urllib import urlopen
URL = 'http://mipagina.com'
doc = urlopen(URL).read()

Alternativas (cont.)

  • Usar parsers de html/xml. Estos parsers tiene que poder leer tagsoup porque se encontrarán con código no válido:
    • BeautifulSoup, lxml, amara.

Nosotros usaremos Amara: http://wiki.xml3k.org/Amara (Tutorial)

Amara

  • Interfaz rápida con API más cercana al xml
import amara
URL = '....'  # URL puede ser una url, una ruta de un fichero o una cadena de texto
doc = amara.parse(URL)
  • API más pythonica:
from amara import bindery
URL = '....'  
doc = bindery.parse(URL)

Tagsoup

  • Para usar html no válido:
from amara.bindery import html
URL = '....'  
doc = html.parse(URL)

Instalar

Para instalar la última versión:

$ sudo pip install http://files.akara.info/00-amara-latest.tar.bz2

También puedes descargar el código del repositorio:

$ git clone https://github.com/zepheira/amara.git

Es necesario tener instalado un compilador de C y la cabeceras de python (en debian/ubuntu hay que instalar python-dev) Para windows hay versiones precompiladas.

Ejemplos habituales de uso

Búsqueda por expresiones XPATH

Icon present.gif
Tip: Hay herramientas como firebug que permite copiar el XPATH de un elemento.

Ej.: Búsqueda de las imágenes de un artículo

>>> from amara.bindery import html
>>> URL = 'http://heraldo.es'
>>> doc = html.parse(URL)
>>> imagenes = doc.xml_select(u'//img') # las imágenes van en etiquetas img
>>> len(imagenes)
65
>>> primera_imagen = imagenes[0]

Ej.: Búsqueda de las imágenes (cont.)

>>> print primera_imagen.xml_encode()
<img src="/MODULOS/global/publico/interfaces/img/logo-Heraldo.png" alt="Últimas noticias de Aragón, Zaragoza, Huesca y Teruel del periódico digital Heraldo.es"/>
>>> for im in imagenes:
        print im.src
/MODULOS/global/publico/interfaces/img/logo-Heraldo.png
/uploads/imagenes/iconos/titulos/jmj.jpg
/uploads/imagenes/rec70/_cuatrovientos6_011b2ad5.jpg
...

Búsqueda de las entradas de una revista

Barrapunto publica sus entradas como

<div class="article">
<div class="generaltitle">
	<div class="title">
		<h3>
			<a href="//softlibre.barrapunto.com/">Software Libre</a>: Todo listo para la celebración de los 20 años de Linux
 
		</h3>
	</div>
</div>
<div class="details">
...		
</div>
...
</div>

Búsqueda de las entradas de una revista (cont.)

Para extraer los nombres de los artículos de la primera página:

>>> from amara.bindery import html
>>> from amara.lib import U  # Extrae los nodos de texto de un fragmento
>>> articulos = doc.xml_select(u'//div[@class="article"]')
>>> len(articulos)
15
>>> for ar in articulos:
	print U(ar.div).strip()  # Navega por el nodo artículo.
                                 # Cuidado con los espacios en blanco y saltos
 
Software Libre: Todo listo para la celebración de los 20os de Linux
Publicado SmartOS, sistema operativo basado en Illumos
Un dispositivo permite a los invidentes ver a través de su lengua
El fin de la ley de Moore
...

Búsqueda de las entradas de una revista (cont.)

Más ejemplos en http://wiki.xml3k.org/Amara/Recipes

Expresiones XPATH útiles

# Nodo que contenga una cadena de texto:
expresion = u'.//text()[contains(., "python")]'
expresion = u'.//text()[contains(., "%s")]' % cadena.decode('utf-8')
# Nodos o atributos que contengan una cadena:
expresion = u'.//@*[contains(., "%s")]'

Inyección de marcado

Se puede transformar un documento para añadirle o quitarle información.

  • En este ejemplo añadimos unos links a los nombre de los autores, detectados por expresiones regulares:

https://github.com/zepheira/amara/blob/master/demo/inject_markup.py