viernes, 27 de agosto de 2010

Gestión de conexiones múltiples de bases de datos, en Python, y 2

Hola. En este post vamos a terminar de ver cómo crear un sistema de gestión de conexiones múltiples de bases de datos, en Python. Como se comentó en el anterior artículo, este tipo de diseño o proyecto si se quiere llamar así, se puede realizar con un ORM como SQLAlchemy (del cual estoy preparando un artículo muy interesante, en español, sobre su funcionamiento, y espero, si mi hijo me deja, publicarlo cuanto antes), o de cualquier otra manera. En este caso, utilizamos clases y orientación a objetos para realizar el trabajo.

Quizás el lector crea que utilizar clases implica que se está programando con el paradigma de POO. El caso es que no es así, ya que se pueden utilizar clases pero sin la filosofía del paradigma que lo contempla. Por ejemplo, una clase con un único método no es utilizar POO de forma correcta, aunque sea una clase, es una función mágica ó una pretty function, encapsulada en una clase, pero no es POO. Esto puede ser algo extraño pero la realidad es que es así.

Bueno, vamos a trabajar....

En el artículo anterior creamos una clase que gestiona "algo", que está compuesto por una colección de objetos identificados de forma unívoca por cadenas de caracteres. Si el lector se percata de ello, no se ha escrito ni una línea de código de nada referente al acceso a bases de datos, solamente una clase que gestiona una abstracción.

Para ver como funciona esta clase vamos a crear otra que gestione la recuperación de registros (filas) de tablas de bases de datos, de las cuales tenemos configuradas sus conexiones. Esto que parece algo trivial en realidad no lo es tanto, ya que con esta técnica podríamos obtener, por ejemplo, filas de tablas de diferentes orígenes, mezclándolas en una lista de tuplas (donde cada tupla es una fila de una tabla). Imaginemos por ejemplo, que queremos obtener datos de clientes de diferentes bases de datos, para homogeneizar sus atributos, o analizar ciertas codificaciones, ver la repetición de códigos de clientes, comparativas, sacar datos de diferentes orígenes para exportarlos a Excel ó CSV, etc.

A continuación vamos a diseñar una clase que utiliza la clase conexion (vista en el post anterior) para gestionar las conexiones que hagamos a los distintos orígenes de datos.

Nuestra clase se llamará lanzar_sql, y estará compuesta por dos atributos, uno de ellos una instanciación de la clase conexion y otra una lista que contendrá las tuplas (filas) de una selección de sentencias SQL. Esta nueva clase lo que hará será obtener filas de las tablas de las posibles conexiones creadas. De esta manera podemos manipular los resultados en una estructura. Veamos esto mejor con un gráfico:

Esto es, podemos ver que el atributo conexiones es una instanciación de la clase conexion. Así tendremos que nuestra clase lanzar_sql tendrá la información tal como así:

La implementación es:


class lanzar_sql(object):
def __init__(self):
self.__registros = []
self.conexiones = conexion()

def lanzar_sql(self,cadena_sql, nombre_conexion = None):
nombre_conexiones = self.conexiones.listar_conexiones()
if nombre_conexion is None:
for i in nombre_conexiones:
(conexion, filas ) =\
self.__lanzar_sentencia_sql(cadena_sql, i)
if f_esvacio(conexion) or f_esvacio(filas): break
self.__registros.append((conexion,filas))
else:
(conexion, filas ) =\
self.__lanzar_sentencia_sql(cadena_sql, \
nombre_conexion)

if f_esvacio(conexion) or f_esvacio(filas): pass
else: self.__registros.append((conexion,filas))

def __lanzar_sentencia_sql(self, cadena_sql, nombre_conexion):
nombre_conexiones = self.conexiones.listar_conexiones()
for i in nombre_conexiones:
if str(i).strip() == str(nombre_conexion).strip():
objeto_conexion =\
self.conexiones.devolver_conexion(i)
cursor = objeto_conexion.cursor()
# Lanzamos SQL.
aux = []
filas = cursor.execute(cadena_sql)
for j in filas: aux.append(j)
cursor.close()
# Devolvemos filas.
return nombre_conexion, aux
# Nos vamos.
return None, None

def devolver_registros(self):
return self.__registros

def limpiar_registros(self):
self.__registros = []
return True



Explicación de la clase lanzar_sql

Esta clase, como se ha comentado anteriormente tiene 2 atributos para guardar la información de conexiones e información recuperada y 4 métodos que pasamos a comentar:

lanzar_sql: Método que lanza la sentencia SQL (se espera una SELECT por diseño) pasada como parámetro y recupera (y guarda) las filas devueltas por la sentencia. Si no se especifica una nombre de conexión al que haga referencia (la conexión a la base de datos que queremos que haga el SELECT) por defecto se aplica a todas las conexiones. Esto es, si tenemos en varios orígenes de datos una tabla TABLA1, y hacemos lanzar_sql("SELECT * FROM TABLA1"), enviará dicha sentencia SQL a todas las conexiones guardadas en la clase (en realidad en la instanciación del objeto, pero por ser pedagógico hay veces que fuerzo el vocabulario conceptual).

__lanzar_sentencia_sql: Método privado que utiliza el método lanzar_sql. El lector debe de darse cuenta que dicho código se repite varias veces en lanzar_sql, por lo que es mejor sacarlo fuera como un método auxiliar, de manera que nos ahorramos código y lo hacemos más legible (¡la legibilidad cuenta, y mucho!).

