lunes, 14 de junio de 2010

Gestión de ficheros de configuración de aplicaciones, en Python

Hola. En este post vamos a ver cómo gestionar ficheros de configuración de aplicaciones.

Un fichero profile, también llamado fichero de perfil, fichero de configuración ó de inicialización es un archivo de texto plano, normalmente con extensión .INI (aunque no es indispensable), consistente en una serie de etiquetas de sección, las cuales se encierran entre corchetes ([]), y claves, las cuales son seguidas por un signo igual (=) y un valor. Cambiando los valores asignados a las claves se puede especificar una configuración personalizada (custom) para cada instalación de una aplicación.

Cuando se quiere crear un fichero de configuración, se tiene que seleccionar el nombre de la sección y clave y determinar los valores que se usarán para dicha clave.

Por ejemplo, un fichero profile podría contener información sobre un usuario de una aplicación. En el siguiente ejemplo, la información de un usuario es el nombre de la sección y los otros valores son las claves.

[INFO_USUARIO]
NOMBRE=ANGEL LUIS GARCIA GARCIA
PROFESION=PROGRAMADOR INFORMÁTICO
PAÍS=ESPAÑA
PASSWORD=123456

Dentro de una sección no puede haber nombres de claves repetidas (aunque sí se pueden repetir los valores de las claves). Por ejemplo, el siguiente fichero profile es incorrecto:

[INFO_USUARIO]
NOMBRE=ANGEL LUIS GARCIA GARCIA
PROFESION=PROGRAMADOR INFORMÁTICO
PROFESION=ANALISTA DE SISTEMAS

Por ende, no puede haber nombres de secciones repetidas. El siguiente ejemplo, es incorrecto:

[INFO_USUARIO]
NOMBRE=ANGEL LUIS GARCIA GARCIA
PROFESION=PROGRAMADOR INFORMÁTICO

[INFO_USUARIO]
PAÍS=ESPAÑA

Un fichero profile correcto sería el siguiente:

[INFO_USUARIO]
NOMBRE=ANGEL LUIS GARCIA GARCIA
PROFESION=PROGRAMADOR INFORMÁTICO
PROFESION2=ANALISTA DE SISTEMAS
PAÍS=ESPAÑA

Para poder manipular ficheros profiles (también llamados .INI), he creado una clase denominada profilePython.En esta clase se pueden utilizar los siguientes 3 métodos:

profile: Método que devuelve el valor de una clave en una sección. Su sintáxis es la siguiente:

profile(nombre_seccion, nombre_clave)
profile(nombre_seccion, nombre_clave, valor_por_defecto)


donde el parámetro nombre_seccion es el nombre de la sección; el parámetro nombre_clave es el nombre de la clave, y el parámetro valor_por_defecto, que es opcional, es el valor que devuelve el método si no se ha encontrado la clave dentro de la sección especificada.

set_profile: Método con el que se pueden crear secciones, claves y asignar valores a claves. Su sintaxis es la siguiente:

set_profile(nombre_seccion): Crea una sección con nombre nombre_seccion.
set_profile(nombre_seccion,nombre_clave): Crea una clave con nombre nombre_clave dentro de la sección con nombre nombre_seccion.
set_profile(nombre_seccion,nombre_clave,nombre_valor): Crea ó asigna si ya existe, el valor nombre_valor a la clave nombre_clave dentro de la sección nombre_seccion.

save_profile: Método para guardar la estructura de secciones y claves con sus respectivos valores en un fichero profile, cuyo nombre se define a la hora de la instanciación de la clase. Su sintaxis es la siguiente:

save_profile(): Guarda las secciones con sus claves y correspondientes valores en un fichero profile, en disco. Por defecto, si hay un fichero profile con el mismo nombre, se crea una copia de seguridad del mismo.
save_profile(False): Si se parametriza con valor False, no se crea copia de seguridad del fichero profile en disco.

¿Cómo se crea un fichero profile?

Mediante la instanciación de la clase profilePython. Así, si queremos que nuestro fichero de inicialización se llame config.ini, tendremos que es tan sencillo como escribir:

fich_ini = profilePython('config.ini')

La instanciación crea si no existe el fichero config.ini. Si el fichero existe, el objeto se guarda toda la estructura de secciones y claves con sus respectivos valores que se encuentra en dicho fichero. Si no se incluye ruta absoluta se crea en la misma ruta donde esté el fichero que contiene la aplicación Python.

Crear un fichero de configuración de una aplicación

Vamos a crear un fichero de configuración, con 2 secciones, una para los datos personales del usuario y otra para una personalización de la aplicación. El fichero de configuración se llamará config_apli.ini. Suponemos que la clase profilePython está en el fichero class_profiles.py. Lo primero de todo, importar la clase profilePython:

from class_profiles import profilePython

Instanciamos la clase:

config = profilePython('config_apli.ini')

Creamos una sección llamada DATOS_PERSONALES, con 4 claves:

