Saltar al contenido

Indice / Practicas / EJ-1.2

EJ-1.2 · Configuracion con valores por defecto

Practica de: 1.4 JSON con el modulo jsonDificultad: M

Tres archivos de configuración que dicen cosas distintas sobre lo mismo. Hay que resolver cuál gana, campo por campo, y dejar escrito lo que se descartó.

Qué hay que hacer

Leer configuracion_entrada.json, configuracion_parcial.json y configuracion_invalida.json - en ese orden - aplicándolos sobre un juego de valores por defecto, y producir:

Sin pandas: json.load, un esquema de validación y un poco de recorrido recursivo.

ELI5Explicado facil

Es cómo se configura casi todo el software del mundo: hay un valor de fábrica, lo pisa el archivo del sistema, lo pisa el del usuario, lo pisa la variable de entorno. Gana el último que hable… siempre que lo que diga tenga sentido.

La parte que se olvida es la segunda: si el último dice una barbaridad, no gana. Y hay que poder mostrar cuál era la barbaridad.

Archivos

Las dos ausencias que no son lo mismo

Una clave que falta no es un error

configuracion_parcial.json no trae log ni base_datos.pool. Eso es normal: un archivo parcial dice solo lo que quiere cambiar, y lo que no menciona queda como estaba. No va al informe.

configuracion_invalida.json, en cambio, sí trae base_datos.nombre… en null. La clave está y no dice nada, y eso sí es un error. Es exactamente la distinción de la lección 1.4 entre «nunca me lo dijeron» y «me dijeron que no hay dato».

El esquema

Siete claves, su valor por defecto y su regla
ClavePor defectoVálido si
aplicacion"sin nombre"Texto no vacío
entorno"desarrollo"Uno de: desarrollo, pruebas, produccion
puerto8000Entero entre 1024 y 65535
log.nivel"INFO"Uno de los cinco niveles de logging
base_datos.host"localhost"Texto no vacío
base_datos.nombre"academica"Texto no vacío
base_datos.pool5Entero entre 1 y 100

Cómo saber si te salió

python comparar.py mi_config.json config_efectiva.json

comparar.py compara JSON recursivamente y te dice la ruta exacta de la diferencia, por ejemplo base_datos.pool. El informe de texto se mira contra el extracto de esta página.

El contrato de salida

config_efectiva.json · 5 filas

ColumnaTipoQue lleva
aplicaciontextoNombre de la aplicacion
entornotextoUno de: desarrollo, pruebas, produccion
puertoenteroEntre 1024 y 65535
logobjeto{ nivel } con uno de: DEBUG, INFO, WARNING, ERROR, CRITICAL
base_datosobjeto{ host, nombre, pool } con host y nombre no vacios y pool entre 1 y 100

Orden de las filas: No hay filas: es un objeto. El orden de las claves lo fija sort_keys=True, para que dos soluciones correctas den el mismo archivo. Indice: no aplica: es un JSON, no una tabla. Con pandas la equivalencia seria to_csv(index=False)

errores_config.txt · 15 filas

ColumnaTipoQue lleva

Orden de las filas: Por archivo en el orden de lectura, y dentro de cada archivo por clave alfabetica. La clave anidada se escribe con punto: base_datos.host. Indice: no aplica: es texto plano. Con pandas la equivalencia seria to_csv(index=False)

Que hacer con cada defecto

