LiteralString en Python: cadenas confiables

Publicado el: 29/08/2026
Tempo de leitura: 5 minutos
Striking image of a red-bellied python showcasing its vibrant scales in dramatic lighting.

typing.LiteralString representa cadenas consideradas literales o derivadas únicamente de otras cadenas literales. Ayuda a APIs sensibles a distinguir texto definido por desarrolladores de contenido dinámico recibido de usuarios, archivos, redes, configuración o bases de datos. Esta distinción puede reducir riesgos de inyección en SQL, comandos, templates y formatos, pero no sustituye parametrización, escaping, validación, autorización ni privilegios mínimos.

Esta guía explica inferencia, operaciones con strings, parámetros SQL, identificadores dinámicos, shell, templates, builders, cast, límites de taint analysis, stubs, compatibilidad, pruebas y seguridad por capas.

El problema de strings dinámicas

def ejecutar_sql(query: str) -> None:
    ...

nombre = input("Nombre: ")
ejecutar_sql(f"SELECT * FROM usuarios WHERE nombre = '{nombre}'")

La firma acepta cualquier string. El analizador no distingue una consulta fija de una construida con entrada externa. El ejemplo de runtime es vulnerable a inyección.

Declarar LiteralString

from typing import LiteralString


def ejecutar_sql(query: LiteralString) -> None:
    ...

ejecutar_sql("SELECT * FROM usuarios")

Una cadena escrita directamente en el código es compatible. Una variable general de tipo str normalmente no lo es.

La entrada externa debe rechazarse

query: str = input("SQL: ")
ejecutar_sql(query)  # error estático esperado

El analizador informa que una string arbitraria no cumple el contrato confiable. Esto crea una barrera útil en revisión y CI.

LiteralString es subtipo de str

Una LiteralString puede usarse donde se acepta str. Lo contrario no es válido. Mostrar, registrar y procesar texto normalmente sigue funcionando.

Concatenar literales

prefijo: LiteralString = "SELECT id, nombre "
sufijo: LiteralString = "FROM usuarios"
query = prefijo + sufijo
ejecutar_sql(query)

Las operaciones que combinan solo LiteralStrings pueden conservar la confianza. La inferencia exacta depende del analizador.

f-strings

tabla: LiteralString = "usuarios"
query = f"SELECT * FROM {tabla}"

Una f-string puede seguir siendo LiteralString cuando todas las partes interpoladas son confiables. Si alguna expresión es solo str, la confianza debería perderse.

No interpoles datos SQL

Incluso con LiteralString, separa estructura y valores:

def consultar(sql: LiteralString, parametros: tuple[object, ...]) -> None:
    ...

nombre = input("Nombre: ")
consultar(
    "SELECT * FROM usuarios WHERE nombre = ?",
    (nombre,),
)

La consulta es literal y el dato externo se envía por separado. El driver maneja encoding y escaping. La parametrización es la defensa principal.

Nombres de tabla dinámicos

Los drivers suelen parametrizar valores, no identificadores. Selecciona identificadores desde un conjunto cerrado:

from typing import Literal

Tabla = Literal["usuarios", "pedidos"]


def nombre_tabla(tabla: Tabla) -> LiteralString:
    if tabla == "usuarios":
        return "usuarios"
    return "pedidos"

No uses cast sobre entrada arbitraria. Valida con allowlists y devuelve literales conocidos.

Comandos de shell

def ejecutar_comando(comando: LiteralString) -> None:
    ...

El tipo puede desalentar comandos construidos con entrada externa, pero el diseño más seguro evita shell=True y pasa una lista:

subprocess.run(["git", "show", revision], check=True)

LiteralString no vuelve seguro el parsing del shell.

Templates HTML

def renderizar(template: LiteralString, contexto: dict[str, object]) -> str:
    ...

El renderer puede exigir un template controlado por desarrolladores y recibir datos dinámicos por separado. El engine todavía debe aplicar escaping contextual para HTML, atributos, JavaScript, CSS y URLs.

Formatos de logging

def logear(formato: LiteralString, *args: object) -> None:
    ...

logear("usuario %s entró", nombre)

Mantener el formato literal evita placeholders controlados externamente. Los valores siguen separados.

Internacionalización

Texto traducido desde catálogos es str, aunque el catálogo pertenezca al proyecto. No lo fuerces a LiteralString solo para satisfacer una API. La traducción necesita otro modelo de confianza.

Archivos de configuración

Texto leído de YAML, JSON o variables de entorno es dinámico. No se vuelve literal porque el archivo esté en el repositorio. Deploys y operadores pueden modificarlo.

Funciones que preservan confianza

def agregar_limite(query: LiteralString) -> LiteralString:
    return query + " LIMIT 100"

Una función puede recibir y devolver LiteralString si añade únicamente contenido literal. Si incorpora un str arbitrario, el retorno debería ser str.

Builders seguros

def ordenar_por_nombre(query: LiteralString) -> LiteralString:
    return query + " ORDER BY nombre"

Para opciones dinámicas, mapea enums o Literals a fragmentos fijos. Evita fragmentos libres.

cast no sanitiza

from typing import cast