config.set_profile('DATOS_PERSONALES','EMPRESA','MI EMPRESA SA') config.set_profile('DATOS_PERSONALES','CIF','A123456789') config.set_profile('DATOS_PERSONALES','TELEFONO','968000000') config.set_profile('DATOS_PERSONALES','DIRECCION','CALLE MAR MENOR, 30 MURCIA')

Creamos una sección llamada CUSTOM_APLIC, con 2 claves:

config.set_profile('CUSTOM_APLIC','ACTIVAR_CONTA','SI') config.set_profile('CUSTOM_APLIC','IVA',18)

Finalmente, guardamos esta estructura en el fichero, indicándole que no queremos que haga copia de seguridad, mediante:

config.save_profile(False)

El fichero resultante es el siguiente:


Una vez cargada nuestra aplicación necesitamos recuperar la información de configuración. Para ello tan sencillo como hacer lo siguiente:

from class_profiles import profilePython

config = profilePython('config_apli.ini')

Obtenemos los valores de los datos personales específicos del cliente que utiliza la aplicación. Por ejemplo:

empresa = config.profile('DATOS_PERSONALES','EMPRESA')

print empresa



Pudiera ser que utilizásemos para diferentes versiones de nuestra aplicación un mismo fichero de configuración, con el consiguiente problema que hay claves que se utilizan en una versión y no en otra. No hay problema, podemos utilizar el valor por defecto del método profile. Así, si queremos buscar una clave que no sabemos si está o no el fichero, podemos decirle que cargue un valor por defecto. Por ejemplo:

web = config.profile('DATOS_PERSONALES','WEB','SIN PÁGINA WEB')

print web



Para poder modificar un valor de una clave dentro de una sección, solamente habría que utilizar el método set_profile. Imaginemos que queremos cambiar el valor de la clave IVA de la sección CUSTOM_APLIC. Tan sencillo como:

from class_profiles import profilePython

config = profilePython('config_apli.ini')

config.set_profile('CUSTOM_APLIC','IVA',16)


config.save_profile(False)


Imaginemos ahora que queremos cambiar el nombre de la empresa, y queremos que se haga una copia del fichero profile original.

Tan sencillo como:

from class_profiles import profilePython

config = profilePython('config_apli.ini')


config.set_profile('DATOS_PERSONALES','EMPRESA','MURTHON SL')


config.save_profile()




Como vemos en los ficheros seleccionados en la captura, se ha creado un fichero backup, que podemos editar, y que contiene el fichero original:


Implementación

Para implementar la clase profilePython he utilizado dos módulos, a saber: os, para la búsqueda de ficheros en el sistema de archivos y time, para crear nombres de ficheros backup únicos a la hora de guardar los datos en el fichero profile.

La clase se compone de 2 atributos ocultos:

__fichero: nombre del fichero que contiene la estructura de fichero profile.
__lineas_fichero: Lista que contiene en cada elemento una línea del fichero. Así cada elemento puede ser una etiqueta de sección o una relación clave = valor.

La clase se compone de los siguientes 3 métodos ocultos ó métodos de soporte (y que son utilizados por los métodos públicos):

def __esvacio(self,cadena): Método que devuelve True si la cadena pasada como parámetro está vacía y False en caso contrario.
def __cargar_fichero(self): Método para cargar la estructura de fichero profile __fichero en el atributo __lineas_fichero.
def __posicionar(self, seccion, clave = None): Método de búsqueda para posicionarse dentro de la estructura guardada en __lineas_fichero.

La clase se compone de los siguientes 3 métodos públicos, anteriormente descritos:

def profile(self, seccion, clave, por_defecto = None): Método para obtener valor de clave en sección.
def set_profile(self, seccion, clave = None, valor = None): Método para crear secciones y claves (y darles valores).
def save_profile(self, backup = True): Método para salvar datos en fichero profile.

Código

El código completo de esta clase se presenta a continuación. Está bastante comentado, por lo que el lector no tendrá, espero, ningún tipo de complejidad en su entendimiento, ya que por otra parte, mi estilo de programación, tampoco lo induce.


# -*- coding: utf8 -*

import os
import time

class profilePython(object):
'''
Clase que implementa el mecanismo de utilización de ficheros
de configuración.
'''
def __init__(self, filename):
'''
Inicialización.
'''
# Nombre del fichero.
self.__fichero = filename
# Líneas del fichero.
self.__lineas_fichero = []
# Carga del fichero.
self.__cargar_fichero()

def __esvacio(self,cadena):
'''
Método que devuelve True si la cadena pasada como parámetro
está vacía y False en caso contrario.
'''
if cadena is None: return True
try:
if len(str(cadena)) == 0: return True
except:
pass
return False

def __cargar_fichero(self):
'''
Carga del fichero en memoria.
'''
try:
# Abrimos el fichero.
fichero = open(os.path.realpath(self.__fichero),'r')
seguir = True
except:
seguir = False
if seguir:
# Volcamos el contenido del fichero a una estructura.
while True:
f = fichero.readline()
if not f: break
if not self.__esvacio(f):
# Quitamos retornos de carro y demás.
f = f.replace("\n","")
f = f.replace("\r","")
f = f.strip()
# Lo añadimos a la lista de líneas.
self.__lineas_fichero.append(f)
# Cerramos el fichero.
fichero.close()

