Indice / Practicas / EJ-1.2
EJ-1.2 · Configuracion con valores por defecto
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:
config_efectiva.json- la configuración que queda vigente.errores_config.txt- los valores que se rechazaron, con el archivo, la clave, el motivo y el valor.
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
- Entrada ·
configuracion_entrada.json - Entrada ·
configuracion_parcial.json - Entrada ·
configuracion_invalida.json - Objetivo ·
config_efectiva.json - Objetivo ·
errores_config.txt - Herramienta ·
comparar.py
Las dos ausencias que no son lo mismo
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
| Clave | Por defecto | Válido si |
|---|---|---|
aplicacion | "sin nombre" | Texto no vacío |
entorno | "desarrollo" | Uno de: desarrollo, pruebas, produccion |
puerto | 8000 | Entero 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.pool | 5 | Entero 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
| Columna | Tipo | Que lleva |
|---|---|---|
aplicacion | texto | Nombre de la aplicacion |
entorno | texto | Uno de: desarrollo, pruebas, produccion |
puerto | entero | Entre 1024 y 65535 |
log | objeto | { nivel } con uno de: DEBUG, INFO, WARNING, ERROR, CRITICAL |
base_datos | objeto | { host, nombre, pool } con host y nombre no vacios y pool entre 1 y 100 |
errores_config.txt · 15 filas
| Columna | Tipo | Que lleva |
|---|
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| clave que falta en un archivo | configuracion_parcial.json no trae log ni base_datos.pool | NO NO es un error: queda vigente el valor anterior o el por defecto, y no se informa nada | config_efectiva.json, sin linea en el informe |
| entorno fuera del dominio | configuracion_invalida.json con 'prod' | rechazar rechazar el valor. El dominio es desarrollo, pruebas, produccion: 'prod' se parece pero no esta | errores_config.txt con motivo dominio_desconocido |
| puerto fuera de rango | configuracion_invalida.json con 99999 | rechazar rechazar el valor. El rango es 1024 a 65535 | errores_config.txt con motivo fuera_de_rango |
| nivel de log fuera del dominio | configuracion_invalida.json con 'TRACE' | rechazar rechazar el valor. Los niveles son los cinco de logging | errores_config.txt con motivo dominio_desconocido |
| host vacio y nombre en null | configuracion_invalida.json | rechazar rechazar los dos valores. Una cadena vacia y un null son dos formas distintas de no decir nada, y las dos se informan como ausente | errores_config.txt con motivo ausente |
| pool negativo | configuracion_invalida.json con -3 | rechazar rechazar el valor. El rango es 1 a 100 | errores_config.txt con motivo fuera_de_rango |
| clave desconocida | 0 casos en estos tres archivos | la la comprobacion va igual: una clave que no esta en el esquema se rechaza en vez de copiarse | errores_config.txt con motivo restriccion_violada |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- leer los tres archivos en el orden declarado
- por cada archivo, recorrer sus claves aplanadas en orden alfabetico
- si la clave no esta en el esquema: rechazar
- si el valor no pasa su validacion: rechazar y dejar vigente el anterior
- 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
| archivos de entrada | 3 |
|---|---|
| valores encontrados | 19 |
| valores aplicados | 13 |
| valores rechazados | 6 |
| errores_config.txt | 15 |
| por motivo | ausente: 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
{
"aplicacion": "Portal académico",
"base_datos": {
"host": "db.interno",
"nombre": "academica_test",
"pool": 10
},
"entorno": "pruebas",
"log": {
"nivel": "INFO"
},
"puerto": 8081
}| ERRORES DE CONFIGURACION |
|---|
| ========================================================================== |
| archivos leidos : 3 |
| valores aplicados : 13 |
| valores rechazados : 6 |
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.
sort_keys=Trueen eljson.dump. Sin eso, dos soluciones correctas producen archivos distintos y no hay nada que comparar.- El valor rechazado se escribe con
json.dumps, no constr: así la cadena vacía sale como""y el nulo comonull. Constr, los dos se verían como una celda en blanco.