devolver_registros: Método que devuelve la lista de registros (filas de las tablas) que contiene el objeto. ¿Y cómo distinguir las filas de los orígenes? Pues porque cada elemento de la lista está compuesto por dos subelementos, el nombre de la conexión y una tupla (fila de la tabla). Esto que parece algo confuso lo veremos a continuación en un ejemplo mucho más claro.

limpiar_registros: Método para eliminar las filas recuperadas.

Ejemplo

Puede que no esté claro el funcionamiento de esta clase, así que para dejar claro el asunto vamos a crear un ejemplo de acceso a dos bases de datos Microsoft Access, que atacaremos mediante pyodbc, y lo gestionaremos todo mediante la clase lanzar_sql, que utiliza la clase conexion.

Creamos una base de datos BD1.mdb, con las tablas TABLA_BD1 Y TABLA_BD, tal como sigue:


Creamos una base de datos BD2.mdb, con las tablas TABLA_BD2 Y TABLA_BD, tal como sigue:


Creamos sus correspondientes conexiones ODBC:


Imaginemos que las dos clases que hemos creado hasta el momento, conexion y lanzar_sql, están en un fichero denominado conexiones.py.

NOTA: Se tiene que tener instalado pyodbc. En el área de descarga solamente hay que elegir la versión de Python que tenemos. En mi caso Python 2.5.4. (Algún día hablaré el porqué no he migrado todavía a Python 3, aunque creo que el lector sabrá porqué argumento esto...).


Cargamos las clases creadas y el módulo para atacar las bases de datos vía ODBC:

from conexiones import *
import pyodbc

Abrimos conexiones:

conexion1 = pyodbc.connect('DSN=BD1;UID=;PWD=')
conexion2 = pyodbc.connect('DSN=BD2;UID=;PWD=')

Creamos un objeto conexión:

conexion = lanzar_sql()

Agregamos conexiones de varias bases de datos.

conexion.conexiones.agregar_conexion('access1',conexion1)
conexion.conexiones.agregar_conexion('access2',conexion2)

NOTA: El lector debe de darse cuenta de que el método agregar_conexion tiene su origen en la clase conexion, que ha sido instanciada en un atributo de la clase lanzar_sql. ¡Esto sí que es programación orientada a objetos! Esto es, aunque no hemos utilizado la herencia, que es uno de los mecanismos fundamentales de la POO, sí que lo hemos hecho de la encapsulación y abstracción.

Si queremos ver las conexiones que tenemos activas basta con:

print conexion.conexiones.listar_conexiones()

Y nos devuelve:

['access1', 'access2']
Pulse cualquier tecla para continuar

Lanzamos sentencias SQL SELECT:

conexion.lanzar_sql('SELECT * FROM TABLA_BD1','access1')
conexion.lanzar_sql('SELECT * FROM TABLA_BD2','access2')

Sacamos por pantalla los registros con la etiqueta de la conexión a la que pertenecen.

print "DATOS:"
registros = conexion.devolver_registros()
for i in registros: print i

Y obtenemos:

DATOS:
('access1', [(1, u'1', u'1', u'1', u'1', u'1')])
('access2', [(1, u'2', u'2', u'2', u'2', u'2')])
Pulse cualquier tecla para continuar

Si queremos solos las filas, sin los identificadores de conexión, para una exportación a CSV, por ejemplo, únicamente habría que espscificar el elemento i[1], tal que así:

registros = conexion.devolver_registros()
for i in registros: print i[1]

Devolviendo:

[(1, u'1', u'1', u'1', u'1', u'1')]
[(1, u'2', u'2', u'2', u'2', u'2')]

Podemos borrar los resultados con:

conexion.limpiar_registros()

Para finalizar vamos a lanzar una única sentencia SELECT SQL para todas las conexiones:

conexion.lanzar_sql('SELECT * FROM TABLA_BD')

Para obtener los registros con la etiqueta de la conexión a la que pertenecen.

registros = conexion.devolver_registros()
for i in registros: print i

Obteniendo el siguiente resultado:

('access1', [(1, u'a', u'a', u'a', u'a', u'a'), (2, u'b', u'b', u'b', u'b', u'b')])
('access2', [(1, u'c', u'c', u'c', u'c', u'c'), (2, u'd', u'd', u'd', u'd', u'd')])
Pulse cualquier tecla para continuar

Siempre que finalicemos habrá que cerrar las conexiones, tal que así:

conexion1.close()
conexion2.close()

CONCLUSIONES

En estos dos posts hemos visto como crear un sistema de gestión de conexiones múltiples de bases de datos, utilizando POO, en este caso la encapsulación y abstracción. Hemos creado dos clases, conexion y lanzar_sql, en donde esta última utiliza a la primera para implementar el mecanismo de gestión de conexiones. Decir que las clases creadas contienen los atributos y métodos mínimos para su funcionamiento. Se deja al lector su ampliación, ya que su utilidad puede ser importante si se implementan nuevos métodos y funcionalidades a partir de la base expuesta.

Se ha presentado un ejemplo con bases de datos Access para la obtención de información. Aunque es muy simple su uso, se abre todo un abanico de posibilidades de utilización y manejo de datos de diferentes orígenes, utilizando únicamente algunas de las estructuras que nos ofrece Python, esto es, listas y tuplas.

Evidentemente, hay en el mercado paquetes de software, como SQLAlchemy, con el que se pueden realizar este tipo de tareas, pero con estos artículos quiero demostrar que si se saben ciertas nociones de POO (tampoco hay que ser un experto), así como qué es lo que se quiere llegar a obtener, Python es una herramienta fantástica, con unos recursos de implementación realmente impresionantes.

Espero os sirvan estos artículos. Saludos.

No hay comentarios:

Publicar un comentario