def __posicionar(self, seccion, clave = None):
'''
Método para posicionarse en la sección, clave (opcional).
Devuelve True, posicion de la clave (si encuentra la clave).
Devuelve True, posición de la sección (si encuentra sección y
clave = None).
Devuelve False, -1 (si no encuentra sección).
Devuelve False, posición de la sección (si no encuentra clave).
'''
# Buscamos la sección..
encontrado = False
puntero = 0
for i in self.__lineas_fichero:
puntero += 1
if i.strip() == '['+str(seccion)+']':
encontrado = True
break
if not encontrado: return False, -1
if clave is not None:
# Buscamos la clave.
encontrado = False
for i in range(puntero,len(self.__lineas_fichero)):
# Buscamos el carácter '='.
linea = self.__lineas_fichero[i].split('=')
if len(linea) == 1:
# Nos vamos. La clave no está en esta sección.
break
if len(linea) == 2:
# Miramos si es lo que buscamos.
if str(linea[0]).strip() == str(clave).strip():
# Devolvemos posición.
return True, i
# La clave no existe. Devolvemos posición de la sección.
return False, puntero
else:
# Devolvemos la posición de la sección.
return True, puntero

def profile(self, seccion, clave, por_defecto = None):
'''
Método para obtener valor de clave en sección.
'''
# La sección y clave no pueden estar vacíos.
if self.__esvacio(seccion) or self.__esvacio(clave): return por_defecto
# Buscamos la clave.
seguir, posicion = self.__posicionar(seccion, clave)
if seguir:
# Obtenemos el valor de la clave.
linea = self.__lineas_fichero[posicion].split('=')
try:
return str(linea[1]).strip()
except:
return por_defecto
else: return por_defecto

def set_profile(self, seccion, clave = None, valor = None):
'''
Método para crear secciones y claves (y darles valores).
'''
# Si no hay sección, nos vamos.
if self.__esvacio(seccion): return
# Incluimos una sección.
# Si la sección no existe, se crea.
seguir, posicion = self.__posicionar(seccion)
if not seguir:
self.__lineas_fichero.append('['+str(seccion).strip()+']')
# Asignamos ó incluimos si no existe, el valor
# de una clave a una sección.
if not self.__esvacio(clave):
# Vemos si la (sección,clave) ya existe.
seguir, posicion = self.__posicionar(seccion,clave)
# Formateamos valor.
if valor is None: valor = ''
# Si existe, damos nuevo valor.
if seguir:
self.__lineas_fichero[posicion] = str(clave).strip()+' = '+\
str(valor).strip()
else:
# Si no existe, creamos una nueva clave con su valor.
if posicion != -1:
# Insertamos nueva clave
clave_valor = str(clave).strip()+' = '+str(valor).strip()
self.__lineas_fichero.insert(posicion,clave_valor)

def save_profile(self, backup = True):
'''
Método para salvar datos en fichero profile. Si backup es True
se crea una copia de seguridad del mismo. Devuelve True si se
guardó el fichero y False en caso contrario.
'''
# Formamos etiqueta identificación.
etiqueta = '_backup_'+str(time.time()).strip()
try:
if backup:
# Renombramos fichero.
os.rename(os.path.realpath(self.__fichero),\
self.__fichero+etiqueta)
except: pass
ret = True
try:
# Creamos fichero.
fichero = open(os.path.realpath(self.__fichero),'w')
# Guardamos cada una de las líneas.
for i in self.__lineas_fichero:
linea = str(i) + '\r\n'
fichero.write(linea)
# Cerramos fichero.
fichero.close()
except: ret = False
# Devolvemos estado.
return ret



CONCLUSIONES

En este post hemos visto como implementar la clase profilePython, para el manejo de ficheros de perfil ó de configuración (también conocidos como .INI) de aplicaciones. Este tipo de herramientas son muy frecuentes utilizarlas en el desarrollo de programas, ya que nos da la posibilidad de crear configuraciones personalizadas de las mismas, de una manera fácil y sencilla.

Un cordial saludo.

3 comentarios:

  1. Python ya tiene esto: ConfigParser.

    "The ConfigParser class implements a basic configuration file parser language which provides a structure similar to what you would find on Microsoft Windows INI files."

    ResponderEliminar
  2. Hola Alejandro! No lo sabía! Bueno, me quedo con lo bien que me lo he pasado haciéndolo. Gracias por comentármelo!!!! Saludos.

    ResponderEliminar
  3. ¡Hala, figura!

    Veo que eres todo un experto en programación.

    Perdona, que no te he dicho quién soy, adivina adivinanzaaaaa

    http://blog.lucreciaseoscurece.com/

    ¿Todo bien? ;-)

    ResponderEliminar