entrada = input("SQL: ")
query = cast(LiteralString, entrada)

El cast solo silencia el analizador. No valida, escapa ni cambia el valor. Aplicarlo a entrada no confiable destruye la garantía.

Validar no crea automáticamente LiteralString

Después de una regex o allowlist, el analizador puede seguir viendo str. Prefiere funciones que seleccionen literales conocidos o devuelvan una unión Literal cerrada. Evita un helper genérico “confía en esta string”.

Literal frente a LiteralString

Literal["asc", "desc"] describe valores exactos, compatibles con LiteralString. Literal es mejor para opciones cerradas. LiteralString sirve cuando la API acepta texto construido exclusivamente desde literales confiables.

TypeGuard no prueba el origen

TypeGuard no puede determinar de forma general que una string arbitraria provino del código fuente. El origen es una propiedad del flujo estático, no del contenido observable.

Bibliotecas externas

Un retorno anotado como str pierde confianza aunque la implementación actual devuelva constantes. Una biblioteca puede prometer LiteralString solo si conserva realmente el origen en todo su contrato.

Archivos stub

Bibliotecas de base de datos, logging y templates pueden usar LiteralString en .pyi. Prueba la experiencia con mypy y pyright. Una anotación demasiado restrictiva puede bloquear usos legítimos.

No es taint tracking completo

LiteralString es una aproximación simple. No rastrea fuentes, sanitizadores, contextos, codificaciones, drivers o flujos entre procesos como un sistema completo de taint analysis.

Seguridad por capas

Úsalo como capa adicional. Parámetros SQL, arrays de argumentos, escaping contextual, allowlists, validación, autorización, privilegios mínimos y pruebas siguen siendo necesarios.

Compatibilidad

Usa typing_extensions.LiteralString en versiones anteriores. La precisión depende también del analizador. Mantén herramientas actualizadas y añade pruebas de tipado al CI.

Errores comunes

  • Hacer cast de entrada externa: no existe sanitización.
  • Interpolar valores SQL: usa parámetros del driver.
  • Suponer que el shell es seguro: evita shell=True.
  • Tratar configuración como literal: es dato dinámico.
  • Confundirlo con taint analysis completo: el modelo es limitado.
  • Confiar solo en el tipo: runtime necesita defensas.

Ejemplo completo: repositorio SQL

from typing import Literal, LiteralString

Orden = Literal["nombre", "creado_en"]


def columna_orden(orden: Orden) -> LiteralString:
    if orden == "nombre":
        return "nombre"
    return "creado_en"


def listar_usuarios(conexion, termino: str, orden: Orden):
    columna = columna_orden(orden)
    query: LiteralString
    if columna == "nombre":
        query = (
            "SELECT id, nombre FROM usuarios "
            "WHERE nombre LIKE ? ORDER BY nombre"
        )
    else:
        query = (
            "SELECT id, nombre FROM usuarios "
            "WHERE nombre LIKE ? ORDER BY creado_en"
        )
    cursor = conexion.execute(query, (f"%{termino}%",))
    return cursor.fetchall()

Los valores de búsqueda están parametrizados. La columna se elige mediante una unión Literal y cada consulta final es un literal de código. La entrada externa nunca se convierte en estructura SQL.

Pruebas estáticas

from typing import assert_type

assert_type(columna_orden("nombre"), LiteralString)

texto: str = input()
# ejecutar_sql(texto) debería fallar

Incluye casos aceptados y rechazados para proteger la frontera cuando cambien stubs y analizadores.

Cuándo usarlo

Usa LiteralString en APIs donde la estructura textual debe estar definida por el desarrollador: consultas, templates, formatos y expresiones. No lo uses en funciones normales que procesan texto arbitrario.

Conclusión

LiteralString declara que una API espera texto originado en literales confiables. Ayuda a bloquear strings dinámicas en posiciones sensibles e incentiva separar estructura y datos.

La documentación oficial de LiteralString en Python explica la inferencia. Úsalo como defensa adicional, nunca como sustituto de consultas parametrizadas, escaping, allowlists, validación, autorización y privilegios mínimos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    get_origin y get_args: inspecciona tipos genéricos

    Aprende get_origin y get_args en Python para inspeccionar genéricos, uniones, Annotated, Literal, alias y metadatos de runtime.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dataclass_transform en Python: clases generadas

    Aprende dataclass_transform en Python para tipar decorators, clases base y metaclases que generan campos, __init__ y métodos.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    TypeVarTuple en Python: genéricos variádicos

    Aprende TypeVarTuple en Python para conservar tuplas heterogéneas, modelar dimensiones y crear genéricos con parámetros variables.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    assert_type y reveal_type: prueba inferencia de tipos

    Aprende assert_type y reveal_type en Python para inspeccionar inferencia, probar APIs tipadas y evitar regresiones estáticas.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    get_type_hints en Python: lee anotaciones

    Aprende get_type_hints en Python para resolver referencias futuras, conservar Annotated e inspeccionar funciones y clases con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    runtime_checkable en Python: Protocol runtime

    Aprende runtime_checkable en Python para comprobar Protocol con isinstance, entender límites y diseñar contratos estructurales seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026