martes, 24 de agosto de 2010

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


Hola. En este post vamos a ver una manera (de tantas de las que puede haber) de gestionar varias conexiones a una misma o distintas bases de datos, en Python, mediante la creación de una clase que se ocupe de este trabajo.

La gestión de conexiones tiene que ser independiente de donde se conecte, ya que de lo contrario no tendría gracia (¡imagine el lector cuantos SGBD hay en el mercado!), y sería un trabajo de chinos (sin desmerecer a ese gran país).

La cuestión es crear una clase que gestione "algo", que serán conexiones a bases de datos en este caso, identificadas por nombres unívocos (esto es, no podrán repetirse), aunque puede haber varias conexiones hacia un mismo origen de datos (algo muy frecuente en las problemáticas de sistemas de gestión).

Lo que se pretende es crear la clase conexion, que va a abstraer el manejo de objetos conexión, de manera que podamos aislar la clase y ser lo más asépticos posibles. Aquí la cuestión es principal es la reutilización de código, es decir, si creamos una clase que sea independiente de cualquier SGBD (sistema gestor de bases de datos), podremos utilizarla en cualquiera de nuestros proyectos, sin temor a tener que versionar o reescribir código de nuevo.

Imaginemos que tenemos 3 bases de datos diferentes, en los siguientes sistemas:

- Base de datos BD1 en un SGBD MySQL.
- Base de datos BD2 en un SGBD Firebird.
- Base de datos BD4 en un SGBD pysqlite.

Queremos crear una conexión a nuestra base de datos denominada BD1 de MySQL, además de crear tres conexiones a nuestra base de datos BD2 de Firebird. Sin embargo en este caso tenemos que conectar por un problema de licencias (es un ejemplo, no es el caso) una conexión por kinterbasdb y dos conexiones por ODBC (pyodbc). Y por último una conexión a BD4 a través de pysqlite.

¿Cómo gestionar tantas conexiones? ¿Sería posible incluirlas todas en una única estructura de manera que fuese sencilla su utilización? El problema expuesto tendría esta forma:

Definiendo la clase conexion

Si analizamos la problemática que se nos plantea e intentamos solucionarla con programación orientada a objetos, evidentemente pensamos en la creación de una clase, que debe de implementar, como atributo principal, una lista de conexiones.

Esta lista de conexiones debe de poderse enumerar de alguna manera, esto es, las conexiones deben de poderse identificar de manera unívoca. Por tanto a cada conexión se le dará como identificador una cadena de caracteres, que podría tener una carga semántica (esto es, es mejor poner como nombre de una conexión, por ejemplo, "con_mysql_bd1_1", que "1", a fin de poder identificar a qué nos referimos más tarde), aunque en el ejemplo expuesto no lo haga hecho (por una comprensión pedagógica mejor).

Si pensamos un poco más, podemos llegar a la conclusión (¡hay muchas y seguro que el lector llegará a más que yo!) que para poder administrar un conjunto de conexiones tendremos que implementar, como mínimo, los métodos de agregar una conexión, eliminar una conexión, devolver un objeto conexión y por último devolver una lista con los identificadores de conexión (quizás este último método no sea necesario para el mínimo funcionamiento, pero sí que es recomendable).

Funciones auxiliares: f_esvacio

Una función muy útil que se utilizará en la implementación de esta clase es f_esvacio(cadena), que devuelve cierto si cadena es un objeto vacío (None ó que no contiene caracteres), y falso en caso contrario. La implementación es la siguiente:

def f_esvacio(cadena):
if cadena is None: return True
try:
if len(cadena) == 0: return True
except:
pass
if len(str(cadena)) == 0: return True
return False

Como se puede comprobar, el funcionamiento es casi trivial.

Implementación de la clase conexion

# Clase de gestión de conexiones.
class conexion(object):
def __init__(self):
self.__lista_conexiones = []

def agregar_conexion(self, nombre_conexion, objeto_conexion):
if f_esvacio(nombre_conexion): return False
if f_esvacio(objeto_conexion): return False
for i in self.__lista_conexiones:
if str(i[0]).strip() == str(nombre_conexion).strip():
return False
# Agregamos conexión.
self.__lista_conexiones.append((nombre_conexion,\
objeto_conexion))
# Nos vamos.
return True

def eliminar_conexion(self, nombre_conexion):
if f_esvacio(nombre_conexion): return False
for i in self.__lista_conexiones:
if str(i[0]).strip() == str(nombre_conexion).strip():
self.__lista_conexiones.remove((i[0],i[1]))
# Nos vamos.
return True
# Nos vamos.
return False

def listar_conexiones(self):
aux = []
for i in self.__lista_conexiones: aux.append(i[0])
return aux

def devolver_conexion(self, nombre_conexion):
if f_esvacio(nombre_conexion): return False
for i in self.__lista_conexiones:
if str(i[0]).strip() == str(nombre_conexion).strip():
return i[1]
# Nos vamos.
return False

Como se ha comentado anteriormente, esta clase tiene un único atributo (en el __init__) oculto, __lista_conexiones, que contendrá una lista de tuplas (nombre_conexion, objeto_conexion). El método agregar_conexion es bastante restrictivo y solo admite agregar una conexión si el identificador de conexión es válido (contiene caracteres), existe el objeto conexión, y dicho objeto conexión no tiene un identificador igual a otro existente en el atributo __lista_conexiones. El método eliminar_conexion elimina una conexión a partir del identificador de conexión pasado como parámetro. El método listar_conexiones devuelve una lista con los identificadores de conexión de la estructura. El método devolver_conexion devuelve el objeto conexión a partir del identificador de conexión pasado como parámetro. Si por cualquier razón el identificador de conexión no existiese, se devuelve falso.

En el siguiente post veremos la implementación de una clase que utiliza la clase conexion para gestionar múltiples conexiones con bases de datos, así como un ejemplo completo de uso.

Saludos.

1 comentario: