Nunca conviertas una variable de entorno a bool con un cast
Este bug de configuración de una sola línea enciende tu kill switch. Las variables de entorno siempre son strings, y en casi todos los lenguajes un string no vacío es "truthy" — así que bool(os.environ["KILL_SWITCH"]) devuelve True sin importar si el operador escribió "true", "false" o "0". La bandera que construiste para detener el sistema acaba de convertirse en una bandera que no puede decir que no.
Por qué el cast directo miente
La regla de verdad de Python se trata de vacío, no de significado: un objeto es verdadero salvo que sea uno de los valores falsy documentados — None, False, cero numérico o una colección vacía como ''. El string "false" tiene cinco caracteres, así que es truthy. La misma trampa existe en todo el stack:
- Python:
bool("false")→True. Solo""es falsy. - JavaScript/Node: los valores de
process.envson strings;Boolean("false")→true, yif (process.env.FLAG)pasa tanto con"0"como con"false". - Ruby: todo string es truthy — incluso
"false"y"".
Hay exactamente dos valores de string que un cast directo maneja bien: no definido y vacío. Todo lo que un operador escribiría en la práctica — true, false, 1, 0, yes, no — se interpreta mal o solo a medias.
Parsea con un vocabulario compartido
La solución es un único parser con un vocabulario explícito de true/false, usado tanto para variables de entorno como para configuración estructurada, de modo que false signifique false en todas partes:
_TRUE = {"1", "true", "t", "yes", "y", "on"}
_FALSE = {"0", "false", "f", "no", "n", "off"}
def env_bool(name: str, default: bool = False) -> bool:
raw = os.environ.get(name)
if raw is None or raw.strip() == "":
return default # blank template value != forced off
v = raw.strip().lower()
if v in _TRUE:
return True
if v in _FALSE:
return False
raise ValueError(f"{name}={raw!r} is not a boolean")
Tres propiedades importan más que la lista exacta de palabras:
| Propiedad | Por qué importa |
|---|---|
| Sin definir/en blanco → default | Una línea FLAG= vacía en una plantilla .env no debería forzar la bandera a off (ni a on) |
| Valor desconocido → error | FLAG=fales debería fallar al arrancar, no elegir un lado en silencio |
| Un vocabulario en todas partes | Env vars, YAML y flags de CLI deben coincidir en qué significa "false" |
La última fila es la sutil. Si tu parser de entorno acepta yes pero tu loader de YAML no, el mismo valor de despliegue cambia de significado según dónde esté escrito.
Referencias previas: qué aceptan los parsers reales
- Go —
strconv.ParseBoolacepta exactamente1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False; cualquier otro valor devuelve un error. Ese es todo el diseño en una firma de función: vocabulario cerrado, fallo ruidoso. - Pydantic — valida un campo
booldesde un string que, en minúsculas, sea uno de0, off, f, false, n, no, 1, on, t, true, y, yes— la misma idea del vocabulario compartido, aplicada a configuración estructurada. - El viejo
distutils.util.strtoboolde Python también lo hacía — perodistutilsfue eliminado en Python 3.12 (PEP 632), y por eso tantas bases de código terminaron con un reemplazo casero (y a menudo con bugs). Escribe la versión de diez líneas de arriba una sola vez, o usa la de Pydantic.
La metodología twelve-factor lleva la configuración a variables de entorno precisamente porque son un estándar agnóstico de lenguaje y de sistema operativo — lo que también implica que solo pueden transportar strings. La disciplina de tipos es tu trabajo.
Notas para usuarios avanzados
- Falla al arrancar, no al usar. Parsea cada bandera booleana cuando el proceso inicia y falla con valores basura. Un
ValueErroren el deploy es una línea de log; un kill switch mal leído a las 3 a.m. es un incidente. - Busca el bug con grep.
bool(os.environ,Boolean(process.envy unif (process.env.X)a secas se encuentran en una sola búsqueda. Marca también comparaciones de strings comoenv == "True"— se rompen en cuanto alguien escribetrue. - Cuidado con el código generado por IA. Los asistentes suelen emitir
os.environ.get("DEBUG", False)y luego evaluarlo como truthy — una regla de revisión ("todo booleano de entorno pasa porenv_bool") atrapa esta clase de bug de forma mecánica. - Ojo con
FLAG=0. La memoria muscular de ops escribe0para apagar. Un parser que solo aceptatrue/falselee"0"como un error en el mejor de los casos — incluye el par numérico.
Recursos
- Truth Value Testing — documentación de Python
strconv.ParseBool— biblioteca estándar de Go- Booleans — tipos de biblioteca estándar en Pydantic
- PEP 632 — Deprecate and remove distutils (se llevó a
strtobool) - What's New in Python 3.12 — eliminación de distutils
- The Twelve-Factor App — Config
Este artículo acompaña al reel #105 de la serie Yeda AI Tips. ¿Construyendo un flujo de ingeniería asistido por IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.