DefectoDondeAccionAdonde va
clave que falta en un archivoconfiguracion_parcial.json no trae log ni base_datos.poolNO NO es un error: queda vigente el valor anterior o el por defecto, y no se informa nadaconfig_efectiva.json, sin linea en el informe
entorno fuera del dominioconfiguracion_invalida.json con 'prod'rechazar rechazar el valor. El dominio es desarrollo, pruebas, produccion: 'prod' se parece pero no estaerrores_config.txt con motivo dominio_desconocido
puerto fuera de rangoconfiguracion_invalida.json con 99999rechazar rechazar el valor. El rango es 1024 a 65535errores_config.txt con motivo fuera_de_rango
nivel de log fuera del dominioconfiguracion_invalida.json con 'TRACE'rechazar rechazar el valor. Los niveles son los cinco de loggingerrores_config.txt con motivo dominio_desconocido
host vacio y nombre en nullconfiguracion_invalida.jsonrechazar rechazar los dos valores. Una cadena vacia y un null son dos formas distintas de no decir nada, y las dos se informan como ausenteerrores_config.txt con motivo ausente
pool negativoconfiguracion_invalida.json con -3rechazar rechazar el valor. El rango es 1 a 100errores_config.txt con motivo fuera_de_rango
clave desconocida0 casos en estos tres archivosla la comprobacion va igual: una clave que no esta en el esquema se rechaza en vez de copiarseerrores_config.txt con motivo restriccion_violada
Orden de comprobacion

Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:

  1. leer los tres archivos en el orden declarado
  2. por cada archivo, recorrer sus claves aplanadas en orden alfabetico
  3. si la clave no esta en el esquema: rechazar
  4. si el valor no pasa su validacion: rechazar y dejar vigente el anterior
  5. si pasa: pisar el valor vigente

El resultado depende del orden de los archivos y hay que decirlo: si configuracion_invalida se leyera primero, sus valores validos serian los que quedan. Como se lee ultimo y casi todos sus valores son invalidos, la configuracion efectiva es basicamente la de configuracion_parcial.

Conteos clave

Si estos numeros no te dan, algo se desvio antes de que llegues a comparar los archivos.

archivos de entrada3
valores encontrados19
valores aplicados13
valores rechazados6
errores_config.txt15
por motivoausente: 2, dominio_desconocido: 2, fuera_de_rango: 2, restriccion_violada: 0

Invariantes

Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:

config['entorno'] in ('desarrollo', 'pruebas', 'produccion')
1024 <= config['puerto'] <= 65535
config['log']['nivel'] in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
1 <= config['base_datos']['pool'] <= 100
config['base_datos']['host'] != ''
config['base_datos']['nombre'] is not None
config['entorno'] == 'pruebas'
config['puerto'] == 8081
config['log']['nivel'] == 'INFO'
config['base_datos']['pool'] == 10
aplicados + rechazados == 19
PISTASSi te trabas, abri esto
  • Aplanar el JSON a claves con punto ('base_datos.host') convierte un problema de recorrido anidado en un problema de diccionario plano. Volver a anidarlo al final es otra funcion de cuatro lineas.
  • Una clave que FALTA no es un error: es el caso normal de un archivo parcial. Solo se informa la que esta y no sirve.
  • El valor rechazado se escribe con json.dumps, no con str: asi la cadena vacia sale como "" y el nulo como null, y se distinguen en el informe.
  • log.nivel queda en INFO aunque configuracion_invalida traiga TRACE: el valor rechazado deja vigente el de configuracion_entrada, no el por defecto. Los dos coinciden aca, y por eso conviene revisar tambien base_datos.pool, que queda en 10 y no en 5.

Así tienen que quedar

config_efectiva.json (objetivo) · 2 de 1 registros
{
  "aplicacion": "Portal académico",
  "base_datos": {
    "host": "db.interno",
    "nombre": "academica_test",
    "pool": 10
  },
  "entorno": "pruebas",
  "log": {
    "nivel": "INFO"
  },
  "puerto": 8081
}
errores_config.txt (objetivo) · 16 filas, se muestran 5
ERRORES DE CONFIGURACION
==========================================================================
archivos leidos : 3
valores aplicados : 13
valores rechazados : 6
El valor que queda no es siempre el por defecto

Cuando se rechaza un valor, queda vigente el anterior: el de un archivo previo, o el por defecto si ningún archivo lo había fijado.

base_datos.pool es el caso que lo muestra: configuracion_invalida trae -3 y se rechaza, pero el resultado es 10 - el de configuracion_entrada - no el 5 por defecto. Si te da 5, estás volviendo al default en vez de conservar lo último válido.

Dos detalles del formato
  • sort_keys=True en el json.dump. Sin eso, dos soluciones correctas producen archivos distintos y no hay nada que comparar.
  • El valor rechazado se escribe con json.dumps, no con str: así la cadena vacía sale como "" y el nulo como null. Con str, los dos se verían como una celda en blanco.